diff --git a/api/_build/openapi.yaml b/api/_build/openapi.yaml index 403a45c..6b39558 100644 --- a/api/_build/openapi.yaml +++ b/api/_build/openapi.yaml @@ -11,52 +11,52 @@ paths: parameters: - $ref: '#/components/parameters/cursor' - $ref: '#/components/parameters/title_sort' - - name: sort_forward - in: query + - in: query + name: sort_forward schema: type: boolean default: true - - name: word - in: query + - in: query + name: word schema: type: string - - name: status - in: query - description: List of title statuses to filter + - in: query + name: status schema: type: array items: $ref: '#/components/schemas/TitleStatus' - explode: false + description: List of title statuses to filter style: form - - name: rating - in: query + explode: false + - in: query + name: rating schema: type: number format: double - - name: release_year - in: query + - in: query + name: release_year schema: type: integer format: int32 - - name: release_season - in: query + - in: query + name: release_season schema: $ref: '#/components/schemas/ReleaseSeason' - - name: limit - in: query + - in: query + name: limit schema: type: integer format: int32 default: 10 - - name: offset - in: query + - in: query + name: offset schema: type: integer format: int32 default: 0 - - name: fields - in: query + - in: query + name: fields schema: type: string default: all @@ -69,10 +69,10 @@ paths: type: object properties: data: - description: List of titles type: array items: $ref: '#/components/schemas/Title' + description: List of titles cursor: $ref: '#/components/schemas/CursorObj' required: @@ -88,14 +88,14 @@ paths: get: summary: Get title description parameters: - - name: title_id - in: path + - in: path + name: title_id required: true schema: type: integer format: int64 - - name: fields - in: query + - in: query + name: fields schema: type: string default: all @@ -118,13 +118,13 @@ paths: get: summary: Get user info parameters: - - name: user_id - in: path + - in: path + name: user_id required: true schema: type: string - - name: fields - in: query + - in: query + name: fields schema: type: string default: all @@ -142,59 +142,59 @@ paths: '500': description: Unknown server error patch: - operationId: updateUser summary: Partially update a user account description: | Update selected user profile fields (excluding password). Password updates must be done via the dedicated auth-service (`/auth/`). Fields not provided in the request body remain unchanged. + operationId: updateUser parameters: - name: user_id in: path - description: User ID (primary key) required: true schema: type: integer format: int64 + description: User ID (primary key) example: 123 requestBody: required: true content: application/json: schema: - description: Only provided fields are updated. Omitted fields remain unchanged. type: object properties: avatar_id: - description: ID of the user avatar (references `images.id`); set to `null` to remove avatar type: integer format: int64 - example: 42 nullable: true + description: ID of the user avatar (references `images.id`); set to `null` to remove avatar + example: 42 mail: - description: User email (must be unique and valid) type: string format: email - example: john.doe.updated@example.com pattern: '^[a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\\.[a-zA-Z0-9_-]+$' + description: User email (must be unique and valid) + example: john.doe.updated@example.com nickname: - description: 'Username (alphanumeric + `_` or `-`, 3–16 chars)' type: string - example: john_doe_43 + pattern: '^[a-zA-Z0-9_-]{3,16}$' + description: 'Username (alphanumeric + `_` or `-`, 3–16 chars)' maxLength: 16 minLength: 3 - pattern: '^[a-zA-Z0-9_-]{3,16}$' + example: john_doe_43 disp_name: + type: string description: Display name - type: string - example: John Smith maxLength: 32 + example: John Smith user_desc: - description: User description / bio type: string - example: Just a curious developer. + description: User description / bio maxLength: 512 + example: Just a curious developer. additionalProperties: false + description: Only provided fields are updated. Omitted fields remain unchanged. responses: '200': description: User updated successfully. Returns updated user representation (excluding sensitive fields). @@ -222,64 +222,64 @@ paths: parameters: - $ref: '#/components/parameters/cursor' - $ref: '#/components/parameters/title_sort' - - name: user_id - in: path + - in: path + name: user_id required: true schema: type: string - - name: sort_forward - in: query + - in: query + name: sort_forward schema: type: boolean default: true - - name: word - in: query + - in: query + name: word schema: type: string - - name: status - in: query - description: List of title statuses to filter + - in: query + name: status schema: type: array items: $ref: '#/components/schemas/TitleStatus' - explode: false + description: List of title statuses to filter style: form - - name: watch_status - in: query + explode: false + - in: query + name: watch_status schema: type: array items: $ref: '#/components/schemas/UserTitleStatus' - explode: false style: form - - name: rating - in: query + explode: false + - in: query + name: rating schema: type: number format: double - - name: my_rate - in: query + - in: query + name: my_rate schema: type: integer format: int32 - - name: release_year - in: query + - in: query + name: release_year schema: type: integer format: int32 - - name: release_season - in: query + - in: query + name: release_season schema: $ref: '#/components/schemas/ReleaseSeason' - - name: limit - in: query + - in: query + name: limit schema: type: integer format: int32 default: 10 - - name: fields - in: query + - in: query + name: fields schema: type: string default: all @@ -309,43 +309,57 @@ paths: '500': description: Unknown server error post: - operationId: addUserTitle summary: Add a title to a user description: 'User adding title to list af watched, status required' + operationId: addUserTitle parameters: - name: user_id in: path - description: ID of the user to assign the title to required: true schema: type: integer format: int64 + description: ID of the user to assign the title to example: 123 requestBody: required: true content: application/json: schema: - type: object - properties: - title_id: - type: integer - format: int64 - status: - $ref: '#/components/schemas/UserTitleStatus' - rate: - type: integer - format: int32 - required: - - title_id - - status + $ref: '#/components/schemas/UserTitle' responses: '200': description: Title successfully added to user content: application/json: schema: - $ref: '#/components/schemas/UserTitleMini' + type: object + properties: + data: + type: object + required: + - user_id + - title_id + - status + properties: + user_id: + type: integer + format: int64 + title_id: + type: integer + format: int64 + status: + $ref: '#/components/schemas/UserTitleStatus' + rate: + type: integer + format: int32 + review_id: + type: integer + format: int64 + ctime: + type: string + format: date-time + additionalProperties: false '400': description: 'Invalid request body (missing fields, invalid types, etc.)' '401': @@ -358,77 +372,6 @@ paths: description: Conflict — title already assigned to user (if applicable) '500': description: Internal server error - patch: - operationId: updateUserTitle - summary: Update a usertitle - description: User updating title list of watched - parameters: - - name: user_id - in: path - description: ID of the user to assign the title to - required: true - schema: - type: integer - format: int64 - example: 123 - requestBody: - required: true - content: - application/json: - schema: - type: object - properties: - title_id: - type: integer - format: int64 - status: - $ref: '#/components/schemas/UserTitleStatus' - rate: - type: integer - format: int32 - required: - - title_id - responses: - '200': - description: Title successfully updated - content: - application/json: - schema: - $ref: '#/components/schemas/UserTitleMini' - '400': - description: 'Invalid request body (missing fields, invalid types, etc.)' - '401': - description: Unauthorized — missing or invalid auth token - '403': - description: Forbidden — user not allowed to update title - '404': - description: User or Title not found - '500': - description: Internal server error - delete: - operationId: deleteUserTitle - summary: Delete a usertitle - description: User deleting title from list of watched - parameters: - - name: user_id - in: path - description: ID of the user to assign the title to - required: true - schema: - type: integer - format: int64 - example: 123 - responses: - '200': - description: Title successfully deleted - '401': - description: Unauthorized — missing or invalid auth token - '403': - description: Forbidden — user not allowed to delete title - '404': - description: User or Title not found - '500': - description: Internal server error components: parameters: cursor: @@ -444,36 +387,25 @@ components: schema: $ref: '#/components/schemas/TitleSort' schemas: + CursorObj: + type: object + required: + - id + properties: + id: + type: integer + format: int64 + param: + type: string TitleSort: - description: Title sort order type: string + description: Title sort order default: id enum: - id - year - rating - views - TitleStatus: - description: Title status - type: string - enum: - - finished - - ongoing - - planned - ReleaseSeason: - description: Title release season - type: string - enum: - - winter - - spring - - summer - - fall - StorageType: - description: Image storage type - type: string - enum: - - s3 - - local Image: type: object properties: @@ -481,11 +413,65 @@ components: type: integer format: int64 storage_type: - $ref: '#/components/schemas/StorageType' + type: string + description: Image storage type + enum: + - s3 + - local image_path: type: string + TitleStatus: + type: string + description: Title status + enum: + - finished + - ongoing + - planned + ReleaseSeason: + type: string + description: Title release season + enum: + - winter + - spring + - summer + - fall + UserTitleStatus: + type: string + description: User's title status + enum: + - finished + - planned + - dropped + - in-progress + Review: + type: object + additionalProperties: true + Tag: + type: object + description: 'A localized tag: keys are language codes (ISO 639-1), values are tag names' + additionalProperties: + type: string + example: + en: Shojo + ru: Сёдзё + ja: 少女 + Tags: + type: array + description: Array of localized tags + items: + $ref: '#/components/schemas/Tag' + example: + - en: Shojo + ru: Сёдзё + ja: 少女 + - en: Shounen + ru: Сёнен + ja: 少年 Studio: type: object + required: + - id + - name properties: id: type: integer @@ -496,50 +482,21 @@ components: $ref: '#/components/schemas/Image' description: type: string - required: - - id - - name - Tag: - description: 'A localized tag: keys are language codes (ISO 639-1), values are tag names' - type: object - example: - en: Shojo - ru: Сёдзё - ja: 少女 - additionalProperties: - type: string - Tags: - description: Array of localized tags - type: array - items: - $ref: '#/components/schemas/Tag' - example: - - en: Shojo - ru: Сёдзё - ja: 少女 - - en: Shounen - ru: Сёнен - ja: 少年 Title: type: object + required: + - id + - title_names + - tags properties: id: - description: Unique title ID (primary key) type: integer format: int64 + description: Unique title ID (primary key) example: 1 title_names: - description: 'Localized titles. Key = language (ISO 639-1), value = list of names' type: object - example: - en: - - Attack on Titan - - AoT - ru: - - Атака титанов - - Титаны - ja: - - 進撃の巨人 + description: 'Localized titles. Key = language (ISO 639-1), value = list of names' additionalProperties: type: array items: @@ -549,6 +506,15 @@ components: example: - Attack on Titan - AoT + example: + en: + - Attack on Titan + - AoT + ru: + - Атака титанов + - Титаны + ja: + - 進撃の巨人 studio: $ref: '#/components/schemas/Studio' tags: @@ -580,68 +546,50 @@ components: type: number format: double additionalProperties: true - required: - - id - - title_names - - tags - CursorObj: - type: object - properties: - id: - type: integer - format: int64 - param: - type: string - required: - - id User: type: object properties: id: - description: Unique user ID (primary key) type: integer format: int64 + description: Unique user ID (primary key) example: 1 image: $ref: '#/components/schemas/Image' mail: - description: User email type: string format: email + description: User email example: john.doe@example.com nickname: + type: string description: Username (alphanumeric + _ or -) - type: string - example: john_doe_42 maxLength: 16 + example: john_doe_42 disp_name: + type: string description: Display name - type: string - example: John Doe maxLength: 32 + example: John Doe user_desc: - description: User description type: string - example: Just a regular user. + description: User description maxLength: 512 + example: Just a regular user. creation_date: - description: Timestamp when the user was created type: string format: date-time + description: Timestamp when the user was created example: '2025-10-10T23:45:47.908073Z' required: - user_id - nickname - UserTitleStatus: - description: User's title status - type: string - enum: - - finished - - planned - - dropped - - in-progress UserTitle: type: object + required: + - user_id + - title_id + - status properties: user_id: type: integer @@ -659,34 +607,4 @@ components: ctime: type: string format: date-time - required: - - user_id - - title_id - - status - UserTitleMini: - type: object - properties: - user_id: - type: integer - format: int64 - title_id: - type: integer - format: int64 - status: - $ref: '#/components/schemas/UserTitleStatus' - rate: - type: integer - format: int32 - review_id: - type: integer - format: int64 - ctime: - type: string - format: date-time - required: - - user_id - - title_id - - status - Review: - type: object additionalProperties: true diff --git a/api/api.gen.go b/api/api.gen.go index 6af01d0..f3e935c 100644 --- a/api/api.gen.go +++ b/api/api.gen.go @@ -16,6 +16,12 @@ import ( openapi_types "github.com/oapi-codegen/runtime/types" ) +// Defines values for ImageStorageType. +const ( + Local ImageStorageType = "local" + S3 ImageStorageType = "s3" +) + // Defines values for ReleaseSeason. const ( Fall ReleaseSeason = "fall" @@ -24,12 +30,6 @@ const ( Winter ReleaseSeason = "winter" ) -// Defines values for StorageType. -const ( - Local StorageType = "local" - S3 StorageType = "s3" -) - // Defines values for TitleSort. const ( Id TitleSort = "id" @@ -65,15 +65,15 @@ type Image struct { ImagePath *string `json:"image_path,omitempty"` // StorageType Image storage type - StorageType *StorageType `json:"storage_type,omitempty"` + StorageType *ImageStorageType `json:"storage_type,omitempty"` } +// ImageStorageType Image storage type +type ImageStorageType string + // ReleaseSeason Title release season type ReleaseSeason string -// StorageType Image storage type -type StorageType string - // Studio defines model for Studio. type Studio struct { Description *string `json:"description,omitempty"` @@ -151,21 +151,10 @@ type UserTitle struct { ReviewId *int64 `json:"review_id,omitempty"` // Status User's title status - Status UserTitleStatus `json:"status"` - Title *Title `json:"title,omitempty"` - UserId int64 `json:"user_id"` -} - -// UserTitleMini defines model for UserTitleMini. -type UserTitleMini struct { - Ctime *time.Time `json:"ctime,omitempty"` - Rate *int32 `json:"rate,omitempty"` - ReviewId *int64 `json:"review_id,omitempty"` - - // Status User's title status - Status UserTitleStatus `json:"status"` - TitleId int64 `json:"title_id"` - UserId int64 `json:"user_id"` + Status UserTitleStatus `json:"status"` + Title *Title `json:"title,omitempty"` + UserId int64 `json:"user_id"` + AdditionalProperties map[string]interface{} `json:"-"` } // UserTitleStatus User's title status @@ -237,32 +226,11 @@ type GetUsersUserIdTitlesParams struct { Fields *string `form:"fields,omitempty" json:"fields,omitempty"` } -// UpdateUserTitleJSONBody defines parameters for UpdateUserTitle. -type UpdateUserTitleJSONBody struct { - Rate *int32 `json:"rate,omitempty"` - - // Status User's title status - Status *UserTitleStatus `json:"status,omitempty"` - TitleId int64 `json:"title_id"` -} - -// AddUserTitleJSONBody defines parameters for AddUserTitle. -type AddUserTitleJSONBody struct { - Rate *int32 `json:"rate,omitempty"` - - // Status User's title status - Status UserTitleStatus `json:"status"` - TitleId int64 `json:"title_id"` -} - // UpdateUserJSONRequestBody defines body for UpdateUser for application/json ContentType. type UpdateUserJSONRequestBody UpdateUserJSONBody -// UpdateUserTitleJSONRequestBody defines body for UpdateUserTitle for application/json ContentType. -type UpdateUserTitleJSONRequestBody UpdateUserTitleJSONBody - // AddUserTitleJSONRequestBody defines body for AddUserTitle for application/json ContentType. -type AddUserTitleJSONRequestBody AddUserTitleJSONBody +type AddUserTitleJSONRequestBody = UserTitle // Getter for additional properties for Title. Returns the specified // element and whether it was found @@ -506,6 +474,145 @@ func (a Title) MarshalJSON() ([]byte, error) { return json.Marshal(object) } +// Getter for additional properties for UserTitle. Returns the specified +// element and whether it was found +func (a UserTitle) Get(fieldName string) (value interface{}, found bool) { + if a.AdditionalProperties != nil { + value, found = a.AdditionalProperties[fieldName] + } + return +} + +// Setter for additional properties for UserTitle +func (a *UserTitle) Set(fieldName string, value interface{}) { + if a.AdditionalProperties == nil { + a.AdditionalProperties = make(map[string]interface{}) + } + a.AdditionalProperties[fieldName] = value +} + +// Override default JSON handling for UserTitle to handle AdditionalProperties +func (a *UserTitle) UnmarshalJSON(b []byte) error { + object := make(map[string]json.RawMessage) + err := json.Unmarshal(b, &object) + if err != nil { + return err + } + + if raw, found := object["ctime"]; found { + err = json.Unmarshal(raw, &a.Ctime) + if err != nil { + return fmt.Errorf("error reading 'ctime': %w", err) + } + delete(object, "ctime") + } + + if raw, found := object["rate"]; found { + err = json.Unmarshal(raw, &a.Rate) + if err != nil { + return fmt.Errorf("error reading 'rate': %w", err) + } + delete(object, "rate") + } + + if raw, found := object["review_id"]; found { + err = json.Unmarshal(raw, &a.ReviewId) + if err != nil { + return fmt.Errorf("error reading 'review_id': %w", err) + } + delete(object, "review_id") + } + + if raw, found := object["status"]; found { + err = json.Unmarshal(raw, &a.Status) + if err != nil { + return fmt.Errorf("error reading 'status': %w", err) + } + delete(object, "status") + } + + if raw, found := object["title"]; found { + err = json.Unmarshal(raw, &a.Title) + if err != nil { + return fmt.Errorf("error reading 'title': %w", err) + } + delete(object, "title") + } + + if raw, found := object["user_id"]; found { + err = json.Unmarshal(raw, &a.UserId) + if err != nil { + return fmt.Errorf("error reading 'user_id': %w", err) + } + delete(object, "user_id") + } + + if len(object) != 0 { + a.AdditionalProperties = make(map[string]interface{}) + for fieldName, fieldBuf := range object { + var fieldVal interface{} + err := json.Unmarshal(fieldBuf, &fieldVal) + if err != nil { + return fmt.Errorf("error unmarshaling field %s: %w", fieldName, err) + } + a.AdditionalProperties[fieldName] = fieldVal + } + } + return nil +} + +// Override default JSON handling for UserTitle to handle AdditionalProperties +func (a UserTitle) MarshalJSON() ([]byte, error) { + var err error + object := make(map[string]json.RawMessage) + + if a.Ctime != nil { + object["ctime"], err = json.Marshal(a.Ctime) + if err != nil { + return nil, fmt.Errorf("error marshaling 'ctime': %w", err) + } + } + + if a.Rate != nil { + object["rate"], err = json.Marshal(a.Rate) + if err != nil { + return nil, fmt.Errorf("error marshaling 'rate': %w", err) + } + } + + if a.ReviewId != nil { + object["review_id"], err = json.Marshal(a.ReviewId) + if err != nil { + return nil, fmt.Errorf("error marshaling 'review_id': %w", err) + } + } + + object["status"], err = json.Marshal(a.Status) + if err != nil { + return nil, fmt.Errorf("error marshaling 'status': %w", err) + } + + if a.Title != nil { + object["title"], err = json.Marshal(a.Title) + if err != nil { + return nil, fmt.Errorf("error marshaling 'title': %w", err) + } + } + + object["user_id"], err = json.Marshal(a.UserId) + if err != nil { + return nil, fmt.Errorf("error marshaling 'user_id': %w", err) + } + + for fieldName, field := range a.AdditionalProperties { + object[fieldName], err = json.Marshal(field) + if err != nil { + return nil, fmt.Errorf("error marshaling '%s': %w", fieldName, err) + } + } + return json.Marshal(object) +} + // ServerInterface represents all server handlers. type ServerInterface interface { // Get titles @@ -520,15 +627,9 @@ type ServerInterface interface { // Partially update a user account // (PATCH /users/{user_id}) UpdateUser(c *gin.Context, userId int64) - // Delete a usertitle - // (DELETE /users/{user_id}/titles) - DeleteUserTitle(c *gin.Context, userId int64) // Get user titles // (GET /users/{user_id}/titles) GetUsersUserIdTitles(c *gin.Context, userId string, params GetUsersUserIdTitlesParams) - // Update a usertitle - // (PATCH /users/{user_id}/titles) - UpdateUserTitle(c *gin.Context, userId int64) // Add a title to a user // (POST /users/{user_id}/titles) AddUserTitle(c *gin.Context, userId int64) @@ -743,30 +844,6 @@ func (siw *ServerInterfaceWrapper) UpdateUser(c *gin.Context) { siw.Handler.UpdateUser(c, userId) } -// DeleteUserTitle operation middleware -func (siw *ServerInterfaceWrapper) DeleteUserTitle(c *gin.Context) { - - var err error - - // ------------- Path parameter "user_id" ------------- - var userId int64 - - err = runtime.BindStyledParameterWithOptions("simple", "user_id", c.Param("user_id"), &userId, runtime.BindStyledParameterOptions{Explode: false, Required: true}) - if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter user_id: %w", err), http.StatusBadRequest) - return - } - - for _, middleware := range siw.HandlerMiddlewares { - middleware(c) - if c.IsAborted() { - return - } - } - - siw.Handler.DeleteUserTitle(c, userId) -} - // GetUsersUserIdTitles operation middleware func (siw *ServerInterfaceWrapper) GetUsersUserIdTitles(c *gin.Context) { @@ -890,30 +967,6 @@ func (siw *ServerInterfaceWrapper) GetUsersUserIdTitles(c *gin.Context) { siw.Handler.GetUsersUserIdTitles(c, userId, params) } -// UpdateUserTitle operation middleware -func (siw *ServerInterfaceWrapper) UpdateUserTitle(c *gin.Context) { - - var err error - - // ------------- Path parameter "user_id" ------------- - var userId int64 - - err = runtime.BindStyledParameterWithOptions("simple", "user_id", c.Param("user_id"), &userId, runtime.BindStyledParameterOptions{Explode: false, Required: true}) - if err != nil { - siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter user_id: %w", err), http.StatusBadRequest) - return - } - - for _, middleware := range siw.HandlerMiddlewares { - middleware(c) - if c.IsAborted() { - return - } - } - - siw.Handler.UpdateUserTitle(c, userId) -} - // AddUserTitle operation middleware func (siw *ServerInterfaceWrapper) AddUserTitle(c *gin.Context) { @@ -969,9 +1022,7 @@ func RegisterHandlersWithOptions(router gin.IRouter, si ServerInterface, options router.GET(options.BaseURL+"/titles/:title_id", wrapper.GetTitlesTitleId) router.GET(options.BaseURL+"/users/:user_id", wrapper.GetUsersUserId) router.PATCH(options.BaseURL+"/users/:user_id", wrapper.UpdateUser) - router.DELETE(options.BaseURL+"/users/:user_id/titles", wrapper.DeleteUserTitle) router.GET(options.BaseURL+"/users/:user_id/titles", wrapper.GetUsersUserIdTitles) - router.PATCH(options.BaseURL+"/users/:user_id/titles", wrapper.UpdateUserTitle) router.POST(options.BaseURL+"/users/:user_id/titles", wrapper.AddUserTitle) } @@ -1187,54 +1238,6 @@ func (response UpdateUser500Response) VisitUpdateUserResponse(w http.ResponseWri return nil } -type DeleteUserTitleRequestObject struct { - UserId int64 `json:"user_id"` -} - -type DeleteUserTitleResponseObject interface { - VisitDeleteUserTitleResponse(w http.ResponseWriter) error -} - -type DeleteUserTitle200Response struct { -} - -func (response DeleteUserTitle200Response) VisitDeleteUserTitleResponse(w http.ResponseWriter) error { - w.WriteHeader(200) - return nil -} - -type DeleteUserTitle401Response struct { -} - -func (response DeleteUserTitle401Response) VisitDeleteUserTitleResponse(w http.ResponseWriter) error { - w.WriteHeader(401) - return nil -} - -type DeleteUserTitle403Response struct { -} - -func (response DeleteUserTitle403Response) VisitDeleteUserTitleResponse(w http.ResponseWriter) error { - w.WriteHeader(403) - return nil -} - -type DeleteUserTitle404Response struct { -} - -func (response DeleteUserTitle404Response) VisitDeleteUserTitleResponse(w http.ResponseWriter) error { - w.WriteHeader(404) - return nil -} - -type DeleteUserTitle500Response struct { -} - -func (response DeleteUserTitle500Response) VisitDeleteUserTitleResponse(w http.ResponseWriter) error { - w.WriteHeader(500) - return nil -} - type GetUsersUserIdTitlesRequestObject struct { UserId string `json:"user_id"` Params GetUsersUserIdTitlesParams @@ -1288,64 +1291,6 @@ func (response GetUsersUserIdTitles500Response) VisitGetUsersUserIdTitlesRespons return nil } -type UpdateUserTitleRequestObject struct { - UserId int64 `json:"user_id"` - Body *UpdateUserTitleJSONRequestBody -} - -type UpdateUserTitleResponseObject interface { - VisitUpdateUserTitleResponse(w http.ResponseWriter) error -} - -type UpdateUserTitle200JSONResponse UserTitleMini - -func (response UpdateUserTitle200JSONResponse) VisitUpdateUserTitleResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(200) - - return json.NewEncoder(w).Encode(response) -} - -type UpdateUserTitle400Response struct { -} - -func (response UpdateUserTitle400Response) VisitUpdateUserTitleResponse(w http.ResponseWriter) error { - w.WriteHeader(400) - return nil -} - -type UpdateUserTitle401Response struct { -} - -func (response UpdateUserTitle401Response) VisitUpdateUserTitleResponse(w http.ResponseWriter) error { - w.WriteHeader(401) - return nil -} - -type UpdateUserTitle403Response struct { -} - -func (response UpdateUserTitle403Response) VisitUpdateUserTitleResponse(w http.ResponseWriter) error { - w.WriteHeader(403) - return nil -} - -type UpdateUserTitle404Response struct { -} - -func (response UpdateUserTitle404Response) VisitUpdateUserTitleResponse(w http.ResponseWriter) error { - w.WriteHeader(404) - return nil -} - -type UpdateUserTitle500Response struct { -} - -func (response UpdateUserTitle500Response) VisitUpdateUserTitleResponse(w http.ResponseWriter) error { - w.WriteHeader(500) - return nil -} - type AddUserTitleRequestObject struct { UserId int64 `json:"user_id"` Body *AddUserTitleJSONRequestBody @@ -1355,7 +1300,18 @@ type AddUserTitleResponseObject interface { VisitAddUserTitleResponse(w http.ResponseWriter) error } -type AddUserTitle200JSONResponse UserTitleMini +type AddUserTitle200JSONResponse struct { + Data *struct { + Ctime *time.Time `json:"ctime,omitempty"` + Rate *int32 `json:"rate,omitempty"` + ReviewId *int64 `json:"review_id,omitempty"` + + // Status User's title status + Status UserTitleStatus `json:"status"` + TitleId int64 `json:"title_id"` + UserId int64 `json:"user_id"` + } `json:"data,omitempty"` +} func (response AddUserTitle200JSONResponse) VisitAddUserTitleResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") @@ -1426,15 +1382,9 @@ type StrictServerInterface interface { // Partially update a user account // (PATCH /users/{user_id}) UpdateUser(ctx context.Context, request UpdateUserRequestObject) (UpdateUserResponseObject, error) - // Delete a usertitle - // (DELETE /users/{user_id}/titles) - DeleteUserTitle(ctx context.Context, request DeleteUserTitleRequestObject) (DeleteUserTitleResponseObject, error) // Get user titles // (GET /users/{user_id}/titles) GetUsersUserIdTitles(ctx context.Context, request GetUsersUserIdTitlesRequestObject) (GetUsersUserIdTitlesResponseObject, error) - // Update a usertitle - // (PATCH /users/{user_id}/titles) - UpdateUserTitle(ctx context.Context, request UpdateUserTitleRequestObject) (UpdateUserTitleResponseObject, error) // Add a title to a user // (POST /users/{user_id}/titles) AddUserTitle(ctx context.Context, request AddUserTitleRequestObject) (AddUserTitleResponseObject, error) @@ -1570,33 +1520,6 @@ func (sh *strictHandler) UpdateUser(ctx *gin.Context, userId int64) { } } -// DeleteUserTitle operation middleware -func (sh *strictHandler) DeleteUserTitle(ctx *gin.Context, userId int64) { - var request DeleteUserTitleRequestObject - - request.UserId = userId - - handler := func(ctx *gin.Context, request interface{}) (interface{}, error) { - return sh.ssi.DeleteUserTitle(ctx, request.(DeleteUserTitleRequestObject)) - } - for _, middleware := range sh.middlewares { - handler = middleware(handler, "DeleteUserTitle") - } - - response, err := handler(ctx, request) - - if err != nil { - ctx.Error(err) - ctx.Status(http.StatusInternalServerError) - } else if validResponse, ok := response.(DeleteUserTitleResponseObject); ok { - if err := validResponse.VisitDeleteUserTitleResponse(ctx.Writer); err != nil { - ctx.Error(err) - } - } else if response != nil { - ctx.Error(fmt.Errorf("unexpected response type: %T", response)) - } -} - // GetUsersUserIdTitles operation middleware func (sh *strictHandler) GetUsersUserIdTitles(ctx *gin.Context, userId string, params GetUsersUserIdTitlesParams) { var request GetUsersUserIdTitlesRequestObject @@ -1625,41 +1548,6 @@ func (sh *strictHandler) GetUsersUserIdTitles(ctx *gin.Context, userId string, p } } -// UpdateUserTitle operation middleware -func (sh *strictHandler) UpdateUserTitle(ctx *gin.Context, userId int64) { - var request UpdateUserTitleRequestObject - - request.UserId = userId - - var body UpdateUserTitleJSONRequestBody - if err := ctx.ShouldBindJSON(&body); err != nil { - ctx.Status(http.StatusBadRequest) - ctx.Error(err) - return - } - request.Body = &body - - handler := func(ctx *gin.Context, request interface{}) (interface{}, error) { - return sh.ssi.UpdateUserTitle(ctx, request.(UpdateUserTitleRequestObject)) - } - for _, middleware := range sh.middlewares { - handler = middleware(handler, "UpdateUserTitle") - } - - response, err := handler(ctx, request) - - if err != nil { - ctx.Error(err) - ctx.Status(http.StatusInternalServerError) - } else if validResponse, ok := response.(UpdateUserTitleResponseObject); ok { - if err := validResponse.VisitUpdateUserTitleResponse(ctx.Writer); err != nil { - ctx.Error(err) - } - } else if response != nil { - ctx.Error(fmt.Errorf("unexpected response type: %T", response)) - } -} - // AddUserTitle operation middleware func (sh *strictHandler) AddUserTitle(ctx *gin.Context, userId int64) { var request AddUserTitleRequestObject diff --git a/api/openapi.yaml b/api/openapi.yaml index 23f2058..7da26f8 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -21,3 +21,4 @@ components: $ref: "./parameters/_index.yaml" schemas: $ref: "./schemas/_index.yaml" + \ No newline at end of file diff --git a/api/paths/users-id-titles.yaml b/api/paths/users-id-titles.yaml index 18c805e..23ea761 100644 --- a/api/paths/users-id-titles.yaml +++ b/api/paths/users-id-titles.yaml @@ -108,27 +108,27 @@ post: content: application/json: schema: - type: object - required: - - title_id - - status - properties: - title_id: - type: integer - format: int64 - status: - $ref: '../schemas/enums/UserTitleStatus.yaml' - rate: - type: integer - format: int32 + $ref: '../schemas/UserTitle.yaml' responses: '200': description: Title successfully added to user content: application/json: schema: - $ref: '../schemas/UserTitleMini.yaml' - + type: object + properties: + # success: + # type: boolean + # example: true + # error: + # type: string + # nullable: true + # example: null + data: + $ref: '../schemas/UserTitleMini.yaml' + # required: + # - success + # - error '400': description: Invalid request body (missing fields, invalid types, etc.) '401': @@ -139,83 +139,5 @@ post: description: User or Title not found '409': description: Conflict — title already assigned to user (if applicable) - '500': - description: Internal server error - -patch: - summary: Update a usertitle - description: User updating title list of watched - operationId: updateUserTitle - parameters: - - name: user_id - in: path - required: true - schema: - type: integer - format: int64 - description: ID of the user to assign the title to - example: 123 - requestBody: - required: true - content: - application/json: - schema: - type: object - required: - - title_id - properties: - title_id: - type: integer - format: int64 - status: - $ref: '../schemas/enums/UserTitleStatus.yaml' - rate: - type: integer - format: int32 - - responses: - '200': - description: Title successfully updated - content: - application/json: - schema: - $ref: '../schemas/UserTitleMini.yaml' - - '400': - description: Invalid request body (missing fields, invalid types, etc.) - '401': - description: Unauthorized — missing or invalid auth token - '403': - description: Forbidden — user not allowed to update title - '404': - description: User or Title not found - '500': - description: Internal server error - -delete: - summary: Delete a usertitle - description: User deleting title from list of watched - operationId: deleteUserTitle - parameters: - - name: user_id - in: path - required: true - schema: - type: integer - format: int64 - description: ID of the user to assign the title to - example: 123 - - responses: - '200': - description: Title successfully deleted - # '400': - # description: Invalid request body (missing fields, invalid types, etc.) - '401': - description: Unauthorized — missing or invalid auth token - '403': - description: Forbidden — user not allowed to delete title - '404': - description: User or Title not found '500': description: Internal server error \ No newline at end of file diff --git a/api/schemas/UserTitle.yaml b/api/schemas/UserTitle.yaml index ef619cb..3beaec6 100644 --- a/api/schemas/UserTitle.yaml +++ b/api/schemas/UserTitle.yaml @@ -20,3 +20,4 @@ properties: ctime: type: string format: date-time +additionalProperties: true diff --git a/api/schemas/UserTitleMini.yaml b/api/schemas/UserTitleMini.yaml index e20bcbf..9e45e95 100644 --- a/api/schemas/UserTitleMini.yaml +++ b/api/schemas/UserTitleMini.yaml @@ -20,4 +20,5 @@ properties: format: int64 ctime: type: string - format: date-time \ No newline at end of file + format: date-time +additionalProperties: false diff --git a/auth/auth.gen.go b/auth/auth.gen.go index b24deb5..adb2b06 100644 --- a/auth/auth.gen.go +++ b/auth/auth.gen.go @@ -116,9 +116,9 @@ type PostAuthSignInResponseObject interface { } type PostAuthSignIn200JSONResponse struct { - Error *string `json:"error"` - UserId *string `json:"user_id"` - UserName *string `json:"user_name"` + Error *string `json:"error"` + Success *bool `json:"success,omitempty"` + UserId *string `json:"user_id"` } func (response PostAuthSignIn200JSONResponse) VisitPostAuthSignInResponse(w http.ResponseWriter) error { diff --git a/auth/openapi-auth.yaml b/auth/openapi-auth.yaml index 0fe308c..913c000 100644 --- a/auth/openapi-auth.yaml +++ b/auth/openapi-auth.yaml @@ -59,23 +59,29 @@ paths: type: string format: password responses: - # This one also sets two cookies: access_token and refresh_token "200": description: Sign-in result with JWT + # headers: + # Set-Cookie: + # schema: + # type: array + # items: + # type: string + # explode: true + # style: simple content: application/json: schema: type: object properties: + success: + type: boolean error: type: string nullable: true user_id: type: string nullable: true - user_name: - type: string - nullable: true "401": description: Access denied due to invalid credentials content: diff --git a/deploy/api_gen.ps1 b/deploy/api_gen.ps1 deleted file mode 100644 index c8966b7..0000000 --- a/deploy/api_gen.ps1 +++ /dev/null @@ -1,4 +0,0 @@ -cd ./api -openapi-format .\openapi.yaml --output .\_build\openapi.yaml --yaml -cd .. -oapi-codegen --config=api\oapi-codegen.yaml api\_build\openapi.yaml diff --git a/modules/auth/handlers/handlers.go b/modules/auth/handlers/handlers.go index 7f675aa..9b9b0d3 100644 --- a/modules/auth/handlers/handlers.go +++ b/modules/auth/handlers/handlers.go @@ -78,6 +78,7 @@ func (s Server) PostAuthSignIn(ctx context.Context, req auth.PostAuthSignInReque } err := "" + success := true pass, ok := UserDb[req.Body.Nickname] if !ok || pass != req.Body.Pass { @@ -95,9 +96,9 @@ func (s Server) PostAuthSignIn(ctx context.Context, req auth.PostAuthSignInReque // Return access token; refresh token can be returned in response or HttpOnly cookie result := auth.PostAuthSignIn200JSONResponse{ - Error: &err, - UserId: &req.Body.Nickname, - UserName: &req.Body.Nickname, + Error: &err, + Success: &success, + UserId: &req.Body.Nickname, } return result, nil } diff --git a/modules/backend/handlers/users.go b/modules/backend/handlers/users.go index 1881f36..927c1c1 100644 --- a/modules/backend/handlers/users.go +++ b/modules/backend/handlers/users.go @@ -101,14 +101,14 @@ func sqlDate2oapi(p_date pgtype.Timestamptz) *time.Time { func sql2usertitlestatus(s sqlc.UsertitleStatusT) (oapi.UserTitleStatus, error) { var status oapi.UserTitleStatus - switch s { - case sqlc.UsertitleStatusTFinished: + switch status { + case "finished": status = oapi.UserTitleStatusFinished - case sqlc.UsertitleStatusTDropped: + case "dropped": status = oapi.UserTitleStatusDropped - case sqlc.UsertitleStatusTPlanned: + case "planned": status = oapi.UserTitleStatusPlanned - case sqlc.UsertitleStatusTInProgress: + case "in-progress": status = oapi.UserTitleStatusInProgress default: return status, fmt.Errorf("unexpected tittle status: %s", s) @@ -140,9 +140,9 @@ func UserTitleStatus2Sqlc(s *[]oapi.UserTitleStatus) ([]sqlc.UsertitleStatusT, e } func UserTitleStatus2Sqlc1(s *oapi.UserTitleStatus) (*sqlc.UsertitleStatusT, error) { - var sqlc_status sqlc.UsertitleStatusT = sqlc.UsertitleStatusTFinished + var sqlc_status sqlc.UsertitleStatusT if s == nil { - return &sqlc_status, nil + return nil, nil } switch *s { @@ -304,7 +304,7 @@ func (s Server) GetUsersUserIdTitles(ctx context.Context, request oapi.GetUsersU tmp := fmt.Sprint(*t.Title.ReleaseYear) new_cursor.Param = &tmp case "rating": - tmp := strconv.FormatFloat(*t.Title.Rating, 'f', -1, 64) // падает + tmp := strconv.FormatFloat(*t.Title.Rating, 'f', -1, 64) new_cursor.Param = &tmp } } @@ -360,7 +360,7 @@ func (s Server) UpdateUser(ctx context.Context, request oapi.UpdateUserRequestOb } func (s Server) AddUserTitle(ctx context.Context, request oapi.AddUserTitleRequestObject) (oapi.AddUserTitleResponseObject, error) { - //TODO: add review if exists + status, err := UserTitleStatus2Sqlc1(&request.Body.Status) if err != nil { log.Errorf("%v", err) @@ -369,7 +369,7 @@ func (s Server) AddUserTitle(ctx context.Context, request oapi.AddUserTitleReque params := sqlc.InsertUserTitleParams{ UserID: request.UserId, - TitleID: request.Body.TitleId, + TitleID: request.Body.Title.Id, Status: *status, Rate: request.Body.Rate, ReviewID: request.Body.ReviewId, @@ -404,5 +404,5 @@ func (s Server) AddUserTitle(ctx context.Context, request oapi.AddUserTitleReque UserId: user_title.UserID, } - return oapi.AddUserTitle200JSONResponse(oapi_usertitle), nil + return oapi.AddUserTitle200JSONResponse{Data: &oapi_usertitle}, nil } diff --git a/modules/frontend/src/App.tsx b/modules/frontend/src/App.tsx index 3ecfa2d..5a25313 100644 --- a/modules/frontend/src/App.tsx +++ b/modules/frontend/src/App.tsx @@ -1,34 +1,23 @@ import React from "react"; import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; -import UsersIdPage from "./pages/UsersIdPage/UsersIdPage"; +import UserPage from "./pages/UserPage/UserPage"; import TitlesPage from "./pages/TitlesPage/TitlesPage"; import { LoginPage } from "./pages/LoginPage/LoginPage"; import { Header } from "./components/Header/Header"; const App: React.FC = () => { - // Получаем username из localStorage - const username = localStorage.getItem("username") || undefined; - const userId = localStorage.getItem("userId"); - + const username = "nihonium"; return (
- } /> - } /> - - {/* /profile рендерит UsersIdPage с id из localStorage */} - : } - /> - - } /> + } /> {/* <-- маршрут для логина */} + } /> {/* <-- можно использовать тот же компонент для регистрации */} + } /> } /> ); }; - export default App; \ No newline at end of file diff --git a/modules/frontend/src/api/core/OpenAPI.ts b/modules/frontend/src/api/core/OpenAPI.ts index 185e5c3..6ce873e 100644 --- a/modules/frontend/src/api/core/OpenAPI.ts +++ b/modules/frontend/src/api/core/OpenAPI.ts @@ -20,7 +20,7 @@ export type OpenAPIConfig = { }; export const OpenAPI: OpenAPIConfig = { - BASE: '/api/v1', + BASE: 'http://10.1.0.65:8081/api/v1', VERSION: '1.0.0', WITH_CREDENTIALS: false, CREDENTIALS: 'include', diff --git a/modules/frontend/src/api/models/Image.ts b/modules/frontend/src/api/models/Image.ts index a94de74..1317db7 100644 --- a/modules/frontend/src/api/models/Image.ts +++ b/modules/frontend/src/api/models/Image.ts @@ -4,10 +4,7 @@ /* eslint-disable */ export type Image = { id?: number; - /** - * Image storage type - */ - storage_type?: 's3' | 'local'; + storage_type?: string; image_path?: string; }; diff --git a/modules/frontend/src/api/models/User.ts b/modules/frontend/src/api/models/User.ts index 969023f..cd76dbe 100644 --- a/modules/frontend/src/api/models/User.ts +++ b/modules/frontend/src/api/models/User.ts @@ -2,13 +2,15 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -import type { Image } from './Image'; export type User = { /** * Unique user ID (primary key) */ id?: number; - image?: Image; + /** + * ID of the user avatar (references images table) + */ + avatar_id?: number; /** * User email */ diff --git a/modules/frontend/src/api/models/UserTitle.ts b/modules/frontend/src/api/models/UserTitle.ts index 42b7919..26d5ddc 100644 --- a/modules/frontend/src/api/models/UserTitle.ts +++ b/modules/frontend/src/api/models/UserTitle.ts @@ -2,14 +2,4 @@ /* istanbul ignore file */ /* tslint:disable */ /* eslint-disable */ -import type { Title } from './Title'; -import type { UserTitleStatus } from './UserTitleStatus'; -export type UserTitle = { - user_id: number; - title?: Title; - status: UserTitleStatus; - rate?: number; - review_id?: number; - ctime?: string; -}; - +export type UserTitle = Record; diff --git a/modules/frontend/src/api/services/DefaultService.ts b/modules/frontend/src/api/services/DefaultService.ts index 874971e..52321b8 100644 --- a/modules/frontend/src/api/services/DefaultService.ts +++ b/modules/frontend/src/api/services/DefaultService.ts @@ -20,7 +20,7 @@ export class DefaultService { * @param sort * @param sortForward * @param word - * @param status List of title statuses to filter + * @param status * @param rating * @param releaseYear * @param releaseSeason @@ -35,7 +35,7 @@ export class DefaultService { sort?: TitleSort, sortForward: boolean = true, word?: string, - status?: Array, + status?: TitleStatus, rating?: number, releaseYear?: number, releaseSeason?: ReleaseSeason, @@ -125,112 +125,45 @@ export class DefaultService { }, }); } - /** - * Partially update a user account - * Update selected user profile fields (excluding password). - * Password updates must be done via the dedicated auth-service (`/auth/`). - * Fields not provided in the request body remain unchanged. - * - * @param userId User ID (primary key) - * @param requestBody - * @returns User User updated successfully. Returns updated user representation (excluding sensitive fields). - * @throws ApiError - */ - public static updateUser( - userId: number, - requestBody: { - /** - * ID of the user avatar (references `images.id`); set to `null` to remove avatar - */ - avatar_id?: number | null; - /** - * User email (must be unique and valid) - */ - mail?: string; - /** - * Username (alphanumeric + `_` or `-`, 3–16 chars) - */ - nickname?: string; - /** - * Display name - */ - disp_name?: string; - /** - * User description / bio - */ - user_desc?: string; - }, - ): CancelablePromise { - return __request(OpenAPI, { - method: 'PATCH', - url: '/users/{user_id}', - path: { - 'user_id': userId, - }, - body: requestBody, - mediaType: 'application/json', - errors: { - 400: `Invalid input (e.g., validation failed, nickname/email conflict, malformed JSON)`, - 401: `Unauthorized — missing or invalid authentication token`, - 403: `Forbidden — user is not allowed to modify this resource (e.g., not own profile & no admin rights)`, - 404: `User not found`, - 409: `Conflict — e.g., requested \`nickname\` or \`mail\` already taken by another user`, - 422: `Unprocessable Entity — semantic errors not caught by schema (e.g., invalid \`avatar_id\`)`, - 500: `Unknown server error`, - }, - }); - } /** * Get user titles * @param userId * @param cursor - * @param sort - * @param sortForward * @param word - * @param status List of title statuses to filter + * @param status * @param watchStatus * @param rating - * @param myRate * @param releaseYear * @param releaseSeason * @param limit * @param fields - * @returns any List of user titles + * @returns UserTitle List of user titles * @throws ApiError */ public static getUsersTitles( userId: string, cursor?: string, - sort?: TitleSort, - sortForward: boolean = true, word?: string, - status?: Array, - watchStatus?: Array, + status?: TitleStatus, + watchStatus?: UserTitleStatus, rating?: number, - myRate?: number, releaseYear?: number, releaseSeason?: ReleaseSeason, limit: number = 10, fields: string = 'all', - ): CancelablePromise<{ - data: Array; - cursor: CursorObj; - }> { + ): CancelablePromise> { return __request(OpenAPI, { method: 'GET', - url: '/users/{user_id}/titles', + url: '/users/{user_id}/titles/', path: { 'user_id': userId, }, query: { 'cursor': cursor, - 'sort': sort, - 'sort_forward': sortForward, 'word': word, 'status': status, 'watch_status': watchStatus, 'rating': rating, - 'my_rate': myRate, 'release_year': releaseYear, 'release_season': releaseSeason, 'limit': limit, @@ -238,48 +171,8 @@ export class DefaultService { }, errors: { 400: `Request params are not correct`, - 404: `User not found`, 500: `Unknown server error`, }, }); } - /** - * Add a title to a user - * User adding title to list af watched, status required - * @param userId ID of the user to assign the title to - * @param requestBody - * @returns any Title successfully added to user - * @throws ApiError - */ - public static addUserTitle( - userId: number, - requestBody: UserTitle, - ): CancelablePromise<{ - data?: { - user_id: number; - title_id: number; - status: UserTitleStatus; - rate?: number; - review_id?: number; - ctime?: string; - }; - }> { - return __request(OpenAPI, { - method: 'POST', - url: '/users/{user_id}/titles', - path: { - 'user_id': userId, - }, - body: requestBody, - mediaType: 'application/json', - errors: { - 400: `Invalid request body (missing fields, invalid types, etc.)`, - 401: `Unauthorized — missing or invalid auth token`, - 403: `Forbidden — user not allowed to assign titles to this user`, - 404: `User or Title not found`, - 409: `Conflict — title already assigned to user (if applicable)`, - 500: `Internal server error`, - }, - }); - } } diff --git a/modules/frontend/src/auth/services/AuthService.ts b/modules/frontend/src/auth/services/AuthService.ts index 94578d8..bab9c77 100644 --- a/modules/frontend/src/auth/services/AuthService.ts +++ b/modules/frontend/src/auth/services/AuthService.ts @@ -41,9 +41,9 @@ export class AuthService { pass: string; }, ): CancelablePromise<{ + success?: boolean; error?: string | null; user_id?: string | null; - user_name?: string | null; }> { return __request(OpenAPI, { method: 'POST', diff --git a/modules/frontend/src/components/Header/Header.tsx b/modules/frontend/src/components/Header/Header.tsx index 26f1658..98b1295 100644 --- a/modules/frontend/src/components/Header/Header.tsx +++ b/modules/frontend/src/components/Header/Header.tsx @@ -12,7 +12,7 @@ export const Header: React.FC = ({ username }) => { const toggleMenu = () => setMenuOpen(!menuOpen); return ( -
+
diff --git a/modules/frontend/src/components/cards/UserTitleCardHorizontal.tsx b/modules/frontend/src/components/cards/UserTitleCardHorizontal.tsx deleted file mode 100644 index ad7d5df..0000000 --- a/modules/frontend/src/components/cards/UserTitleCardHorizontal.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import type { UserTitle } from "../../api"; - -export function UserTitleCardHorizontal({ title }: { title: UserTitle }) { - return ( -
- {title.title?.poster?.image_path && ( - - )} -
-

{title.title?.title_names["en"]}

-

{title.title?.release_year} · {title.title?.release_season} · Rating: {title.title?.rating}

-

Status: {title.title?.title_status}

-
-
- ); -} diff --git a/modules/frontend/src/components/cards/UserTitleCardSquare.tsx b/modules/frontend/src/components/cards/UserTitleCardSquare.tsx deleted file mode 100644 index edcf1d5..0000000 --- a/modules/frontend/src/components/cards/UserTitleCardSquare.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import type { UserTitle } from "../../api"; - -export function UserTitleCardSquare({ title }: { title: UserTitle }) { - return ( -
- {title.title?.poster?.image_path && ( - - )} -
-

{title.title?.title_names["en"]}

-
{title.status}
- {title.title?.release_year} • {title.title?.rating} -
-
- ); -} diff --git a/modules/frontend/src/pages/LoginPage/LoginPage.tsx b/modules/frontend/src/pages/LoginPage/LoginPage.tsx index 89ee88c..dcd6965 100644 --- a/modules/frontend/src/pages/LoginPage/LoginPage.tsx +++ b/modules/frontend/src/pages/LoginPage/LoginPage.tsx @@ -18,19 +18,17 @@ export const LoginPage: React.FC = () => { try { if (isLogin) { const res = await AuthService.postAuthSignIn({ nickname, pass: password }); - if (res.user_id && res.user_name) { - // Сохраняем user_id и username в localStorage - localStorage.setItem("userId", res.user_id); - localStorage.setItem("username", res.user_name); - - navigate("/profile"); // редирект на профиль + if (res.success) { + // TODO: сохранить JWT в localStorage/cookie + console.log("Logged in user id:", res.user_id); + navigate("/"); // редирект после успешного входа } else { setError(res.error || "Login failed"); } } else { - // SignUp оставляем без сохранения данных const res = await AuthService.postAuthSignUp({ nickname, pass: password }); - if (res.user_id) { + if (res.success) { + console.log("User signed up with id:", res.user_id); setIsLogin(true); // переключаемся на login после регистрации } else { setError(res.error || "Sign up failed"); diff --git a/modules/frontend/src/pages/UserPage/UserPage.tsx b/modules/frontend/src/pages/UserPage/UserPage.tsx index 2e39e6b..52c5574 100644 --- a/modules/frontend/src/pages/UserPage/UserPage.tsx +++ b/modules/frontend/src/pages/UserPage/UserPage.tsx @@ -35,9 +35,9 @@ const UserPage: React.FC = () => {
- {user.image?.image_path ? ( + {user.avatar_id ? ( User Avatar diff --git a/modules/frontend/src/pages/UsersIdPage/UsersIdPage.tsx b/modules/frontend/src/pages/UsersIdPage/UsersIdPage.tsx deleted file mode 100644 index 342f22c..0000000 --- a/modules/frontend/src/pages/UsersIdPage/UsersIdPage.tsx +++ /dev/null @@ -1,183 +0,0 @@ -// pages/UserPage/UserPage.tsx -import { useEffect, useState } from "react"; -import { useParams } from "react-router-dom"; -import { DefaultService } from "../../api/services/DefaultService"; -import { SearchBar } from "../../components/SearchBar/SearchBar"; -import { TitlesSortBox } from "../../components/TitlesSortBox/TitlesSortBox"; -import { LayoutSwitch } from "../../components/LayoutSwitch/LayoutSwitch"; -import { ListView } from "../../components/ListView/ListView"; -import { UserTitleCardSquare } from "../../components/cards/UserTitleCardSquare"; -import { UserTitleCardHorizontal } from "../../components/cards/UserTitleCardHorizontal"; -import type { User, UserTitle, CursorObj, TitleSort } from "../../api"; - -const PAGE_SIZE = 10; - -type UsersIdPageProps = { - userId?: string; -}; - -export default function UsersIdPage({ userId }: UsersIdPageProps) { - const params = useParams(); - const id = userId || params?.id; - - const [user, setUser] = useState(null); - const [loadingUser, setLoadingUser] = useState(true); - const [errorUser, setErrorUser] = useState(null); - - // Для списка тайтлов - const [titles, setTitles] = useState([]); - const [nextPage, setNextPage] = useState([]); - const [cursor, setCursor] = useState(null); - const [loadingTitles, setLoadingTitles] = useState(true); - const [loadingMore, setLoadingMore] = useState(false); - const [search, setSearch] = useState(""); - const [sort, setSort] = useState("id"); - const [sortForward, setSortForward] = useState(true); - const [layout, setLayout] = useState<"square" | "horizontal">("square"); - - // --- Получение данных пользователя --- - useEffect(() => { - const fetchUser = async () => { - if (!id) return; - setLoadingUser(true); - try { - const result = await DefaultService.getUsers(id, "all"); - setUser(result); - setErrorUser(null); - } catch (err: any) { - console.error(err); - setErrorUser(err?.message || "Failed to fetch user data"); - } finally { - setLoadingUser(false); - } - }; - fetchUser(); - }, [id]); - - // --- Получение списка тайтлов пользователя --- - const fetchPage = async (cursorObj: CursorObj | null) => { - if (!id) return { items: [], nextCursor: null }; - const cursorStr = cursorObj - ? btoa(JSON.stringify(cursorObj)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "") - : ""; - - try { - const result = await DefaultService.getUsersTitles( - id, - cursorStr, - sort, - sortForward, - search.trim() || undefined, - undefined, // status фильтр, можно добавить - undefined, // watchStatus - undefined, // rating - undefined, // myRate - undefined, // releaseYear - undefined, // releaseSeason - PAGE_SIZE, - "all" - ); - - if (!result?.data?.length) return { items: [], nextCursor: null }; - - return { items: result.data, nextCursor: result.cursor ?? null }; - } catch (err: any) { - if (err.status === 204) return { items: [], nextCursor: null }; - throw err; - } - }; - - // Инициализация: загружаем сразу две страницы - useEffect(() => { - const initLoad = async () => { - setLoadingTitles(true); - setTitles([]); - setNextPage([]); - setCursor(null); - - const firstPage = await fetchPage(null); - const secondPage = firstPage.nextCursor ? await fetchPage(firstPage.nextCursor) : { items: [], nextCursor: null }; - - setTitles(firstPage.items); - setNextPage(secondPage.items); - setCursor(secondPage.nextCursor); - setLoadingTitles(false); - }; - initLoad(); - }, [id, search, sort, sortForward]); - - const handleLoadMore = async () => { - if (nextPage.length === 0) { - setLoadingMore(false); - return; - } - setLoadingMore(true); - - setTitles(prev => [...prev, ...nextPage]); - setNextPage([]); - - if (cursor) { - try { - const next = await fetchPage(cursor); - if (next.items.length > 0) setNextPage(next.items); - setCursor(next.nextCursor); - } catch (err) { - console.error(err); - } - } - - setLoadingMore(false); - }; - - // const getAvatarUrl = (avatarId?: number) => (avatarId ? `/api/images/${avatarId}` : "/default-avatar.png"); - - return ( -
- - {/* --- Карточка пользователя --- */} - {loadingUser &&
Loading user...
} - {errorUser &&
{errorUser}
} - {user && ( -
- {user.nickname} -

{user.disp_name || user.nickname}

- {user.mail &&

{user.mail}

} - {user.user_desc &&

{user.user_desc}

} - {user.creation_date &&

Registered: {new Date(user.creation_date).toLocaleDateString()}

} -
- )} - - {/* --- Панель поиска, сортировки и лейаута --- */} -
- - - -
- - {/* --- Список тайтлов --- */} - {loadingTitles &&
Loading titles...
} - {!loadingTitles && titles.length === 0 &&
No titles found.
} - - {titles.length > 0 && ( - <> - - items={titles} - layout={layout} - hasMore={!!cursor || nextPage.length > 1} - loadingMore={loadingMore} - onLoadMore={handleLoadMore} - renderItem={(title, layout) => - layout === "square" ? : - } - /> - - {!cursor && nextPage.length === 0 && ( -
- Результатов больше нет, было найдено {titles.length} тайтлов. -
- )} - - )} -
- ); -}