diff --git a/api/api.gen.go b/api/api.gen.go index c3ef4da..a235db8 100644 --- a/api/api.gen.go +++ b/api/api.gen.go @@ -34,21 +34,6 @@ 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"` @@ -56,7 +41,7 @@ type Title struct { EpisodesLen *map[string]float64 `json:"episodes_len,omitempty"` // Id Unique title ID (primary key) - Id int64 `json:"id"` + Id *int64 `json:"id,omitempty"` PosterId *int64 `json:"poster_id,omitempty"` Rating *float64 `json:"rating,omitempty"` RatingCount *int32 `json:"rating_count,omitempty"` @@ -64,13 +49,10 @@ type Title struct { // ReleaseSeason Title release season ReleaseSeason *ReleaseSeason `json:"release_season,omitempty"` ReleaseYear *int32 `json:"release_year,omitempty"` - Studio *Studio `json:"studio,omitempty"` - - // Tags Array of localized tags - Tags Tags `json:"tags"` + StudioId *int64 `json:"studio_id,omitempty"` // TitleNames Localized titles. Key = language (ISO 639-1), value = list of names - TitleNames map[string][]string `json:"title_names"` + TitleNames *map[string][]string `json:"title_names,omitempty"` // TitleStatus Title status TitleStatus *TitleStatus `json:"title_status,omitempty"` @@ -111,8 +93,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 *int32 `form:"limit,omitempty" json:"limit,omitempty"` - Offset *int32 `form:"offset,omitempty" json:"offset,omitempty"` + Limit *int `form:"limit,omitempty" json:"limit,omitempty"` + Offset *int `form:"offset,omitempty" json:"offset,omitempty"` Fields *string `form:"fields,omitempty" json:"fields,omitempty"` } @@ -121,6 +103,9 @@ 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) { @@ -218,20 +203,12 @@ func (a *Title) UnmarshalJSON(b []byte) error { delete(object, "release_year") } - if raw, found := object["studio"]; found { - err = json.Unmarshal(raw, &a.Studio) + if raw, found := object["studio_id"]; found { + err = json.Unmarshal(raw, &a.StudioId) if err != nil { - return fmt.Errorf("error reading 'studio': %w", err) + return fmt.Errorf("error reading 'studio_id': %w", err) } - delete(object, "studio") - } - - if raw, found := object["tags"]; found { - err = json.Unmarshal(raw, &a.Tags) - if err != nil { - return fmt.Errorf("error reading 'tags': %w", err) - } - delete(object, "tags") + delete(object, "studio_id") } if raw, found := object["title_names"]; found { @@ -290,9 +267,11 @@ func (a Title) MarshalJSON() ([]byte, error) { } } - object["id"], err = json.Marshal(a.Id) - if err != nil { - return nil, fmt.Errorf("error marshaling 'id': %w", err) + if a.Id != nil { + object["id"], err = json.Marshal(a.Id) + if err != nil { + return nil, fmt.Errorf("error marshaling 'id': %w", err) + } } if a.PosterId != nil { @@ -330,21 +309,18 @@ func (a Title) MarshalJSON() ([]byte, error) { } } - if a.Studio != nil { - object["studio"], err = json.Marshal(a.Studio) + if a.StudioId != nil { + object["studio_id"], err = json.Marshal(a.StudioId) if err != nil { - return nil, fmt.Errorf("error marshaling 'studio': %w", err) + return nil, fmt.Errorf("error marshaling 'studio_id': %w", err) } } - object["tags"], err = json.Marshal(a.Tags) - if err != nil { - return nil, fmt.Errorf("error marshaling 'tags': %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.TitleNames != nil { + 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 { @@ -368,6 +344,9 @@ 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) @@ -464,6 +443,19 @@ 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) { @@ -527,6 +519,7 @@ 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) } @@ -571,6 +564,27 @@ 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 @@ -618,6 +632,9 @@ 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) @@ -662,6 +679,39 @@ 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 0356898..4187ebb 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -2,10 +2,8 @@ openapi: 3.1.1 info: title: Titles, Users, Reviews, Tags, and Media API version: 1.0.0 - servers: - url: /api/v1 - paths: /title: get: @@ -37,13 +35,11 @@ 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 @@ -65,7 +61,6 @@ paths: description: Request params are not correct '500': description: Unknown server error - # /title/{title_id}: # get: # summary: Get title description @@ -248,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: @@ -572,7 +567,6 @@ components: - finished - ongoing - planned - ReleaseSeason: type: string description: Title release season @@ -581,7 +575,6 @@ components: - spring - summer - fall - UserTitleStatus: type: string description: User's title status @@ -590,57 +583,8 @@ 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 @@ -661,10 +605,9 @@ components: en: ["Attack on Titan", "AoT"] ru: ["Атака титанов", "Титаны"] ja: ["進撃の巨人"] - studio: - $ref: '#/components/schemas/Studio' - tags: - $ref: '#/components/schemas/Tags' + studio_id: + type: integer + format: int64 poster_id: type: integer format: int64 @@ -693,7 +636,6 @@ components: type: number format: double additionalProperties: true - User: type: object properties: @@ -737,7 +679,12 @@ components: - user_id - nickname # - creation_date - UserTitle: type: object additionalProperties: true + Review: + type: object + additionalProperties: true + Tag: + type: object + additionalProperties: true diff --git a/modules/backend/handlers/titles.go b/modules/backend/handlers/titles.go index 99217ca..85f9f45 100644 --- a/modules/backend/handlers/titles.go +++ b/modules/backend/handlers/titles.go @@ -57,25 +57,21 @@ func ReleaseSeason2sqlc(s *oapi.ReleaseSeason) (*sqlc.ReleaseSeasonT, error) { return &t, nil } -type TileNames *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 { +func jsonb2map4names(b []byte) (*map[string][]string, error) { + var t map[string][]string + 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 } -type EpisodeLens *map[string]float64 - -func jsonb2EpisodeLens(b []byte) (EpisodeLens, error) { - var t EpisodeLens - if err := json.Unmarshal(b, t); err != nil { +func jsonb2map4len(b []byte) (*map[string]float64, error) { + var t map[string]float64 + 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) { @@ -99,8 +95,6 @@ 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 @@ -110,12 +104,12 @@ func (s Server) GetTitle(ctx context.Context, request oapi.GetTitleRequestObject } for _, title := range titles { - title_names, err := jsonb2TitleNames(title.TitleNames) + title_names, err := jsonb2map4names(title.TitleNames) if err != nil { log.Errorf("%v", err) return oapi.GetTitle500Response{}, err } - episodes_lens, err := jsonb2EpisodeLens(title.EpisodesLen) + episodes_lens, err := jsonb2map4len(title.EpisodesLen) if err != nil { log.Errorf("%v", err) return oapi.GetTitle500Response{}, err @@ -128,7 +122,6 @@ 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, diff --git a/modules/backend/handlers/users.go b/modules/backend/handlers/users.go index 9e59261..3da61de 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), diff --git a/modules/backend/queries.sql b/modules/backend/queries.sql index a4c0bb9..b90ec6a 100644 --- a/modules/backend/queries.sql +++ b/modules/backend/queries.sql @@ -13,40 +13,6 @@ 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: 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 a73889c..2ef4178 100644 --- a/sql/queries.sql.go +++ b/sql/queries.sql.go @@ -41,52 +41,6 @@ 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 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 @@ -118,67 +72,6 @@ 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 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