From e792d5780b67d210a60e71a2566848c247384042 Mon Sep 17 00:00:00 2001 From: Iron_Felix Date: Mon, 24 Nov 2025 05:14:23 +0300 Subject: [PATCH] feat: GetUsertitles implemented --- api/_build/openapi.yaml | 25 +++- api/api.gen.go | 36 ++++- api/paths/users-id-titles.yaml | 25 +++- modules/backend/handlers/common.go | 64 +++++---- modules/backend/handlers/titles.go | 110 ++++++++-------- modules/backend/handlers/users.go | 205 ++++++++++++++++++++--------- modules/backend/queries.sql | 72 ++++++---- sql/queries.sql.go | 202 ++++++++++++++-------------- 8 files changed, 456 insertions(+), 283 deletions(-) diff --git a/api/_build/openapi.yaml b/api/_build/openapi.yaml index 722b7af..b3eacb4 100644 --- a/api/_build/openapi.yaml +++ b/api/_build/openapi.yaml @@ -146,11 +146,17 @@ paths: summary: Get user titles parameters: - $ref: '#/components/parameters/cursor' + - $ref: '#/components/parameters/title_sort' - in: path name: user_id required: true schema: type: string + - in: query + name: sort_forward + schema: + type: boolean + default: true - in: query name: word schema: @@ -173,6 +179,11 @@ paths: schema: type: number format: double + - in: query + name: my_rate + schema: + type: integer + format: int32 - in: query name: release_year schema: @@ -199,9 +210,17 @@ paths: content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/UserTitle' + type: object + properties: + data: + type: array + items: + $ref: '#/components/schemas/UserTitle' + cursor: + $ref: '#/components/schemas/CursorObj' + required: + - data + - cursor '204': description: No titles found '400': diff --git a/api/api.gen.go b/api/api.gen.go index 54c8fc1..e1f94c2 100644 --- a/api/api.gen.go +++ b/api/api.gen.go @@ -183,13 +183,16 @@ type GetUsersUserIdParams struct { // GetUsersUserIdTitlesParams defines parameters for GetUsersUserIdTitles. type GetUsersUserIdTitlesParams struct { - Cursor *Cursor `form:"cursor,omitempty" json:"cursor,omitempty"` - Word *string `form:"word,omitempty" json:"word,omitempty"` + Cursor *Cursor `form:"cursor,omitempty" json:"cursor,omitempty"` + Sort *TitleSort `form:"sort,omitempty" json:"sort,omitempty"` + SortForward *bool `form:"sort_forward,omitempty" json:"sort_forward,omitempty"` + Word *string `form:"word,omitempty" json:"word,omitempty"` // Status List of title statuses to filter Status *[]TitleStatus `form:"status,omitempty" json:"status,omitempty"` WatchStatus *UserTitleStatus `form:"watch_status,omitempty" json:"watch_status,omitempty"` Rating *float64 `form:"rating,omitempty" json:"rating,omitempty"` + MyRate *int32 `form:"my_rate,omitempty" json:"my_rate,omitempty"` ReleaseYear *int32 `form:"release_year,omitempty" json:"release_year,omitempty"` ReleaseSeason *ReleaseSeason `form:"release_season,omitempty" json:"release_season,omitempty"` Limit *int32 `form:"limit,omitempty" json:"limit,omitempty"` @@ -803,6 +806,22 @@ func (siw *ServerInterfaceWrapper) GetUsersUserIdTitles(c *gin.Context) { return } + // ------------- Optional query parameter "sort" ------------- + + err = runtime.BindQueryParameter("form", true, false, "sort", c.Request.URL.Query(), ¶ms.Sort) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter sort: %w", err), http.StatusBadRequest) + return + } + + // ------------- Optional query parameter "sort_forward" ------------- + + err = runtime.BindQueryParameter("form", true, false, "sort_forward", c.Request.URL.Query(), ¶ms.SortForward) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter sort_forward: %w", err), http.StatusBadRequest) + return + } + // ------------- Optional query parameter "word" ------------- err = runtime.BindQueryParameter("form", true, false, "word", c.Request.URL.Query(), ¶ms.Word) @@ -835,6 +854,14 @@ func (siw *ServerInterfaceWrapper) GetUsersUserIdTitles(c *gin.Context) { return } + // ------------- Optional query parameter "my_rate" ------------- + + err = runtime.BindQueryParameter("form", true, false, "my_rate", c.Request.URL.Query(), ¶ms.MyRate) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter my_rate: %w", err), http.StatusBadRequest) + return + } + // ------------- Optional query parameter "release_year" ------------- err = runtime.BindQueryParameter("form", true, false, "release_year", c.Request.URL.Query(), ¶ms.ReleaseYear) @@ -1057,7 +1084,10 @@ type GetUsersUserIdTitlesResponseObject interface { VisitGetUsersUserIdTitlesResponse(w http.ResponseWriter) error } -type GetUsersUserIdTitles200JSONResponse []UserTitle +type GetUsersUserIdTitles200JSONResponse struct { + Cursor CursorObj `json:"cursor"` + Data []UserTitle `json:"data"` +} func (response GetUsersUserIdTitles200JSONResponse) VisitGetUsersUserIdTitlesResponse(w http.ResponseWriter) error { w.Header().Set("Content-Type", "application/json") diff --git a/api/paths/users-id-titles.yaml b/api/paths/users-id-titles.yaml index 0788319..91b212d 100644 --- a/api/paths/users-id-titles.yaml +++ b/api/paths/users-id-titles.yaml @@ -2,11 +2,17 @@ get: summary: Get user titles parameters: - $ref: '../parameters/cursor.yaml' + - $ref: "../parameters/title_sort.yaml" - in: path name: user_id required: true schema: type: string + - in: query + name: sort_forward + schema: + type: boolean + default: true - in: query name: word schema: @@ -29,6 +35,11 @@ get: schema: type: number format: double + - in: query + name: my_rate + schema: + type: integer + format: int32 - in: query name: release_year schema: @@ -55,9 +66,17 @@ get: content: application/json: schema: - type: array - items: - $ref: '../schemas/UserTitle.yaml' + type: object + properties: + data: + type: array + items: + $ref: '../schemas/UserTitle.yaml' + cursor: + $ref: '../schemas/CursorObj.yaml' + required: + - data + - cursor '204': description: No titles found '400': diff --git a/modules/backend/handlers/common.go b/modules/backend/handlers/common.go index 6618d49..89a3d59 100644 --- a/modules/backend/handlers/common.go +++ b/modules/backend/handlers/common.go @@ -17,7 +17,24 @@ func NewServer(db *sqlc.Queries) Server { return Server{db: db} } -func (s Server) mapTitle(ctx context.Context, title sqlc.SearchTitlesRow) (oapi.Title, error) { +func (s Server) mapTitle(ctx context.Context, title sqlc.GetTitleByIDRow) (oapi.Title, error) { + + oapi_title := oapi.Title{ + EpisodesAired: title.EpisodesAired, + EpisodesAll: title.EpisodesAired, + // EpisodesLen: &episodes_lens, + Id: title.ID, + // Poster: &oapi_image, + Rating: title.Rating, + RatingCount: title.RatingCount, + // ReleaseSeason: &release_season, + ReleaseYear: title.ReleaseYear, + // Studio: &oapi_studio, + // Tags: oapi_tag_names, + // TitleNames: title_names, + // TitleStatus: oapi_status, + // AdditionalProperties: + } title_names := make(map[string][]string, 0) err := json.Unmarshal(title.TitleNames, &title_names) @@ -25,10 +42,13 @@ func (s Server) mapTitle(ctx context.Context, title sqlc.SearchTitlesRow) (oapi. return oapi.Title{}, fmt.Errorf("unmarshal TitleNames: %v", err) } - episodes_lens := make(map[string]float64, 0) - err = json.Unmarshal(title.EpisodesLen, &episodes_lens) - if err != nil { - return oapi.Title{}, fmt.Errorf("unmarshal EpisodesLen: %v", err) + if title.EpisodesLen != nil && len(title.EpisodesLen) > 0 { + episodes_lens := make(map[string]float64, 0) + err = json.Unmarshal(title.EpisodesLen, &episodes_lens) + if err != nil { + return oapi.Title{}, fmt.Errorf("unmarshal EpisodesLen: %v", err) + } + oapi_title.EpisodesLen = &episodes_lens } oapi_tag_names := make(oapi.Tags, 0) @@ -38,17 +58,19 @@ func (s Server) mapTitle(ctx context.Context, title sqlc.SearchTitlesRow) (oapi. } var oapi_studio oapi.Studio - - oapi_studio.Id = title.StudioID if title.StudioName != nil { oapi_studio.Name = *title.StudioName } - oapi_studio.Description = title.StudioDesc - if title.StudioIllustID != nil { - oapi_studio.Poster.Id = title.StudioIllustID - oapi_studio.Poster.ImagePath = title.StudioImagePath - oapi_studio.Poster.StorageType = &title.StudioStorageType + if title.StudioID != 0 { + oapi_studio.Id = title.StudioID + oapi_studio.Description = title.StudioDesc + if title.StudioIllustID != nil { + oapi_studio.Poster.Id = title.StudioIllustID + oapi_studio.Poster.ImagePath = title.StudioImagePath + oapi_studio.Poster.StorageType = &title.StudioStorageType + } } + oapi_title.Studio = &oapi_studio var oapi_image oapi.Image @@ -62,27 +84,13 @@ func (s Server) mapTitle(ctx context.Context, title sqlc.SearchTitlesRow) (oapi. if title.ReleaseSeason != nil { release_season = oapi.ReleaseSeason(*title.ReleaseSeason) } + oapi_title.ReleaseSeason = &release_season oapi_status, err := TitleStatus2oapi(&title.TitleStatus) if err != nil { return oapi.Title{}, fmt.Errorf("TitleStatus2oapi: %v", err) } - oapi_title := oapi.Title{ - EpisodesAired: title.EpisodesAired, - EpisodesAll: title.EpisodesAired, - EpisodesLen: &episodes_lens, - Id: title.ID, - Poster: &oapi_image, - Rating: title.Rating, - RatingCount: title.RatingCount, - ReleaseSeason: &release_season, - ReleaseYear: title.ReleaseYear, - Studio: &oapi_studio, - Tags: oapi_tag_names, - TitleNames: title_names, - TitleStatus: oapi_status, - // AdditionalProperties: - } + oapi_title.TitleStatus = oapi_status return oapi_title, nil } diff --git a/modules/backend/handlers/titles.go b/modules/backend/handlers/titles.go index 78323f6..d593314 100644 --- a/modules/backend/handlers/titles.go +++ b/modules/backend/handlers/titles.go @@ -26,25 +26,25 @@ type SqlcStatus struct { planned string } -func TitleStatus2Sqlc(s *[]oapi.TitleStatus) (*SqlcStatus, error) { - var sqlc_status SqlcStatus - if s == nil { - return &sqlc_status, nil - } - for _, t := range *s { - switch t { - case oapi.TitleStatusFinished: - sqlc_status.finished = "finished" - case oapi.TitleStatusOngoing: - sqlc_status.ongoing = "ongoing" - case oapi.TitleStatusPlanned: - sqlc_status.planned = "planned" - default: - return nil, fmt.Errorf("unexpected tittle status: %s", t) - } - } - return &sqlc_status, nil -} +// func TitleStatus2Sqlc(s *[]oapi.TitleStatus) (*SqlcStatus, error) { +// var sqlc_status SqlcStatus +// if s == nil { +// return &sqlc_status, nil +// } +// for _, t := range *s { +// switch t { +// case oapi.TitleStatusFinished: +// sqlc_status.finished = "finished" +// case oapi.TitleStatusOngoing: +// sqlc_status.ongoing = "ongoing" +// case oapi.TitleStatusPlanned: +// sqlc_status.planned = "planned" +// default: +// return nil, fmt.Errorf("unexpected tittle status: %s", t) +// } +// } +// return &sqlc_status, nil +// } func TitleStatus2oapi(s *sqlc.TitleStatusT) (*oapi.TitleStatus, error) { if s == nil { @@ -169,29 +169,8 @@ func (s Server) GetTitlesTitleId(ctx context.Context, request oapi.GetTitlesTitl log.Errorf("%v", err) return oapi.GetTitlesTitleId500Response{}, nil } - _sqlc_title := sqlc.SearchTitlesRow{ - ID: sqlc_title.ID, - StudioID: sqlc_title.StudioID, - PosterID: sqlc_title.PosterID, - TitleStatus: sqlc_title.TitleStatus, - Rating: sqlc_title.Rating, - RatingCount: sqlc_title.RatingCount, - ReleaseYear: sqlc_title.ReleaseYear, - ReleaseSeason: sqlc_title.ReleaseSeason, - Season: sqlc_title.Season, - EpisodesAired: sqlc_title.EpisodesAired, - EpisodesAll: sqlc_title.EpisodesAll, - EpisodesLen: sqlc_title.EpisodesLen, - TitleStorageType: sqlc_title.TitleStorageType, - TitleImagePath: sqlc_title.TitleImagePath, - TagNames: sqlc_title.TitleNames, - StudioName: sqlc_title.StudioName, - StudioIllustID: sqlc_title.StudioIllustID, - StudioDesc: sqlc_title.StudioDesc, - StudioStorageType: sqlc_title.StudioStorageType, - StudioImagePath: sqlc_title.StudioImagePath, - } - oapi_title, err = s.mapTitle(ctx, _sqlc_title) + + oapi_title, err = s.mapTitle(ctx, sqlc_title) if err != nil { log.Errorf("%v", err) return oapi.GetTitlesTitleId500Response{}, nil @@ -204,11 +183,6 @@ func (s Server) GetTitles(ctx context.Context, request oapi.GetTitlesRequestObje opai_titles := make([]oapi.Title, 0) word := Word2Sqlc(request.Params.Word) - status, err := TitleStatus2Sqlc(request.Params.Status) - if err != nil { - log.Errorf("%v", err) - return oapi.GetTitles400Response{}, err - } season, err := ReleaseSeason2sqlc(request.Params.ReleaseSeason) if err != nil { @@ -216,16 +190,22 @@ func (s Server) GetTitles(ctx context.Context, request oapi.GetTitlesRequestObje return oapi.GetTitles400Response{}, err } + var statuses_sort []string + if request.Params.Status != nil { + for _, s := range *request.Params.Status { + ss := string(s) // s type is alias for string + statuses_sort = append(statuses_sort, ss) + } + } + params := sqlc.SearchTitlesParams{ Word: word, - Ongoing: status.ongoing, - Finished: status.finished, - Planned: status.planned, + TitleStatuses: statuses_sort, Rating: request.Params.Rating, ReleaseYear: request.Params.ReleaseYear, ReleaseSeason: season, - Forward: true, - SortBy: "id", + Forward: true, // default + SortBy: "id", // default Limit: request.Params.Limit, } @@ -235,6 +215,7 @@ func (s Server) GetTitles(ctx context.Context, request oapi.GetTitlesRequestObje if request.Params.Sort != nil { params.SortBy = string(*request.Params.Sort) if request.Params.Cursor != nil { + // here we set CursorYear CursorID CursorRating fields err := ParseCursorInto(string(*request.Params.Sort), string(*request.Params.Cursor), ¶ms) if err != nil { log.Errorf("%v", err) @@ -256,7 +237,30 @@ func (s Server) GetTitles(ctx context.Context, request oapi.GetTitlesRequestObje for _, title := range titles { - t, err := s.mapTitle(ctx, title) + _title := sqlc.GetTitleByIDRow{ + ID: title.ID, + // StudioID: title.StudioID, + PosterID: title.PosterID, + TitleStatus: title.TitleStatus, + Rating: title.Rating, + RatingCount: title.RatingCount, + ReleaseYear: title.ReleaseYear, + ReleaseSeason: title.ReleaseSeason, + Season: title.Season, + EpisodesAired: title.EpisodesAired, + EpisodesAll: title.EpisodesAll, + // EpisodesLen: title.EpisodesLen, + TitleStorageType: title.TitleStorageType, + TitleImagePath: title.TitleImagePath, + TagNames: title.TitleNames, + StudioName: title.StudioName, + // StudioIllustID: title.StudioIllustID, + // StudioDesc: title.StudioDesc, + // StudioStorageType: title.StudioStorageType, + // StudioImagePath: title.StudioImagePath, + } + + t, err := s.mapTitle(ctx, _title) if err != nil { log.Errorf("%v", err) return oapi.GetTitles500Response{}, nil diff --git a/modules/backend/handlers/users.go b/modules/backend/handlers/users.go index 1ea2c1a..3223389 100644 --- a/modules/backend/handlers/users.go +++ b/modules/backend/handlers/users.go @@ -5,6 +5,7 @@ import ( "fmt" oapi "nyanimedb/api" sqlc "nyanimedb/sql" + "strconv" "time" "github.com/jackc/pgx/v5" @@ -53,8 +54,12 @@ func (s Server) GetUsersUserId(ctx context.Context, req oapi.GetUsersUserIdReque return oapi.GetUsersUserId200JSONResponse(mapUser(user)), nil } -func sqlDate2oapi(p_date pgtype.Timestamptz) (time.Time, error) { - return time.Time{}, nil +func sqlDate2oapi(p_date pgtype.Timestamptz) *time.Time { + if p_date.Valid { + t := p_date.Time + return &t + } + return nil } type SqlcUserStatus struct { @@ -64,26 +69,94 @@ type SqlcUserStatus struct { in_progress string } -func UserTitleStatus2Sqlc(s *[]oapi.UserTitleStatus) (*SqlcUserStatus, error) { - var sqlc_status SqlcUserStatus - if s == nil { - return &sqlc_status, nil +// func UserTitleStatus2Sqlc(s *[]oapi.UserTitleStatus) (*SqlcUserStatus, error) { +// var sqlc_status SqlcUserStatus +// if s == nil { +// return &sqlc_status, nil +// } +// for _, t := range *s { +// switch t { +// case oapi.UserTitleStatusFinished: +// sqlc_status.finished = "finished" +// case oapi.UserTitleStatusDropped: +// sqlc_status.dropped = "dropped" +// case oapi.UserTitleStatusPlanned: +// sqlc_status.planned = "planned" +// case oapi.UserTitleStatusInProgress: +// sqlc_status.in_progress = "in-progress" +// default: +// return nil, fmt.Errorf("unexpected tittle status: %s", t) +// } +// } +// return &sqlc_status, nil +// } + +func sql2usertitlestatus(s sqlc.UsertitleStatusT) (oapi.UserTitleStatus, error) { + var status oapi.UserTitleStatus + + switch status { + case "finished": + status = oapi.UserTitleStatusFinished + case "dropped": + status = oapi.UserTitleStatusDropped + case "planned": + status = oapi.UserTitleStatusPlanned + case "in-progress": + status = oapi.UserTitleStatusInProgress + default: + return status, fmt.Errorf("unexpected tittle status: %s", s) } - for _, t := range *s { - switch t { - case oapi.UserTitleStatusFinished: - sqlc_status.finished = "finished" - case oapi.UserTitleStatusDropped: - sqlc_status.dropped = "dropped" - case oapi.UserTitleStatusPlanned: - sqlc_status.planned = "planned" - case oapi.UserTitleStatusInProgress: - sqlc_status.in_progress = "in-progress" - default: - return nil, fmt.Errorf("unexpected tittle status: %s", t) - } + + return status, nil +} + +func (s Server) mapUsertitle(ctx context.Context, t sqlc.SearchUserTitlesRow) (oapi.UserTitle, error) { + + oapi_usertitle := oapi.UserTitle{ + Ctime: sqlDate2oapi(t.UserCtime), + Rate: t.UserRate, + ReviewId: t.ReviewID, + // Status: , + // Title: , + UserId: t.UserID, } - return &sqlc_status, nil + + status, err := sql2usertitlestatus(t.UsertitleStatus) + if err != nil { + return oapi_usertitle, fmt.Errorf("mapUsertitle: %v", err) + } + oapi_usertitle.Status = status + + _title := sqlc.GetTitleByIDRow{ + ID: t.ID, + // StudioID: title.StudioID, + PosterID: t.PosterID, + TitleStatus: t.TitleStatus, + Rating: t.Rating, + RatingCount: t.RatingCount, + ReleaseYear: t.ReleaseYear, + ReleaseSeason: t.ReleaseSeason, + Season: t.Season, + EpisodesAired: t.EpisodesAired, + EpisodesAll: t.EpisodesAll, + // EpisodesLen: title.EpisodesLen, + TitleStorageType: t.TitleStorageType, + TitleImagePath: t.TitleImagePath, + TagNames: t.TitleNames, + StudioName: t.StudioName, + // StudioIllustID: title.StudioIllustID, + // StudioDesc: title.StudioDesc, + // StudioStorageType: title.StudioStorageType, + // StudioImagePath: title.StudioImagePath, + } + + oapi_title, err := s.mapTitle(ctx, _title) + if err != nil { + return oapi_usertitle, fmt.Errorf("mapUsertitle: %v", err) + } + oapi_usertitle.Title = &oapi_title + + return oapi_usertitle, nil } func (s Server) GetUsersUserIdTitles(ctx context.Context, request oapi.GetUsersUserIdTitlesRequestObject) (oapi.GetUsersUserIdTitlesResponseObject, error) { @@ -91,11 +164,6 @@ func (s Server) GetUsersUserIdTitles(ctx context.Context, request oapi.GetUsersU oapi_usertitles := make([]oapi.UserTitle, 0) word := Word2Sqlc(request.Params.Word) - status, err := TitleStatus2Sqlc(request.Params.Status) - if err != nil { - log.Errorf("%v", err) - return oapi.GetUsersUserIdTitles400Response{}, err - } season, err := ReleaseSeason2sqlc(request.Params.ReleaseSeason) if err != nil { @@ -103,23 +171,33 @@ func (s Server) GetUsersUserIdTitles(ctx context.Context, request oapi.GetUsersU return oapi.GetUsersUserIdTitles400Response{}, err } + var statuses_sort []string + if request.Params.Status != nil { + for _, s := range *request.Params.Status { + ss := string(s) // s type is alias for string + statuses_sort = append(statuses_sort, ss) + } + } + + var watch_status []string + if request.Params.WatchStatus != nil { + for _, s := range *request.Params.WatchStatus { + ss := string(s) // s type is alias for string + watch_status = append(statuses_sort, ss) + } + } + params := sqlc.SearchUserTitlesParams{ - Forward: true, - SortBy: "id", - // CursorYear : - // CursorID *int64 `json:"cursor_id"` - // CursorRating *float64 `json:"cursor_rating"` - // Word *string `json:"word"` - // Ongoing string `json:"ongoing"` - // Planned string `json:"planned"` - // Dropped string `json:"dropped"` - // InProgress string `json:"in-progress"` - // Finished string `json:"finished"` - // Rate *int32 `json:"rate"` - // Rating *float64 `json:"rating"` - // ReleaseYear *int32 `json:"release_year"` - // ReleaseSeason *ReleaseSeasonT `json:"release_season"` - // Limit *int32 `json:"limit"` + Word: word, + TitleStatuses: statuses_sort, + UsertitleStatuses: watch_status, + Rating: request.Params.Rating, + Rate: request.Params.MyRate, + ReleaseYear: request.Params.ReleaseYear, + ReleaseSeason: season, + Forward: true, // default + SortBy: "id", // default + Limit: request.Params.Limit, } if request.Params.SortForward != nil { @@ -128,6 +206,7 @@ func (s Server) GetUsersUserIdTitles(ctx context.Context, request oapi.GetUsersU if request.Params.Sort != nil { params.SortBy = string(*request.Params.Sort) if request.Params.Cursor != nil { + // here we set CursorYear CursorID CursorRating fields err := ParseCursorInto(string(*request.Params.Sort), string(*request.Params.Cursor), ¶ms) if err != nil { log.Errorf("%v", err) @@ -144,30 +223,30 @@ func (s Server) GetUsersUserIdTitles(ctx context.Context, request oapi.GetUsersU if len(titles) == 0 { return oapi.GetUsersUserIdTitles204Response{}, nil } + + var new_cursor oapi.CursorObj + for _, title := range titles { - _sqlc_title := sqlc.SearchTitlesRow{ - ID: sqlc_title.ID, - StudioID: sqlc_title.StudioID, - PosterID: sqlc_title.PosterID, - TitleStatus: sqlc_title.TitleStatus, - Rating: sqlc_title.Rating, - RatingCount: sqlc_title.RatingCount, - ReleaseYear: sqlc_title.ReleaseYear, - ReleaseSeason: sqlc_title.ReleaseSeason, - Season: sqlc_title.Season, - EpisodesAired: sqlc_title.EpisodesAired, - EpisodesAll: sqlc_title.EpisodesAll, - EpisodesLen: sqlc_title.EpisodesLen, - TitleStorageType: sqlc_title.TitleStorageType, - TitleImagePath: sqlc_title.TitleImagePath, - TagNames: sqlc_title.TitleNames, - StudioName: sqlc_title.StudioName, - StudioIllustID: sqlc_title.StudioIllustID, - StudioDesc: sqlc_title.StudioDesc, - StudioStorageType: sqlc_title.StudioStorageType, - StudioImagePath: sqlc_title.StudioImagePath, + + t, err := s.mapUsertitle(ctx, title) + if err != nil { + log.Errorf("%v", err) + return oapi.GetUsersUserIdTitles500Response{}, nil + } + oapi_usertitles = append(oapi_usertitles, t) + + new_cursor.Id = t.Title.Id + if request.Params.Sort != nil { + switch string(*request.Params.Sort) { + case "year": + tmp := fmt.Sprint(*t.Title.ReleaseYear) + new_cursor.Param = &tmp + case "rating": + tmp := strconv.FormatFloat(*t.Title.Rating, 'f', -1, 64) + new_cursor.Param = &tmp + } } } - return oapi.GetUsersUserIdTitles200JSONResponse(userTitles), nil + return oapi.GetUsersUserIdTitles200JSONResponse{Cursor: new_cursor, Data: oapi_usertitles}, nil } diff --git a/modules/backend/queries.sql b/modules/backend/queries.sql index ee2a84a..9734ff2 100644 --- a/modules/backend/queries.sql +++ b/modules/backend/queries.sql @@ -101,25 +101,30 @@ GROUP BY -- name: SearchTitles :many SELECT - t.*, + t.id as id, + t.title_names as title_names, + t.poster_id as poster_id, + t.title_status as title_status, + t.rating as rating, + t.rating_count as rating_count, + t.release_year as release_year, + t.release_season as release_season, + t.season as season, + t.episodes_aired as episodes_aired, + t.episodes_all as episodes_all, i.storage_type::text as title_storage_type, i.image_path as title_image_path, COALESCE( jsonb_agg(g.tag_names) FILTER (WHERE g.tag_names IS NOT NULL), '[]'::jsonb )::jsonb as tag_names, - s.studio_name as studio_name, - s.illust_id as studio_illust_id, - s.studio_desc as studio_desc, - si.storage_type::text as studio_storage_type, - si.image_path as studio_image_path + s.studio_name as studio_name FROM titles as t LEFT JOIN images as i ON (t.poster_id = i.id) LEFT JOIN title_tags as tt ON (t.id = tt.title_id) LEFT JOIN tags as g ON (tt.tag_id = g.id) LEFT JOIN studios as s ON (t.studio_id = s.id) -LEFT JOIN images as si ON (s.illust_id = si.id) WHERE CASE @@ -199,7 +204,7 @@ WHERE AND (sqlc.narg('release_season')::release_season_t IS NULL OR t.release_season = sqlc.narg('release_season')::release_season_t) GROUP BY - t.id, i.id, s.id, si.id + t.id, i.id, s.id ORDER BY CASE WHEN sqlc.arg('forward')::boolean THEN @@ -223,7 +228,17 @@ LIMIT COALESCE(sqlc.narg('limit')::int, 100); -- 100 is default limit -- name: SearchUserTitles :many SELECT - t.*, + t.id as id, + t.title_names as title_names, + t.poster_id as poster_id, + t.title_status as title_status, + t.rating as rating, + t.rating_count as rating_count, + t.release_year as release_year, + t.release_season as release_season, + t.season as season, + t.episodes_aired as episodes_aired, + t.episodes_all as episodes_all, u.user_id as user_id, u.status as usertitle_status, u.rate as user_rate, @@ -231,20 +246,18 @@ SELECT u.ctime as user_ctime, i.storage_type::text as title_storage_type, i.image_path as title_image_path, - jsonb_agg(g.tag_name)'[]'::jsonb as tag_names, - s.studio_name as studio_name, - s.illust_id as studio_illust_id, - s.studio_desc as studio_desc, - si.storage_type::text as studio_storage_type, - si.image_path as studio_image_path + COALESCE( + jsonb_agg(g.tag_names) FILTER (WHERE g.tag_names IS NOT NULL), + '[]'::jsonb + )::jsonb as tag_names, + s.studio_name as studio_name -FROM usertitles as u -LEFT JOIN titles as t ON (u.title_id = t.id) -LEFT JOIN images as i ON (t.poster_id = i.id) -LEFT JOIN title_tags as tt ON (t.id = tt.title_id) -LEFT JOIN tags as g ON (tt.tag_id = g.id) -LEFT JOIN studios as s ON (t.studio_id = s.id) -LEFT JOIN images as si ON (s.illust_id = si.id) +FROM usertitles as u +JOIN titles as t ON (u.title_id = t.id) +LEFT JOIN images as i ON (t.poster_id = i.id) +LEFT JOIN title_tags as tt ON (t.id = tt.title_id) +LEFT JOIN tags as g ON (tt.tag_id = g.id) +LEFT JOIN studios as s ON (t.studio_id = s.id) WHERE CASE @@ -312,15 +325,24 @@ WHERE END ) - AND (u.status::text IN (sqlc.arg('ongoing')::text, sqlc.arg('planned')::text, sqlc.arg('dropped')::text, sqlc.arg('in-progress')::text)) - AND (t.title_status::text IN (sqlc.arg('ongoing')::text, sqlc.arg('finished')::text, sqlc.arg('planned')::text)) + AND ( + -- Если массив пуст (NULL или []) — не фильтруем + cardinality(sqlc.arg('title_statuses')::text[]) = 0 + OR + t.title_status = ANY(sqlc.arg('title_statuses')::text[]) + ) + AND ( + cardinality(sqlc.arg('usertitle_statuses')::text[]) = 0 + OR + u.status = ANY(sqlc.arg('usertitle_statuses')::text[]) + ) AND (sqlc.narg('rate')::int IS NULL OR u.rate >= sqlc.narg('rate')::int) AND (sqlc.narg('rating')::float IS NULL OR t.rating >= sqlc.narg('rating')::float) AND (sqlc.narg('release_year')::int IS NULL OR t.release_year = sqlc.narg('release_year')::int) AND (sqlc.narg('release_season')::release_season_t IS NULL OR t.release_season = sqlc.narg('release_season')::release_season_t) GROUP BY - t.id, i.id, s.id, si.id + t.id, i.id, s.id ORDER BY CASE WHEN sqlc.arg('forward')::boolean THEN diff --git a/sql/queries.sql.go b/sql/queries.sql.go index bf2f08a..f7535db 100644 --- a/sql/queries.sql.go +++ b/sql/queries.sql.go @@ -342,25 +342,30 @@ func (q *Queries) InsertTitleTags(ctx context.Context, arg InsertTitleTagsParams const searchTitles = `-- name: SearchTitles :many SELECT - t.id, t.title_names, t.studio_id, t.poster_id, t.title_status, t.rating, t.rating_count, t.release_year, t.release_season, t.season, t.episodes_aired, t.episodes_all, t.episodes_len, + t.id as id, + t.title_names as title_names, + t.poster_id as poster_id, + t.title_status as title_status, + t.rating as rating, + t.rating_count as rating_count, + t.release_year as release_year, + t.release_season as release_season, + t.season as season, + t.episodes_aired as episodes_aired, + t.episodes_all as episodes_all, i.storage_type::text as title_storage_type, i.image_path as title_image_path, COALESCE( jsonb_agg(g.tag_names) FILTER (WHERE g.tag_names IS NOT NULL), '[]'::jsonb )::jsonb as tag_names, - s.studio_name as studio_name, - s.illust_id as studio_illust_id, - s.studio_desc as studio_desc, - si.storage_type::text as studio_storage_type, - si.image_path as studio_image_path + s.studio_name as studio_name FROM titles as t LEFT JOIN images as i ON (t.poster_id = i.id) LEFT JOIN title_tags as tt ON (t.id = tt.title_id) LEFT JOIN tags as g ON (tt.tag_id = g.id) LEFT JOIN studios as s ON (t.studio_id = s.id) -LEFT JOIN images as si ON (s.illust_id = si.id) WHERE CASE @@ -440,7 +445,7 @@ WHERE AND ($10::release_season_t IS NULL OR t.release_season = $10::release_season_t) GROUP BY - t.id, i.id, s.id, si.id + t.id, i.id, s.id ORDER BY CASE WHEN $1::boolean THEN @@ -478,27 +483,21 @@ type SearchTitlesParams struct { } type SearchTitlesRow struct { - ID int64 `json:"id"` - TitleNames json.RawMessage `json:"title_names"` - StudioID int64 `json:"studio_id"` - PosterID *int64 `json:"poster_id"` - TitleStatus TitleStatusT `json:"title_status"` - Rating *float64 `json:"rating"` - RatingCount *int32 `json:"rating_count"` - ReleaseYear *int32 `json:"release_year"` - ReleaseSeason *ReleaseSeasonT `json:"release_season"` - Season *int32 `json:"season"` - EpisodesAired *int32 `json:"episodes_aired"` - EpisodesAll *int32 `json:"episodes_all"` - EpisodesLen []byte `json:"episodes_len"` - TitleStorageType string `json:"title_storage_type"` - TitleImagePath *string `json:"title_image_path"` - TagNames json.RawMessage `json:"tag_names"` - StudioName *string `json:"studio_name"` - StudioIllustID *int64 `json:"studio_illust_id"` - StudioDesc *string `json:"studio_desc"` - StudioStorageType string `json:"studio_storage_type"` - StudioImagePath *string `json:"studio_image_path"` + ID int64 `json:"id"` + TitleNames json.RawMessage `json:"title_names"` + PosterID *int64 `json:"poster_id"` + TitleStatus TitleStatusT `json:"title_status"` + Rating *float64 `json:"rating"` + RatingCount *int32 `json:"rating_count"` + ReleaseYear *int32 `json:"release_year"` + ReleaseSeason *ReleaseSeasonT `json:"release_season"` + Season *int32 `json:"season"` + EpisodesAired *int32 `json:"episodes_aired"` + EpisodesAll *int32 `json:"episodes_all"` + TitleStorageType string `json:"title_storage_type"` + TitleImagePath *string `json:"title_image_path"` + TagNames json.RawMessage `json:"tag_names"` + StudioName *string `json:"studio_name"` } func (q *Queries) SearchTitles(ctx context.Context, arg SearchTitlesParams) ([]SearchTitlesRow, error) { @@ -525,7 +524,6 @@ func (q *Queries) SearchTitles(ctx context.Context, arg SearchTitlesParams) ([]S if err := rows.Scan( &i.ID, &i.TitleNames, - &i.StudioID, &i.PosterID, &i.TitleStatus, &i.Rating, @@ -535,15 +533,10 @@ func (q *Queries) SearchTitles(ctx context.Context, arg SearchTitlesParams) ([]S &i.Season, &i.EpisodesAired, &i.EpisodesAll, - &i.EpisodesLen, &i.TitleStorageType, &i.TitleImagePath, &i.TagNames, &i.StudioName, - &i.StudioIllustID, - &i.StudioDesc, - &i.StudioStorageType, - &i.StudioImagePath, ); err != nil { return nil, err } @@ -558,7 +551,17 @@ func (q *Queries) SearchTitles(ctx context.Context, arg SearchTitlesParams) ([]S const searchUserTitles = `-- name: SearchUserTitles :many SELECT - t.id, t.title_names, t.studio_id, t.poster_id, t.title_status, t.rating, t.rating_count, t.release_year, t.release_season, t.season, t.episodes_aired, t.episodes_all, t.episodes_len, + t.id as id, + t.title_names as title_names, + t.poster_id as poster_id, + t.title_status as title_status, + t.rating as rating, + t.rating_count as rating_count, + t.release_year as release_year, + t.release_season as release_season, + t.season as season, + t.episodes_aired as episodes_aired, + t.episodes_all as episodes_all, u.user_id as user_id, u.status as usertitle_status, u.rate as user_rate, @@ -566,20 +569,18 @@ SELECT u.ctime as user_ctime, i.storage_type::text as title_storage_type, i.image_path as title_image_path, - jsonb_agg(g.tag_name)'[]'::jsonb as tag_names, - s.studio_name as studio_name, - s.illust_id as studio_illust_id, - s.studio_desc as studio_desc, - si.storage_type::text as studio_storage_type, - si.image_path as studio_image_path + COALESCE( + jsonb_agg(g.tag_names) FILTER (WHERE g.tag_names IS NOT NULL), + '[]'::jsonb + )::jsonb as tag_names, + s.studio_name as studio_name FROM usertitles as u -LEFT JOIN titles as t ON (u.title_id = t.id) +JOIN titles as t ON (u.title_id = t.id) LEFT JOIN images as i ON (t.poster_id = i.id) LEFT JOIN title_tags as tt ON (t.id = tt.title_id) LEFT JOIN tags as g ON (tt.tag_id = g.id) LEFT JOIN studios as s ON (t.studio_id = s.id) -LEFT JOIN images as si ON (s.illust_id = si.id) WHERE CASE @@ -647,15 +648,24 @@ WHERE END ) - AND (u.status::text IN ($7::text, $8::text, $9::text, $10::text)) - AND (t.title_status::text IN ($7::text, $11::text, $8::text)) - AND ($12::int IS NULL OR u.rate >= $12::int) - AND ($13::float IS NULL OR t.rating >= $13::float) - AND ($14::int IS NULL OR t.release_year = $14::int) - AND ($15::release_season_t IS NULL OR t.release_season = $15::release_season_t) + AND ( + -- Если массив пуст (NULL или []) — не фильтруем + cardinality($7::text[]) = 0 + OR + t.title_status = ANY($7::text[]) + ) + AND ( + cardinality($8::text[]) = 0 + OR + u.status = ANY($8::text[]) + ) + AND ($9::int IS NULL OR u.rate >= $9::int) + AND ($10::float IS NULL OR t.rating >= $10::float) + AND ($11::int IS NULL OR t.release_year = $11::int) + AND ($12::release_season_t IS NULL OR t.release_season = $12::release_season_t) GROUP BY - t.id, i.id, s.id, si.id + t.id, i.id, s.id ORDER BY CASE WHEN $1::boolean THEN @@ -677,55 +687,46 @@ ORDER BY CASE WHEN $2::text <> 'id' THEN t.id END ASC -LIMIT COALESCE($16::int, 100) +LIMIT COALESCE($13::int, 100) ` type SearchUserTitlesParams struct { - Forward bool `json:"forward"` - SortBy string `json:"sort_by"` - CursorYear *int32 `json:"cursor_year"` - CursorID *int64 `json:"cursor_id"` - CursorRating *float64 `json:"cursor_rating"` - Word *string `json:"word"` - Ongoing string `json:"ongoing"` - Planned string `json:"planned"` - Dropped string `json:"dropped"` - InProgress string `json:"in-progress"` - Finished string `json:"finished"` - Rate *int32 `json:"rate"` - Rating *float64 `json:"rating"` - ReleaseYear *int32 `json:"release_year"` - ReleaseSeason *ReleaseSeasonT `json:"release_season"` - Limit *int32 `json:"limit"` + Forward bool `json:"forward"` + SortBy string `json:"sort_by"` + CursorYear *int32 `json:"cursor_year"` + CursorID *int64 `json:"cursor_id"` + CursorRating *float64 `json:"cursor_rating"` + Word *string `json:"word"` + TitleStatuses []string `json:"title_statuses"` + UsertitleStatuses []string `json:"usertitle_statuses"` + Rate *int32 `json:"rate"` + Rating *float64 `json:"rating"` + ReleaseYear *int32 `json:"release_year"` + ReleaseSeason *ReleaseSeasonT `json:"release_season"` + Limit *int32 `json:"limit"` } type SearchUserTitlesRow struct { - ID *int64 `json:"id"` - TitleNames []byte `json:"title_names"` - StudioID *int64 `json:"studio_id"` - PosterID *int64 `json:"poster_id"` - TitleStatus *TitleStatusT `json:"title_status"` - Rating *float64 `json:"rating"` - RatingCount *int32 `json:"rating_count"` - ReleaseYear *int32 `json:"release_year"` - ReleaseSeason *ReleaseSeasonT `json:"release_season"` - Season *int32 `json:"season"` - EpisodesAired *int32 `json:"episodes_aired"` - EpisodesAll *int32 `json:"episodes_all"` - EpisodesLen []byte `json:"episodes_len"` - UserID int64 `json:"user_id"` - UsertitleStatus UsertitleStatusT `json:"usertitle_status"` - UserRate *int32 `json:"user_rate"` - ReviewID *int64 `json:"review_id"` - UserCtime pgtype.Timestamptz `json:"user_ctime"` - TitleStorageType string `json:"title_storage_type"` - TitleImagePath *string `json:"title_image_path"` - TagNames json.RawMessage `json:"tag_names"` - StudioName *string `json:"studio_name"` - StudioIllustID *int64 `json:"studio_illust_id"` - StudioDesc *string `json:"studio_desc"` - StudioStorageType string `json:"studio_storage_type"` - StudioImagePath *string `json:"studio_image_path"` + ID int64 `json:"id"` + TitleNames json.RawMessage `json:"title_names"` + PosterID *int64 `json:"poster_id"` + TitleStatus TitleStatusT `json:"title_status"` + Rating *float64 `json:"rating"` + RatingCount *int32 `json:"rating_count"` + ReleaseYear *int32 `json:"release_year"` + ReleaseSeason *ReleaseSeasonT `json:"release_season"` + Season *int32 `json:"season"` + EpisodesAired *int32 `json:"episodes_aired"` + EpisodesAll *int32 `json:"episodes_all"` + UserID int64 `json:"user_id"` + UsertitleStatus UsertitleStatusT `json:"usertitle_status"` + UserRate *int32 `json:"user_rate"` + ReviewID *int64 `json:"review_id"` + UserCtime pgtype.Timestamptz `json:"user_ctime"` + TitleStorageType string `json:"title_storage_type"` + TitleImagePath *string `json:"title_image_path"` + TagNames json.RawMessage `json:"tag_names"` + StudioName *string `json:"studio_name"` } // 100 is default limit @@ -737,11 +738,8 @@ func (q *Queries) SearchUserTitles(ctx context.Context, arg SearchUserTitlesPara arg.CursorID, arg.CursorRating, arg.Word, - arg.Ongoing, - arg.Planned, - arg.Dropped, - arg.InProgress, - arg.Finished, + arg.TitleStatuses, + arg.UsertitleStatuses, arg.Rate, arg.Rating, arg.ReleaseYear, @@ -758,7 +756,6 @@ func (q *Queries) SearchUserTitles(ctx context.Context, arg SearchUserTitlesPara if err := rows.Scan( &i.ID, &i.TitleNames, - &i.StudioID, &i.PosterID, &i.TitleStatus, &i.Rating, @@ -768,7 +765,6 @@ func (q *Queries) SearchUserTitles(ctx context.Context, arg SearchUserTitlesPara &i.Season, &i.EpisodesAired, &i.EpisodesAll, - &i.EpisodesLen, &i.UserID, &i.UsertitleStatus, &i.UserRate, @@ -778,10 +774,6 @@ func (q *Queries) SearchUserTitles(ctx context.Context, arg SearchUserTitlesPara &i.TitleImagePath, &i.TagNames, &i.StudioName, - &i.StudioIllustID, - &i.StudioDesc, - &i.StudioStorageType, - &i.StudioImagePath, ); err != nil { return nil, err }