diff --git a/modules/backend/queries.sql b/modules/backend/queries.sql index 16f8120..42b99d1 100644 --- a/modules/backend/queries.sql +++ b/modules/backend/queries.sql @@ -78,7 +78,7 @@ SELECT t.*, i.storage_type::text as title_storage_type, i.image_path as title_image_path, - jsonb_agg_strict(g.tag_name)'[]'::jsonb as tag_names, + 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, @@ -101,7 +101,7 @@ SELECT t.*, i.storage_type::text as title_storage_type, i.image_path as title_image_path, - jsonb_agg_strict(g.tag_name)'[]'::jsonb as tag_names, + 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, diff --git a/sql/queries.sql.go b/sql/queries.sql.go index 9987e78..c67033c 100644 --- a/sql/queries.sql.go +++ b/sql/queries.sql.go @@ -8,6 +8,8 @@ package sqlc import ( "context" "time" + + "github.com/jackc/pgx/v5/pgtype" ) const createImage = `-- name: CreateImage :one @@ -41,6 +43,56 @@ func (q *Queries) GetImageByID(ctx context.Context, illustID int64) (Image, erro return i, err } +const getReviewByID = `-- name: GetReviewByID :one + + + +SELECT id, data, rating, user_id, title_id, created_at +FROM reviews +WHERE review_id = $1::bigint +` + +// 100 is default limit +// -- name: ListTitles :many +// SELECT title_id, title_names, studio_id, poster_id, signal_ids, +// +// title_status, rating, rating_count, release_year, release_season, +// season, episodes_aired, episodes_all, episodes_len +// +// FROM titles +// ORDER BY title_id +// LIMIT $1 OFFSET $2; +// -- name: UpdateTitle :one +// UPDATE titles +// SET +// +// title_names = COALESCE(sqlc.narg('title_names'), title_names), +// studio_id = COALESCE(sqlc.narg('studio_id'), studio_id), +// poster_id = COALESCE(sqlc.narg('poster_id'), poster_id), +// signal_ids = COALESCE(sqlc.narg('signal_ids'), signal_ids), +// title_status = COALESCE(sqlc.narg('title_status'), title_status), +// release_year = COALESCE(sqlc.narg('release_year'), release_year), +// release_season = COALESCE(sqlc.narg('release_season'), release_season), +// episodes_aired = COALESCE(sqlc.narg('episodes_aired'), episodes_aired), +// episodes_all = COALESCE(sqlc.narg('episodes_all'), episodes_all), +// episodes_len = COALESCE(sqlc.narg('episodes_len'), episodes_len) +// +// WHERE title_id = sqlc.arg('title_id') +// RETURNING *; +func (q *Queries) GetReviewByID(ctx context.Context, reviewID int64) (Review, error) { + row := q.db.QueryRow(ctx, getReviewByID, reviewID) + var i Review + err := row.Scan( + &i.ID, + &i.Data, + &i.Rating, + &i.UserID, + &i.TitleID, + &i.CreatedAt, + ) + return i, err +} + const getStudioByID = `-- name: GetStudioByID :one SELECT id, studio_name, illust_id, studio_desc FROM studios @@ -68,7 +120,7 @@ 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, i.storage_type::text as title_storage_type, i.image_path as title_image_path, - jsonb_agg_strict(g.tag_name)'[]'::jsonb as tag_names, + 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, @@ -76,7 +128,7 @@ SELECT si.image_path as studio_image_path FROM titles as t -LEFT JOIN images as i ON (t.poster_id = i.id) +LEFT JOIN images as i ON (t.image_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) @@ -289,7 +341,7 @@ 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, i.storage_type::text as title_storage_type, i.image_path as title_image_path, - jsonb_agg_strict(g.tag_name)'[]'::jsonb as tag_names, + 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, @@ -297,7 +349,7 @@ SELECT si.image_path as studio_image_path FROM titles as t -LEFT JOIN images as i ON (t.poster_id = i.id) +LEFT JOIN images as i ON (t.image_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) @@ -496,183 +548,111 @@ func (q *Queries) SearchTitles(ctx context.Context, arg SearchTitlesParams) ([]S 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) - - - - - - - - - - - - - - - - -SELECT id, data, rating, user_id, title_id, created_at -FROM reviews -WHERE review_id = $1::bigint +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 -// SELECT -// -// t.*, -// 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, -// jsonb_agg_strict(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 -// -// 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) -// 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 -// 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 (u.::text IN (sqlc.arg('ongoing')::text, sqlc.arg('finished')::text, sqlc.arg('planned')::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, si.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 -// 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 -// END -// END DESC, -// CASE WHEN sqlc.arg('sort_by')::text <> 'id' THEN t.id END ASC -// -// LIMIT COALESCE(sqlc.narg('limit')::int, 100); -- 100 is default limit -// -- name: ListTitles :many -// SELECT title_id, title_names, studio_id, poster_id, signal_ids, -// -// title_status, rating, rating_count, release_year, release_season, -// season, episodes_aired, episodes_all, episodes_len -// -// FROM titles -// ORDER BY title_id -// LIMIT $1 OFFSET $2; -// -- name: UpdateTitle :one -// UPDATE titles -// SET -// -// title_names = COALESCE(sqlc.narg('title_names'), title_names), -// studio_id = COALESCE(sqlc.narg('studio_id'), studio_id), -// poster_id = COALESCE(sqlc.narg('poster_id'), poster_id), -// signal_ids = COALESCE(sqlc.narg('signal_ids'), signal_ids), -// title_status = COALESCE(sqlc.narg('title_status'), title_status), -// release_year = COALESCE(sqlc.narg('release_year'), release_year), -// release_season = COALESCE(sqlc.narg('release_season'), release_season), -// episodes_aired = COALESCE(sqlc.narg('episodes_aired'), episodes_aired), -// episodes_all = COALESCE(sqlc.narg('episodes_all'), episodes_all), -// episodes_len = COALESCE(sqlc.narg('episodes_len'), episodes_len) -// -// WHERE title_id = sqlc.arg('title_id') -// RETURNING *; -func (q *Queries) SearchUserTitles(ctx context.Context, reviewID int64) ([]Review, error) { - rows, err := q.db.Query(ctx, searchUserTitles, reviewID) +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() - items := []Review{} + items := []SearchUserTitlesRow{} for rows.Next() { - var i Review + var i SearchUserTitlesRow if err := rows.Scan( - &i.ID, - &i.Data, - &i.Rating, &i.UserID, &i.TitleID, - &i.CreatedAt, + &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 }