Compare commits
2 commits
2025bb451f
...
34d9341e75
| Author | SHA1 | Date | |
|---|---|---|---|
| 34d9341e75 | |||
| fbf3f1d3a2 |
9 changed files with 671 additions and 24 deletions
6
api/_build/oapi-codegen.yaml
Normal file
6
api/_build/oapi-codegen.yaml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
package: oapi
|
||||||
|
generate:
|
||||||
|
strict-server: true
|
||||||
|
gin-server: true
|
||||||
|
models: true
|
||||||
|
output: api/api.gen.go
|
||||||
436
api/_build/openapi.yaml
Normal file
436
api/_build/openapi.yaml
Normal file
|
|
@ -0,0 +1,436 @@
|
||||||
|
openapi: 3.0.4
|
||||||
|
info:
|
||||||
|
title: 'Titles, Users, Reviews, Tags, and Media API'
|
||||||
|
version: 1.0.0
|
||||||
|
servers:
|
||||||
|
- url: /api/v1
|
||||||
|
paths:
|
||||||
|
/titles:
|
||||||
|
get:
|
||||||
|
summary: Get titles
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/cursor'
|
||||||
|
- $ref: '#/components/parameters/title_sort'
|
||||||
|
- in: query
|
||||||
|
name: sort_forward
|
||||||
|
schema:
|
||||||
|
type: boolean
|
||||||
|
default: true
|
||||||
|
- in: query
|
||||||
|
name: word
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: status
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/TitleStatus'
|
||||||
|
- in: query
|
||||||
|
name: rating
|
||||||
|
schema:
|
||||||
|
type: number
|
||||||
|
format: double
|
||||||
|
- in: query
|
||||||
|
name: release_year
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int32
|
||||||
|
- in: query
|
||||||
|
name: release_season
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ReleaseSeason'
|
||||||
|
- in: query
|
||||||
|
name: limit
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int32
|
||||||
|
default: 10
|
||||||
|
- in: query
|
||||||
|
name: offset
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int32
|
||||||
|
default: 0
|
||||||
|
- in: query
|
||||||
|
name: fields
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
default: all
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: List of titles with cursor
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
data:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Title'
|
||||||
|
description: List of titles
|
||||||
|
cursor:
|
||||||
|
$ref: '#/components/schemas/CursorObj'
|
||||||
|
required:
|
||||||
|
- data
|
||||||
|
- cursor
|
||||||
|
'204':
|
||||||
|
description: No titles found
|
||||||
|
'400':
|
||||||
|
description: Request params are not correct
|
||||||
|
'500':
|
||||||
|
description: Unknown server error
|
||||||
|
'/titles/{title_id}':
|
||||||
|
get:
|
||||||
|
summary: Get title description
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: title_id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
- in: query
|
||||||
|
name: fields
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
default: all
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Title description
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Title'
|
||||||
|
'204':
|
||||||
|
description: No title found
|
||||||
|
'400':
|
||||||
|
description: Request params are not correct
|
||||||
|
'404':
|
||||||
|
description: Title not found
|
||||||
|
'500':
|
||||||
|
description: Unknown server error
|
||||||
|
'/users/{user_id}':
|
||||||
|
get:
|
||||||
|
summary: Get user info
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: user_id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: fields
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
default: all
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: User info
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/User'
|
||||||
|
'400':
|
||||||
|
description: Request params are not correct
|
||||||
|
'404':
|
||||||
|
description: User not found
|
||||||
|
'500':
|
||||||
|
description: Unknown server error
|
||||||
|
'/users/{user_id}/titles/':
|
||||||
|
get:
|
||||||
|
summary: Get user titles
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/cursor'
|
||||||
|
- in: path
|
||||||
|
name: user_id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: word
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
- in: query
|
||||||
|
name: status
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/TitleStatus'
|
||||||
|
- in: query
|
||||||
|
name: watch_status
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/UserTitleStatus'
|
||||||
|
- in: query
|
||||||
|
name: rating
|
||||||
|
schema:
|
||||||
|
type: number
|
||||||
|
format: double
|
||||||
|
- in: query
|
||||||
|
name: release_year
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int32
|
||||||
|
- in: query
|
||||||
|
name: release_season
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/ReleaseSeason'
|
||||||
|
- in: query
|
||||||
|
name: limit
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int32
|
||||||
|
default: 10
|
||||||
|
- in: query
|
||||||
|
name: fields
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
default: all
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: List of user titles
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/UserTitle'
|
||||||
|
'204':
|
||||||
|
description: No titles found
|
||||||
|
'400':
|
||||||
|
description: Request params are not correct
|
||||||
|
'500':
|
||||||
|
description: Unknown server error
|
||||||
|
components:
|
||||||
|
parameters:
|
||||||
|
cursor:
|
||||||
|
in: query
|
||||||
|
name: cursor
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
title_sort:
|
||||||
|
in: query
|
||||||
|
name: sort
|
||||||
|
required: false
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/TitleSort'
|
||||||
|
schemas:
|
||||||
|
CursorObj:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
param:
|
||||||
|
type: string
|
||||||
|
TitleSort:
|
||||||
|
type: string
|
||||||
|
description: Title sort order
|
||||||
|
default: id
|
||||||
|
enum:
|
||||||
|
- id
|
||||||
|
- year
|
||||||
|
- rating
|
||||||
|
- views
|
||||||
|
Image:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
storage_type:
|
||||||
|
type: string
|
||||||
|
image_path:
|
||||||
|
type: string
|
||||||
|
TitleStatus:
|
||||||
|
type: string
|
||||||
|
description: Title status
|
||||||
|
enum:
|
||||||
|
- finished
|
||||||
|
- ongoing
|
||||||
|
- planned
|
||||||
|
ReleaseSeason:
|
||||||
|
type: string
|
||||||
|
description: Title release season
|
||||||
|
enum:
|
||||||
|
- winter
|
||||||
|
- spring
|
||||||
|
- summer
|
||||||
|
- fall
|
||||||
|
UserTitleStatus:
|
||||||
|
type: string
|
||||||
|
description: User's title status
|
||||||
|
enum:
|
||||||
|
- finished
|
||||||
|
- planned
|
||||||
|
- dropped
|
||||||
|
- in-progress
|
||||||
|
Review:
|
||||||
|
type: object
|
||||||
|
additionalProperties: true
|
||||||
|
Tag:
|
||||||
|
type: object
|
||||||
|
description: 'A localized tag: keys are language codes (ISO 639-1), values are tag names'
|
||||||
|
additionalProperties:
|
||||||
|
type: string
|
||||||
|
example:
|
||||||
|
en: Shojo
|
||||||
|
ru: Сёдзё
|
||||||
|
ja: 少女
|
||||||
|
Tags:
|
||||||
|
type: array
|
||||||
|
description: Array of localized tags
|
||||||
|
items:
|
||||||
|
$ref: '#/components/schemas/Tag'
|
||||||
|
example:
|
||||||
|
- en: Shojo
|
||||||
|
ru: Сёдзё
|
||||||
|
ja: 少女
|
||||||
|
- en: Shounen
|
||||||
|
ru: Сёнен
|
||||||
|
ja: 少年
|
||||||
|
Studio:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
- name
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
poster:
|
||||||
|
$ref: '#/components/schemas/Image'
|
||||||
|
description:
|
||||||
|
type: string
|
||||||
|
Title:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- id
|
||||||
|
- title_names
|
||||||
|
- tags
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
description: Unique title ID (primary key)
|
||||||
|
example: 1
|
||||||
|
title_names:
|
||||||
|
type: object
|
||||||
|
description: 'Localized titles. Key = language (ISO 639-1), value = list of names'
|
||||||
|
additionalProperties:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
type: string
|
||||||
|
example: Attack on Titan
|
||||||
|
minItems: 1
|
||||||
|
example:
|
||||||
|
- Attack on Titan
|
||||||
|
- AoT
|
||||||
|
example:
|
||||||
|
en:
|
||||||
|
- Attack on Titan
|
||||||
|
- AoT
|
||||||
|
ru:
|
||||||
|
- Атака титанов
|
||||||
|
- Титаны
|
||||||
|
ja:
|
||||||
|
- 進撃の巨人
|
||||||
|
studio:
|
||||||
|
$ref: '#/components/schemas/Studio'
|
||||||
|
tags:
|
||||||
|
$ref: '#/components/schemas/Tags'
|
||||||
|
poster:
|
||||||
|
$ref: '#/components/schemas/Image'
|
||||||
|
title_status:
|
||||||
|
$ref: '#/components/schemas/TitleStatus'
|
||||||
|
rating:
|
||||||
|
type: number
|
||||||
|
format: double
|
||||||
|
rating_count:
|
||||||
|
type: integer
|
||||||
|
format: int32
|
||||||
|
release_year:
|
||||||
|
type: integer
|
||||||
|
format: int32
|
||||||
|
release_season:
|
||||||
|
$ref: '#/components/schemas/ReleaseSeason'
|
||||||
|
episodes_aired:
|
||||||
|
type: integer
|
||||||
|
format: int32
|
||||||
|
episodes_all:
|
||||||
|
type: integer
|
||||||
|
format: int32
|
||||||
|
episodes_len:
|
||||||
|
type: object
|
||||||
|
additionalProperties:
|
||||||
|
type: number
|
||||||
|
format: double
|
||||||
|
additionalProperties: true
|
||||||
|
User:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
description: Unique user ID (primary key)
|
||||||
|
example: 1
|
||||||
|
avatar_id:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
description: ID of the user avatar (references images table)
|
||||||
|
example: null
|
||||||
|
mail:
|
||||||
|
type: string
|
||||||
|
format: email
|
||||||
|
description: User email
|
||||||
|
example: john.doe@example.com
|
||||||
|
nickname:
|
||||||
|
type: string
|
||||||
|
description: Username (alphanumeric + _ or -)
|
||||||
|
maxLength: 16
|
||||||
|
example: john_doe_42
|
||||||
|
disp_name:
|
||||||
|
type: string
|
||||||
|
description: Display name
|
||||||
|
maxLength: 32
|
||||||
|
example: John Doe
|
||||||
|
user_desc:
|
||||||
|
type: string
|
||||||
|
description: User description
|
||||||
|
maxLength: 512
|
||||||
|
example: Just a regular user.
|
||||||
|
creation_date:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
description: Timestamp when the user was created
|
||||||
|
example: '2025-10-10T23:45:47.908073Z'
|
||||||
|
required:
|
||||||
|
- user_id
|
||||||
|
- nickname
|
||||||
|
UserTitle:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- user_id
|
||||||
|
- title_id
|
||||||
|
- status
|
||||||
|
properties:
|
||||||
|
user_id:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
title_id:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
status:
|
||||||
|
$ref: '#/components/schemas/UserTitleStatus'
|
||||||
|
rate:
|
||||||
|
type: integer
|
||||||
|
format: int32
|
||||||
|
review_id:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
ctime:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
additionalProperties: true
|
||||||
|
|
@ -47,6 +47,12 @@ const (
|
||||||
UserTitleStatusPlanned UserTitleStatus = "planned"
|
UserTitleStatusPlanned UserTitleStatus = "planned"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// CursorObj defines model for CursorObj.
|
||||||
|
type CursorObj struct {
|
||||||
|
Id int64 `json:"id"`
|
||||||
|
Param *string `json:"param,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// Image defines model for Image.
|
// Image defines model for Image.
|
||||||
type Image struct {
|
type Image struct {
|
||||||
Id *int64 `json:"id,omitempty"`
|
Id *int64 `json:"id,omitempty"`
|
||||||
|
|
@ -108,7 +114,7 @@ type TitleStatus string
|
||||||
// User defines model for User.
|
// User defines model for User.
|
||||||
type User struct {
|
type User struct {
|
||||||
// AvatarId ID of the user avatar (references images table)
|
// AvatarId ID of the user avatar (references images table)
|
||||||
AvatarId *int64 `json:"avatar_id"`
|
AvatarId *int64 `json:"avatar_id,omitempty"`
|
||||||
|
|
||||||
// CreationDate Timestamp when the user was created
|
// CreationDate Timestamp when the user was created
|
||||||
CreationDate *time.Time `json:"creation_date,omitempty"`
|
CreationDate *time.Time `json:"creation_date,omitempty"`
|
||||||
|
|
@ -906,7 +912,12 @@ type GetTitlesResponseObject interface {
|
||||||
VisitGetTitlesResponse(w http.ResponseWriter) error
|
VisitGetTitlesResponse(w http.ResponseWriter) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetTitles200JSONResponse []Title
|
type GetTitles200JSONResponse struct {
|
||||||
|
Cursor CursorObj `json:"cursor"`
|
||||||
|
|
||||||
|
// Data List of titles
|
||||||
|
Data []Title `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
func (response GetTitles200JSONResponse) VisitGetTitlesResponse(w http.ResponseWriter) error {
|
func (response GetTitles200JSONResponse) VisitGetTitlesResponse(w http.ResponseWriter) error {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
get:
|
get:
|
||||||
summary: Get titles
|
summary: Get titles
|
||||||
parameters:
|
parameters:
|
||||||
- $ref: ../parameters/cursor.yaml
|
- $ref: "../parameters/cursor.yaml"
|
||||||
- $ref: ../parameters/title_sort.yaml
|
- $ref: "../parameters/title_sort.yaml"
|
||||||
- in: query
|
- in: query
|
||||||
name: sort_forward
|
name: sort_forward
|
||||||
schema:
|
schema:
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue