From 2edf96571b0edcbaf4d8e03a456b41e6a79163d0 Mon Sep 17 00:00:00 2001 From: Iron_Felix Date: Sat, 15 Nov 2025 18:20:27 +0300 Subject: [PATCH 1/9] feat: field studio_name added to title in openapi --- api/api.gen.go | 93 ++++++------------------------- api/openapi.yaml | 46 +++++++-------- modules/backend/handlers/users.go | 2 +- 3 files changed, 41 insertions(+), 100 deletions(-) diff --git a/api/api.gen.go b/api/api.gen.go index a235db8..9adc19d 100644 --- a/api/api.gen.go +++ b/api/api.gen.go @@ -50,6 +50,7 @@ type Title struct { ReleaseSeason *ReleaseSeason `json:"release_season,omitempty"` ReleaseYear *int32 `json:"release_year,omitempty"` StudioId *int64 `json:"studio_id,omitempty"` + StudioName *string `json:"studio_name,omitempty"` // TitleNames Localized titles. Key = language (ISO 639-1), value = list of names TitleNames *map[string][]string `json:"title_names,omitempty"` @@ -103,9 +104,6 @@ type GetUsersUserIdParams struct { Fields *string `form:"fields,omitempty" json:"fields,omitempty"` } -// PostUsersJSONRequestBody defines body for PostUsers for application/json ContentType. -type PostUsersJSONRequestBody = User - // Getter for additional properties for Title. Returns the specified // element and whether it was found func (a Title) Get(fieldName string) (value interface{}, found bool) { @@ -211,6 +209,14 @@ func (a *Title) UnmarshalJSON(b []byte) error { delete(object, "studio_id") } + if raw, found := object["studio_name"]; found { + err = json.Unmarshal(raw, &a.StudioName) + if err != nil { + return fmt.Errorf("error reading 'studio_name': %w", err) + } + delete(object, "studio_name") + } + if raw, found := object["title_names"]; found { err = json.Unmarshal(raw, &a.TitleNames) if err != nil { @@ -316,6 +322,13 @@ func (a Title) MarshalJSON() ([]byte, error) { } } + if a.StudioName != nil { + object["studio_name"], err = json.Marshal(a.StudioName) + if err != nil { + return nil, fmt.Errorf("error marshaling 'studio_name': %w", err) + } + } + if a.TitleNames != nil { object["title_names"], err = json.Marshal(a.TitleNames) if err != nil { @@ -344,9 +357,6 @@ type ServerInterface interface { // Get titles // (GET /title) GetTitle(c *gin.Context, params GetTitleParams) - // Add new user - // (POST /users) - PostUsers(c *gin.Context) // Get user info // (GET /users/{user_id}) GetUsersUserId(c *gin.Context, userId string, params GetUsersUserIdParams) @@ -443,19 +453,6 @@ func (siw *ServerInterfaceWrapper) GetTitle(c *gin.Context) { siw.Handler.GetTitle(c, params) } -// PostUsers operation middleware -func (siw *ServerInterfaceWrapper) PostUsers(c *gin.Context) { - - for _, middleware := range siw.HandlerMiddlewares { - middleware(c) - if c.IsAborted() { - return - } - } - - siw.Handler.PostUsers(c) -} - // GetUsersUserId operation middleware func (siw *ServerInterfaceWrapper) GetUsersUserId(c *gin.Context) { @@ -519,7 +516,6 @@ func RegisterHandlersWithOptions(router gin.IRouter, si ServerInterface, options } router.GET(options.BaseURL+"/title", wrapper.GetTitle) - router.POST(options.BaseURL+"/users", wrapper.PostUsers) router.GET(options.BaseURL+"/users/:user_id", wrapper.GetUsersUserId) } @@ -564,27 +560,6 @@ func (response GetTitle500Response) VisitGetTitleResponse(w http.ResponseWriter) return nil } -type PostUsersRequestObject struct { - Body *PostUsersJSONRequestBody -} - -type PostUsersResponseObject interface { - VisitPostUsersResponse(w http.ResponseWriter) error -} - -type PostUsers200JSONResponse struct { - Error *string `json:"error,omitempty"` - Success *bool `json:"success,omitempty"` - UserJson *User `json:"user_json,omitempty"` -} - -func (response PostUsers200JSONResponse) VisitPostUsersResponse(w http.ResponseWriter) error { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(200) - - return json.NewEncoder(w).Encode(response) -} - type GetUsersUserIdRequestObject struct { UserId string `json:"user_id"` Params GetUsersUserIdParams @@ -632,9 +607,6 @@ type StrictServerInterface interface { // Get titles // (GET /title) GetTitle(ctx context.Context, request GetTitleRequestObject) (GetTitleResponseObject, error) - // Add new user - // (POST /users) - PostUsers(ctx context.Context, request PostUsersRequestObject) (PostUsersResponseObject, error) // Get user info // (GET /users/{user_id}) GetUsersUserId(ctx context.Context, request GetUsersUserIdRequestObject) (GetUsersUserIdResponseObject, error) @@ -679,39 +651,6 @@ func (sh *strictHandler) GetTitle(ctx *gin.Context, params GetTitleParams) { } } -// PostUsers operation middleware -func (sh *strictHandler) PostUsers(ctx *gin.Context) { - var request PostUsersRequestObject - - var body PostUsersJSONRequestBody - if err := ctx.ShouldBindJSON(&body); err != nil { - ctx.Status(http.StatusBadRequest) - ctx.Error(err) - return - } - request.Body = &body - - handler := func(ctx *gin.Context, request interface{}) (interface{}, error) { - return sh.ssi.PostUsers(ctx, request.(PostUsersRequestObject)) - } - for _, middleware := range sh.middlewares { - handler = middleware(handler, "PostUsers") - } - - response, err := handler(ctx, request) - - if err != nil { - ctx.Error(err) - ctx.Status(http.StatusInternalServerError) - } else if validResponse, ok := response.(PostUsersResponseObject); ok { - if err := validResponse.VisitPostUsersResponse(ctx.Writer); err != nil { - ctx.Error(err) - } - } else if response != nil { - ctx.Error(fmt.Errorf("unexpected response type: %T", response)) - } -} - // GetUsersUserId operation middleware func (sh *strictHandler) GetUsersUserId(ctx *gin.Context, userId string, params GetUsersUserIdParams) { var request GetUsersUserIdRequestObject diff --git a/api/openapi.yaml b/api/openapi.yaml index 4187ebb..c899595 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -243,28 +243,28 @@ paths: # items: # $ref: '#/components/schemas/User' - post: - summary: Add new user - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/User' - responses: - '200': - description: Add result - content: - application/json: - schema: - type: object - properties: - success: - type: boolean - error: - type: string - user_json: - $ref: '#/components/schemas/User' + # post: + # summary: Add new user + # requestBody: + # required: true + # content: + # application/json: + # schema: + # $ref: '#/components/schemas/User' + # responses: + # '200': + # description: Add result + # content: + # application/json: + # schema: + # type: object + # properties: + # success: + # type: boolean + # error: + # type: string + # user_json: + # $ref: '#/components/schemas/User' # /users/{user_id}/titles: # get: @@ -608,6 +608,8 @@ components: studio_id: type: integer format: int64 + studio_name: + type: string poster_id: type: integer format: int64 diff --git a/modules/backend/handlers/users.go b/modules/backend/handlers/users.go index 3da61de..9e59261 100644 --- a/modules/backend/handlers/users.go +++ b/modules/backend/handlers/users.go @@ -25,7 +25,7 @@ import ( func mapUser(u sqlc.GetUserByIDRow) oapi.User { return oapi.User{ AvatarId: u.AvatarID, - CreationDate: u.CreationDate, + CreationDate: &u.CreationDate, DispName: u.DispName, Id: &u.ID, Mail: (*types.Email)(u.Mail), From 3cca6ee16821dda7be772056f0ba3f62beab30c3 Mon Sep 17 00:00:00 2001 From: Iron_Felix Date: Sat, 15 Nov 2025 18:56:46 +0300 Subject: [PATCH 2/9] feat: statements for studios table added --- modules/backend/queries.sql | 14 ++++++++++++ sql/queries.sql.go | 45 +++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/modules/backend/queries.sql b/modules/backend/queries.sql index b90ec6a..29c941e 100644 --- a/modules/backend/queries.sql +++ b/modules/backend/queries.sql @@ -13,6 +13,20 @@ SELECT id, avatar_id, mail, nickname, disp_name, user_desc, creation_date FROM users WHERE id = $1; + +-- name: GetStudioByID :one +SELECT * +FROM studios +WHERE id = sqlc.arg('studio_id')::int; + +-- name: InsertStudio :one +INSERT INTO studios (studio_name, illust_id, studio_desc) +VALUES ( + sqlc.arg('studio_name')::text, + sqlc.narg('illust_id')::bigint, + sqlc.narg('studio_desc')::text) +RETURNING id, studio_name, illust_id, studio_desc; + -- -- name: ListUsers :many -- SELECT user_id, avatar_id, passhash, mail, nickname, disp_name, user_desc, creation_date -- FROM users diff --git a/sql/queries.sql.go b/sql/queries.sql.go index 2ef4178..b3ee9f4 100644 --- a/sql/queries.sql.go +++ b/sql/queries.sql.go @@ -41,6 +41,24 @@ func (q *Queries) GetImageByID(ctx context.Context, id int64) (Image, error) { return i, err } +const getStudioByID = `-- name: GetStudioByID :one +SELECT id, studio_name, illust_id, studio_desc +FROM studios +WHERE id = $1::int +` + +func (q *Queries) GetStudioByID(ctx context.Context, studioID int32) (Studio, error) { + row := q.db.QueryRow(ctx, getStudioByID, studioID) + var i Studio + err := row.Scan( + &i.ID, + &i.StudioName, + &i.IllustID, + &i.StudioDesc, + ) + return i, err +} + const getUserByID = `-- name: GetUserByID :one SELECT id, avatar_id, mail, nickname, disp_name, user_desc, creation_date FROM users @@ -72,6 +90,33 @@ func (q *Queries) GetUserByID(ctx context.Context, id int64) (GetUserByIDRow, er return i, err } +const insertStudio = `-- name: InsertStudio :one +INSERT INTO studios (studio_name, illust_id, studio_desc) +VALUES ( + $1::text, + $2::bigint, + $3::text) +RETURNING id, studio_name, illust_id, studio_desc +` + +type InsertStudioParams struct { + StudioName string `json:"studio_name"` + IllustID *int64 `json:"illust_id"` + StudioDesc *string `json:"studio_desc"` +} + +func (q *Queries) InsertStudio(ctx context.Context, arg InsertStudioParams) (Studio, error) { + row := q.db.QueryRow(ctx, insertStudio, arg.StudioName, arg.IllustID, arg.StudioDesc) + var i Studio + err := row.Scan( + &i.ID, + &i.StudioName, + &i.IllustID, + &i.StudioDesc, + ) + return i, err +} + const searchTitles = `-- name: SearchTitles :many From e6dc27fd55abe64c96ae51e746c2c31625d9982c Mon Sep 17 00:00:00 2001 From: Iron_Felix Date: Sat, 15 Nov 2025 19:19:39 +0300 Subject: [PATCH 3/9] feat: statements for tags added --- modules/backend/queries.sql | 20 ++++++++++++ sql/queries.sql.go | 62 +++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/modules/backend/queries.sql b/modules/backend/queries.sql index 29c941e..a4c0bb9 100644 --- a/modules/backend/queries.sql +++ b/modules/backend/queries.sql @@ -27,6 +27,26 @@ VALUES ( sqlc.narg('studio_desc')::text) RETURNING id, studio_name, illust_id, studio_desc; +-- name: GetTitleTags :many +SELECT + tag_names +FROM tags as g +JOIN title_tags as t ON(t.tag_id = g.id) +WHERE t.title_id = sqlc.arg('title_id')::bigint; + +-- name: InsertTitleTags :one +INSERT INTO title_tags (title_id, tag_id) +VALUES ( + sqlc.arg('title_id')::bigint, + sqlc.arg('tag_id')::bigint) +RETURNING title_id, tag_id; + +-- name: InsertTag :one +INSERT INTO tags (tag_names) +VALUES ( + sqlc.arg('tag_names')::jsonb) +RETURNING id, tag_names; + -- -- name: ListUsers :many -- SELECT user_id, avatar_id, passhash, mail, nickname, disp_name, user_desc, creation_date -- FROM users diff --git a/sql/queries.sql.go b/sql/queries.sql.go index b3ee9f4..a73889c 100644 --- a/sql/queries.sql.go +++ b/sql/queries.sql.go @@ -59,6 +59,34 @@ func (q *Queries) GetStudioByID(ctx context.Context, studioID int32) (Studio, er return i, err } +const getTitleTags = `-- name: GetTitleTags :many +SELECT + tag_names +FROM tags as g +JOIN title_tags as t ON(t.tag_id = g.id) +WHERE t.title_id = $1::bigint +` + +func (q *Queries) GetTitleTags(ctx context.Context, titleID int64) ([][]byte, error) { + rows, err := q.db.Query(ctx, getTitleTags, titleID) + if err != nil { + return nil, err + } + defer rows.Close() + var items [][]byte + for rows.Next() { + var tag_names []byte + if err := rows.Scan(&tag_names); err != nil { + return nil, err + } + items = append(items, tag_names) + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const getUserByID = `-- name: GetUserByID :one SELECT id, avatar_id, mail, nickname, disp_name, user_desc, creation_date FROM users @@ -117,6 +145,40 @@ func (q *Queries) InsertStudio(ctx context.Context, arg InsertStudioParams) (Stu return i, err } +const insertTag = `-- name: InsertTag :one +INSERT INTO tags (tag_names) +VALUES ( + $1::jsonb) +RETURNING id, tag_names +` + +func (q *Queries) InsertTag(ctx context.Context, tagNames []byte) (Tag, error) { + row := q.db.QueryRow(ctx, insertTag, tagNames) + var i Tag + err := row.Scan(&i.ID, &i.TagNames) + return i, err +} + +const insertTitleTags = `-- name: InsertTitleTags :one +INSERT INTO title_tags (title_id, tag_id) +VALUES ( + $1::bigint, + $2::bigint) +RETURNING title_id, tag_id +` + +type InsertTitleTagsParams struct { + TitleID int64 `json:"title_id"` + TagID int64 `json:"tag_id"` +} + +func (q *Queries) InsertTitleTags(ctx context.Context, arg InsertTitleTagsParams) (TitleTag, error) { + row := q.db.QueryRow(ctx, insertTitleTags, arg.TitleID, arg.TagID) + var i TitleTag + err := row.Scan(&i.TitleID, &i.TagID) + return i, err +} + const searchTitles = `-- name: SearchTitles :many From 4949a3c25faeb933e6c92ae9938001adf7fefab8 Mon Sep 17 00:00:00 2001 From: Iron_Felix Date: Sat, 15 Nov 2025 23:00:40 +0300 Subject: [PATCH 4/9] refactor: new types --- api/api.gen.go | 4 ++-- api/openapi.yaml | 2 ++ modules/backend/handlers/titles.go | 27 +++++++++++++++++---------- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/api/api.gen.go b/api/api.gen.go index 9adc19d..ebb4c01 100644 --- a/api/api.gen.go +++ b/api/api.gen.go @@ -94,8 +94,8 @@ type GetTitleParams struct { Rating *float64 `form:"rating,omitempty" json:"rating,omitempty"` ReleaseYear *int32 `form:"release_year,omitempty" json:"release_year,omitempty"` ReleaseSeason *ReleaseSeason `form:"release_season,omitempty" json:"release_season,omitempty"` - Limit *int `form:"limit,omitempty" json:"limit,omitempty"` - Offset *int `form:"offset,omitempty" json:"offset,omitempty"` + Limit *int32 `form:"limit,omitempty" json:"limit,omitempty"` + Offset *int32 `form:"offset,omitempty" json:"offset,omitempty"` Fields *string `form:"fields,omitempty" json:"fields,omitempty"` } diff --git a/api/openapi.yaml b/api/openapi.yaml index c899595..200cc47 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -35,11 +35,13 @@ paths: name: limit schema: type: integer + format: int32 default: 10 - in: query name: offset schema: type: integer + format: int32 default: 0 - in: query name: fields diff --git a/modules/backend/handlers/titles.go b/modules/backend/handlers/titles.go index 85f9f45..99217ca 100644 --- a/modules/backend/handlers/titles.go +++ b/modules/backend/handlers/titles.go @@ -57,21 +57,25 @@ func ReleaseSeason2sqlc(s *oapi.ReleaseSeason) (*sqlc.ReleaseSeasonT, error) { return &t, nil } +type TileNames *map[string][]string + // unmarshall jsonb to map[string][]string -func jsonb2map4names(b []byte) (*map[string][]string, error) { - var t map[string][]string - if err := json.Unmarshal(b, &t); err != nil { +func jsonb2TitleNames(b []byte) (TileNames, error) { + var t TileNames + if err := json.Unmarshal(b, t); err != nil { return nil, fmt.Errorf("invalid title_names JSON for title: %w", err) } - return &t, nil + return t, nil } -func jsonb2map4len(b []byte) (*map[string]float64, error) { - var t map[string]float64 - if err := json.Unmarshal(b, &t); err != nil { +type EpisodeLens *map[string]float64 + +func jsonb2EpisodeLens(b []byte) (EpisodeLens, error) { + var t EpisodeLens + if err := json.Unmarshal(b, t); err != nil { return nil, fmt.Errorf("invalid episodes_len JSON for title: %w", err) } - return &t, nil + return t, nil } func (s Server) GetTitle(ctx context.Context, request oapi.GetTitleRequestObject) (oapi.GetTitleResponseObject, error) { @@ -95,6 +99,8 @@ func (s Server) GetTitle(ctx context.Context, request oapi.GetTitleRequestObject Rating: request.Params.Rating, ReleaseYear: request.Params.ReleaseYear, ReleaseSeason: season, + Offset: request.Params.Offset, + Limit: request.Params.Limit, }) if err != nil { return oapi.GetTitle500Response{}, nil @@ -104,12 +110,12 @@ func (s Server) GetTitle(ctx context.Context, request oapi.GetTitleRequestObject } for _, title := range titles { - title_names, err := jsonb2map4names(title.TitleNames) + title_names, err := jsonb2TitleNames(title.TitleNames) if err != nil { log.Errorf("%v", err) return oapi.GetTitle500Response{}, err } - episodes_lens, err := jsonb2map4len(title.EpisodesLen) + episodes_lens, err := jsonb2EpisodeLens(title.EpisodesLen) if err != nil { log.Errorf("%v", err) return oapi.GetTitle500Response{}, err @@ -122,6 +128,7 @@ func (s Server) GetTitle(ctx context.Context, request oapi.GetTitleRequestObject ReleaseSeason: (*oapi.ReleaseSeason)(title.ReleaseSeason), ReleaseYear: title.ReleaseYear, StudioId: &title.StudioID, + // StudioName: , TitleNames: title_names, TitleStatus: (*oapi.TitleStatus)(&title.TitleStatus), EpisodesAired: title.EpisodesAired, From e18f4a44c3acf4b0fc36889690e853dd7b416c0e Mon Sep 17 00:00:00 2001 From: Iron_Felix Date: Sun, 16 Nov 2025 01:47:11 +0300 Subject: [PATCH 5/9] feat: more fields for titles and refactored schemas --- api/api.gen.go | 71 ++++++++++++++++++++++++++++-------------------- api/openapi.yaml | 71 ++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 101 insertions(+), 41 deletions(-) diff --git a/api/api.gen.go b/api/api.gen.go index ebb4c01..c3ef4da 100644 --- a/api/api.gen.go +++ b/api/api.gen.go @@ -34,6 +34,21 @@ const ( // ReleaseSeason Title release season type ReleaseSeason string +// Studio defines model for Studio. +type Studio struct { + Description *string `json:"description,omitempty"` + Id *int64 `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + PosterId *int64 `json:"poster_id,omitempty"` + PosterPath *string `json:"poster_path,omitempty"` +} + +// Tag A localized tag: keys are language codes (ISO 639-1), values are tag names +type Tag map[string]string + +// Tags Array of localized tags +type Tags = []Tag + // Title defines model for Title. type Title struct { EpisodesAired *int32 `json:"episodes_aired,omitempty"` @@ -41,7 +56,7 @@ type Title struct { EpisodesLen *map[string]float64 `json:"episodes_len,omitempty"` // Id Unique title ID (primary key) - Id *int64 `json:"id,omitempty"` + Id int64 `json:"id"` PosterId *int64 `json:"poster_id,omitempty"` Rating *float64 `json:"rating,omitempty"` RatingCount *int32 `json:"rating_count,omitempty"` @@ -49,11 +64,13 @@ type Title struct { // ReleaseSeason Title release season ReleaseSeason *ReleaseSeason `json:"release_season,omitempty"` ReleaseYear *int32 `json:"release_year,omitempty"` - StudioId *int64 `json:"studio_id,omitempty"` - StudioName *string `json:"studio_name,omitempty"` + Studio *Studio `json:"studio,omitempty"` + + // Tags Array of localized tags + Tags Tags `json:"tags"` // TitleNames Localized titles. Key = language (ISO 639-1), value = list of names - TitleNames *map[string][]string `json:"title_names,omitempty"` + TitleNames map[string][]string `json:"title_names"` // TitleStatus Title status TitleStatus *TitleStatus `json:"title_status,omitempty"` @@ -201,20 +218,20 @@ func (a *Title) UnmarshalJSON(b []byte) error { delete(object, "release_year") } - if raw, found := object["studio_id"]; found { - err = json.Unmarshal(raw, &a.StudioId) + if raw, found := object["studio"]; found { + err = json.Unmarshal(raw, &a.Studio) if err != nil { - return fmt.Errorf("error reading 'studio_id': %w", err) + return fmt.Errorf("error reading 'studio': %w", err) } - delete(object, "studio_id") + delete(object, "studio") } - if raw, found := object["studio_name"]; found { - err = json.Unmarshal(raw, &a.StudioName) + if raw, found := object["tags"]; found { + err = json.Unmarshal(raw, &a.Tags) if err != nil { - return fmt.Errorf("error reading 'studio_name': %w", err) + return fmt.Errorf("error reading 'tags': %w", err) } - delete(object, "studio_name") + delete(object, "tags") } if raw, found := object["title_names"]; found { @@ -273,11 +290,9 @@ func (a Title) MarshalJSON() ([]byte, error) { } } - if a.Id != nil { - object["id"], err = json.Marshal(a.Id) - if err != nil { - return nil, fmt.Errorf("error marshaling 'id': %w", err) - } + object["id"], err = json.Marshal(a.Id) + if err != nil { + return nil, fmt.Errorf("error marshaling 'id': %w", err) } if a.PosterId != nil { @@ -315,25 +330,21 @@ func (a Title) MarshalJSON() ([]byte, error) { } } - if a.StudioId != nil { - object["studio_id"], err = json.Marshal(a.StudioId) + if a.Studio != nil { + object["studio"], err = json.Marshal(a.Studio) if err != nil { - return nil, fmt.Errorf("error marshaling 'studio_id': %w", err) + return nil, fmt.Errorf("error marshaling 'studio': %w", err) } } - if a.StudioName != nil { - object["studio_name"], err = json.Marshal(a.StudioName) - if err != nil { - return nil, fmt.Errorf("error marshaling 'studio_name': %w", err) - } + object["tags"], err = json.Marshal(a.Tags) + if err != nil { + return nil, fmt.Errorf("error marshaling 'tags': %w", err) } - if a.TitleNames != nil { - object["title_names"], err = json.Marshal(a.TitleNames) - if err != nil { - return nil, fmt.Errorf("error marshaling 'title_names': %w", err) - } + object["title_names"], err = json.Marshal(a.TitleNames) + if err != nil { + return nil, fmt.Errorf("error marshaling 'title_names': %w", err) } if a.TitleStatus != nil { diff --git a/api/openapi.yaml b/api/openapi.yaml index 200cc47..0356898 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -2,8 +2,10 @@ openapi: 3.1.1 info: title: Titles, Users, Reviews, Tags, and Media API version: 1.0.0 + servers: - url: /api/v1 + paths: /title: get: @@ -63,6 +65,7 @@ paths: description: Request params are not correct '500': description: Unknown server error + # /title/{title_id}: # get: # summary: Get title description @@ -569,6 +572,7 @@ components: - finished - ongoing - planned + ReleaseSeason: type: string description: Title release season @@ -577,6 +581,7 @@ components: - spring - summer - fall + UserTitleStatus: type: string description: User's title status @@ -585,8 +590,57 @@ components: - 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 + properties: + id: + type: integer + format: int64 + name: + type: string + poster_id: + type: integer + format: int64 + poster_path: + type: string + description: + type: string + + Title: type: object + required: + - id + - title_names + - tags properties: id: type: integer @@ -607,11 +661,10 @@ components: en: ["Attack on Titan", "AoT"] ru: ["Атака титанов", "Титаны"] ja: ["進撃の巨人"] - studio_id: - type: integer - format: int64 - studio_name: - type: string + studio: + $ref: '#/components/schemas/Studio' + tags: + $ref: '#/components/schemas/Tags' poster_id: type: integer format: int64 @@ -640,6 +693,7 @@ components: type: number format: double additionalProperties: true + User: type: object properties: @@ -683,12 +737,7 @@ components: - user_id - nickname # - creation_date + UserTitle: type: object additionalProperties: true - Review: - type: object - additionalProperties: true - Tag: - type: object - additionalProperties: true From d1180a426ff4f043baaaec58afd894b4c2f5ffb4 Mon Sep 17 00:00:00 2001 From: Iron_Felix Date: Sun, 16 Nov 2025 02:14:38 +0300 Subject: [PATCH 6/9] feat: new schema for images --- api/api.gen.go | 23 +++++++++++++++-------- api/openapi.yaml | 16 +++++++++++++--- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/api/api.gen.go b/api/api.gen.go index c3ef4da..b3c51f6 100644 --- a/api/api.gen.go +++ b/api/api.gen.go @@ -31,6 +31,13 @@ const ( Planned TitleStatus = "planned" ) +// Image defines model for Image. +type Image struct { + Id *int64 `json:"id,omitempty"` + ImagePath *string `json:"image_path,omitempty"` + StorageType *string `json:"storage_type,omitempty"` +} + // ReleaseSeason Title release season type ReleaseSeason string @@ -57,7 +64,7 @@ type Title struct { // Id Unique title ID (primary key) Id int64 `json:"id"` - PosterId *int64 `json:"poster_id,omitempty"` + Poster *Image `json:"poster,omitempty"` Rating *float64 `json:"rating,omitempty"` RatingCount *int32 `json:"rating_count,omitempty"` @@ -178,12 +185,12 @@ func (a *Title) UnmarshalJSON(b []byte) error { delete(object, "id") } - if raw, found := object["poster_id"]; found { - err = json.Unmarshal(raw, &a.PosterId) + if raw, found := object["poster"]; found { + err = json.Unmarshal(raw, &a.Poster) if err != nil { - return fmt.Errorf("error reading 'poster_id': %w", err) + return fmt.Errorf("error reading 'poster': %w", err) } - delete(object, "poster_id") + delete(object, "poster") } if raw, found := object["rating"]; found { @@ -295,10 +302,10 @@ func (a Title) MarshalJSON() ([]byte, error) { return nil, fmt.Errorf("error marshaling 'id': %w", err) } - if a.PosterId != nil { - object["poster_id"], err = json.Marshal(a.PosterId) + if a.Poster != nil { + object["poster"], err = json.Marshal(a.Poster) if err != nil { - return nil, fmt.Errorf("error marshaling 'poster_id': %w", err) + return nil, fmt.Errorf("error marshaling 'poster': %w", err) } } diff --git a/api/openapi.yaml b/api/openapi.yaml index 0356898..d523f06 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -565,6 +565,17 @@ paths: components: schemas: + Image: + type: object + properties: + id: + type: integer + format: int64 + storage_type: + type: string + image_path: + type: string + TitleStatus: type: string description: Title status @@ -665,9 +676,8 @@ components: $ref: '#/components/schemas/Studio' tags: $ref: '#/components/schemas/Tags' - poster_id: - type: integer - format: int64 + poster: + $ref: '#/components/schemas/Image' title_status: $ref: '#/components/schemas/TitleStatus' rating: From 13a283ae8da0e138c1723d53517d1c9c6f01cc32 Mon Sep 17 00:00:00 2001 From: Iron_Felix Date: Sun, 16 Nov 2025 02:38:36 +0300 Subject: [PATCH 7/9] feat: GetTitles now returns all the field needed --- api/api.gen.go | 3 +- api/openapi.yaml | 7 +- modules/backend/handlers/titles.go | 147 +++++++++++++++++++++-------- modules/backend/queries.sql | 4 +- sql/migrations/000001_init.up.sql | 1 + sql/queries.sql.go | 8 +- 6 files changed, 119 insertions(+), 51 deletions(-) diff --git a/api/api.gen.go b/api/api.gen.go index b3c51f6..0057c22 100644 --- a/api/api.gen.go +++ b/api/api.gen.go @@ -46,8 +46,7 @@ type Studio struct { Description *string `json:"description,omitempty"` Id *int64 `json:"id,omitempty"` Name *string `json:"name,omitempty"` - PosterId *int64 `json:"poster_id,omitempty"` - PosterPath *string `json:"poster_path,omitempty"` + Poster *Image `json:"poster,omitempty"` } // Tag A localized tag: keys are language codes (ISO 639-1), values are tag names diff --git a/api/openapi.yaml b/api/openapi.yaml index d523f06..4628c6d 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -637,11 +637,8 @@ components: format: int64 name: type: string - poster_id: - type: integer - format: int64 - poster_path: - type: string + poster: + $ref: '#/components/schemas/Image' description: type: string diff --git a/modules/backend/handlers/titles.go b/modules/backend/handlers/titles.go index 99217ca..182f450 100644 --- a/modules/backend/handlers/titles.go +++ b/modules/backend/handlers/titles.go @@ -25,13 +25,14 @@ func TitleStatus2Sqlc(s *oapi.TitleStatus) (*sqlc.TitleStatusT, error) { return nil, nil } var t sqlc.TitleStatusT - if *s == "finished" { - t = "finished" - } else if *s == "ongoing" { - t = "ongoing" - } else if *s == "planned" { - t = "planned" - } else { + switch *s { + case oapi.Finished: + t = sqlc.TitleStatusTFinished + case oapi.Ongoing: + t = sqlc.TitleStatusTOngoing + case oapi.Planned: + t = sqlc.TitleStatusTPlanned + default: return nil, fmt.Errorf("unexpected tittle status: %s", *s) } return &t, nil @@ -41,41 +42,86 @@ func ReleaseSeason2sqlc(s *oapi.ReleaseSeason) (*sqlc.ReleaseSeasonT, error) { if s == nil { return nil, nil } - //TODO var t sqlc.ReleaseSeasonT - if *s == oapi.Winter { + switch *s { + case oapi.Winter: t = sqlc.ReleaseSeasonTWinter - } else if *s == "spring" { - t = "spring" - } else if *s == "summer" { - t = "summer" - } else if *s == "fall" { - t = "fall" - } else { + case oapi.Spring: + t = sqlc.ReleaseSeasonTSpring + case oapi.Summer: + t = sqlc.ReleaseSeasonTSummer + case oapi.Fall: + t = sqlc.ReleaseSeasonTFall + default: return nil, fmt.Errorf("unexpected release season: %s", *s) } return &t, nil } -type TileNames *map[string][]string +type TitleNames *map[string][]string +type EpisodeLens *map[string]float64 +type TagNames []map[string]string -// unmarshall jsonb to map[string][]string -func jsonb2TitleNames(b []byte) (TileNames, error) { - var t TileNames - if err := json.Unmarshal(b, t); err != nil { - return nil, fmt.Errorf("invalid title_names JSON for title: %w", err) +func (s Server) GetTagsByTitleId(ctx context.Context, id int64) (oapi.Tags, error) { + sqlc_title_tags, err := s.db.GetTitleTags(ctx, id) + if err != nil { + log.Errorf("%v", err) + return nil, err } - return t, nil + + var oapi_tag_names oapi.Tags + for _, title_tag := range sqlc_title_tags { + var oapi_tag_name map[string]string + err = json.Unmarshal(title_tag, &oapi_tag_name) + if err != nil { + log.Errorf("invalid JSON for %s: %v", "TagNames", err) + return nil, err + } + oapi_tag_names = append(oapi_tag_names, oapi_tag_name) + } + + return oapi_tag_names, nil } -type EpisodeLens *map[string]float64 +func (s Server) GetImage(ctx context.Context, id int64) (oapi.Image, error) { -func jsonb2EpisodeLens(b []byte) (EpisodeLens, error) { - var t EpisodeLens - if err := json.Unmarshal(b, t); err != nil { - return nil, fmt.Errorf("invalid episodes_len JSON for title: %w", err) + var oapi_image oapi.Image + + sqlc_image, err := s.db.GetImageByID(ctx, id) + if err != nil { + log.Errorf("%v", err) + return oapi_image, err } - return t, nil + //can cast and dont use brain cause all this fiels required + oapi_image.Id = &sqlc_image.ID + oapi_image.ImagePath = &sqlc_image.ImagePath + oapi_image.StorageType = (*string)(&sqlc_image.StorageType) + + return oapi_image, nil +} + +func (s Server) GetStudio(ctx context.Context, id int64) (oapi.Studio, error) { + + var oapi_studio oapi.Studio + + sqlc_studio, err := s.db.GetStudioByID(ctx, id) + if err != nil { + log.Errorf("%v", err) + return oapi_studio, err + } + + oapi_studio.Id = &sqlc_studio.ID + oapi_studio.Name = sqlc_studio.StudioName + oapi_studio.Description = sqlc_studio.StudioDesc + + oapi_illust, err := s.GetImage(ctx, *sqlc_studio.IllustID) + if err != nil { + log.Errorf("%v", err) + return oapi_studio, err + } + oapi_studio.Poster = &oapi_illust + + return oapi_studio, nil } func (s Server) GetTitle(ctx context.Context, request oapi.GetTitleRequestObject) (oapi.GetTitleResponseObject, error) { @@ -103,6 +149,7 @@ func (s Server) GetTitle(ctx context.Context, request oapi.GetTitleRequestObject Limit: request.Params.Limit, }) if err != nil { + log.Errorf("%v", err) return oapi.GetTitle500Response{}, nil } if len(titles) == 0 { @@ -110,26 +157,50 @@ func (s Server) GetTitle(ctx context.Context, request oapi.GetTitleRequestObject } for _, title := range titles { - title_names, err := jsonb2TitleNames(title.TitleNames) + + var title_names TitleNames + err := json.Unmarshal(title.TitleNames, &title_names) if err != nil { - log.Errorf("%v", err) + log.Errorf("invalid JSON for %s: %v", "TitleNames", err) return oapi.GetTitle500Response{}, err } - episodes_lens, err := jsonb2EpisodeLens(title.EpisodesLen) + + var episodes_lens EpisodeLens + err = json.Unmarshal(title.EpisodesLen, &episodes_lens) if err != nil { - log.Errorf("%v", err) + log.Errorf("invalid JSON for %s: %v", "EpisodeLens", err) return oapi.GetTitle500Response{}, err } + + oapi_tag_names, err := s.GetTagsByTitleId(ctx, title.ID) + if err != nil { + log.Errorf("error while getting tags %v", err) + return oapi.GetTitle500Response{}, err + } + + oapi_image, err := s.GetImage(ctx, *title.PosterID) + if err != nil { + log.Errorf("error while getting image %v", err) + return oapi.GetTitle500Response{}, err + } + + oapi_studio, err := s.GetStudio(ctx, title.StudioID) + if err != nil { + log.Errorf("error while getting studio %v", err) + return oapi.GetTitle500Response{}, err + } + t := oapi.Title{ - Id: &title.ID, - PosterId: title.PosterID, + + Id: title.ID, + Poster: &oapi_image, Rating: title.Rating, RatingCount: title.RatingCount, ReleaseSeason: (*oapi.ReleaseSeason)(title.ReleaseSeason), ReleaseYear: title.ReleaseYear, - StudioId: &title.StudioID, - // StudioName: , - TitleNames: title_names, + Studio: &oapi_studio, + Tags: oapi_tag_names, + TitleNames: *title_names, TitleStatus: (*oapi.TitleStatus)(&title.TitleStatus), EpisodesAired: title.EpisodesAired, EpisodesAll: title.EpisodesAll, diff --git a/modules/backend/queries.sql b/modules/backend/queries.sql index a4c0bb9..7717f9f 100644 --- a/modules/backend/queries.sql +++ b/modules/backend/queries.sql @@ -1,7 +1,7 @@ -- name: GetImageByID :one SELECT id, storage_type, image_path FROM images -WHERE id = $1; +WHERE id = sqlc.arg('illust_id'); -- name: CreateImage :one INSERT INTO images (storage_type, image_path) @@ -17,7 +17,7 @@ WHERE id = $1; -- name: GetStudioByID :one SELECT * FROM studios -WHERE id = sqlc.arg('studio_id')::int; +WHERE id = sqlc.arg('studio_id')::bigint; -- name: InsertStudio :one INSERT INTO studios (studio_name, illust_id, studio_desc) diff --git a/sql/migrations/000001_init.up.sql b/sql/migrations/000001_init.up.sql index c325dc8..669143a 100644 --- a/sql/migrations/000001_init.up.sql +++ b/sql/migrations/000001_init.up.sql @@ -14,6 +14,7 @@ CREATE TABLE providers ( CREATE TABLE tags ( id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY, + -- example: { "ru": "Сёдзё", "en": "Shojo", "jp": "少女"} tag_names jsonb NOT NULL ); diff --git a/sql/queries.sql.go b/sql/queries.sql.go index a73889c..865ec73 100644 --- a/sql/queries.sql.go +++ b/sql/queries.sql.go @@ -34,8 +34,8 @@ FROM images WHERE id = $1 ` -func (q *Queries) GetImageByID(ctx context.Context, id int64) (Image, error) { - row := q.db.QueryRow(ctx, getImageByID, id) +func (q *Queries) GetImageByID(ctx context.Context, illustID int64) (Image, error) { + row := q.db.QueryRow(ctx, getImageByID, illustID) var i Image err := row.Scan(&i.ID, &i.StorageType, &i.ImagePath) return i, err @@ -44,10 +44,10 @@ func (q *Queries) GetImageByID(ctx context.Context, id int64) (Image, error) { const getStudioByID = `-- name: GetStudioByID :one SELECT id, studio_name, illust_id, studio_desc FROM studios -WHERE id = $1::int +WHERE id = $1::bigint ` -func (q *Queries) GetStudioByID(ctx context.Context, studioID int32) (Studio, error) { +func (q *Queries) GetStudioByID(ctx context.Context, studioID int64) (Studio, error) { row := q.db.QueryRow(ctx, getStudioByID, studioID) var i Studio err := row.Scan( From cefbbec1dc2fa2ad1bcc7dc711fe2ddb16f7eb56 Mon Sep 17 00:00:00 2001 From: Iron_Felix Date: Sun, 16 Nov 2025 02:43:28 +0300 Subject: [PATCH 8/9] feat: wrote query to get one title --- modules/backend/queries.sql | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/backend/queries.sql b/modules/backend/queries.sql index 7717f9f..f58d33c 100644 --- a/modules/backend/queries.sql +++ b/modules/backend/queries.sql @@ -72,6 +72,11 @@ RETURNING id, tag_names; -- DELETE FROM users -- WHERE user_id = $1; +--name: GetTitleByID :one +SELECT * +FROM titles +WHERE id = sqlc.arg("title_id")::bigint; + -- name: SearchTitles :many SELECT * From 47989ab10da73e351f9550dbae328698c38bc68e Mon Sep 17 00:00:00 2001 From: Iron_Felix Date: Sun, 16 Nov 2025 03:38:51 +0300 Subject: [PATCH 9/9] feat: /titles/{id} endpoint implemented --- api/api.gen.go | 117 +++++++++++++++++++++++ api/openapi.yaml | 51 +++++----- modules/backend/handlers/titles.go | 144 ++++++++++++++++------------- modules/backend/queries.sql | 4 +- sql/queries.sql.go | 81 ++++++++++------ 5 files changed, 283 insertions(+), 114 deletions(-) diff --git a/api/api.gen.go b/api/api.gen.go index 0057c22..3f4f5dd 100644 --- a/api/api.gen.go +++ b/api/api.gen.go @@ -122,6 +122,11 @@ type GetTitleParams struct { Fields *string `form:"fields,omitempty" json:"fields,omitempty"` } +// GetTitleTitleIdParams defines parameters for GetTitleTitleId. +type GetTitleTitleIdParams struct { + Fields *string `form:"fields,omitempty" json:"fields,omitempty"` +} + // GetUsersUserIdParams defines parameters for GetUsersUserId. type GetUsersUserIdParams struct { Fields *string `form:"fields,omitempty" json:"fields,omitempty"` @@ -374,6 +379,9 @@ type ServerInterface interface { // Get titles // (GET /title) GetTitle(c *gin.Context, params GetTitleParams) + // Get title description + // (GET /title/{title_id}) + GetTitleTitleId(c *gin.Context, titleId int64, params GetTitleTitleIdParams) // Get user info // (GET /users/{user_id}) GetUsersUserId(c *gin.Context, userId string, params GetUsersUserIdParams) @@ -470,6 +478,41 @@ func (siw *ServerInterfaceWrapper) GetTitle(c *gin.Context) { siw.Handler.GetTitle(c, params) } +// GetTitleTitleId operation middleware +func (siw *ServerInterfaceWrapper) GetTitleTitleId(c *gin.Context) { + + var err error + + // ------------- Path parameter "title_id" ------------- + var titleId int64 + + err = runtime.BindStyledParameterWithOptions("simple", "title_id", c.Param("title_id"), &titleId, runtime.BindStyledParameterOptions{Explode: false, Required: true}) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter title_id: %w", err), http.StatusBadRequest) + return + } + + // Parameter object where we will unmarshal all parameters from the context + var params GetTitleTitleIdParams + + // ------------- Optional query parameter "fields" ------------- + + err = runtime.BindQueryParameter("form", true, false, "fields", c.Request.URL.Query(), ¶ms.Fields) + if err != nil { + siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter fields: %w", err), http.StatusBadRequest) + return + } + + for _, middleware := range siw.HandlerMiddlewares { + middleware(c) + if c.IsAborted() { + return + } + } + + siw.Handler.GetTitleTitleId(c, titleId, params) +} + // GetUsersUserId operation middleware func (siw *ServerInterfaceWrapper) GetUsersUserId(c *gin.Context) { @@ -533,6 +576,7 @@ func RegisterHandlersWithOptions(router gin.IRouter, si ServerInterface, options } router.GET(options.BaseURL+"/title", wrapper.GetTitle) + router.GET(options.BaseURL+"/title/:title_id", wrapper.GetTitleTitleId) router.GET(options.BaseURL+"/users/:user_id", wrapper.GetUsersUserId) } @@ -577,6 +621,48 @@ func (response GetTitle500Response) VisitGetTitleResponse(w http.ResponseWriter) return nil } +type GetTitleTitleIdRequestObject struct { + TitleId int64 `json:"title_id"` + Params GetTitleTitleIdParams +} + +type GetTitleTitleIdResponseObject interface { + VisitGetTitleTitleIdResponse(w http.ResponseWriter) error +} + +type GetTitleTitleId200JSONResponse Title + +func (response GetTitleTitleId200JSONResponse) VisitGetTitleTitleIdResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type GetTitleTitleId400Response struct { +} + +func (response GetTitleTitleId400Response) VisitGetTitleTitleIdResponse(w http.ResponseWriter) error { + w.WriteHeader(400) + return nil +} + +type GetTitleTitleId404Response struct { +} + +func (response GetTitleTitleId404Response) VisitGetTitleTitleIdResponse(w http.ResponseWriter) error { + w.WriteHeader(404) + return nil +} + +type GetTitleTitleId500Response struct { +} + +func (response GetTitleTitleId500Response) VisitGetTitleTitleIdResponse(w http.ResponseWriter) error { + w.WriteHeader(500) + return nil +} + type GetUsersUserIdRequestObject struct { UserId string `json:"user_id"` Params GetUsersUserIdParams @@ -624,6 +710,9 @@ type StrictServerInterface interface { // Get titles // (GET /title) GetTitle(ctx context.Context, request GetTitleRequestObject) (GetTitleResponseObject, error) + // Get title description + // (GET /title/{title_id}) + GetTitleTitleId(ctx context.Context, request GetTitleTitleIdRequestObject) (GetTitleTitleIdResponseObject, error) // Get user info // (GET /users/{user_id}) GetUsersUserId(ctx context.Context, request GetUsersUserIdRequestObject) (GetUsersUserIdResponseObject, error) @@ -668,6 +757,34 @@ func (sh *strictHandler) GetTitle(ctx *gin.Context, params GetTitleParams) { } } +// GetTitleTitleId operation middleware +func (sh *strictHandler) GetTitleTitleId(ctx *gin.Context, titleId int64, params GetTitleTitleIdParams) { + var request GetTitleTitleIdRequestObject + + request.TitleId = titleId + request.Params = params + + handler := func(ctx *gin.Context, request interface{}) (interface{}, error) { + return sh.ssi.GetTitleTitleId(ctx, request.(GetTitleTitleIdRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "GetTitleTitleId") + } + + response, err := handler(ctx, request) + + if err != nil { + ctx.Error(err) + ctx.Status(http.StatusInternalServerError) + } else if validResponse, ok := response.(GetTitleTitleIdResponseObject); ok { + if err := validResponse.VisitGetTitleTitleIdResponse(ctx.Writer); err != nil { + ctx.Error(err) + } + } else if response != nil { + ctx.Error(fmt.Errorf("unexpected response type: %T", response)) + } +} + // GetUsersUserId operation middleware func (sh *strictHandler) GetUsersUserId(ctx *gin.Context, userId string, params GetUsersUserIdParams) { var request GetUsersUserIdRequestObject diff --git a/api/openapi.yaml b/api/openapi.yaml index 4628c6d..9ea20f4 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -66,29 +66,34 @@ paths: '500': description: Unknown server error -# /title/{title_id}: -# get: -# summary: Get title description -# parameters: -# - in: path -# name: title_id -# required: true -# schema: -# type: string -# - in: query -# name: fields -# schema: -# type: string -# default: all -# responses: -# '200': -# description: Title description -# content: -# application/json: -# schema: -# $ref: '#/components/schemas/Title' -# '404': -# description: Title not found + /title/{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' + '404': + description: Title not found + '400': + description: Request params are not correct + '500': + description: Unknown server error # patch: # summary: Update title info diff --git a/modules/backend/handlers/titles.go b/modules/backend/handlers/titles.go index 182f450..3bbaa10 100644 --- a/modules/backend/handlers/titles.go +++ b/modules/backend/handlers/titles.go @@ -58,15 +58,15 @@ func ReleaseSeason2sqlc(s *oapi.ReleaseSeason) (*sqlc.ReleaseSeasonT, error) { return &t, nil } -type TitleNames *map[string][]string -type EpisodeLens *map[string]float64 -type TagNames []map[string]string +// type TitleNames map[string][]string +// type EpisodeLens map[string]float64 +// type TagNames []map[string]string func (s Server) GetTagsByTitleId(ctx context.Context, id int64) (oapi.Tags, error) { + sqlc_title_tags, err := s.db.GetTitleTags(ctx, id) if err != nil { - log.Errorf("%v", err) - return nil, err + return nil, fmt.Errorf("query GetTitleTags: %v", err) } var oapi_tag_names oapi.Tags @@ -74,8 +74,7 @@ func (s Server) GetTagsByTitleId(ctx context.Context, id int64) (oapi.Tags, erro var oapi_tag_name map[string]string err = json.Unmarshal(title_tag, &oapi_tag_name) if err != nil { - log.Errorf("invalid JSON for %s: %v", "TagNames", err) - return nil, err + return nil, fmt.Errorf("unmarshalling title_tag: %v", err) } oapi_tag_names = append(oapi_tag_names, oapi_tag_name) } @@ -89,8 +88,7 @@ func (s Server) GetImage(ctx context.Context, id int64) (oapi.Image, error) { sqlc_image, err := s.db.GetImageByID(ctx, id) if err != nil { - log.Errorf("%v", err) - return oapi_image, err + return oapi_image, fmt.Errorf("query GetImageByID: %v", err) } //can cast and dont use brain cause all this fiels required oapi_image.Id = &sqlc_image.ID @@ -106,8 +104,7 @@ func (s Server) GetStudio(ctx context.Context, id int64) (oapi.Studio, error) { sqlc_studio, err := s.db.GetStudioByID(ctx, id) if err != nil { - log.Errorf("%v", err) - return oapi_studio, err + return oapi_studio, fmt.Errorf("query GetStudioByID: %v", err) } oapi_studio.Id = &sqlc_studio.ID @@ -116,16 +113,82 @@ func (s Server) GetStudio(ctx context.Context, id int64) (oapi.Studio, error) { oapi_illust, err := s.GetImage(ctx, *sqlc_studio.IllustID) if err != nil { - log.Errorf("%v", err) - return oapi_studio, err + return oapi_studio, fmt.Errorf("GetImage: %v", err) } oapi_studio.Poster = &oapi_illust return oapi_studio, nil } +func (s Server) mapTitle(ctx context.Context, title sqlc.Title) (oapi.Title, error) { + var oapi_title oapi.Title + + var title_names map[string][]string + err := json.Unmarshal(title.TitleNames, &title_names) + if err != nil { + return oapi_title, fmt.Errorf("unmarshal TitleNames: %v", err) + } + + var episodes_lens map[string]float64 + err = json.Unmarshal(title.EpisodesLen, &episodes_lens) + if err != nil { + return oapi_title, fmt.Errorf("unmarshal EpisodesLen: %v", err) + } + + oapi_tag_names, err := s.GetTagsByTitleId(ctx, title.ID) + if err != nil { + return oapi_title, fmt.Errorf("GetTagsByTitleId: %v", err) + } + + oapi_image, err := s.GetImage(ctx, *title.PosterID) + if err != nil { + return oapi_title, fmt.Errorf("GetImage: %v", err) + } + + oapi_studio, err := s.GetStudio(ctx, title.StudioID) + if err != nil { + return oapi_title, fmt.Errorf("GetStudio: %v", err) + } + + oapi_title = oapi.Title{ + + Id: title.ID, + Poster: &oapi_image, + Rating: title.Rating, + RatingCount: title.RatingCount, + ReleaseSeason: (*oapi.ReleaseSeason)(title.ReleaseSeason), + ReleaseYear: title.ReleaseYear, + Studio: &oapi_studio, + Tags: oapi_tag_names, + TitleNames: title_names, + TitleStatus: (*oapi.TitleStatus)(&title.TitleStatus), + EpisodesAired: title.EpisodesAired, + EpisodesAll: title.EpisodesAll, + EpisodesLen: &episodes_lens, + } + return oapi_title, nil +} + +func (s Server) GetTitleTitleId(ctx context.Context, request oapi.GetTitleTitleIdRequestObject) (oapi.GetTitleTitleIdResponseObject, error) { + var oapi_title oapi.Title + + sqlc_title, err := s.db.GetTitleByID(ctx, request.TitleId) + if err != nil { + log.Errorf("%v", err) + return oapi.GetTitleTitleId500Response{}, nil + } + + oapi_title, err = s.mapTitle(ctx, sqlc_title) + if err != nil { + log.Errorf("%v", err) + return oapi.GetTitleTitleId500Response{}, nil + } + + return oapi.GetTitleTitleId200JSONResponse(oapi_title), nil +} + func (s Server) GetTitle(ctx context.Context, request oapi.GetTitleRequestObject) (oapi.GetTitleResponseObject, error) { - var result []oapi.Title + var opai_titles []oapi.Title word := Word2Sqlc(request.Params.Word) status, err := TitleStatus2Sqlc(request.Params.Status) @@ -158,56 +221,13 @@ func (s Server) GetTitle(ctx context.Context, request oapi.GetTitleRequestObject for _, title := range titles { - var title_names TitleNames - err := json.Unmarshal(title.TitleNames, &title_names) + t, err := s.mapTitle(ctx, title) if err != nil { - log.Errorf("invalid JSON for %s: %v", "TitleNames", err) - return oapi.GetTitle500Response{}, err + log.Errorf("%v", err) + return oapi.GetTitle500Response{}, nil } - - var episodes_lens EpisodeLens - err = json.Unmarshal(title.EpisodesLen, &episodes_lens) - if err != nil { - log.Errorf("invalid JSON for %s: %v", "EpisodeLens", err) - return oapi.GetTitle500Response{}, err - } - - oapi_tag_names, err := s.GetTagsByTitleId(ctx, title.ID) - if err != nil { - log.Errorf("error while getting tags %v", err) - return oapi.GetTitle500Response{}, err - } - - oapi_image, err := s.GetImage(ctx, *title.PosterID) - if err != nil { - log.Errorf("error while getting image %v", err) - return oapi.GetTitle500Response{}, err - } - - oapi_studio, err := s.GetStudio(ctx, title.StudioID) - if err != nil { - log.Errorf("error while getting studio %v", err) - return oapi.GetTitle500Response{}, err - } - - t := oapi.Title{ - - Id: title.ID, - Poster: &oapi_image, - Rating: title.Rating, - RatingCount: title.RatingCount, - ReleaseSeason: (*oapi.ReleaseSeason)(title.ReleaseSeason), - ReleaseYear: title.ReleaseYear, - Studio: &oapi_studio, - Tags: oapi_tag_names, - TitleNames: *title_names, - TitleStatus: (*oapi.TitleStatus)(&title.TitleStatus), - EpisodesAired: title.EpisodesAired, - EpisodesAll: title.EpisodesAll, - EpisodesLen: episodes_lens, - } - result = append(result, t) + opai_titles = append(opai_titles, t) } - return oapi.GetTitle200JSONResponse(result), nil + return oapi.GetTitle200JSONResponse(opai_titles), nil } diff --git a/modules/backend/queries.sql b/modules/backend/queries.sql index f58d33c..0570604 100644 --- a/modules/backend/queries.sql +++ b/modules/backend/queries.sql @@ -72,10 +72,10 @@ RETURNING id, tag_names; -- DELETE FROM users -- WHERE user_id = $1; ---name: GetTitleByID :one +-- name: GetTitleByID :one SELECT * FROM titles -WHERE id = sqlc.arg("title_id")::bigint; +WHERE id = sqlc.arg('title_id')::bigint; -- name: SearchTitles :many SELECT diff --git a/sql/queries.sql.go b/sql/queries.sql.go index 865ec73..8539d8d 100644 --- a/sql/queries.sql.go +++ b/sql/queries.sql.go @@ -59,6 +59,60 @@ func (q *Queries) GetStudioByID(ctx context.Context, studioID int64) (Studio, er return i, err } +const getTitleByID = `-- name: GetTitleByID :one + + + + +SELECT id, title_names, studio_id, poster_id, title_status, rating, rating_count, release_year, release_season, season, episodes_aired, episodes_all, episodes_len +FROM titles +WHERE id = $1::bigint +` + +// -- name: ListUsers :many +// SELECT user_id, avatar_id, passhash, mail, nickname, disp_name, user_desc, creation_date +// FROM users +// ORDER BY user_id +// LIMIT $1 OFFSET $2; +// -- name: CreateUser :one +// INSERT INTO users (avatar_id, passhash, mail, nickname, disp_name, user_desc, creation_date) +// VALUES ($1, $2, $3, $4, $5, $6, $7) +// RETURNING user_id, avatar_id, nickname, disp_name, user_desc, creation_date; +// -- name: UpdateUser :one +// UPDATE users +// SET +// +// avatar_id = COALESCE(sqlc.narg('avatar_id'), avatar_id), +// disp_name = COALESCE(sqlc.narg('disp_name'), disp_name), +// user_desc = COALESCE(sqlc.narg('user_desc'), user_desc), +// passhash = COALESCE(sqlc.narg('passhash'), passhash) +// +// WHERE user_id = sqlc.arg('user_id') +// RETURNING user_id, avatar_id, nickname, disp_name, user_desc, creation_date; +// -- name: DeleteUser :exec +// DELETE FROM users +// WHERE user_id = $1; +func (q *Queries) GetTitleByID(ctx context.Context, titleID int64) (Title, error) { + row := q.db.QueryRow(ctx, getTitleByID, titleID) + var i Title + err := row.Scan( + &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, + ) + return i, err +} + const getTitleTags = `-- name: GetTitleTags :many SELECT tag_names @@ -180,10 +234,6 @@ func (q *Queries) InsertTitleTags(ctx context.Context, arg InsertTitleTagsParams } const searchTitles = `-- name: SearchTitles :many - - - - SELECT id, title_names, studio_id, poster_id, title_status, rating, rating_count, release_year, release_season, season, episodes_aired, episodes_all, episodes_len FROM titles @@ -228,29 +278,6 @@ type SearchTitlesParams struct { Limit *int32 `json:"limit"` } -// -- name: ListUsers :many -// SELECT user_id, avatar_id, passhash, mail, nickname, disp_name, user_desc, creation_date -// FROM users -// ORDER BY user_id -// LIMIT $1 OFFSET $2; -// -- name: CreateUser :one -// INSERT INTO users (avatar_id, passhash, mail, nickname, disp_name, user_desc, creation_date) -// VALUES ($1, $2, $3, $4, $5, $6, $7) -// RETURNING user_id, avatar_id, nickname, disp_name, user_desc, creation_date; -// -- name: UpdateUser :one -// UPDATE users -// SET -// -// avatar_id = COALESCE(sqlc.narg('avatar_id'), avatar_id), -// disp_name = COALESCE(sqlc.narg('disp_name'), disp_name), -// user_desc = COALESCE(sqlc.narg('user_desc'), user_desc), -// passhash = COALESCE(sqlc.narg('passhash'), passhash) -// -// WHERE user_id = sqlc.arg('user_id') -// RETURNING user_id, avatar_id, nickname, disp_name, user_desc, creation_date; -// -- name: DeleteUser :exec -// DELETE FROM users -// WHERE user_id = $1; func (q *Queries) SearchTitles(ctx context.Context, arg SearchTitlesParams) ([]Title, error) { rows, err := q.db.Query(ctx, searchTitles, arg.Word,