Compare commits
3 commits
db53ae04e3
...
e12812d202
| Author | SHA1 | Date | |
|---|---|---|---|
| e12812d202 | |||
| 948e036e8c | |||
| 71e2661fb9 |
15 changed files with 565 additions and 1052 deletions
123
api/api.gen.go
123
api/api.gen.go
|
|
@ -8,28 +8,48 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/oapi-codegen/runtime"
|
||||
strictgin "github.com/oapi-codegen/runtime/strictmiddleware/gin"
|
||||
openapi_types "github.com/oapi-codegen/runtime/types"
|
||||
)
|
||||
|
||||
// Title defines model for Title.
|
||||
type Title map[string]interface{}
|
||||
// User defines model for User.
|
||||
type User struct {
|
||||
// AvatarId ID of the user avatar (references images table)
|
||||
AvatarId *int64 `json:"avatar_id"`
|
||||
|
||||
// GetTitleParams defines parameters for GetTitle.
|
||||
type GetTitleParams struct {
|
||||
Query *string `form:"query,omitempty" json:"query,omitempty"`
|
||||
Limit *int `form:"limit,omitempty" json:"limit,omitempty"`
|
||||
Offset *int `form:"offset,omitempty" json:"offset,omitempty"`
|
||||
// CreationDate Timestamp when the user was created
|
||||
CreationDate time.Time `json:"creation_date"`
|
||||
|
||||
// DispName Display name
|
||||
DispName *string `json:"disp_name,omitempty"`
|
||||
|
||||
// Id Unique user ID (primary key)
|
||||
Id *int64 `json:"id,omitempty"`
|
||||
|
||||
// Mail User email
|
||||
Mail *openapi_types.Email `json:"mail,omitempty"`
|
||||
|
||||
// Nickname Username (alphanumeric + _ or -)
|
||||
Nickname string `json:"nickname"`
|
||||
|
||||
// UserDesc User description
|
||||
UserDesc *string `json:"user_desc,omitempty"`
|
||||
}
|
||||
|
||||
// GetUsersUserIdParams defines parameters for GetUsersUserId.
|
||||
type GetUsersUserIdParams struct {
|
||||
Fields *string `form:"fields,omitempty" json:"fields,omitempty"`
|
||||
}
|
||||
|
||||
// ServerInterface represents all server handlers.
|
||||
type ServerInterface interface {
|
||||
// Get titles
|
||||
// (GET /title)
|
||||
GetTitle(c *gin.Context, params GetTitleParams)
|
||||
// Get user info
|
||||
// (GET /users/{user_id})
|
||||
GetUsersUserId(c *gin.Context, userId string, params GetUsersUserIdParams)
|
||||
}
|
||||
|
||||
// ServerInterfaceWrapper converts contexts to parameters.
|
||||
|
|
@ -41,37 +61,22 @@ type ServerInterfaceWrapper struct {
|
|||
|
||||
type MiddlewareFunc func(c *gin.Context)
|
||||
|
||||
// GetTitle operation middleware
|
||||
func (siw *ServerInterfaceWrapper) GetTitle(c *gin.Context) {
|
||||
// GetUsersUserId operation middleware
|
||||
func (siw *ServerInterfaceWrapper) GetUsersUserId(c *gin.Context) {
|
||||
|
||||
var err error
|
||||
|
||||
// ------------- Path parameter "user_id" -------------
|
||||
var userId string
|
||||
|
||||
err = runtime.BindStyledParameterWithOptions("simple", "user_id", c.Param("user_id"), &userId, runtime.BindStyledParameterOptions{Explode: false, Required: true})
|
||||
if err != nil {
|
||||
siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter user_id: %w", err), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// Parameter object where we will unmarshal all parameters from the context
|
||||
var params GetTitleParams
|
||||
|
||||
// ------------- Optional query parameter "query" -------------
|
||||
|
||||
err = runtime.BindQueryParameter("form", true, false, "query", c.Request.URL.Query(), ¶ms.Query)
|
||||
if err != nil {
|
||||
siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter query: %w", err), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// ------------- Optional query parameter "limit" -------------
|
||||
|
||||
err = runtime.BindQueryParameter("form", true, false, "limit", c.Request.URL.Query(), ¶ms.Limit)
|
||||
if err != nil {
|
||||
siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter limit: %w", err), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// ------------- Optional query parameter "offset" -------------
|
||||
|
||||
err = runtime.BindQueryParameter("form", true, false, "offset", c.Request.URL.Query(), ¶ms.Offset)
|
||||
if err != nil {
|
||||
siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter offset: %w", err), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
var params GetUsersUserIdParams
|
||||
|
||||
// ------------- Optional query parameter "fields" -------------
|
||||
|
||||
|
|
@ -88,7 +93,7 @@ func (siw *ServerInterfaceWrapper) GetTitle(c *gin.Context) {
|
|||
}
|
||||
}
|
||||
|
||||
siw.Handler.GetTitle(c, params)
|
||||
siw.Handler.GetUsersUserId(c, userId, params)
|
||||
}
|
||||
|
||||
// GinServerOptions provides options for the Gin server.
|
||||
|
|
@ -118,39 +123,40 @@ func RegisterHandlersWithOptions(router gin.IRouter, si ServerInterface, options
|
|||
ErrorHandler: errorHandler,
|
||||
}
|
||||
|
||||
router.GET(options.BaseURL+"/title", wrapper.GetTitle)
|
||||
router.GET(options.BaseURL+"/users/:user_id", wrapper.GetUsersUserId)
|
||||
}
|
||||
|
||||
type GetTitleRequestObject struct {
|
||||
Params GetTitleParams
|
||||
type GetUsersUserIdRequestObject struct {
|
||||
UserId string `json:"user_id"`
|
||||
Params GetUsersUserIdParams
|
||||
}
|
||||
|
||||
type GetTitleResponseObject interface {
|
||||
VisitGetTitleResponse(w http.ResponseWriter) error
|
||||
type GetUsersUserIdResponseObject interface {
|
||||
VisitGetUsersUserIdResponse(w http.ResponseWriter) error
|
||||
}
|
||||
|
||||
type GetTitle200JSONResponse []Title
|
||||
type GetUsersUserId200JSONResponse User
|
||||
|
||||
func (response GetTitle200JSONResponse) VisitGetTitleResponse(w http.ResponseWriter) error {
|
||||
func (response GetUsersUserId200JSONResponse) VisitGetUsersUserIdResponse(w http.ResponseWriter) error {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(200)
|
||||
|
||||
return json.NewEncoder(w).Encode(response)
|
||||
}
|
||||
|
||||
type GetTitle204Response struct {
|
||||
type GetUsersUserId404Response struct {
|
||||
}
|
||||
|
||||
func (response GetTitle204Response) VisitGetTitleResponse(w http.ResponseWriter) error {
|
||||
w.WriteHeader(204)
|
||||
func (response GetUsersUserId404Response) VisitGetUsersUserIdResponse(w http.ResponseWriter) error {
|
||||
w.WriteHeader(404)
|
||||
return nil
|
||||
}
|
||||
|
||||
// StrictServerInterface represents all server handlers.
|
||||
type StrictServerInterface interface {
|
||||
// Get titles
|
||||
// (GET /title)
|
||||
GetTitle(ctx context.Context, request GetTitleRequestObject) (GetTitleResponseObject, error)
|
||||
// Get user info
|
||||
// (GET /users/{user_id})
|
||||
GetUsersUserId(ctx context.Context, request GetUsersUserIdRequestObject) (GetUsersUserIdResponseObject, error)
|
||||
}
|
||||
|
||||
type StrictHandlerFunc = strictgin.StrictGinHandlerFunc
|
||||
|
|
@ -165,17 +171,18 @@ type strictHandler struct {
|
|||
middlewares []StrictMiddlewareFunc
|
||||
}
|
||||
|
||||
// GetTitle operation middleware
|
||||
func (sh *strictHandler) GetTitle(ctx *gin.Context, params GetTitleParams) {
|
||||
var request GetTitleRequestObject
|
||||
// GetUsersUserId operation middleware
|
||||
func (sh *strictHandler) GetUsersUserId(ctx *gin.Context, userId string, params GetUsersUserIdParams) {
|
||||
var request GetUsersUserIdRequestObject
|
||||
|
||||
request.UserId = userId
|
||||
request.Params = params
|
||||
|
||||
handler := func(ctx *gin.Context, request interface{}) (interface{}, error) {
|
||||
return sh.ssi.GetTitle(ctx, request.(GetTitleRequestObject))
|
||||
return sh.ssi.GetUsersUserId(ctx, request.(GetUsersUserIdRequestObject))
|
||||
}
|
||||
for _, middleware := range sh.middlewares {
|
||||
handler = middleware(handler, "GetTitle")
|
||||
handler = middleware(handler, "GetUsersUserId")
|
||||
}
|
||||
|
||||
response, err := handler(ctx, request)
|
||||
|
|
@ -183,8 +190,8 @@ func (sh *strictHandler) GetTitle(ctx *gin.Context, params GetTitleParams) {
|
|||
if err != nil {
|
||||
ctx.Error(err)
|
||||
ctx.Status(http.StatusInternalServerError)
|
||||
} else if validResponse, ok := response.(GetTitleResponseObject); ok {
|
||||
if err := validResponse.VisitGetTitleResponse(ctx.Writer); err != nil {
|
||||
} else if validResponse, ok := response.(GetUsersUserIdResponseObject); ok {
|
||||
if err := validResponse.VisitGetUsersUserIdResponse(ctx.Writer); err != nil {
|
||||
ctx.Error(err)
|
||||
}
|
||||
} else if response != nil {
|
||||
|
|
|
|||
298
api/openapi.yaml
298
api/openapi.yaml
|
|
@ -5,40 +5,40 @@ info:
|
|||
servers:
|
||||
- url: https://api.example.com
|
||||
paths:
|
||||
/title:
|
||||
get:
|
||||
summary: Get titles
|
||||
parameters:
|
||||
- in: query
|
||||
name: query
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: limit
|
||||
schema:
|
||||
type: integer
|
||||
default: 10
|
||||
- in: query
|
||||
name: offset
|
||||
schema:
|
||||
type: integer
|
||||
default: 0
|
||||
- in: query
|
||||
name: fields
|
||||
schema:
|
||||
type: string
|
||||
default: all
|
||||
responses:
|
||||
'200':
|
||||
description: List of titles
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Title'
|
||||
'204':
|
||||
description: No titles found
|
||||
# /title:
|
||||
# get:
|
||||
# summary: Get titles
|
||||
# parameters:
|
||||
# - in: query
|
||||
# name: query
|
||||
# schema:
|
||||
# type: string
|
||||
# - in: query
|
||||
# name: limit
|
||||
# schema:
|
||||
# type: integer
|
||||
# default: 10
|
||||
# - in: query
|
||||
# name: offset
|
||||
# schema:
|
||||
# type: integer
|
||||
# default: 0
|
||||
# - in: query
|
||||
# name: fields
|
||||
# schema:
|
||||
# type: string
|
||||
# default: all
|
||||
# responses:
|
||||
# '200':
|
||||
# description: List of titles
|
||||
# content:
|
||||
# application/json:
|
||||
# schema:
|
||||
# type: array
|
||||
# items:
|
||||
# $ref: '#/components/schemas/Title'
|
||||
# '204':
|
||||
# description: No titles found
|
||||
|
||||
# /title/{title_id}:
|
||||
# get:
|
||||
|
|
@ -124,122 +124,122 @@ paths:
|
|||
# '204':
|
||||
# description: No reviews found
|
||||
|
||||
# /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'
|
||||
# '404':
|
||||
# description: User not found
|
||||
/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'
|
||||
'404':
|
||||
description: User not found
|
||||
|
||||
# patch:
|
||||
# summary: Update user
|
||||
# parameters:
|
||||
# - in: path
|
||||
# name: user_id
|
||||
# required: true
|
||||
# schema:
|
||||
# type: string
|
||||
# requestBody:
|
||||
# required: true
|
||||
# content:
|
||||
# application/json:
|
||||
# schema:
|
||||
# $ref: '#/components/schemas/User'
|
||||
# responses:
|
||||
# '200':
|
||||
# description: Update result
|
||||
# content:
|
||||
# application/json:
|
||||
# schema:
|
||||
# type: object
|
||||
# properties:
|
||||
# success:
|
||||
# type: boolean
|
||||
# error:
|
||||
# type: string
|
||||
# patch:
|
||||
# summary: Update user
|
||||
# parameters:
|
||||
# - in: path
|
||||
# name: user_id
|
||||
# required: true
|
||||
# schema:
|
||||
# type: string
|
||||
# requestBody:
|
||||
# required: true
|
||||
# content:
|
||||
# application/json:
|
||||
# schema:
|
||||
# $ref: '#/components/schemas/User'
|
||||
# responses:
|
||||
# '200':
|
||||
# description: Update result
|
||||
# content:
|
||||
# application/json:
|
||||
# schema:
|
||||
# type: object
|
||||
# properties:
|
||||
# success:
|
||||
# type: boolean
|
||||
# error:
|
||||
# type: string
|
||||
|
||||
# delete:
|
||||
# summary: Delete user
|
||||
# parameters:
|
||||
# - in: path
|
||||
# name: user_id
|
||||
# required: true
|
||||
# schema:
|
||||
# type: string
|
||||
# responses:
|
||||
# '200':
|
||||
# description: Delete result
|
||||
# content:
|
||||
# application/json:
|
||||
# schema:
|
||||
# type: object
|
||||
# properties:
|
||||
# success:
|
||||
# type: boolean
|
||||
# error:
|
||||
# type: string
|
||||
# delete:
|
||||
# summary: Delete user
|
||||
# parameters:
|
||||
# - in: path
|
||||
# name: user_id
|
||||
# required: true
|
||||
# schema:
|
||||
# type: string
|
||||
# responses:
|
||||
# '200':
|
||||
# description: Delete result
|
||||
# content:
|
||||
# application/json:
|
||||
# schema:
|
||||
# type: object
|
||||
# properties:
|
||||
# success:
|
||||
# type: boolean
|
||||
# error:
|
||||
# type: string
|
||||
|
||||
# /users:
|
||||
# get:
|
||||
# summary: Search user
|
||||
# parameters:
|
||||
# - in: query
|
||||
# name: query
|
||||
# schema:
|
||||
# type: string
|
||||
# - in: query
|
||||
# name: fields
|
||||
# schema:
|
||||
# type: string
|
||||
# responses:
|
||||
# '200':
|
||||
# description: List of users
|
||||
# content:
|
||||
# application/json:
|
||||
# schema:
|
||||
# type: array
|
||||
# items:
|
||||
# $ref: '#/components/schemas/User'
|
||||
# /users:
|
||||
# get:
|
||||
# summary: Search user
|
||||
# parameters:
|
||||
# - in: query
|
||||
# name: query
|
||||
# schema:
|
||||
# type: string
|
||||
# - in: query
|
||||
# name: fields
|
||||
# schema:
|
||||
# type: string
|
||||
# responses:
|
||||
# '200':
|
||||
# description: List of users
|
||||
# content:
|
||||
# application/json:
|
||||
# schema:
|
||||
# type: array
|
||||
# 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:
|
||||
|
|
@ -541,14 +541,14 @@ components:
|
|||
User:
|
||||
type: object
|
||||
properties:
|
||||
user_id:
|
||||
id:
|
||||
type: integer
|
||||
format: int32
|
||||
format: int64
|
||||
description: Unique user ID (primary key)
|
||||
example: 1
|
||||
avatar_id:
|
||||
type: integer
|
||||
format: int32
|
||||
format: int64
|
||||
description: ID of the user avatar (references images table)
|
||||
nullable: true
|
||||
example: null
|
||||
|
|
|
|||
|
|
@ -1,719 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"nyanimedb/modules/backend/db"
|
||||
sqlc "nyanimedb/sql"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
db *sqlc.Queries
|
||||
}
|
||||
|
||||
func NewServer(db *db.Queries) Server {
|
||||
return Server{db: db}
|
||||
}
|
||||
|
||||
// —————————————————————————————————————————————
|
||||
// ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ
|
||||
// —————————————————————————————————————————————
|
||||
|
||||
func parseInt32(s string) (int32, error) {
|
||||
i, err := strconv.ParseInt(s, 10, 32)
|
||||
return int32(i), err
|
||||
}
|
||||
|
||||
func ptr[T any](v T) *T { return &v }
|
||||
|
||||
func pgInt4ToPtr(v pgtype.Int4) *int32 {
|
||||
if v.Valid {
|
||||
return &v.Int32
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func pgTextToPtr(v pgtype.Text) *string {
|
||||
if v.Valid {
|
||||
return &v.String
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func pgFloat8ToPtr(v pgtype.Float8) *float64 {
|
||||
if v.Valid {
|
||||
return &v.Float64
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func jsonbToInterface(data []byte) interface{} {
|
||||
if data == nil {
|
||||
return nil
|
||||
}
|
||||
var out interface{}
|
||||
if err := json.Unmarshal(data, &out); err != nil {
|
||||
return string(data) // fallback
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// —————————————————————————————————————————————
|
||||
// ХЕНДЛЕРЫ
|
||||
// —————————————————————————————————————————————
|
||||
|
||||
func (s Server) GetMedia(ctx context.Context, req GetMediaRequestObject) (GetMediaResponseObject, error) {
|
||||
id, err := parseInt32(req.Params.ImageId)
|
||||
if err != nil {
|
||||
return GetMedia200JSONResponse{Success: ptr(false), Error: ptr("invalid image_id")}, nil
|
||||
}
|
||||
img, err := s.db.GetImageByID(ctx, id)
|
||||
if err != nil {
|
||||
if err == pgx.ErrNoRows {
|
||||
return GetMedia200JSONResponse{Success: ptr(false), Error: ptr("image not found")}, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return GetMedia200JSONResponse{
|
||||
Success: ptr(true),
|
||||
ImagePath: ptr(img.ImagePath),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s Server) PostMedia(ctx context.Context, req PostMediaRequestObject) (PostMediaResponseObject, error) {
|
||||
// ❗ Не реализовано: OpenAPI не определяет тело запроса для загрузки
|
||||
return PostMedia200JSONResponse{
|
||||
Success: ptr(false),
|
||||
Error: ptr("upload not implemented: request body not defined in spec"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s Server) GetUsers(ctx context.Context, req GetUsersRequestObject) (GetUsersResponseObject, error) {
|
||||
users, err := s.db.ListUsers(ctx, db.ListUsersParams{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var resp []User
|
||||
for _, u := range users {
|
||||
resp = append(resp, mapUser(u))
|
||||
}
|
||||
return GetUsers200JSONResponse(resp), nil
|
||||
}
|
||||
|
||||
func (s Server) PostUsers(ctx context.Context, req PostUsersRequestObject) (PostUsersResponseObject, error) {
|
||||
if req.Body == nil {
|
||||
return PostUsers200JSONResponse{
|
||||
Success: ptr(false),
|
||||
Error: ptr("request body is required"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
body := *req.Body
|
||||
|
||||
// Обязательные поля
|
||||
nickname, ok := body["nickname"].(string)
|
||||
if !ok || nickname == "" {
|
||||
return PostUsers200JSONResponse{Success: ptr(false), Error: ptr("nickname is required")}, nil
|
||||
}
|
||||
|
||||
mail, ok := body["mail"].(string)
|
||||
if !ok || mail == "" {
|
||||
return PostUsers200JSONResponse{Success: ptr(false), Error: ptr("mail is required")}, nil
|
||||
}
|
||||
|
||||
password, ok := body["password"].(string)
|
||||
if !ok || password == "" {
|
||||
return PostUsers200JSONResponse{Success: ptr(false), Error: ptr("password is required")}, nil
|
||||
}
|
||||
|
||||
// Опциональные поля
|
||||
var avatarID *int32
|
||||
if v, ok := body["avatar_id"].(float64); ok {
|
||||
id := int32(v)
|
||||
avatarID = &id
|
||||
}
|
||||
|
||||
dispName, _ := body["disp_name"].(string)
|
||||
userDesc, _ := body["user_desc"].(string)
|
||||
|
||||
// 🔐 Хешируем пароль
|
||||
passhashBytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return PostUsers200JSONResponse{Success: ptr(false), Error: ptr("failed to hash password")}, nil
|
||||
}
|
||||
passhash := string(passhashBytes)
|
||||
|
||||
// Сохраняем в БД
|
||||
_, err = s.db.CreateUser(ctx, db.CreateUserParams{
|
||||
AvatarID: pgtype.Int4{
|
||||
Int32: 0,
|
||||
Valid: avatarID != nil,
|
||||
},
|
||||
Passhash: passhash,
|
||||
Mail: pgtype.Text{
|
||||
String: mail,
|
||||
Valid: true,
|
||||
},
|
||||
Nickname: nickname,
|
||||
DispName: pgtype.Text{
|
||||
String: dispName,
|
||||
Valid: dispName != "",
|
||||
},
|
||||
UserDesc: pgtype.Text{
|
||||
String: userDesc,
|
||||
Valid: userDesc != "",
|
||||
},
|
||||
CreationDate: pgtype.Timestamp{
|
||||
Time: time.Now(),
|
||||
Valid: true,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
// Проверяем нарушение уникальности (например, дубль mail или nickname)
|
||||
if err.Error() == "ERROR: duplicate key value violates unique constraint \"users_mail_key\"" ||
|
||||
err.Error() == "ERROR: duplicate key value violates unique constraint \"users_nickname_key\"" {
|
||||
return PostUsers200JSONResponse{
|
||||
Success: ptr(false),
|
||||
Error: ptr("user with this email or nickname already exists"),
|
||||
}, nil
|
||||
}
|
||||
return PostUsers200JSONResponse{Success: ptr(false), Error: ptr("database error")}, nil
|
||||
}
|
||||
|
||||
// Получаем созданного пользователя (без passhash и mail!)
|
||||
// Предположим, что у вас есть запрос GetUserByNickname или аналогичный
|
||||
// Но проще — вернуть только ID и nickname
|
||||
|
||||
// ⚠️ Поскольку мы не знаем user_id, можно:
|
||||
// а) добавить RETURNING в CreateUser (рекомендуется),
|
||||
// б) сделать отдельный SELECT.
|
||||
|
||||
// Пока вернём минимальный ответ
|
||||
userResp := User{
|
||||
"nickname": nickname,
|
||||
// "user_id" можно добавить, если обновите query.sql
|
||||
}
|
||||
|
||||
return PostUsers200JSONResponse{
|
||||
Success: ptr(true),
|
||||
UserJson: &userResp,
|
||||
}, nil
|
||||
}
|
||||
func (s Server) DeleteUsersUserId(ctx context.Context, req DeleteUsersUserIdRequestObject) (DeleteUsersUserIdResponseObject, error) {
|
||||
userID, err := parseInt32(req.UserId)
|
||||
if err != nil {
|
||||
return DeleteUsersUserId200JSONResponse{Success: ptr(false), Error: ptr("invalid user_id")}, nil
|
||||
}
|
||||
err = s.db.DeleteUser(ctx, userID)
|
||||
if err != nil {
|
||||
if err == pgx.ErrNoRows {
|
||||
return DeleteUsersUserId200JSONResponse{Success: ptr(false), Error: ptr("user not found")}, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return DeleteUsersUserId200JSONResponse{Success: ptr(true)}, nil
|
||||
}
|
||||
|
||||
func (s Server) GetUsersUserId(ctx context.Context, req GetUsersUserIdRequestObject) (GetUsersUserIdResponseObject, error) {
|
||||
userID, err := parseInt32(req.UserId)
|
||||
if err != nil {
|
||||
return GetUsersUserId404Response{}, nil
|
||||
}
|
||||
user, err := s.db.GetUserByID(ctx, userID)
|
||||
if err != nil {
|
||||
if err == pgx.ErrNoRows {
|
||||
return GetUsersUserId404Response{}, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return GetUsersUserId200JSONResponse(mapUser(user)), nil
|
||||
}
|
||||
|
||||
func (s Server) PatchUsersUserId(ctx context.Context, req PatchUsersUserIdRequestObject) (PatchUsersUserIdResponseObject, error) {
|
||||
userID, err := parseInt32(req.UserId)
|
||||
if err != nil {
|
||||
return PatchUsersUserId200JSONResponse{Success: ptr(false), Error: ptr("invalid user_id")}, nil
|
||||
}
|
||||
if req.Body == nil {
|
||||
return PatchUsersUserId200JSONResponse{Success: ptr(false), Error: ptr("empty body")}, nil
|
||||
}
|
||||
|
||||
body := *req.Body
|
||||
args := db.UpdateUserParams{
|
||||
UserID: userID,
|
||||
}
|
||||
|
||||
if v, ok := body["avatar_id"].(float64); ok {
|
||||
args.AvatarID = pgtype.Int4{Int32: int32(v), Valid: true}
|
||||
// args.AvatarIDValid = true
|
||||
}
|
||||
if v, ok := body["disp_name"].(string); ok {
|
||||
args.DispName = pgtype.Text{String: v, Valid: true}
|
||||
// args.DispNameValid = true
|
||||
}
|
||||
if v, ok := body["user_desc"].(string); ok {
|
||||
args.UserDesc = pgtype.Text{String: v, Valid: true}
|
||||
// args.UserDescValid = true
|
||||
}
|
||||
|
||||
_, err = s.db.UpdateUser(ctx, args)
|
||||
if err != nil {
|
||||
return PatchUsersUserId200JSONResponse{Success: ptr(false), Error: ptr(err.Error())}, nil
|
||||
}
|
||||
return PatchUsersUserId200JSONResponse{Success: ptr(true)}, nil
|
||||
}
|
||||
|
||||
func (s Server) GetUsersUserIdReviews(ctx context.Context, req GetUsersUserIdReviewsRequestObject) (GetUsersUserIdReviewsResponseObject, error) {
|
||||
userID, err := parseInt32(req.UserId)
|
||||
if err != nil {
|
||||
return GetUsersUserIdReviews200JSONResponse{}, nil
|
||||
}
|
||||
limit := int32(20)
|
||||
offset := int32(0)
|
||||
// if req.Params.Limit != nil {
|
||||
// limit = int32(*req.Params.Limit)
|
||||
// }
|
||||
// if req.Params.Offset != nil {
|
||||
// offset = int32(*req.Params.Offset)
|
||||
// }
|
||||
|
||||
reviews, err := s.db.ListReviewsByUser(ctx, db.ListReviewsByUserParams{
|
||||
UserID: userID,
|
||||
Limit: limit,
|
||||
Offset: offset,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var resp []Review
|
||||
for _, r := range reviews {
|
||||
resp = append(resp, mapReview(r))
|
||||
}
|
||||
return GetUsersUserIdReviews200JSONResponse(resp), nil
|
||||
}
|
||||
|
||||
func (s Server) DeleteUsersUserIdTitles(ctx context.Context, req DeleteUsersUserIdTitlesRequestObject) (DeleteUsersUserIdTitlesResponseObject, error) {
|
||||
userID, err := parseInt32(req.UserId)
|
||||
if err != nil {
|
||||
return DeleteUsersUserIdTitles200JSONResponse{Success: ptr(false), Error: ptr("invalid user_id")}, nil
|
||||
}
|
||||
|
||||
if req.Params.TitleId != nil {
|
||||
titleID, err := parseInt32(*req.Params.TitleId)
|
||||
if err != nil {
|
||||
return DeleteUsersUserIdTitles200JSONResponse{Success: ptr(false), Error: ptr("invalid title_id")}, nil
|
||||
}
|
||||
err = s.db.DeleteUserTitle(ctx, db.DeleteUserTitleParams{
|
||||
UserID: userID,
|
||||
Column2: titleID,
|
||||
})
|
||||
if err != nil && err != pgx.ErrNoRows {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
// else {
|
||||
// err = s.db.DeleteAllUserTitles(ctx, userID)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// }
|
||||
return DeleteUsersUserIdTitles200JSONResponse{Success: ptr(true)}, nil
|
||||
}
|
||||
|
||||
func (s Server) GetUsersUserIdTitles(ctx context.Context, req GetUsersUserIdTitlesRequestObject) (GetUsersUserIdTitlesResponseObject, error) {
|
||||
userID, err := parseInt32(req.UserId)
|
||||
if err != nil {
|
||||
return GetUsersUserIdTitles200JSONResponse{}, nil
|
||||
}
|
||||
limit := int32(100)
|
||||
offset := int32(0)
|
||||
if req.Params.Limit != nil {
|
||||
limit = int32(*req.Params.Limit)
|
||||
}
|
||||
if req.Params.Offset != nil {
|
||||
offset = int32(*req.Params.Offset)
|
||||
}
|
||||
|
||||
titles, err := s.db.ListUserTitles(ctx, db.ListUserTitlesParams{
|
||||
UserID: userID,
|
||||
Limit: limit,
|
||||
Offset: offset,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var resp []UserTitle
|
||||
for _, t := range titles {
|
||||
resp = append(resp, mapUserTitle(t))
|
||||
}
|
||||
return GetUsersUserIdTitles200JSONResponse(resp), nil
|
||||
}
|
||||
|
||||
func (s Server) PatchUsersUserIdTitles(ctx context.Context, req PatchUsersUserIdTitlesRequestObject) (PatchUsersUserIdTitlesResponseObject, error) {
|
||||
// userID, err := parseInt32(req.UserId)
|
||||
// if err != nil {
|
||||
// return PatchUsersUserIdTitles200JSONResponse{Success: ptr(false), Error: ptr("invalid user_id")}, nil
|
||||
// }
|
||||
// if req.Body == nil {
|
||||
// return PatchUsersUserIdTitles200JSONResponse{Success: ptr(false), Error: ptr("empty body")}, nil
|
||||
// }
|
||||
|
||||
// body := *req.Body
|
||||
// titleID, ok := body["title_id"].(float64)
|
||||
// if !ok {
|
||||
// return PatchUsersUserIdTitles200JSONResponse{Success: ptr(false), Error: ptr("title_id required")}, nil
|
||||
// }
|
||||
|
||||
// args := db.UpdateUserTitleParams{
|
||||
// UserID: userID,
|
||||
// TitleID: int32(titleID),
|
||||
// }
|
||||
|
||||
// if v, ok := body["status"].(string); ok {
|
||||
// args.Status = db.UsertitleStatusT(v)
|
||||
// // args.StatusValid = true
|
||||
// }
|
||||
// if v, ok := body["rate"].(float64); ok {
|
||||
// args.Rate = pgtype.Int4{Int32: int32(v), Valid: true}
|
||||
// // args.RateValid = true
|
||||
// }
|
||||
// if v, ok := body["review_id"].(float64); ok {
|
||||
// args.ReviewID = pgtype.Int4{Int32: int32(v), Valid: true}
|
||||
// // args.ReviewIDValid = true
|
||||
// }
|
||||
|
||||
// _, err = s.db.UpdateUserTitle(ctx, args)
|
||||
// if err != nil {
|
||||
// return PatchUsersUserIdTitles200JSONResponse{Success: ptr(false), Error: ptr(err.Error())}, nil
|
||||
// }
|
||||
return PatchUsersUserIdTitles200JSONResponse{Success: ptr(true)}, nil
|
||||
}
|
||||
|
||||
func (s Server) PostUsersUserIdTitles(ctx context.Context, req PostUsersUserIdTitlesRequestObject) (PostUsersUserIdTitlesResponseObject, error) {
|
||||
userID, err := parseInt32(req.UserId)
|
||||
if err != nil {
|
||||
return PostUsersUserIdTitles200JSONResponse{Success: ptr(false), Error: ptr("invalid user_id")}, nil
|
||||
}
|
||||
if req.Body == nil {
|
||||
return PostUsersUserIdTitles200JSONResponse{Success: ptr(false), Error: ptr("empty body")}, nil
|
||||
}
|
||||
|
||||
body := req.Body
|
||||
titleID, err := parseInt32(*body.TitleId)
|
||||
if err != nil {
|
||||
return PostUsersUserIdTitles200JSONResponse{Success: ptr(false), Error: ptr("invalid title_id")}, nil
|
||||
}
|
||||
|
||||
status := db.UsertitleStatusT("planned")
|
||||
if body.Status != nil {
|
||||
status = db.UsertitleStatusT(*body.Status)
|
||||
}
|
||||
|
||||
_, err = s.db.CreateUserTitle(ctx, db.CreateUserTitleParams{
|
||||
UserID: userID,
|
||||
TitleID: titleID,
|
||||
Status: status,
|
||||
Rate: pgtype.Int4{Valid: false},
|
||||
ReviewID: pgtype.Int4{Valid: false},
|
||||
})
|
||||
if err != nil {
|
||||
return PostUsersUserIdTitles200JSONResponse{Success: ptr(false), Error: ptr(err.Error())}, nil
|
||||
}
|
||||
return PostUsersUserIdTitles200JSONResponse{Success: ptr(true)}, nil
|
||||
}
|
||||
|
||||
func (s Server) GetTags(ctx context.Context, req GetTagsRequestObject) (GetTagsResponseObject, error) {
|
||||
limit := int32(100)
|
||||
offset := int32(0)
|
||||
if req.Params.Limit != nil {
|
||||
limit = int32(*req.Params.Limit)
|
||||
}
|
||||
if req.Params.Offset != nil {
|
||||
offset = int32(*req.Params.Offset)
|
||||
}
|
||||
tags, err := s.db.ListTags(ctx, db.ListTagsParams{Limit: limit, Offset: offset})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var resp []Tag
|
||||
for _, t := range tags {
|
||||
resp = append(resp, Tag{
|
||||
"tag_id": t.TagID,
|
||||
"tag_names": jsonbToInterface(t.TagNames),
|
||||
})
|
||||
}
|
||||
return GetTags200JSONResponse(resp), nil
|
||||
}
|
||||
|
||||
func (s Server) GetTitle(ctx context.Context, req GetTitleRequestObject) (GetTitleResponseObject, error) {
|
||||
limit := int32(50)
|
||||
offset := int32(0)
|
||||
if req.Params.Limit != nil {
|
||||
limit = int32(*req.Params.Limit)
|
||||
}
|
||||
if req.Params.Offset != nil {
|
||||
offset = int32(*req.Params.Offset)
|
||||
}
|
||||
titles, err := s.db.ListTitles(ctx, db.ListTitlesParams{Limit: limit, Offset: offset})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var resp []Title
|
||||
for _, t := range titles {
|
||||
resp = append(resp, mapTitle(t))
|
||||
}
|
||||
return GetTitle200JSONResponse(resp), nil
|
||||
}
|
||||
|
||||
func (s Server) GetTitleTitleId(ctx context.Context, req GetTitleTitleIdRequestObject) (GetTitleTitleIdResponseObject, error) {
|
||||
titleID, err := parseInt32(req.TitleId)
|
||||
if err != nil {
|
||||
return GetTitleTitleId404Response{}, nil
|
||||
}
|
||||
title, err := s.db.GetTitleByID(ctx, titleID)
|
||||
if err != nil {
|
||||
if err == pgx.ErrNoRows {
|
||||
return GetTitleTitleId404Response{}, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return GetTitleTitleId200JSONResponse(mapTitle(title)), nil
|
||||
}
|
||||
|
||||
func (s Server) PatchTitleTitleId(ctx context.Context, req PatchTitleTitleIdRequestObject) (PatchTitleTitleIdResponseObject, error) {
|
||||
titleID, err := parseInt32(req.TitleId)
|
||||
if err != nil {
|
||||
return PatchTitleTitleId200JSONResponse{Success: ptr(false), Error: ptr("invalid title_id")}, nil
|
||||
}
|
||||
if req.Body == nil {
|
||||
return PatchTitleTitleId200JSONResponse{Success: ptr(false), Error: ptr("empty body")}, nil
|
||||
}
|
||||
|
||||
body := *req.Body
|
||||
args := db.UpdateTitleParams{
|
||||
TitleID: titleID,
|
||||
}
|
||||
|
||||
if v, ok := body["title_names"].(map[string]interface{}); ok {
|
||||
data, _ := json.Marshal(v)
|
||||
args.TitleNames = data
|
||||
// args.TitleNamesValid = true
|
||||
}
|
||||
if v, ok := body["studio_id"].(float64); ok {
|
||||
args.StudioID = pgtype.Int4{Int32: int32(v), Valid: true}
|
||||
// args.StudioIDValid = true
|
||||
}
|
||||
if v, ok := body["poster_id"].(float64); ok {
|
||||
args.PosterID = pgtype.Int4{Int32: int32(v), Valid: true}
|
||||
// args.PosterIDValid = true
|
||||
}
|
||||
// if v, ok := body["title_status"].(string); ok {
|
||||
// args.TitleStatus = db.NullTitleStatusT(v)
|
||||
// // args.TitleStatusValid = true
|
||||
// }
|
||||
if v, ok := body["release_year"].(float64); ok {
|
||||
args.ReleaseYear = pgtype.Int4{Int32: int32(v), Valid: true}
|
||||
// args.ReleaseYearValid = true
|
||||
}
|
||||
if v, ok := body["episodes_aired"].(float64); ok {
|
||||
args.EpisodesAired = pgtype.Int4{Int32: int32(v), Valid: true}
|
||||
// args.EpisodesAiredValid = true
|
||||
}
|
||||
if v, ok := body["episodes_all"].(float64); ok {
|
||||
args.EpisodesAll = pgtype.Int4{Int32: int32(v), Valid: true}
|
||||
// args.EpisodesAllValid = true
|
||||
}
|
||||
|
||||
_, err = s.db.UpdateTitle(ctx, args)
|
||||
if err != nil {
|
||||
return PatchTitleTitleId200JSONResponse{Success: ptr(false), Error: ptr(err.Error())}, nil
|
||||
}
|
||||
return PatchTitleTitleId200JSONResponse{Success: ptr(true)}, nil
|
||||
}
|
||||
|
||||
func (s Server) GetTitleTitleIdReviews(ctx context.Context, req GetTitleTitleIdReviewsRequestObject) (GetTitleTitleIdReviewsResponseObject, error) {
|
||||
titleID, err := parseInt32(req.TitleId)
|
||||
if err != nil {
|
||||
return GetTitleTitleIdReviews200JSONResponse{}, nil
|
||||
}
|
||||
limit := int32(20)
|
||||
offset := int32(0)
|
||||
if req.Params.Limit != nil {
|
||||
limit = int32(*req.Params.Limit)
|
||||
}
|
||||
if req.Params.Offset != nil {
|
||||
offset = int32(*req.Params.Offset)
|
||||
}
|
||||
|
||||
reviews, err := s.db.ListReviewsByTitle(ctx, db.ListReviewsByTitleParams{
|
||||
TitleID: titleID,
|
||||
Limit: limit,
|
||||
Offset: offset,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var resp []Review
|
||||
for _, r := range reviews {
|
||||
resp = append(resp, mapReview(r))
|
||||
}
|
||||
return GetTitleTitleIdReviews200JSONResponse(resp), nil
|
||||
}
|
||||
|
||||
func (s Server) PostReviews(ctx context.Context, req PostReviewsRequestObject) (PostReviewsResponseObject, error) {
|
||||
if req.Body == nil {
|
||||
return PostReviews200JSONResponse{Success: ptr(false), Error: ptr("empty body")}, nil
|
||||
}
|
||||
|
||||
body := *req.Body
|
||||
userID, ok1 := body["user_id"].(float64)
|
||||
titleID, ok2 := body["title_id"].(float64)
|
||||
reviewText, ok3 := body["review_text"].(string)
|
||||
if !ok1 || !ok2 || !ok3 {
|
||||
return PostReviews200JSONResponse{Success: ptr(false), Error: ptr("user_id, title_id, review_text required")}, nil
|
||||
}
|
||||
|
||||
var imageIDs []int32
|
||||
if ids, ok := body["image_ids"].([]interface{}); ok {
|
||||
for _, id := range ids {
|
||||
if f, ok := id.(float64); ok {
|
||||
imageIDs = append(imageIDs, int32(f))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_, err := s.db.CreateReview(ctx, db.CreateReviewParams{
|
||||
UserID: int32(userID),
|
||||
TitleID: int32(titleID),
|
||||
ImageIds: imageIDs,
|
||||
ReviewText: reviewText,
|
||||
CreationDate: pgtype.Timestamp{Time: time.Now(), Valid: true},
|
||||
})
|
||||
if err != nil {
|
||||
return PostReviews200JSONResponse{Success: ptr(false), Error: ptr(err.Error())}, nil
|
||||
}
|
||||
|
||||
return PostReviews200JSONResponse{Success: ptr(true)}, nil
|
||||
}
|
||||
|
||||
func (s Server) DeleteReviewsReviewId(ctx context.Context, req DeleteReviewsReviewIdRequestObject) (DeleteReviewsReviewIdResponseObject, error) {
|
||||
reviewID, err := parseInt32(req.ReviewId)
|
||||
if err != nil {
|
||||
return DeleteReviewsReviewId200JSONResponse{Success: ptr(false), Error: ptr("invalid review_id")}, nil
|
||||
}
|
||||
err = s.db.DeleteReview(ctx, reviewID)
|
||||
if err != nil {
|
||||
if err == pgx.ErrNoRows {
|
||||
return DeleteReviewsReviewId200JSONResponse{Success: ptr(false), Error: ptr("review not found")}, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return DeleteReviewsReviewId200JSONResponse{Success: ptr(true)}, nil
|
||||
}
|
||||
|
||||
func (s Server) PatchReviewsReviewId(ctx context.Context, req PatchReviewsReviewIdRequestObject) (PatchReviewsReviewIdResponseObject, error) {
|
||||
reviewID, err := parseInt32(req.ReviewId)
|
||||
if err != nil {
|
||||
return PatchReviewsReviewId200JSONResponse{Success: ptr(false), Error: ptr("invalid review_id")}, nil
|
||||
}
|
||||
if req.Body == nil {
|
||||
return PatchReviewsReviewId200JSONResponse{Success: ptr(false), Error: ptr("empty body")}, nil
|
||||
}
|
||||
|
||||
body := *req.Body
|
||||
args := db.UpdateReviewParams{
|
||||
ReviewID: reviewID,
|
||||
}
|
||||
|
||||
if v, ok := body["review_text"].(string); ok {
|
||||
args.ReviewText = pgtype.Text{String: v, Valid: true}
|
||||
// args.ReviewTextValid = true
|
||||
}
|
||||
if ids, ok := body["image_ids"].([]interface{}); ok {
|
||||
var imageIDs []int32
|
||||
for _, id := range ids {
|
||||
if f, ok := id.(float64); ok {
|
||||
imageIDs = append(imageIDs, int32(f))
|
||||
}
|
||||
}
|
||||
args.ImageIds = imageIDs
|
||||
// args.ImageIdsValid = true
|
||||
}
|
||||
|
||||
_, err = s.db.UpdateReview(ctx, args)
|
||||
if err != nil {
|
||||
return PatchReviewsReviewId200JSONResponse{Success: ptr(false), Error: ptr(err.Error())}, nil
|
||||
}
|
||||
return PatchReviewsReviewId200JSONResponse{Success: ptr(true)}, nil
|
||||
}
|
||||
|
||||
// —————————————————————————————————————————————
|
||||
// МАППИНГИ
|
||||
// —————————————————————————————————————————————
|
||||
|
||||
func mapUser(u db.Users) User {
|
||||
return User{
|
||||
"user_id": u.UserID,
|
||||
"avatar_id": pgInt4ToPtr(u.AvatarID),
|
||||
"nickname": u.Nickname,
|
||||
"disp_name": pgTextToPtr(u.DispName),
|
||||
"user_desc": pgTextToPtr(u.UserDesc),
|
||||
"creation_date": u.CreationDate.Time,
|
||||
// mail и passhash НЕ возвращаем!
|
||||
}
|
||||
}
|
||||
|
||||
func mapTitle(t db.Titles) Title {
|
||||
var releaseSeason interface{}
|
||||
if t.ReleaseSeason.Valid {
|
||||
releaseSeason = string(t.ReleaseSeason.ReleaseSeasonT)
|
||||
}
|
||||
|
||||
return Title{
|
||||
"title_id": t.TitleID,
|
||||
"title_names": jsonbToInterface(t.TitleNames),
|
||||
"studio_id": t.StudioID,
|
||||
"poster_id": pgInt4ToPtr(t.PosterID),
|
||||
"signal_ids": t.SignalIds,
|
||||
"title_status": string(t.TitleStatus),
|
||||
"rating": pgFloat8ToPtr(t.Rating),
|
||||
"rating_count": pgInt4ToPtr(t.RatingCount),
|
||||
"release_year": pgInt4ToPtr(t.ReleaseYear),
|
||||
"release_season": releaseSeason,
|
||||
"season": pgInt4ToPtr(t.Season),
|
||||
"episodes_aired": pgInt4ToPtr(t.EpisodesAired),
|
||||
"episodes_all": pgInt4ToPtr(t.EpisodesAll),
|
||||
"episodes_len": jsonbToInterface(t.EpisodesLen),
|
||||
}
|
||||
}
|
||||
|
||||
func mapReview(r db.Reviews) Review {
|
||||
return Review{
|
||||
"review_id": r.ReviewID,
|
||||
"user_id": r.UserID,
|
||||
"title_id": r.TitleID,
|
||||
"image_ids": r.ImageIds,
|
||||
"review_text": r.ReviewText,
|
||||
"creation_date": r.CreationDate.Time,
|
||||
}
|
||||
}
|
||||
|
||||
func mapUserTitle(ut db.Usertitles) UserTitle {
|
||||
return UserTitle{
|
||||
"usertitle_id": ut.UsertitleID,
|
||||
"user_id": ut.UserID,
|
||||
"title_id": ut.TitleID,
|
||||
"status": string(ut.Status),
|
||||
"rate": pgInt4ToPtr(ut.Rate),
|
||||
"review_id": pgInt4ToPtr(ut.ReviewID),
|
||||
}
|
||||
}
|
||||
51
modules/backend/handlers/users.go
Normal file
51
modules/backend/handlers/users.go
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
oapi "nyanimedb/api"
|
||||
sqlc "nyanimedb/sql"
|
||||
"strconv"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/oapi-codegen/runtime/types"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
db *sqlc.Queries
|
||||
}
|
||||
|
||||
func NewServer(db *sqlc.Queries) Server {
|
||||
return Server{db: db}
|
||||
}
|
||||
|
||||
func parseInt64(s string) (int32, error) {
|
||||
i, err := strconv.ParseInt(s, 10, 64)
|
||||
return int32(i), err
|
||||
}
|
||||
|
||||
func mapUser(u sqlc.GetUserByIDRow) oapi.User {
|
||||
return oapi.User{
|
||||
AvatarId: u.AvatarID,
|
||||
CreationDate: u.CreationDate,
|
||||
DispName: u.DispName,
|
||||
Id: &u.ID,
|
||||
Mail: (*types.Email)(u.Mail),
|
||||
Nickname: u.Nickname,
|
||||
UserDesc: u.UserDesc,
|
||||
}
|
||||
}
|
||||
|
||||
func (s Server) GetUsersUserId(ctx context.Context, req oapi.GetUsersUserIdRequestObject) (oapi.GetUsersUserIdResponseObject, error) {
|
||||
userID, err := parseInt64(req.UserId)
|
||||
if err != nil {
|
||||
return oapi.GetUsersUserId404Response{}, nil
|
||||
}
|
||||
user, err := s.db.GetUserByID(context.TODO(), int64(userID))
|
||||
if err != nil {
|
||||
if err == pgx.ErrNoRows {
|
||||
return oapi.GetUsersUserId404Response{}, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return oapi.GetUsersUserId200JSONResponse(mapUser(user)), nil
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"time"
|
||||
|
||||
oapi "nyanimedb/api"
|
||||
handlers "nyanimedb/modules/backend/handlers"
|
||||
|
||||
"github.com/gin-contrib/cors"
|
||||
"github.com/gin-gonic/gin"
|
||||
|
|
@ -42,7 +43,7 @@ func main() {
|
|||
|
||||
queries := sqlc.New(conn)
|
||||
|
||||
server := NewServer(queries)
|
||||
server := handlers.NewServer(queries)
|
||||
// r.LoadHTMLGlob("templates/*")
|
||||
|
||||
r.Use(cors.New(cors.Config{
|
||||
|
|
|
|||
|
|
@ -1,17 +1,17 @@
|
|||
-- name: GetImageByID :one
|
||||
SELECT image_id, storage_type, image_path
|
||||
SELECT id, storage_type, image_path
|
||||
FROM images
|
||||
WHERE image_id = $1;
|
||||
WHERE id = $1;
|
||||
|
||||
-- -- name: CreateImage :one
|
||||
-- INSERT INTO images (storage_type, image_path)
|
||||
-- VALUES ($1, $2)
|
||||
-- RETURNING image_id, storage_type, image_path;
|
||||
-- name: CreateImage :one
|
||||
INSERT INTO images (storage_type, image_path)
|
||||
VALUES ($1, $2)
|
||||
RETURNING id, storage_type, image_path;
|
||||
|
||||
-- -- name: GetUserByID :one
|
||||
-- SELECT user_id, avatar_id, passhash, mail, nickname, disp_name, user_desc, creation_date
|
||||
-- FROM users
|
||||
-- WHERE user_id = $1;
|
||||
-- name: GetUserByID :one
|
||||
SELECT id, avatar_id, mail, nickname, disp_name, user_desc, creation_date
|
||||
FROM users
|
||||
WHERE id = $1;
|
||||
|
||||
-- -- name: ListUsers :many
|
||||
-- SELECT user_id, avatar_id, passhash, mail, nickname, disp_name, user_desc, creation_date
|
||||
|
|
|
|||
57
modules/frontend/package-lock.json
generated
57
modules/frontend/package-lock.json
generated
|
|
@ -10,7 +10,8 @@
|
|||
"dependencies": {
|
||||
"axios": "^1.12.2",
|
||||
"react": "^19.1.1",
|
||||
"react-dom": "^19.1.1"
|
||||
"react-dom": "^19.1.1",
|
||||
"react-router-dom": "^7.9.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.36.0",
|
||||
|
|
@ -2061,6 +2062,15 @@
|
|||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cookie": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz",
|
||||
"integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
|
|
@ -3346,6 +3356,7 @@
|
|||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz",
|
||||
"integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"scheduler": "^0.27.0"
|
||||
},
|
||||
|
|
@ -3363,6 +3374,44 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-router": {
|
||||
"version": "7.9.4",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.9.4.tgz",
|
||||
"integrity": "sha512-SD3G8HKviFHg9xj7dNODUKDFgpG4xqD5nhyd0mYoB5iISepuZAvzSr8ywxgxKJ52yRzf/HWtVHc9AWwoTbljvA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cookie": "^1.0.1",
|
||||
"set-cookie-parser": "^2.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18",
|
||||
"react-dom": ">=18"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-router-dom": {
|
||||
"version": "7.9.4",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.9.4.tgz",
|
||||
"integrity": "sha512-f30P6bIkmYvnHHa5Gcu65deIXoA2+r3Eb6PJIAddvsT9aGlchMatJ51GgpU470aSqRRbFX22T70yQNUGuW3DfA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"react-router": "7.9.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18",
|
||||
"react-dom": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve-from": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
|
||||
|
|
@ -3466,6 +3515,12 @@
|
|||
"semver": "bin/semver.js"
|
||||
}
|
||||
},
|
||||
"node_modules/set-cookie-parser": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
|
||||
"integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@
|
|||
"dependencies": {
|
||||
"axios": "^1.12.2",
|
||||
"react": "^19.1.1",
|
||||
"react-dom": "^19.1.1"
|
||||
"react-dom": "^19.1.1",
|
||||
"react-router-dom": "^7.9.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.36.0",
|
||||
|
|
|
|||
|
|
@ -1,8 +1,15 @@
|
|||
import React from "react";
|
||||
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
|
||||
import UserPage from "./components/UserPage/UserPage";
|
||||
|
||||
const App: React.FC = () => {
|
||||
return <UserPage />;
|
||||
return (
|
||||
<Router>
|
||||
<Routes>
|
||||
<Route path="/users/:id" element={<UserPage />} />
|
||||
</Routes>
|
||||
</Router>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
export default App;
|
||||
|
|
@ -1,17 +1,21 @@
|
|||
import React, { useEffect, useState } from "react";
|
||||
import { DefaultService } from "../../api/services/DefaultService"; // adjust path
|
||||
import { useParams } from "react-router-dom"; // <-- import
|
||||
import { DefaultService } from "../../api/services/DefaultService";
|
||||
import type { User } from "../../api/models/User";
|
||||
import styles from "./UserPage.module.css";
|
||||
|
||||
const UserPage: React.FC = () => {
|
||||
const { id } = useParams<{ id: string }>(); // <-- get user id from URL
|
||||
const [user, setUser] = useState<User | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!id) return;
|
||||
|
||||
const getUserInfo = async () => {
|
||||
try {
|
||||
const userInfo = await DefaultService.getUsers("1", "all");
|
||||
const userInfo = await DefaultService.getUsers(id, "all"); // <-- use dynamic id
|
||||
setUser(userInfo);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
|
@ -21,7 +25,7 @@ const UserPage: React.FC = () => {
|
|||
}
|
||||
};
|
||||
getUserInfo();
|
||||
}, []);
|
||||
}, [id]);
|
||||
|
||||
if (loading) return <div className={styles.loader}>Loading...</div>;
|
||||
if (error) return <div className={styles.error}>{error}</div>;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,12 @@
|
|||
DROP TRIGGER IF EXISTS trg_update_title_rating ON usertitles;
|
||||
DROP TRIGGER IF EXISTS trg_notify_new_signal ON signals;
|
||||
|
||||
DROP FUNCTION IF EXISTS update_title_rating();
|
||||
DROP FUNCTION IF EXISTS notify_new_signal();
|
||||
|
||||
DROP TABLE IF EXISTS signals;
|
||||
DROP TABLE IF EXISTS title_tags;
|
||||
DROP TABLE IF EXISTS usertitles;
|
||||
DROP TABLE IF EXISTS reviews;
|
||||
DROP TABLE IF EXISTS titles;
|
||||
DROP TABLE IF EXISTS studios;
|
||||
DROP TABLE IF EXISTS users;
|
||||
|
|
|
|||
|
|
@ -1,99 +1,141 @@
|
|||
-- TODO:
|
||||
-- title table triggers
|
||||
-- maybe jsonb constraints
|
||||
-- actions (delete)
|
||||
-- clean unused images
|
||||
CREATE TYPE usertitle_status_t AS ENUM ('finished', 'planned', 'dropped', 'in-progress');
|
||||
CREATE TYPE storage_type_t AS ENUM ('local', 's3');
|
||||
CREATE TYPE title_status_t AS ENUM ('finished', 'ongoing', 'planned');
|
||||
CREATE TYPE release_season_t AS ENUM ('winter', 'spring', 'summer', 'fall');
|
||||
|
||||
CREATE TABLE providers (
|
||||
provider_id serial PRIMARY KEY,
|
||||
provider_name varchar(64) NOT NULL
|
||||
-- token
|
||||
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
provider_name text NOT NULL,
|
||||
credentials jsonb
|
||||
);
|
||||
|
||||
CREATE TABLE tags (
|
||||
tag_id serial PRIMARY KEY,
|
||||
tag_names jsonb NOT NULL --mb constraints
|
||||
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
tag_names jsonb NOT NULL
|
||||
);
|
||||
|
||||
|
||||
-- clean unused images
|
||||
CREATE TABLE images (
|
||||
image_id serial PRIMARY KEY,
|
||||
storage_type storage_type_t NOT NULL,
|
||||
image_path varchar(256) UNIQUE NOT NULL
|
||||
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
storage_type storage_type_t NOT NULL,
|
||||
image_path text UNIQUE NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE users (
|
||||
user_id serial PRIMARY KEY,
|
||||
avatar_id int REFERENCES images (image_id),
|
||||
passhash text NOT NULL,
|
||||
mail varchar(64) CHECK (mail ~ '[a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9_-]+'),
|
||||
nickname varchar(16) NOT NULL CHECK (nickname ~ '^[a-zA-Z0-9_-]+$'),
|
||||
disp_name varchar(32),
|
||||
user_desc varchar(512),
|
||||
-- timestamp tl dr, also add access ts
|
||||
creation_date timestamp NOT NULL
|
||||
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
avatar_id bigint REFERENCES images (id),
|
||||
passhash text NOT NULL,
|
||||
mail text CHECK (mail ~ '[a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+\.[a-zA-Z0-9_-]+'),
|
||||
nickname text NOT NULL CHECK (nickname ~ '^[a-zA-Z0-9_-]+$'),
|
||||
disp_name text,
|
||||
user_desc text,
|
||||
creation_date timestamptz NOT NULL,
|
||||
last_login timestamptz
|
||||
);
|
||||
|
||||
CREATE TABLE studios (
|
||||
studio_id serial PRIMARY KEY,
|
||||
studio_name varchar(64) UNIQUE,
|
||||
illust_id int REFERENCES images (image_id),
|
||||
studio_desc text
|
||||
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
studio_name text UNIQUE,
|
||||
illust_id bigint REFERENCES images (id),
|
||||
studio_desc text
|
||||
);
|
||||
|
||||
CREATE TABLE titles (
|
||||
title_id serial PRIMARY KEY,
|
||||
title_names jsonb NOT NULL,
|
||||
studio_id int NOT NULL REFERENCES studios,
|
||||
poster_id int REFERENCES images (image_id),
|
||||
--signal_ids int[] NOT NULL,
|
||||
title_status title_status_t NOT NULL,
|
||||
rating float CHECK (rating > 0 AND rating <= 10), --by trigger
|
||||
rating_count int CHECK (rating_count >= 0), --by trigger
|
||||
release_year int CHECK (release_year >= 1900),
|
||||
release_season release_season_t,
|
||||
season int CHECK (season >= 0),
|
||||
episodes_aired int CHECK (episodes_aired >= 0),
|
||||
episodes_all int CHECK (episodes_all >= 0),
|
||||
episodes_len jsonb,
|
||||
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
title_names jsonb NOT NULL,
|
||||
studio_id bigint NOT NULL REFERENCES studios (id),
|
||||
poster_id bigint REFERENCES images (id),
|
||||
title_status title_status_t NOT NULL,
|
||||
rating float CHECK (rating >= 0 AND rating <= 10),
|
||||
rating_count int CHECK (rating_count >= 0),
|
||||
release_year int CHECK (release_year >= 1900),
|
||||
release_season release_season_t,
|
||||
season int CHECK (season >= 0),
|
||||
episodes_aired int CHECK (episodes_aired >= 0),
|
||||
episodes_all int CHECK (episodes_all >= 0),
|
||||
episodes_len jsonb,
|
||||
CHECK ((episodes_aired IS NULL AND episodes_all IS NULL)
|
||||
OR (episodes_aired IS NOT NULL AND episodes_all IS NOT NULL
|
||||
AND episodes_aired <= episodes_all))
|
||||
);
|
||||
|
||||
CREATE TABLE reviews (
|
||||
review_id serial PRIMARY KEY, --???
|
||||
user_id int NOT NULL REFERENCES users,
|
||||
title_id int NOT NULL REFERENCES titles,
|
||||
--image_ids int[], move somewhere
|
||||
review_text text NOT NULL,
|
||||
creation_date timestamp NOT NULL
|
||||
-- constrai (title, user)
|
||||
);
|
||||
|
||||
CREATE TABLE usertitles (
|
||||
usertitle_id serial PRIMARY KEY, -- bigserial, replace by (,)
|
||||
user_id int NOT NULL REFERENCES users,
|
||||
title_id int NOT NULL REFERENCES titles,
|
||||
status usertitle_status_t NOT NULL,
|
||||
rate int CHECK (rate > 0 AND rate <= 10),
|
||||
review_id int REFERENCES reviews
|
||||
PRIMARY KEY (user_id, title_id),
|
||||
user_id bigint NOT NULL REFERENCES users (id),
|
||||
title_id bigint NOT NULL REFERENCES titles (id),
|
||||
status usertitle_status_t NOT NULL,
|
||||
rate int CHECK (rate > 0 AND rate <= 10),
|
||||
review_text text,
|
||||
review_date timestamptz
|
||||
);
|
||||
|
||||
CREATE TABLE title_tags (
|
||||
PRIMARY KEY (title_id, tag_id),
|
||||
title_id int NOT NULL REFERENCES titles,
|
||||
tag_id int NOT NULL REFERENCES tags
|
||||
PRIMARY KEY (title_id, tag_id),
|
||||
title_id bigint NOT NULL REFERENCES titles (id),
|
||||
tag_id bigint NOT NULL REFERENCES tags (id)
|
||||
);
|
||||
|
||||
CREATE TABLE signals (
|
||||
signal_id serial PRIMARY KEY,
|
||||
-- title_id
|
||||
raw_data jsonb NOT NULL,
|
||||
provider_id int NOT NULL REFERENCES providers,
|
||||
dirty bool NOT NULL
|
||||
);
|
||||
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
title_id bigint REFERENCES titles (id),
|
||||
raw_data jsonb NOT NULL,
|
||||
provider_id bigint NOT NULL REFERENCES providers (id),
|
||||
pending boolean NOT NULL
|
||||
);
|
||||
|
||||
-- Functions
|
||||
CREATE OR REPLACE FUNCTION update_title_rating()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
IF (TG_OP = 'INSERT') OR (TG_OP = 'UPDATE' AND NEW.rate IS DISTINCT FROM OLD.rate) THEN
|
||||
UPDATE titles
|
||||
SET
|
||||
rating = sub.avg_rating,
|
||||
rating_count = sub.rating_count
|
||||
FROM (
|
||||
SELECT
|
||||
title_id,
|
||||
AVG(rate)::float AS avg_rating,
|
||||
COUNT(rate) AS rating_count
|
||||
FROM usertitles
|
||||
WHERE title_id = NEW.title_id AND rate IS NOT NULL
|
||||
GROUP BY title_id
|
||||
) AS sub
|
||||
WHERE titles.id = sub.title_id;
|
||||
END IF;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE OR REPLACE FUNCTION notify_new_signal()
|
||||
RETURNS TRIGGER AS $$
|
||||
DECLARE
|
||||
payload JSON;
|
||||
BEGIN
|
||||
payload := json_build_object(
|
||||
'signal_id', NEW.id,
|
||||
'title_id', NEW.title_id,
|
||||
'provider_id', NEW.provider_id,
|
||||
'pending', NEW.pending,
|
||||
'timestamp', NOW()
|
||||
);
|
||||
PERFORM pg_notify('new_signal', payload::text);
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Triggers
|
||||
|
||||
CREATE TRIGGER trg_update_title_rating
|
||||
AFTER INSERT OR UPDATE OF rate ON usertitles
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_title_rating();
|
||||
|
||||
CREATE TRIGGER trg_notify_new_signal
|
||||
AFTER INSERT ON signals
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION notify_new_signal();
|
||||
|
|
@ -7,6 +7,7 @@ package sqlc
|
|||
import (
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
|
@ -185,48 +186,42 @@ func (ns NullUsertitleStatusT) Value() (driver.Value, error) {
|
|||
}
|
||||
|
||||
type Image struct {
|
||||
ImageID int32 `json:"image_id"`
|
||||
ID int64 `json:"id"`
|
||||
StorageType StorageTypeT `json:"storage_type"`
|
||||
ImagePath string `json:"image_path"`
|
||||
}
|
||||
|
||||
type Provider struct {
|
||||
ProviderID int32 `json:"provider_id"`
|
||||
ID int64 `json:"id"`
|
||||
ProviderName string `json:"provider_name"`
|
||||
}
|
||||
|
||||
type Review struct {
|
||||
ReviewID int32 `json:"review_id"`
|
||||
UserID int32 `json:"user_id"`
|
||||
TitleID int32 `json:"title_id"`
|
||||
ReviewText string `json:"review_text"`
|
||||
CreationDate pgtype.Timestamp `json:"creation_date"`
|
||||
Credentials []byte `json:"credentials"`
|
||||
}
|
||||
|
||||
type Signal struct {
|
||||
SignalID int32 `json:"signal_id"`
|
||||
ID int64 `json:"id"`
|
||||
TitleID *int64 `json:"title_id"`
|
||||
RawData []byte `json:"raw_data"`
|
||||
ProviderID int32 `json:"provider_id"`
|
||||
Dirty bool `json:"dirty"`
|
||||
ProviderID int64 `json:"provider_id"`
|
||||
Pending bool `json:"pending"`
|
||||
}
|
||||
|
||||
type Studio struct {
|
||||
StudioID int32 `json:"studio_id"`
|
||||
ID int64 `json:"id"`
|
||||
StudioName *string `json:"studio_name"`
|
||||
IllustID *int32 `json:"illust_id"`
|
||||
IllustID *int64 `json:"illust_id"`
|
||||
StudioDesc *string `json:"studio_desc"`
|
||||
}
|
||||
|
||||
type Tag struct {
|
||||
TagID int32 `json:"tag_id"`
|
||||
ID int64 `json:"id"`
|
||||
TagNames []byte `json:"tag_names"`
|
||||
}
|
||||
|
||||
type Title struct {
|
||||
TitleID int32 `json:"title_id"`
|
||||
ID int64 `json:"id"`
|
||||
TitleNames []byte `json:"title_names"`
|
||||
StudioID int32 `json:"studio_id"`
|
||||
PosterID *int32 `json:"poster_id"`
|
||||
StudioID int64 `json:"studio_id"`
|
||||
PosterID *int64 `json:"poster_id"`
|
||||
TitleStatus TitleStatusT `json:"title_status"`
|
||||
Rating *float64 `json:"rating"`
|
||||
RatingCount *int32 `json:"rating_count"`
|
||||
|
|
@ -239,26 +234,27 @@ type Title struct {
|
|||
}
|
||||
|
||||
type TitleTag struct {
|
||||
TitleID int32 `json:"title_id"`
|
||||
TagID int32 `json:"tag_id"`
|
||||
TitleID int64 `json:"title_id"`
|
||||
TagID int64 `json:"tag_id"`
|
||||
}
|
||||
|
||||
type User struct {
|
||||
UserID int32 `json:"user_id"`
|
||||
AvatarID *int32 `json:"avatar_id"`
|
||||
Passhash string `json:"passhash"`
|
||||
Mail *string `json:"mail"`
|
||||
Nickname string `json:"nickname"`
|
||||
DispName *string `json:"disp_name"`
|
||||
UserDesc *string `json:"user_desc"`
|
||||
CreationDate pgtype.Timestamp `json:"creation_date"`
|
||||
ID int64 `json:"id"`
|
||||
AvatarID *int64 `json:"avatar_id"`
|
||||
Passhash string `json:"passhash"`
|
||||
Mail *string `json:"mail"`
|
||||
Nickname string `json:"nickname"`
|
||||
DispName *string `json:"disp_name"`
|
||||
UserDesc *string `json:"user_desc"`
|
||||
CreationDate time.Time `json:"creation_date"`
|
||||
LastLogin pgtype.Timestamptz `json:"last_login"`
|
||||
}
|
||||
|
||||
type Usertitle struct {
|
||||
UsertitleID int32 `json:"usertitle_id"`
|
||||
UserID int32 `json:"user_id"`
|
||||
TitleID int32 `json:"title_id"`
|
||||
Status UsertitleStatusT `json:"status"`
|
||||
Rate *int32 `json:"rate"`
|
||||
ReviewID *int32 `json:"review_id"`
|
||||
UserID int64 `json:"user_id"`
|
||||
TitleID int64 `json:"title_id"`
|
||||
Status UsertitleStatusT `json:"status"`
|
||||
Rate *int32 `json:"rate"`
|
||||
ReviewText *string `json:"review_text"`
|
||||
ReviewDate pgtype.Timestamptz `json:"review_date"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,17 +7,67 @@ package sqlc
|
|||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
const getImageByID = `-- name: GetImageByID :one
|
||||
SELECT image_id, storage_type, image_path
|
||||
FROM images
|
||||
WHERE image_id = $1
|
||||
const createImage = `-- name: CreateImage :one
|
||||
INSERT INTO images (storage_type, image_path)
|
||||
VALUES ($1, $2)
|
||||
RETURNING id, storage_type, image_path
|
||||
`
|
||||
|
||||
func (q *Queries) GetImageByID(ctx context.Context, imageID int32) (Image, error) {
|
||||
row := q.db.QueryRow(ctx, getImageByID, imageID)
|
||||
type CreateImageParams struct {
|
||||
StorageType StorageTypeT `json:"storage_type"`
|
||||
ImagePath string `json:"image_path"`
|
||||
}
|
||||
|
||||
func (q *Queries) CreateImage(ctx context.Context, arg CreateImageParams) (Image, error) {
|
||||
row := q.db.QueryRow(ctx, createImage, arg.StorageType, arg.ImagePath)
|
||||
var i Image
|
||||
err := row.Scan(&i.ImageID, &i.StorageType, &i.ImagePath)
|
||||
err := row.Scan(&i.ID, &i.StorageType, &i.ImagePath)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getImageByID = `-- name: GetImageByID :one
|
||||
SELECT id, storage_type, image_path
|
||||
FROM images
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetImageByID(ctx context.Context, id int64) (Image, error) {
|
||||
row := q.db.QueryRow(ctx, getImageByID, id)
|
||||
var i Image
|
||||
err := row.Scan(&i.ID, &i.StorageType, &i.ImagePath)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getUserByID = `-- name: GetUserByID :one
|
||||
SELECT id, avatar_id, mail, nickname, disp_name, user_desc, creation_date
|
||||
FROM users
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
type GetUserByIDRow struct {
|
||||
ID int64 `json:"id"`
|
||||
AvatarID *int64 `json:"avatar_id"`
|
||||
Mail *string `json:"mail"`
|
||||
Nickname string `json:"nickname"`
|
||||
DispName *string `json:"disp_name"`
|
||||
UserDesc *string `json:"user_desc"`
|
||||
CreationDate time.Time `json:"creation_date"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetUserByID(ctx context.Context, id int64) (GetUserByIDRow, error) {
|
||||
row := q.db.QueryRow(ctx, getUserByID, id)
|
||||
var i GetUserByIDRow
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.AvatarID,
|
||||
&i.Mail,
|
||||
&i.Nickname,
|
||||
&i.DispName,
|
||||
&i.UserDesc,
|
||||
&i.CreationDate,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,4 +11,17 @@ sql:
|
|||
sql_package: "pgx/v5"
|
||||
sql_driver: "github.com/jackc/pgx/v5"
|
||||
emit_json_tags: true
|
||||
emit_pointers_for_null_types: true
|
||||
emit_pointers_for_null_types: true
|
||||
overrides:
|
||||
- db_type: "uuid"
|
||||
nullable: false
|
||||
go_type:
|
||||
import: "github.com/gofrs/uuid"
|
||||
package: "gofrsuuid"
|
||||
type: UUID
|
||||
pointer: true
|
||||
- db_type: "timestamptz"
|
||||
nullable: false
|
||||
go_type:
|
||||
import: "time"
|
||||
type: "Time"
|
||||
Loading…
Add table
Add a link
Reference in a new issue