feat: cursor stub added

This commit is contained in:
Iron_Felix 2025-11-19 03:58:46 +03:00
parent fbf3f1d3a2
commit 34d9341e75
5 changed files with 214 additions and 20 deletions

View file

@ -218,6 +218,9 @@ func (s Server) GetTitlesTitleId(ctx context.Context, request oapi.GetTitlesTitl
func (s Server) GetTitles(ctx context.Context, request oapi.GetTitlesRequestObject) (oapi.GetTitlesResponseObject, error) { func (s Server) GetTitles(ctx context.Context, request oapi.GetTitlesRequestObject) (oapi.GetTitlesResponseObject, error) {
opai_titles := make([]oapi.Title, 0) opai_titles := make([]oapi.Title, 0)
cursor := oapi.CursorObj{
Id: 1,
}
word := Word2Sqlc(request.Params.Word) word := Word2Sqlc(request.Params.Word)
status, err := TitleStatus2Sqlc(request.Params.Status) status, err := TitleStatus2Sqlc(request.Params.Status)
@ -237,7 +240,6 @@ func (s Server) GetTitles(ctx context.Context, request oapi.GetTitlesRequestObje
Rating: request.Params.Rating, Rating: request.Params.Rating,
ReleaseYear: request.Params.ReleaseYear, ReleaseYear: request.Params.ReleaseYear,
ReleaseSeason: season, ReleaseSeason: season,
Offset: request.Params.Offset,
Limit: request.Params.Limit, Limit: request.Params.Limit,
}) })
if err != nil { if err != nil {
@ -258,5 +260,5 @@ func (s Server) GetTitles(ctx context.Context, request oapi.GetTitlesRequestObje
opai_titles = append(opai_titles, t) opai_titles = append(opai_titles, t)
} }
return oapi.GetTitles200JSONResponse(opai_titles), nil return oapi.GetTitles200JSONResponse{Cursor: cursor, Data: opai_titles}, nil
} }

View file

@ -107,9 +107,67 @@ WHERE
AND (sqlc.narg('rating')::float IS NULL OR rating >= sqlc.narg('rating')::float) AND (sqlc.narg('rating')::float IS NULL OR rating >= sqlc.narg('rating')::float)
AND (sqlc.narg('release_year')::int IS NULL OR release_year = sqlc.narg('release_year')::int) AND (sqlc.narg('release_year')::int IS NULL OR release_year = sqlc.narg('release_year')::int)
AND (sqlc.narg('release_season')::release_season_t IS NULL OR release_season = sqlc.narg('release_season')::release_season_t) AND (sqlc.narg('release_season')::release_season_t IS NULL OR release_season = sqlc.narg('release_season')::release_season_t)
ORDER BY
-- Основной ключ: выбранное поле
CASE
WHEN sqlc.arg(forward)::boolean AND sqlc.arg(sort_by)::text = 'id' THEN id
WHEN sqlc.arg(forward)::boolean AND sqlc.arg(sort_by)::text = 'name' THEN name
WHEN sqlc.arg(forward)::boolean AND sqlc.arg(sort_by)::text = 'year' THEN release_year
WHEN sqlc.arg(forward)::boolean AND sqlc.arg(sort_by)::text = 'rating' THEN rating
WHEN sqlc.arg(forward)::boolean AND sqlc.arg(sort_by)::text = 'views' THEN views
END ASC,
CASE
WHEN NOT sqlc.arg(forward)::boolean AND sqlc.arg(sort_by)::text = 'id' THEN id
WHEN NOT sqlc.arg(forward)::boolean AND sqlc.arg(sort_by)::text = 'name' THEN name
WHEN NOT sqlc.arg(forward)::boolean AND sqlc.arg(sort_by)::text = 'year' THEN release_year
WHEN NOT sqlc.arg(forward)::boolean AND sqlc.arg(sort_by)::text = 'rating' THEN rating
WHEN NOT sqlc.arg(forward)::boolean AND sqlc.arg(sort_by)::text = 'views' THEN views
END DESC,
LIMIT COALESCE(sqlc.narg('limit')::int, 100) -- 100 is default limit -- Вторичный ключ: id — только если НЕ сортируем по id
OFFSET sqlc.narg('offset')::int; CASE
WHEN sqlc.arg(sort_by)::text != 'id' AND sqlc.arg(forward)::boolean THEN id
END ASC,
CASE
WHEN sqlc.arg(sort_by)::text != 'id' AND NOT sqlc.arg(forward)::boolean THEN id
END DESC
LIMIT COALESCE(sqlc.narg('limit')::int, 100); -- 100 is default limit
-- OFFSET sqlc.narg('offset')::int;
-- name: SearchUserTitles :many
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 (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)
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
-- -- name: ListTitles :many -- -- name: ListTitles :many
-- SELECT title_id, title_names, studio_id, poster_id, signal_ids, -- SELECT title_id, title_names, studio_id, poster_id, signal_ids,

View file

@ -59,7 +59,7 @@ CREATE TABLE studios (
); );
CREATE TABLE titles ( CREATE TABLE titles (
// TODO: anime type (film, season etc) -- // TODO: anime type (film, season etc)
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
-- example {"ru": ["Атака титанов", "Атака Титанов"],"en": ["Attack on Titan", "AoT"],"ja": ["進撃の巨人", "しんげきのきょじん"]} -- example {"ru": ["Атака титанов", "Атака Титанов"],"en": ["Attack on Titan", "AoT"],"ja": ["進撃の巨人", "しんげきのきょじん"]}
title_names jsonb NOT NULL, title_names jsonb NOT NULL,

View file

@ -212,7 +212,6 @@ type Review struct {
ID int64 `json:"id"` ID int64 `json:"id"`
Data string `json:"data"` Data string `json:"data"`
Rating *int32 `json:"rating"` Rating *int32 `json:"rating"`
IllustID *int64 `json:"illust_id"`
UserID *int64 `json:"user_id"` UserID *int64 `json:"user_id"`
TitleID *int64 `json:"title_id"` TitleID *int64 `json:"title_id"`
CreatedAt pgtype.Timestamptz `json:"created_at"` CreatedAt pgtype.Timestamptz `json:"created_at"`
@ -277,10 +276,10 @@ type User struct {
} }
type Usertitle struct { type Usertitle struct {
UserID int64 `json:"user_id"` UserID int64 `json:"user_id"`
TitleID int64 `json:"title_id"` TitleID int64 `json:"title_id"`
Status UsertitleStatusT `json:"status"` Status UsertitleStatusT `json:"status"`
Rate *int32 `json:"rate"` Rate *int32 `json:"rate"`
ReviewText *string `json:"review_text"` ReviewID *int64 `json:"review_id"`
ReviewDate pgtype.Timestamptz `json:"review_date"` Ctime pgtype.Timestamptz `json:"ctime"`
} }

View file

@ -8,6 +8,8 @@ package sqlc
import ( import (
"context" "context"
"time" "time"
"github.com/jackc/pgx/v5/pgtype"
) )
const createImage = `-- name: CreateImage :one const createImage = `-- name: CreateImage :one
@ -31,7 +33,7 @@ func (q *Queries) CreateImage(ctx context.Context, arg CreateImageParams) (Image
const getImageByID = `-- name: GetImageByID :one const getImageByID = `-- name: GetImageByID :one
SELECT id, storage_type, image_path SELECT id, storage_type, image_path
FROM images FROM images
WHERE id = $1 WHERE id = $1::bigint
` `
func (q *Queries) GetImageByID(ctx context.Context, illustID int64) (Image, error) { func (q *Queries) GetImageByID(ctx context.Context, illustID int64) (Image, error) {
@ -44,11 +46,13 @@ func (q *Queries) GetImageByID(ctx context.Context, illustID int64) (Image, erro
const getReviewByID = `-- name: GetReviewByID :one const getReviewByID = `-- name: GetReviewByID :one
SELECT id, data, rating, illust_id, user_id, title_id, created_at
SELECT id, data, rating, user_id, title_id, created_at
FROM reviews FROM reviews
WHERE review_id = $1::bigint WHERE review_id = $1::bigint
` `
// 100 is default limit
// -- name: ListTitles :many // -- name: ListTitles :many
// SELECT title_id, title_names, studio_id, poster_id, signal_ids, // SELECT title_id, title_names, studio_id, poster_id, signal_ids,
// //
@ -82,7 +86,6 @@ func (q *Queries) GetReviewByID(ctx context.Context, reviewID int64) (Review, er
&i.ID, &i.ID,
&i.Data, &i.Data,
&i.Rating, &i.Rating,
&i.IllustID,
&i.UserID, &i.UserID,
&i.TitleID, &i.TitleID,
&i.CreatedAt, &i.CreatedAt,
@ -312,9 +315,18 @@ WHERE
AND ($3::float IS NULL OR rating >= $3::float) AND ($3::float IS NULL OR rating >= $3::float)
AND ($4::int IS NULL OR release_year = $4::int) AND ($4::int IS NULL OR release_year = $4::int)
AND ($5::release_season_t IS NULL OR release_season = $5::release_season_t) AND ($5::release_season_t IS NULL OR release_season = $5::release_season_t)
ORDER BY CASE
LIMIT COALESCE($7::int, 100) -- 100 is default limit WHEN $6::boolean AND $7::text = 'name' THEN name
OFFSET $6::int WHEN $8::boolean AND $7::text = 'id' THEN id
WHEN $8::boolean AND $7::text = 'name' THEN name
WHEN $8::boolean AND $7::text = 'id' THEN id
END ASC, CASE
WHEN NOT $8::boolean AND $7::text = 'name' THEN name
WHEN NOT $8::boolean AND $7::text = 'id' THEN id
WHEN NOT $8::boolean AND $7::text = 'name' THEN name
WHEN NOT $8::boolean AND $7::text = 'id' THEN id
END DESC
LIMIT COALESCE($9::int, 100)
` `
type SearchTitlesParams struct { type SearchTitlesParams struct {
@ -323,7 +335,9 @@ type SearchTitlesParams struct {
Rating *float64 `json:"rating"` Rating *float64 `json:"rating"`
ReleaseYear *int32 `json:"release_year"` ReleaseYear *int32 `json:"release_year"`
ReleaseSeason *ReleaseSeasonT `json:"release_season"` ReleaseSeason *ReleaseSeasonT `json:"release_season"`
Offset *int32 `json:"offset"` Forward bool `json:"forward"`
OrderBy string `json:"order_by"`
Reverse bool `json:"reverse"`
Limit *int32 `json:"limit"` Limit *int32 `json:"limit"`
} }
@ -334,7 +348,9 @@ func (q *Queries) SearchTitles(ctx context.Context, arg SearchTitlesParams) ([]T
arg.Rating, arg.Rating,
arg.ReleaseYear, arg.ReleaseYear,
arg.ReleaseSeason, arg.ReleaseSeason,
arg.Offset, arg.Forward,
arg.OrderBy,
arg.Reverse,
arg.Limit, arg.Limit,
) )
if err != nil { if err != nil {
@ -368,3 +384,122 @@ func (q *Queries) SearchTitles(ctx context.Context, arg SearchTitlesParams) ([]T
} }
return items, nil return items, nil
} }
const searchUserTitles = `-- name: SearchUserTitles :many
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 ($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)
LIMIT COALESCE($7::int, 100)
`
type SearchUserTitlesParams struct {
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 {
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 []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"`
}
// 100 is default limit
// OFFSET sqlc.narg('offset')::int;
func (q *Queries) SearchUserTitles(ctx context.Context, arg SearchUserTitlesParams) ([]SearchUserTitlesRow, error) {
rows, err := q.db.Query(ctx, searchUserTitles,
arg.Word,
arg.Status,
arg.Rating,
arg.ReleaseYear,
arg.ReleaseSeason,
arg.UsertitleStatus,
arg.Limit,
)
if err != nil {
return nil, err
}
defer rows.Close()
var items []SearchUserTitlesRow
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,
&i.RatingCount,
&i.ReleaseYear,
&i.ReleaseSeason,
&i.Season,
&i.EpisodesAired,
&i.EpisodesAll,
&i.EpisodesLen,
); err != nil {
return nil, err
}
items = append(items, i)
}
if err := rows.Err(); err != nil {
return nil, err
}
return items, nil
}