diff --git a/api/_build/openapi.yaml b/api/_build/openapi.yaml index b3eacb4..c059166 100644 --- a/api/_build/openapi.yaml +++ b/api/_build/openapi.yaml @@ -146,17 +146,11 @@ 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: @@ -164,12 +158,7 @@ paths: - in: query name: status schema: - type: array - items: - $ref: '#/components/schemas/TitleStatus' - description: List of title statuses to filter - style: form - explode: false + $ref: '#/components/schemas/TitleStatus' - in: query name: watch_status schema: @@ -179,11 +168,6 @@ paths: schema: type: number format: double - - in: query - name: my_rate - schema: - type: integer - format: int32 - in: query name: release_year schema: @@ -210,17 +194,9 @@ paths: content: application/json: schema: - type: object - properties: - data: - type: array - items: - $ref: '#/components/schemas/UserTitle' - cursor: - $ref: '#/components/schemas/CursorObj' - required: - - data - - cursor + type: array + items: + $ref: '#/components/schemas/UserTitle' '204': description: No titles found '400': @@ -448,8 +424,9 @@ components: user_id: type: integer format: int64 - title: - $ref: '#/components/schemas/Title' + title_id: + type: integer + format: int64 status: $ref: '#/components/schemas/UserTitleStatus' rate: diff --git a/api/api.gen.go b/api/api.gen.go index e1f94c2..e56f6b8 100644 --- a/api/api.gen.go +++ b/api/api.gen.go @@ -143,7 +143,7 @@ type UserTitle struct { // Status User's title status Status UserTitleStatus `json:"status"` - Title *Title `json:"title,omitempty"` + TitleId int64 `json:"title_id"` UserId int64 `json:"user_id"` AdditionalProperties map[string]interface{} `json:"-"` } @@ -183,16 +183,11 @@ type GetUsersUserIdParams struct { // GetUsersUserIdTitlesParams defines parameters for GetUsersUserIdTitles. type GetUsersUserIdTitlesParams struct { - 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"` + Cursor *Cursor `form:"cursor,omitempty" json:"cursor,omitempty"` + Word *string `form:"word,omitempty" json:"word,omitempty"` + 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"` @@ -498,12 +493,12 @@ func (a *UserTitle) UnmarshalJSON(b []byte) error { delete(object, "status") } - if raw, found := object["title"]; found { - err = json.Unmarshal(raw, &a.Title) + if raw, found := object["title_id"]; found { + err = json.Unmarshal(raw, &a.TitleId) if err != nil { - return fmt.Errorf("error reading 'title': %w", err) + return fmt.Errorf("error reading 'title_id': %w", err) } - delete(object, "title") + delete(object, "title_id") } if raw, found := object["user_id"]; found { @@ -559,11 +554,9 @@ func (a UserTitle) MarshalJSON() ([]byte, error) { 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["title_id"], err = json.Marshal(a.TitleId) + if err != nil { + return nil, fmt.Errorf("error marshaling 'title_id': %w", err) } object["user_id"], err = json.Marshal(a.UserId) @@ -806,22 +799,6 @@ 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) @@ -832,7 +809,7 @@ func (siw *ServerInterfaceWrapper) GetUsersUserIdTitles(c *gin.Context) { // ------------- Optional query parameter "status" ------------- - err = runtime.BindQueryParameter("form", false, false, "status", c.Request.URL.Query(), ¶ms.Status) + err = runtime.BindQueryParameter("form", true, false, "status", c.Request.URL.Query(), ¶ms.Status) if err != nil { siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter status: %w", err), http.StatusBadRequest) return @@ -854,14 +831,6 @@ 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) @@ -1084,10 +1053,7 @@ type GetUsersUserIdTitlesResponseObject interface { VisitGetUsersUserIdTitlesResponse(w http.ResponseWriter) error } -type GetUsersUserIdTitles200JSONResponse struct { - Cursor CursorObj `json:"cursor"` - Data []UserTitle `json:"data"` -} +type GetUsersUserIdTitles200JSONResponse []UserTitle 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 91b212d..0cde5af 100644 --- a/api/paths/users-id-titles.yaml +++ b/api/paths/users-id-titles.yaml @@ -2,17 +2,11 @@ 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: @@ -20,12 +14,7 @@ get: - in: query name: status schema: - type: array - items: - $ref: '../schemas/enums/TitleStatus.yaml' - description: List of title statuses to filter - style: form - explode: false + $ref: '../schemas/enums/TitleStatus.yaml' - in: query name: watch_status schema: @@ -35,11 +24,6 @@ get: schema: type: number format: double - - in: query - name: my_rate - schema: - type: integer - format: int32 - in: query name: release_year schema: @@ -66,17 +50,9 @@ get: content: application/json: schema: - type: object - properties: - data: - type: array - items: - $ref: '../schemas/UserTitle.yaml' - cursor: - $ref: '../schemas/CursorObj.yaml' - required: - - data - - cursor + type: array + items: + $ref: '../schemas/UserTitle.yaml' '204': description: No titles found '400': diff --git a/api/schemas/UserTitle.yaml b/api/schemas/UserTitle.yaml index 3beaec6..658e350 100644 --- a/api/schemas/UserTitle.yaml +++ b/api/schemas/UserTitle.yaml @@ -7,8 +7,9 @@ properties: user_id: type: integer format: int64 - title: - $ref: ./Title.yaml + title_id: + type: integer + format: int64 status: $ref: ./enums/UserTitleStatus.yaml rate: diff --git a/modules/backend/handlers/common.go b/modules/backend/handlers/common.go index 89a3d59..3d61b91 100644 --- a/modules/backend/handlers/common.go +++ b/modules/backend/handlers/common.go @@ -1,10 +1,6 @@ package handlers import ( - "context" - "encoding/json" - "fmt" - oapi "nyanimedb/api" sqlc "nyanimedb/sql" "strconv" ) @@ -17,83 +13,70 @@ func NewServer(db *sqlc.Queries) Server { return Server{db: db} } -func (s Server) mapTitle(ctx context.Context, title sqlc.GetTitleByIDRow) (oapi.Title, error) { +// type Cursor interface { +// ParseCursor(sortBy oapi.TitleSort, data oapi.Cursor) (Cursor, 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: - } +// Values() map[string]interface{} +// // for logs only +// Type() string +// } - title_names := make(map[string][]string, 0) - err := json.Unmarshal(title.TitleNames, &title_names) - if err != nil { - return oapi.Title{}, fmt.Errorf("unmarshal TitleNames: %v", err) - } +// type CursorByID struct { +// ID int64 +// } - 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 - } +// func (c CursorByID) ParseCursor(sortBy oapi.TitleSort, data oapi.Cursor) (Cursor, error) { +// var cur CursorByID +// if err := json.Unmarshal(data, &cur); err != nil { +// return nil, fmt.Errorf("invalid cursor (id): %w", err) +// } +// if cur.ID == 0 { +// return nil, errors.New("cursor id must be non-zero") +// } +// return cur, nil +// } - oapi_tag_names := make(oapi.Tags, 0) - err = json.Unmarshal(title.TagNames, &oapi_tag_names) - if err != nil { - return oapi.Title{}, fmt.Errorf("unmarshalling title_tag: %v", err) - } +// func (c CursorByID) Values() map[string]interface{} { +// return map[string]interface{}{ +// "cursor_id": c.ID, +// "cursor_year": nil, +// "cursor_rating": nil, +// } +// } - var oapi_studio oapi.Studio - if title.StudioName != nil { - oapi_studio.Name = *title.StudioName - } - 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 +// func (c CursorByID) Type() string { return "id" } - var oapi_image oapi.Image +// func NewCursor(sortBy string) (Cursor, error) { +// switch Type(sortBy) { +// case TypeID: +// return CursorByID{}, nil +// case TypeYear: +// return CursorByYear{}, nil +// case TypeRating: +// return CursorByRating{}, nil +// default: +// return nil, fmt.Errorf("unsupported sort_by: %q", sortBy) +// } +// } - if title.PosterID != nil { - oapi_image.Id = title.PosterID - oapi_image.ImagePath = title.TitleImagePath - oapi_image.StorageType = &title.TitleStorageType - } +// decodes a base64-encoded JSON string into a CursorObj +// Returns the parsed CursorObj and an error +// func parseCursor(encoded oapi.Cursor) (*oapi.CursorObj, error) { - var release_season oapi.ReleaseSeason - if title.ReleaseSeason != nil { - release_season = oapi.ReleaseSeason(*title.ReleaseSeason) - } - oapi_title.ReleaseSeason = &release_season +// // Decode base64 +// decoded, err := base64.StdEncoding.DecodeString(encoded) +// if err != nil { +// return nil, fmt.Errorf("parseCursor: %v", err) +// } - oapi_status, err := TitleStatus2oapi(&title.TitleStatus) - if err != nil { - return oapi.Title{}, fmt.Errorf("TitleStatus2oapi: %v", err) - } - oapi_title.TitleStatus = oapi_status +// // Parse JSON +// var cursor oapi.CursorObj +// if err := json.Unmarshal(decoded, &cursor); err != nil { +// return nil, fmt.Errorf("parseCursor: %v", err) +// } - return oapi_title, nil -} +// return &cursor, nil +// } func parseInt64(s string) (int32, error) { i, err := strconv.ParseInt(s, 10, 64) diff --git a/modules/backend/handlers/titles.go b/modules/backend/handlers/titles.go index d593314..84fc87e 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 { @@ -158,6 +158,78 @@ func (s Server) GetStudio(ctx context.Context, id int64) (*oapi.Studio, error) { return &oapi_studio, nil } +func (s Server) mapTitle(ctx context.Context, title sqlc.SearchTitlesRow) (oapi.Title, error) { + + // var oapi_title oapi.Title + + title_names := make(map[string][]string, 0) + err := json.Unmarshal(title.TitleNames, &title_names) + if err != nil { + 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) + } + + oapi_tag_names := make(oapi.Tags, 0) + err = json.Unmarshal(title.TagNames, &oapi_tag_names) + if err != nil { + return oapi.Title{}, fmt.Errorf("unmarshalling title_tag: %v", err) + } + + 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 + } + + var oapi_image oapi.Image + + if title.PosterID != nil { + oapi_image.Id = title.PosterID + oapi_image.ImagePath = title.TitleImagePath + oapi_image.StorageType = &title.TitleStorageType + } + + var release_season oapi.ReleaseSeason + if title.ReleaseSeason != nil { + release_season = oapi.ReleaseSeason(*title.ReleaseSeason) + } + + 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: + } + + return oapi_title, nil +} + func (s Server) GetTitlesTitleId(ctx context.Context, request oapi.GetTitlesTitleIdRequestObject) (oapi.GetTitlesTitleIdResponseObject, error) { var oapi_title oapi.Title @@ -169,8 +241,29 @@ func (s Server) GetTitlesTitleId(ctx context.Context, request oapi.GetTitlesTitl log.Errorf("%v", err) return oapi.GetTitlesTitleId500Response{}, nil } - - oapi_title, err = s.mapTitle(ctx, sqlc_title) + _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) if err != nil { log.Errorf("%v", err) return oapi.GetTitlesTitleId500Response{}, nil @@ -183,6 +276,11 @@ 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 { @@ -190,22 +288,16 @@ 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, - TitleStatuses: statuses_sort, + Ongoing: status.ongoing, + Finished: status.finished, + Planned: status.planned, Rating: request.Params.Rating, ReleaseYear: request.Params.ReleaseYear, ReleaseSeason: season, - Forward: true, // default - SortBy: "id", // default + Forward: true, + SortBy: "id", Limit: request.Params.Limit, } @@ -215,7 +307,6 @@ 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) @@ -237,30 +328,7 @@ func (s Server) GetTitles(ctx context.Context, request oapi.GetTitlesRequestObje for _, title := range titles { - _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) + t, err := s.mapTitle(ctx, title) if err != nil { log.Errorf("%v", err) return oapi.GetTitles500Response{}, nil @@ -271,7 +339,7 @@ func (s Server) GetTitles(ctx context.Context, request oapi.GetTitlesRequestObje if request.Params.Sort != nil { switch string(*request.Params.Sort) { case "year": - tmp := fmt.Sprint(*t.ReleaseYear) + tmp := fmt.Sprint("%d", *t.ReleaseYear) new_cursor.Param = &tmp case "rating": tmp := strconv.FormatFloat(*t.Rating, 'f', -1, 64) diff --git a/modules/backend/handlers/users.go b/modules/backend/handlers/users.go index 3223389..0fa903f 100644 --- a/modules/backend/handlers/users.go +++ b/modules/backend/handlers/users.go @@ -2,16 +2,12 @@ package handlers import ( "context" - "fmt" oapi "nyanimedb/api" sqlc "nyanimedb/sql" - "strconv" "time" "github.com/jackc/pgx/v5" - "github.com/jackc/pgx/v5/pgtype" "github.com/oapi-codegen/runtime/types" - log "github.com/sirupsen/logrus" ) // type Server struct { @@ -54,199 +50,34 @@ func (s Server) GetUsersUserId(ctx context.Context, req oapi.GetUsersUserIdReque return oapi.GetUsersUserId200JSONResponse(mapUser(user)), nil } -func sqlDate2oapi(p_date pgtype.Timestamptz) *time.Time { - if p_date.Valid { - t := p_date.Time - return &t - } - return nil -} - -type SqlcUserStatus struct { - dropped string - finished string - planned string - in_progress string -} - -// 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) - } - - 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, - } - - 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) { - oapi_usertitles := make([]oapi.UserTitle, 0) + var rate int32 = 9 + var review_id int64 = 3 + time := time.Date(2025, 1, 15, 10, 30, 0, 0, time.UTC) - word := Word2Sqlc(request.Params.Word) - - season, err := ReleaseSeason2sqlc(request.Params.ReleaseSeason) - if err != nil { - log.Errorf("%v", err) - return oapi.GetUsersUserIdTitles400Response{}, err + var userTitles = []oapi.UserTitle{ + { + UserId: 101, + TitleId: 2001, + Status: oapi.UserTitleStatusFinished, + Rate: &rate, + Ctime: &time, + }, + { + UserId: 102, + TitleId: 2002, + Status: oapi.UserTitleStatusInProgress, + ReviewId: &review_id, + Ctime: &time, + }, + { + UserId: 103, + TitleId: 2003, + Status: oapi.UserTitleStatusDropped, + Ctime: &time, + }, } - 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{ - 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 { - params.Forward = *request.Params.SortForward - } - 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) - return oapi.GetUsersUserIdTitles400Response{}, nil - } - } - } - // param = nil means it will not be used - titles, err := s.db.SearchUserTitles(ctx, params) - if err != nil { - log.Errorf("%v", err) - return oapi.GetUsersUserIdTitles500Response{}, nil - } - if len(titles) == 0 { - return oapi.GetUsersUserIdTitles204Response{}, nil - } - - var new_cursor oapi.CursorObj - - for _, title := range titles { - - 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{Cursor: new_cursor, Data: oapi_usertitles}, nil + return oapi.GetUsersUserIdTitles200JSONResponse(userTitles), nil } diff --git a/modules/backend/queries.sql b/modules/backend/queries.sql index 9734ff2..8546271 100644 --- a/modules/backend/queries.sql +++ b/modules/backend/queries.sql @@ -95,36 +95,31 @@ 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 t.id = sqlc.arg('title_id')::bigint +WHERE id = sqlc.arg('title_id')::bigint GROUP BY t.id, i.id, s.id, si.id; -- name: SearchTitles :many SELECT - 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, + t.*, 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.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 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 @@ -192,19 +187,13 @@ WHERE END ) - AND ( - -- Если массив пуст (NULL или []) — не фильтруем - cardinality(sqlc.arg('title_statuses')::text[]) = 0 - OR - -- Иначе: статус есть в списке - t.title_status = ANY(sqlc.arg('title_statuses')::text[]) -) + AND (t.title_status::text IN (sqlc.arg('ongoing')::text, sqlc.arg('finished')::text, sqlc.arg('planned')::text)) 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 + t.id, i.id, s.id, si.id ORDER BY CASE WHEN sqlc.arg('forward')::boolean THEN @@ -227,142 +216,37 @@ ORDER BY LIMIT COALESCE(sqlc.narg('limit')::int, 100); -- 100 is default limit -- name: SearchUserTitles :many -SELECT - 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, - u.review_id as review_id, - u.ctime as user_ctime, - 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 - -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 - WHEN sqlc.arg('forward')::boolean THEN - -- forward: greater than cursor (next page) - CASE sqlc.arg('sort_by')::text - WHEN 'year' THEN - (sqlc.narg('cursor_year')::int IS NULL) OR - (t.release_year > sqlc.narg('cursor_year')::int) OR - (t.release_year = sqlc.narg('cursor_year')::int AND t.id > sqlc.narg('cursor_id')::bigint) - - WHEN 'rating' THEN - (sqlc.narg('cursor_rating')::float IS NULL) OR - (t.rating > sqlc.narg('cursor_rating')::float) OR - (t.rating = sqlc.narg('cursor_rating')::float AND t.id > sqlc.narg('cursor_id')::bigint) - - WHEN 'id' THEN - (sqlc.narg('cursor_id')::bigint IS NULL) OR - (t.id > sqlc.narg('cursor_id')::bigint) - - ELSE true -- fallback - END - - ELSE - -- backward: less than cursor (prev page) - CASE sqlc.arg('sort_by')::text - WHEN 'year' THEN - (sqlc.narg('cursor_year')::int IS NULL) OR - (t.release_year < sqlc.narg('cursor_year')::int) OR - (t.release_year = sqlc.narg('cursor_year')::int AND t.id < sqlc.narg('cursor_id')::bigint) - - WHEN 'rating' THEN - (sqlc.narg('cursor_rating')::float IS NULL) OR - (t.rating < sqlc.narg('cursor_rating')::float) OR - (t.rating = sqlc.narg('cursor_rating')::float AND t.id < sqlc.narg('cursor_id')::bigint) - - WHEN 'id' THEN - (sqlc.narg('cursor_id')::bigint IS NULL) OR - (t.id < sqlc.narg('cursor_id')::bigint) - - ELSE true - END +SELECT + * +FROM usertitles as u +JOIN titles as t ON (u.title_id = t.id) +WHERE + CASE + WHEN sqlc.narg('word')::text IS NOT NULL THEN + ( + SELECT bool_and( + EXISTS ( + SELECT 1 + FROM jsonb_each_text(t.title_names) AS t(key, val) + WHERE val ILIKE pattern + ) + ) + FROM unnest( + ARRAY( + SELECT '%' || trim(w) || '%' + FROM unnest(string_to_array(sqlc.narg('word')::text, ' ')) AS w + WHERE trim(w) <> '' + ) + ) AS pattern + ) + ELSE true END - AND ( - CASE - WHEN sqlc.narg('word')::text IS NOT NULL THEN - ( - SELECT bool_and( - EXISTS ( - SELECT 1 - FROM jsonb_each_text(t.title_names) AS t(key, val) - WHERE val ILIKE pattern - ) - ) - FROM unnest( - ARRAY( - SELECT '%' || trim(w) || '%' - FROM unnest(string_to_array(sqlc.narg('word')::text, ' ')) AS w - WHERE trim(w) <> '' - ) - ) AS pattern - ) - ELSE true - END - ) - - 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('status')::title_status_t IS NULL OR t.title_status = sqlc.narg('status')::title_status_t) 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 - -ORDER BY - CASE WHEN sqlc.arg('forward')::boolean THEN - CASE - WHEN sqlc.arg('sort_by')::text = 'id' THEN t.id - WHEN sqlc.arg('sort_by')::text = 'year' THEN t.release_year - WHEN sqlc.arg('sort_by')::text = 'rating' THEN t.rating - WHEN sqlc.arg('sort_by')::text = 'rate' THEN u.rate - END - END ASC, - CASE WHEN NOT sqlc.arg('forward')::boolean THEN - CASE - WHEN sqlc.arg('sort_by')::text = 'id' THEN t.id - WHEN sqlc.arg('sort_by')::text = 'year' THEN t.release_year - WHEN sqlc.arg('sort_by')::text = 'rating' THEN t.rating - WHEN sqlc.arg('sort_by')::text = 'rate' THEN u.rate - END - END DESC, - - CASE WHEN sqlc.arg('sort_by')::text <> 'id' THEN t.id END ASC + AND (sqlc.narg('usertitle_status')::usertitle_status_t IS NULL OR u.usertitle_status = sqlc.narg('usertitle_status')::usertitle_status_t) LIMIT COALESCE(sqlc.narg('limit')::int, 100); -- 100 is default limit diff --git a/sql/queries.sql.go b/sql/queries.sql.go index f7535db..ad1b9a8 100644 --- a/sql/queries.sql.go +++ b/sql/queries.sql.go @@ -138,7 +138,7 @@ 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 t.id = $1::bigint +WHERE id = $1::bigint GROUP BY t.id, i.id, s.id, si.id ` @@ -342,30 +342,25 @@ func (q *Queries) InsertTitleTags(ctx context.Context, arg InsertTitleTagsParams const searchTitles = `-- name: SearchTitles :many SELECT - 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, + 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, 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.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 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 @@ -433,19 +428,13 @@ WHERE END ) - AND ( - -- Если массив пуст (NULL или []) — не фильтруем - cardinality($7::text[]) = 0 - OR - -- Иначе: статус есть в списке - t.title_status = ANY($7::text[]) -) - AND ($8::float IS NULL OR t.rating >= $8::float) - AND ($9::int IS NULL OR t.release_year = $9::int) - AND ($10::release_season_t IS NULL OR t.release_season = $10::release_season_t) + AND (t.title_status::text IN ($7::text, $8::text, $9::text)) + 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 + t.id, i.id, s.id, si.id ORDER BY CASE WHEN $1::boolean THEN @@ -465,7 +454,7 @@ ORDER BY CASE WHEN $2::text <> 'id' THEN t.id END ASC -LIMIT COALESCE($11::int, 100) +LIMIT COALESCE($13::int, 100) ` type SearchTitlesParams struct { @@ -475,7 +464,9 @@ type SearchTitlesParams struct { CursorID *int64 `json:"cursor_id"` CursorRating *float64 `json:"cursor_rating"` Word *string `json:"word"` - TitleStatuses []string `json:"title_statuses"` + Ongoing string `json:"ongoing"` + Finished string `json:"finished"` + Planned string `json:"planned"` Rating *float64 `json:"rating"` ReleaseYear *int32 `json:"release_year"` ReleaseSeason *ReleaseSeasonT `json:"release_season"` @@ -483,21 +474,27 @@ type SearchTitlesParams struct { } type SearchTitlesRow struct { - 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"` + 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"` } func (q *Queries) SearchTitles(ctx context.Context, arg SearchTitlesParams) ([]SearchTitlesRow, error) { @@ -508,7 +505,9 @@ func (q *Queries) SearchTitles(ctx context.Context, arg SearchTitlesParams) ([]S arg.CursorID, arg.CursorRating, arg.Word, - arg.TitleStatuses, + arg.Ongoing, + arg.Finished, + arg.Planned, arg.Rating, arg.ReleaseYear, arg.ReleaseSeason, @@ -524,6 +523,7 @@ 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, @@ -533,10 +533,15 @@ 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 } @@ -550,200 +555,82 @@ func (q *Queries) SearchTitles(ctx context.Context, arg SearchTitlesParams) ([]S const searchUserTitles = `-- name: SearchUserTitles :many -SELECT - 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, - u.review_id as review_id, - u.ctime as user_ctime, - 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 - -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 - WHEN $1::boolean THEN - -- forward: greater than cursor (next page) - CASE $2::text - WHEN 'year' THEN - ($3::int IS NULL) OR - (t.release_year > $3::int) OR - (t.release_year = $3::int AND t.id > $4::bigint) - - WHEN 'rating' THEN - ($5::float IS NULL) OR - (t.rating > $5::float) OR - (t.rating = $5::float AND t.id > $4::bigint) - - WHEN 'id' THEN - ($4::bigint IS NULL) OR - (t.id > $4::bigint) - - ELSE true -- fallback - END - - ELSE - -- backward: less than cursor (prev page) - CASE $2::text - WHEN 'year' THEN - ($3::int IS NULL) OR - (t.release_year < $3::int) OR - (t.release_year = $3::int AND t.id < $4::bigint) - - WHEN 'rating' THEN - ($5::float IS NULL) OR - (t.rating < $5::float) OR - (t.rating = $5::float AND t.id < $4::bigint) - - WHEN 'id' THEN - ($4::bigint IS NULL) OR - (t.id < $4::bigint) - - ELSE true - END +SELECT + user_id, title_id, status, rate, review_id, ctime, id, title_names, studio_id, poster_id, title_status, rating, rating_count, release_year, release_season, season, episodes_aired, episodes_all, episodes_len +FROM usertitles as u +JOIN titles as t ON (u.title_id = t.id) +WHERE + CASE + WHEN $1::text IS NOT NULL THEN + ( + SELECT bool_and( + EXISTS ( + SELECT 1 + FROM jsonb_each_text(t.title_names) AS t(key, val) + WHERE val ILIKE pattern + ) + ) + FROM unnest( + ARRAY( + SELECT '%' || trim(w) || '%' + FROM unnest(string_to_array($1::text, ' ')) AS w + WHERE trim(w) <> '' + ) + ) AS pattern + ) + ELSE true END - AND ( - CASE - WHEN $6::text IS NOT NULL THEN - ( - SELECT bool_and( - EXISTS ( - SELECT 1 - FROM jsonb_each_text(t.title_names) AS t(key, val) - WHERE val ILIKE pattern - ) - ) - FROM unnest( - ARRAY( - SELECT '%' || trim(w) || '%' - FROM unnest(string_to_array($6::text, ' ')) AS w - WHERE trim(w) <> '' - ) - ) AS pattern - ) - ELSE true - END - ) + AND ($2::title_status_t IS NULL OR t.title_status = $2::title_status_t) + AND ($3::float IS NULL OR t.rating >= $3::float) + AND ($4::int IS NULL OR t.release_year = $4::int) + AND ($5::release_season_t IS NULL OR t.release_season = $5::release_season_t) + AND ($6::usertitle_status_t IS NULL OR u.usertitle_status = $6::usertitle_status_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 - -ORDER BY - CASE WHEN $1::boolean THEN - CASE - WHEN $2::text = 'id' THEN t.id - WHEN $2::text = 'year' THEN t.release_year - WHEN $2::text = 'rating' THEN t.rating - WHEN $2::text = 'rate' THEN u.rate - END - END ASC, - CASE WHEN NOT $1::boolean THEN - CASE - WHEN $2::text = 'id' THEN t.id - WHEN $2::text = 'year' THEN t.release_year - WHEN $2::text = 'rating' THEN t.rating - WHEN $2::text = 'rate' THEN u.rate - END - END DESC, - - CASE WHEN $2::text <> 'id' THEN t.id END ASC - -LIMIT COALESCE($13::int, 100) +LIMIT COALESCE($7::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"` - 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"` + Word *string `json:"word"` + Status *TitleStatusT `json:"status"` + Rating *float64 `json:"rating"` + ReleaseYear *int32 `json:"release_year"` + ReleaseSeason *ReleaseSeasonT `json:"release_season"` + UsertitleStatus NullUsertitleStatusT `json:"usertitle_status"` + Limit *int32 `json:"limit"` } type SearchUserTitlesRow struct { - 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"` + UserID int64 `json:"user_id"` + TitleID int64 `json:"title_id"` + Status UsertitleStatusT `json:"status"` + Rate *int32 `json:"rate"` + ReviewID *int64 `json:"review_id"` + Ctime pgtype.Timestamptz `json:"ctime"` + 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"` } // 100 is default limit func (q *Queries) SearchUserTitles(ctx context.Context, arg SearchUserTitlesParams) ([]SearchUserTitlesRow, error) { rows, err := q.db.Query(ctx, searchUserTitles, - arg.Forward, - arg.SortBy, - arg.CursorYear, - arg.CursorID, - arg.CursorRating, arg.Word, - arg.TitleStatuses, - arg.UsertitleStatuses, - arg.Rate, + arg.Status, arg.Rating, arg.ReleaseYear, arg.ReleaseSeason, + arg.UsertitleStatus, arg.Limit, ) if err != nil { @@ -754,8 +641,15 @@ func (q *Queries) SearchUserTitles(ctx context.Context, arg SearchUserTitlesPara for rows.Next() { var i SearchUserTitlesRow if err := rows.Scan( + &i.UserID, + &i.TitleID, + &i.Status, + &i.Rate, + &i.ReviewID, + &i.Ctime, &i.ID, &i.TitleNames, + &i.StudioID, &i.PosterID, &i.TitleStatus, &i.Rating, @@ -765,15 +659,7 @@ func (q *Queries) SearchUserTitles(ctx context.Context, arg SearchUserTitlesPara &i.Season, &i.EpisodesAired, &i.EpisodesAll, - &i.UserID, - &i.UsertitleStatus, - &i.UserRate, - &i.ReviewID, - &i.UserCtime, - &i.TitleStorageType, - &i.TitleImagePath, - &i.TagNames, - &i.StudioName, + &i.EpisodesLen, ); err != nil { return nil, err }