From 47989ab10da73e351f9550dbae328698c38bc68e Mon Sep 17 00:00:00 2001 From: Iron_Felix Date: Sun, 16 Nov 2025 03:38:51 +0300 Subject: [PATCH] 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,