Merge branch 'dev' into dev-ars
This commit is contained in:
commit
1308e265a6
30 changed files with 1224 additions and 896 deletions
|
|
@ -18,14 +18,10 @@ jobs:
|
||||||
- uses: actions/setup-go@v6
|
- uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
go-version: '^1.25'
|
go-version: '^1.25'
|
||||||
check-latest: false
|
|
||||||
cache-dependency-path: |
|
|
||||||
go.sum
|
|
||||||
|
|
||||||
- name: Build backend
|
- name: Build backend
|
||||||
run: |
|
run: |
|
||||||
cd modules/backend
|
cd modules/backend
|
||||||
go mod tidy
|
|
||||||
go build -o nyanimedb .
|
go build -o nyanimedb .
|
||||||
tar -czvf nyanimedb-backend.tar.gz nyanimedb
|
tar -czvf nyanimedb-backend.tar.gz nyanimedb
|
||||||
|
|
||||||
|
|
@ -38,7 +34,6 @@ jobs:
|
||||||
- name: Build auth
|
- name: Build auth
|
||||||
run: |
|
run: |
|
||||||
cd modules/auth
|
cd modules/auth
|
||||||
go mod tidy
|
|
||||||
go build -o auth .
|
go build -o auth .
|
||||||
tar -czvf nyanimedb-auth.tar.gz auth
|
tar -czvf nyanimedb-auth.tar.gz auth
|
||||||
|
|
||||||
|
|
@ -106,7 +101,7 @@ jobs:
|
||||||
tags: meowgit.nekoea.red/nihonium/nyanimedb-frontend:latest
|
tags: meowgit.nekoea.red/nihonium/nyanimedb-frontend:latest
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
runs-on: self-hosted
|
runs-on: debian-test
|
||||||
needs: build
|
needs: build
|
||||||
env:
|
env:
|
||||||
POSTGRES_USER: ${{ secrets.POSTGRES_USER }}
|
POSTGRES_USER: ${{ secrets.POSTGRES_USER }}
|
||||||
|
|
|
||||||
|
|
@ -225,6 +225,7 @@ paths:
|
||||||
description: Unknown server error
|
description: Unknown server error
|
||||||
'/users/{user_id}/titles':
|
'/users/{user_id}/titles':
|
||||||
get:
|
get:
|
||||||
|
operationId: getUserTitles
|
||||||
summary: Get user titles
|
summary: Get user titles
|
||||||
parameters:
|
parameters:
|
||||||
- $ref: '#/components/parameters/cursor'
|
- $ref: '#/components/parameters/cursor'
|
||||||
|
|
@ -365,6 +366,38 @@ paths:
|
||||||
description: Conflict — title already assigned to user (if applicable)
|
description: Conflict — title already assigned to user (if applicable)
|
||||||
'500':
|
'500':
|
||||||
description: Internal server error
|
description: Internal server error
|
||||||
|
'/users/{user_id}/titles/{title_id}':
|
||||||
|
get:
|
||||||
|
operationId: getUserTitle
|
||||||
|
summary: Get user title
|
||||||
|
parameters:
|
||||||
|
- name: user_id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
- name: title_id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: User titles
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/UserTitleMini'
|
||||||
|
'204':
|
||||||
|
description: No user title found
|
||||||
|
'400':
|
||||||
|
description: Request params are not correct
|
||||||
|
'404':
|
||||||
|
description: User or title not found
|
||||||
|
'500':
|
||||||
|
description: Unknown server error
|
||||||
patch:
|
patch:
|
||||||
operationId: updateUserTitle
|
operationId: updateUserTitle
|
||||||
summary: Update a usertitle
|
summary: Update a usertitle
|
||||||
|
|
@ -372,12 +405,16 @@ paths:
|
||||||
parameters:
|
parameters:
|
||||||
- name: user_id
|
- name: user_id
|
||||||
in: path
|
in: path
|
||||||
description: ID of the user to assign the title to
|
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
type: integer
|
type: integer
|
||||||
format: int64
|
format: int64
|
||||||
example: 123
|
- name: title_id
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
requestBody:
|
requestBody:
|
||||||
required: true
|
required: true
|
||||||
content:
|
content:
|
||||||
|
|
@ -385,16 +422,11 @@ paths:
|
||||||
schema:
|
schema:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
title_id:
|
|
||||||
type: integer
|
|
||||||
format: int64
|
|
||||||
status:
|
status:
|
||||||
$ref: '#/components/schemas/UserTitleStatus'
|
$ref: '#/components/schemas/UserTitleStatus'
|
||||||
rate:
|
rate:
|
||||||
type: integer
|
type: integer
|
||||||
format: int32
|
format: int32
|
||||||
required:
|
|
||||||
- title_id
|
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: Title successfully updated
|
description: Title successfully updated
|
||||||
|
|
@ -419,13 +451,12 @@ paths:
|
||||||
parameters:
|
parameters:
|
||||||
- name: user_id
|
- name: user_id
|
||||||
in: path
|
in: path
|
||||||
description: ID of the user to assign the title to
|
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
type: integer
|
type: integer
|
||||||
format: int64
|
format: int64
|
||||||
- name: title_id
|
- name: title_id
|
||||||
in: query
|
in: path
|
||||||
required: true
|
required: true
|
||||||
schema:
|
schema:
|
||||||
type: integer
|
type: integer
|
||||||
|
|
|
||||||
708
api/api.gen.go
708
api/api.gen.go
|
|
@ -219,13 +219,8 @@ type UpdateUserJSONBody struct {
|
||||||
UserDesc *string `json:"user_desc,omitempty"`
|
UserDesc *string `json:"user_desc,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteUserTitleParams defines parameters for DeleteUserTitle.
|
// GetUserTitlesParams defines parameters for GetUserTitles.
|
||||||
type DeleteUserTitleParams struct {
|
type GetUserTitlesParams struct {
|
||||||
TitleId int64 `form:"title_id" json:"title_id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUsersUserIdTitlesParams defines parameters for GetUsersUserIdTitles.
|
|
||||||
type GetUsersUserIdTitlesParams struct {
|
|
||||||
Cursor *Cursor `form:"cursor,omitempty" json:"cursor,omitempty"`
|
Cursor *Cursor `form:"cursor,omitempty" json:"cursor,omitempty"`
|
||||||
Sort *TitleSort `form:"sort,omitempty" json:"sort,omitempty"`
|
Sort *TitleSort `form:"sort,omitempty" json:"sort,omitempty"`
|
||||||
SortForward *bool `form:"sort_forward,omitempty" json:"sort_forward,omitempty"`
|
SortForward *bool `form:"sort_forward,omitempty" json:"sort_forward,omitempty"`
|
||||||
|
|
@ -242,15 +237,6 @@ type GetUsersUserIdTitlesParams struct {
|
||||||
Fields *string `form:"fields,omitempty" json:"fields,omitempty"`
|
Fields *string `form:"fields,omitempty" json:"fields,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateUserTitleJSONBody defines parameters for UpdateUserTitle.
|
|
||||||
type UpdateUserTitleJSONBody struct {
|
|
||||||
Rate *int32 `json:"rate,omitempty"`
|
|
||||||
|
|
||||||
// Status User's title status
|
|
||||||
Status *UserTitleStatus `json:"status,omitempty"`
|
|
||||||
TitleId int64 `json:"title_id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddUserTitleJSONBody defines parameters for AddUserTitle.
|
// AddUserTitleJSONBody defines parameters for AddUserTitle.
|
||||||
type AddUserTitleJSONBody struct {
|
type AddUserTitleJSONBody struct {
|
||||||
Rate *int32 `json:"rate,omitempty"`
|
Rate *int32 `json:"rate,omitempty"`
|
||||||
|
|
@ -260,15 +246,23 @@ type AddUserTitleJSONBody struct {
|
||||||
TitleId int64 `json:"title_id"`
|
TitleId int64 `json:"title_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UpdateUserTitleJSONBody defines parameters for UpdateUserTitle.
|
||||||
|
type UpdateUserTitleJSONBody struct {
|
||||||
|
Rate *int32 `json:"rate,omitempty"`
|
||||||
|
|
||||||
|
// Status User's title status
|
||||||
|
Status *UserTitleStatus `json:"status,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// UpdateUserJSONRequestBody defines body for UpdateUser for application/json ContentType.
|
// UpdateUserJSONRequestBody defines body for UpdateUser for application/json ContentType.
|
||||||
type UpdateUserJSONRequestBody UpdateUserJSONBody
|
type UpdateUserJSONRequestBody UpdateUserJSONBody
|
||||||
|
|
||||||
// UpdateUserTitleJSONRequestBody defines body for UpdateUserTitle for application/json ContentType.
|
|
||||||
type UpdateUserTitleJSONRequestBody UpdateUserTitleJSONBody
|
|
||||||
|
|
||||||
// AddUserTitleJSONRequestBody defines body for AddUserTitle for application/json ContentType.
|
// AddUserTitleJSONRequestBody defines body for AddUserTitle for application/json ContentType.
|
||||||
type AddUserTitleJSONRequestBody AddUserTitleJSONBody
|
type AddUserTitleJSONRequestBody AddUserTitleJSONBody
|
||||||
|
|
||||||
|
// UpdateUserTitleJSONRequestBody defines body for UpdateUserTitle for application/json ContentType.
|
||||||
|
type UpdateUserTitleJSONRequestBody UpdateUserTitleJSONBody
|
||||||
|
|
||||||
// ServerInterface represents all server handlers.
|
// ServerInterface represents all server handlers.
|
||||||
type ServerInterface interface {
|
type ServerInterface interface {
|
||||||
// Get titles
|
// Get titles
|
||||||
|
|
@ -283,18 +277,21 @@ type ServerInterface interface {
|
||||||
// Partially update a user account
|
// Partially update a user account
|
||||||
// (PATCH /users/{user_id})
|
// (PATCH /users/{user_id})
|
||||||
UpdateUser(c *gin.Context, userId int64)
|
UpdateUser(c *gin.Context, userId int64)
|
||||||
// Delete a usertitle
|
|
||||||
// (DELETE /users/{user_id}/titles)
|
|
||||||
DeleteUserTitle(c *gin.Context, userId int64, params DeleteUserTitleParams)
|
|
||||||
// Get user titles
|
// Get user titles
|
||||||
// (GET /users/{user_id}/titles)
|
// (GET /users/{user_id}/titles)
|
||||||
GetUsersUserIdTitles(c *gin.Context, userId string, params GetUsersUserIdTitlesParams)
|
GetUserTitles(c *gin.Context, userId string, params GetUserTitlesParams)
|
||||||
// Update a usertitle
|
|
||||||
// (PATCH /users/{user_id}/titles)
|
|
||||||
UpdateUserTitle(c *gin.Context, userId int64)
|
|
||||||
// Add a title to a user
|
// Add a title to a user
|
||||||
// (POST /users/{user_id}/titles)
|
// (POST /users/{user_id}/titles)
|
||||||
AddUserTitle(c *gin.Context, userId int64)
|
AddUserTitle(c *gin.Context, userId int64)
|
||||||
|
// Delete a usertitle
|
||||||
|
// (DELETE /users/{user_id}/titles/{title_id})
|
||||||
|
DeleteUserTitle(c *gin.Context, userId int64, titleId int64)
|
||||||
|
// Get user title
|
||||||
|
// (GET /users/{user_id}/titles/{title_id})
|
||||||
|
GetUserTitle(c *gin.Context, userId int64, titleId int64)
|
||||||
|
// Update a usertitle
|
||||||
|
// (PATCH /users/{user_id}/titles/{title_id})
|
||||||
|
UpdateUserTitle(c *gin.Context, userId int64, titleId int64)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServerInterfaceWrapper converts contexts to parameters.
|
// ServerInterfaceWrapper converts contexts to parameters.
|
||||||
|
|
@ -514,50 +511,8 @@ func (siw *ServerInterfaceWrapper) UpdateUser(c *gin.Context) {
|
||||||
siw.Handler.UpdateUser(c, userId)
|
siw.Handler.UpdateUser(c, userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteUserTitle operation middleware
|
// GetUserTitles operation middleware
|
||||||
func (siw *ServerInterfaceWrapper) DeleteUserTitle(c *gin.Context) {
|
func (siw *ServerInterfaceWrapper) GetUserTitles(c *gin.Context) {
|
||||||
|
|
||||||
var err error
|
|
||||||
|
|
||||||
// ------------- Path parameter "user_id" -------------
|
|
||||||
var userId int64
|
|
||||||
|
|
||||||
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 DeleteUserTitleParams
|
|
||||||
|
|
||||||
// ------------- Required query parameter "title_id" -------------
|
|
||||||
|
|
||||||
if paramValue := c.Query("title_id"); paramValue != "" {
|
|
||||||
|
|
||||||
} else {
|
|
||||||
siw.ErrorHandler(c, fmt.Errorf("Query argument title_id is required, but not found"), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = runtime.BindQueryParameter("form", true, true, "title_id", c.Request.URL.Query(), ¶ms.TitleId)
|
|
||||||
if err != nil {
|
|
||||||
siw.ErrorHandler(c, fmt.Errorf("Invalid format for parameter title_id: %w", err), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, middleware := range siw.HandlerMiddlewares {
|
|
||||||
middleware(c)
|
|
||||||
if c.IsAborted() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
siw.Handler.DeleteUserTitle(c, userId, params)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUsersUserIdTitles operation middleware
|
|
||||||
func (siw *ServerInterfaceWrapper) GetUsersUserIdTitles(c *gin.Context) {
|
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
|
@ -571,7 +526,7 @@ func (siw *ServerInterfaceWrapper) GetUsersUserIdTitles(c *gin.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parameter object where we will unmarshal all parameters from the context
|
// Parameter object where we will unmarshal all parameters from the context
|
||||||
var params GetUsersUserIdTitlesParams
|
var params GetUserTitlesParams
|
||||||
|
|
||||||
// ------------- Optional query parameter "cursor" -------------
|
// ------------- Optional query parameter "cursor" -------------
|
||||||
|
|
||||||
|
|
@ -676,31 +631,7 @@ func (siw *ServerInterfaceWrapper) GetUsersUserIdTitles(c *gin.Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
siw.Handler.GetUsersUserIdTitles(c, userId, params)
|
siw.Handler.GetUserTitles(c, userId, params)
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateUserTitle operation middleware
|
|
||||||
func (siw *ServerInterfaceWrapper) UpdateUserTitle(c *gin.Context) {
|
|
||||||
|
|
||||||
var err error
|
|
||||||
|
|
||||||
// ------------- Path parameter "user_id" -------------
|
|
||||||
var userId int64
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, middleware := range siw.HandlerMiddlewares {
|
|
||||||
middleware(c)
|
|
||||||
if c.IsAborted() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
siw.Handler.UpdateUserTitle(c, userId)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddUserTitle operation middleware
|
// AddUserTitle operation middleware
|
||||||
|
|
@ -727,6 +658,105 @@ func (siw *ServerInterfaceWrapper) AddUserTitle(c *gin.Context) {
|
||||||
siw.Handler.AddUserTitle(c, userId)
|
siw.Handler.AddUserTitle(c, userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteUserTitle operation middleware
|
||||||
|
func (siw *ServerInterfaceWrapper) DeleteUserTitle(c *gin.Context) {
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// ------------- Path parameter "user_id" -------------
|
||||||
|
var userId int64
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------- 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
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, middleware := range siw.HandlerMiddlewares {
|
||||||
|
middleware(c)
|
||||||
|
if c.IsAborted() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
siw.Handler.DeleteUserTitle(c, userId, titleId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserTitle operation middleware
|
||||||
|
func (siw *ServerInterfaceWrapper) GetUserTitle(c *gin.Context) {
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// ------------- Path parameter "user_id" -------------
|
||||||
|
var userId int64
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------- 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
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, middleware := range siw.HandlerMiddlewares {
|
||||||
|
middleware(c)
|
||||||
|
if c.IsAborted() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
siw.Handler.GetUserTitle(c, userId, titleId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateUserTitle operation middleware
|
||||||
|
func (siw *ServerInterfaceWrapper) UpdateUserTitle(c *gin.Context) {
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// ------------- Path parameter "user_id" -------------
|
||||||
|
var userId int64
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------- 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
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, middleware := range siw.HandlerMiddlewares {
|
||||||
|
middleware(c)
|
||||||
|
if c.IsAborted() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
siw.Handler.UpdateUserTitle(c, userId, titleId)
|
||||||
|
}
|
||||||
|
|
||||||
// GinServerOptions provides options for the Gin server.
|
// GinServerOptions provides options for the Gin server.
|
||||||
type GinServerOptions struct {
|
type GinServerOptions struct {
|
||||||
BaseURL string
|
BaseURL string
|
||||||
|
|
@ -758,10 +788,11 @@ func RegisterHandlersWithOptions(router gin.IRouter, si ServerInterface, options
|
||||||
router.GET(options.BaseURL+"/titles/:title_id", wrapper.GetTitle)
|
router.GET(options.BaseURL+"/titles/:title_id", wrapper.GetTitle)
|
||||||
router.GET(options.BaseURL+"/users/:user_id", wrapper.GetUsersId)
|
router.GET(options.BaseURL+"/users/:user_id", wrapper.GetUsersId)
|
||||||
router.PATCH(options.BaseURL+"/users/:user_id", wrapper.UpdateUser)
|
router.PATCH(options.BaseURL+"/users/:user_id", wrapper.UpdateUser)
|
||||||
router.DELETE(options.BaseURL+"/users/:user_id/titles", wrapper.DeleteUserTitle)
|
router.GET(options.BaseURL+"/users/:user_id/titles", wrapper.GetUserTitles)
|
||||||
router.GET(options.BaseURL+"/users/:user_id/titles", wrapper.GetUsersUserIdTitles)
|
|
||||||
router.PATCH(options.BaseURL+"/users/:user_id/titles", wrapper.UpdateUserTitle)
|
|
||||||
router.POST(options.BaseURL+"/users/:user_id/titles", wrapper.AddUserTitle)
|
router.POST(options.BaseURL+"/users/:user_id/titles", wrapper.AddUserTitle)
|
||||||
|
router.DELETE(options.BaseURL+"/users/:user_id/titles/:title_id", wrapper.DeleteUserTitle)
|
||||||
|
router.GET(options.BaseURL+"/users/:user_id/titles/:title_id", wrapper.GetUserTitle)
|
||||||
|
router.PATCH(options.BaseURL+"/users/:user_id/titles/:title_id", wrapper.UpdateUserTitle)
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetTitlesRequestObject struct {
|
type GetTitlesRequestObject struct {
|
||||||
|
|
@ -976,162 +1007,55 @@ func (response UpdateUser500Response) VisitUpdateUserResponse(w http.ResponseWri
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type DeleteUserTitleRequestObject struct {
|
type GetUserTitlesRequestObject struct {
|
||||||
UserId int64 `json:"user_id"`
|
|
||||||
Params DeleteUserTitleParams
|
|
||||||
}
|
|
||||||
|
|
||||||
type DeleteUserTitleResponseObject interface {
|
|
||||||
VisitDeleteUserTitleResponse(w http.ResponseWriter) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type DeleteUserTitle200Response struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (response DeleteUserTitle200Response) VisitDeleteUserTitleResponse(w http.ResponseWriter) error {
|
|
||||||
w.WriteHeader(200)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type DeleteUserTitle401Response struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (response DeleteUserTitle401Response) VisitDeleteUserTitleResponse(w http.ResponseWriter) error {
|
|
||||||
w.WriteHeader(401)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type DeleteUserTitle403Response struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (response DeleteUserTitle403Response) VisitDeleteUserTitleResponse(w http.ResponseWriter) error {
|
|
||||||
w.WriteHeader(403)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type DeleteUserTitle404Response struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (response DeleteUserTitle404Response) VisitDeleteUserTitleResponse(w http.ResponseWriter) error {
|
|
||||||
w.WriteHeader(404)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type DeleteUserTitle500Response struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (response DeleteUserTitle500Response) VisitDeleteUserTitleResponse(w http.ResponseWriter) error {
|
|
||||||
w.WriteHeader(500)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type GetUsersUserIdTitlesRequestObject struct {
|
|
||||||
UserId string `json:"user_id"`
|
UserId string `json:"user_id"`
|
||||||
Params GetUsersUserIdTitlesParams
|
Params GetUserTitlesParams
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetUsersUserIdTitlesResponseObject interface {
|
type GetUserTitlesResponseObject interface {
|
||||||
VisitGetUsersUserIdTitlesResponse(w http.ResponseWriter) error
|
VisitGetUserTitlesResponse(w http.ResponseWriter) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetUsersUserIdTitles200JSONResponse struct {
|
type GetUserTitles200JSONResponse struct {
|
||||||
Cursor CursorObj `json:"cursor"`
|
Cursor CursorObj `json:"cursor"`
|
||||||
Data []UserTitle `json:"data"`
|
Data []UserTitle `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (response GetUsersUserIdTitles200JSONResponse) VisitGetUsersUserIdTitlesResponse(w http.ResponseWriter) error {
|
func (response GetUserTitles200JSONResponse) VisitGetUserTitlesResponse(w http.ResponseWriter) error {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
w.WriteHeader(200)
|
w.WriteHeader(200)
|
||||||
|
|
||||||
return json.NewEncoder(w).Encode(response)
|
return json.NewEncoder(w).Encode(response)
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetUsersUserIdTitles204Response struct {
|
type GetUserTitles204Response struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (response GetUsersUserIdTitles204Response) VisitGetUsersUserIdTitlesResponse(w http.ResponseWriter) error {
|
func (response GetUserTitles204Response) VisitGetUserTitlesResponse(w http.ResponseWriter) error {
|
||||||
w.WriteHeader(204)
|
w.WriteHeader(204)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetUsersUserIdTitles400Response struct {
|
type GetUserTitles400Response struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (response GetUsersUserIdTitles400Response) VisitGetUsersUserIdTitlesResponse(w http.ResponseWriter) error {
|
func (response GetUserTitles400Response) VisitGetUserTitlesResponse(w http.ResponseWriter) error {
|
||||||
w.WriteHeader(400)
|
w.WriteHeader(400)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetUsersUserIdTitles404Response struct {
|
type GetUserTitles404Response struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (response GetUsersUserIdTitles404Response) VisitGetUsersUserIdTitlesResponse(w http.ResponseWriter) error {
|
func (response GetUserTitles404Response) VisitGetUserTitlesResponse(w http.ResponseWriter) error {
|
||||||
w.WriteHeader(404)
|
w.WriteHeader(404)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetUsersUserIdTitles500Response struct {
|
type GetUserTitles500Response struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (response GetUsersUserIdTitles500Response) VisitGetUsersUserIdTitlesResponse(w http.ResponseWriter) error {
|
func (response GetUserTitles500Response) VisitGetUserTitlesResponse(w http.ResponseWriter) error {
|
||||||
w.WriteHeader(500)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type UpdateUserTitleRequestObject struct {
|
|
||||||
UserId int64 `json:"user_id"`
|
|
||||||
Body *UpdateUserTitleJSONRequestBody
|
|
||||||
}
|
|
||||||
|
|
||||||
type UpdateUserTitleResponseObject interface {
|
|
||||||
VisitUpdateUserTitleResponse(w http.ResponseWriter) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type UpdateUserTitle200JSONResponse UserTitleMini
|
|
||||||
|
|
||||||
func (response UpdateUserTitle200JSONResponse) VisitUpdateUserTitleResponse(w http.ResponseWriter) error {
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
w.WriteHeader(200)
|
|
||||||
|
|
||||||
return json.NewEncoder(w).Encode(response)
|
|
||||||
}
|
|
||||||
|
|
||||||
type UpdateUserTitle400Response struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (response UpdateUserTitle400Response) VisitUpdateUserTitleResponse(w http.ResponseWriter) error {
|
|
||||||
w.WriteHeader(400)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type UpdateUserTitle401Response struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (response UpdateUserTitle401Response) VisitUpdateUserTitleResponse(w http.ResponseWriter) error {
|
|
||||||
w.WriteHeader(401)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type UpdateUserTitle403Response struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (response UpdateUserTitle403Response) VisitUpdateUserTitleResponse(w http.ResponseWriter) error {
|
|
||||||
w.WriteHeader(403)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type UpdateUserTitle404Response struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (response UpdateUserTitle404Response) VisitUpdateUserTitleResponse(w http.ResponseWriter) error {
|
|
||||||
w.WriteHeader(404)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type UpdateUserTitle500Response struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (response UpdateUserTitle500Response) VisitUpdateUserTitleResponse(w http.ResponseWriter) error {
|
|
||||||
w.WriteHeader(500)
|
w.WriteHeader(500)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -1202,6 +1126,164 @@ func (response AddUserTitle500Response) VisitAddUserTitleResponse(w http.Respons
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DeleteUserTitleRequestObject struct {
|
||||||
|
UserId int64 `json:"user_id"`
|
||||||
|
TitleId int64 `json:"title_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeleteUserTitleResponseObject interface {
|
||||||
|
VisitDeleteUserTitleResponse(w http.ResponseWriter) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeleteUserTitle200Response struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (response DeleteUserTitle200Response) VisitDeleteUserTitleResponse(w http.ResponseWriter) error {
|
||||||
|
w.WriteHeader(200)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeleteUserTitle401Response struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (response DeleteUserTitle401Response) VisitDeleteUserTitleResponse(w http.ResponseWriter) error {
|
||||||
|
w.WriteHeader(401)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeleteUserTitle403Response struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (response DeleteUserTitle403Response) VisitDeleteUserTitleResponse(w http.ResponseWriter) error {
|
||||||
|
w.WriteHeader(403)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeleteUserTitle404Response struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (response DeleteUserTitle404Response) VisitDeleteUserTitleResponse(w http.ResponseWriter) error {
|
||||||
|
w.WriteHeader(404)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeleteUserTitle500Response struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (response DeleteUserTitle500Response) VisitDeleteUserTitleResponse(w http.ResponseWriter) error {
|
||||||
|
w.WriteHeader(500)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetUserTitleRequestObject struct {
|
||||||
|
UserId int64 `json:"user_id"`
|
||||||
|
TitleId int64 `json:"title_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetUserTitleResponseObject interface {
|
||||||
|
VisitGetUserTitleResponse(w http.ResponseWriter) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetUserTitle200JSONResponse UserTitleMini
|
||||||
|
|
||||||
|
func (response GetUserTitle200JSONResponse) VisitGetUserTitleResponse(w http.ResponseWriter) error {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(200)
|
||||||
|
|
||||||
|
return json.NewEncoder(w).Encode(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetUserTitle204Response struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (response GetUserTitle204Response) VisitGetUserTitleResponse(w http.ResponseWriter) error {
|
||||||
|
w.WriteHeader(204)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetUserTitle400Response struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (response GetUserTitle400Response) VisitGetUserTitleResponse(w http.ResponseWriter) error {
|
||||||
|
w.WriteHeader(400)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetUserTitle404Response struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (response GetUserTitle404Response) VisitGetUserTitleResponse(w http.ResponseWriter) error {
|
||||||
|
w.WriteHeader(404)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetUserTitle500Response struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (response GetUserTitle500Response) VisitGetUserTitleResponse(w http.ResponseWriter) error {
|
||||||
|
w.WriteHeader(500)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateUserTitleRequestObject struct {
|
||||||
|
UserId int64 `json:"user_id"`
|
||||||
|
TitleId int64 `json:"title_id"`
|
||||||
|
Body *UpdateUserTitleJSONRequestBody
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateUserTitleResponseObject interface {
|
||||||
|
VisitUpdateUserTitleResponse(w http.ResponseWriter) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateUserTitle200JSONResponse UserTitleMini
|
||||||
|
|
||||||
|
func (response UpdateUserTitle200JSONResponse) VisitUpdateUserTitleResponse(w http.ResponseWriter) error {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(200)
|
||||||
|
|
||||||
|
return json.NewEncoder(w).Encode(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateUserTitle400Response struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (response UpdateUserTitle400Response) VisitUpdateUserTitleResponse(w http.ResponseWriter) error {
|
||||||
|
w.WriteHeader(400)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateUserTitle401Response struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (response UpdateUserTitle401Response) VisitUpdateUserTitleResponse(w http.ResponseWriter) error {
|
||||||
|
w.WriteHeader(401)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateUserTitle403Response struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (response UpdateUserTitle403Response) VisitUpdateUserTitleResponse(w http.ResponseWriter) error {
|
||||||
|
w.WriteHeader(403)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateUserTitle404Response struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (response UpdateUserTitle404Response) VisitUpdateUserTitleResponse(w http.ResponseWriter) error {
|
||||||
|
w.WriteHeader(404)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateUserTitle500Response struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (response UpdateUserTitle500Response) VisitUpdateUserTitleResponse(w http.ResponseWriter) error {
|
||||||
|
w.WriteHeader(500)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// StrictServerInterface represents all server handlers.
|
// StrictServerInterface represents all server handlers.
|
||||||
type StrictServerInterface interface {
|
type StrictServerInterface interface {
|
||||||
// Get titles
|
// Get titles
|
||||||
|
|
@ -1216,18 +1298,21 @@ type StrictServerInterface interface {
|
||||||
// Partially update a user account
|
// Partially update a user account
|
||||||
// (PATCH /users/{user_id})
|
// (PATCH /users/{user_id})
|
||||||
UpdateUser(ctx context.Context, request UpdateUserRequestObject) (UpdateUserResponseObject, error)
|
UpdateUser(ctx context.Context, request UpdateUserRequestObject) (UpdateUserResponseObject, error)
|
||||||
// Delete a usertitle
|
|
||||||
// (DELETE /users/{user_id}/titles)
|
|
||||||
DeleteUserTitle(ctx context.Context, request DeleteUserTitleRequestObject) (DeleteUserTitleResponseObject, error)
|
|
||||||
// Get user titles
|
// Get user titles
|
||||||
// (GET /users/{user_id}/titles)
|
// (GET /users/{user_id}/titles)
|
||||||
GetUsersUserIdTitles(ctx context.Context, request GetUsersUserIdTitlesRequestObject) (GetUsersUserIdTitlesResponseObject, error)
|
GetUserTitles(ctx context.Context, request GetUserTitlesRequestObject) (GetUserTitlesResponseObject, error)
|
||||||
// Update a usertitle
|
|
||||||
// (PATCH /users/{user_id}/titles)
|
|
||||||
UpdateUserTitle(ctx context.Context, request UpdateUserTitleRequestObject) (UpdateUserTitleResponseObject, error)
|
|
||||||
// Add a title to a user
|
// Add a title to a user
|
||||||
// (POST /users/{user_id}/titles)
|
// (POST /users/{user_id}/titles)
|
||||||
AddUserTitle(ctx context.Context, request AddUserTitleRequestObject) (AddUserTitleResponseObject, error)
|
AddUserTitle(ctx context.Context, request AddUserTitleRequestObject) (AddUserTitleResponseObject, error)
|
||||||
|
// Delete a usertitle
|
||||||
|
// (DELETE /users/{user_id}/titles/{title_id})
|
||||||
|
DeleteUserTitle(ctx context.Context, request DeleteUserTitleRequestObject) (DeleteUserTitleResponseObject, error)
|
||||||
|
// Get user title
|
||||||
|
// (GET /users/{user_id}/titles/{title_id})
|
||||||
|
GetUserTitle(ctx context.Context, request GetUserTitleRequestObject) (GetUserTitleResponseObject, error)
|
||||||
|
// Update a usertitle
|
||||||
|
// (PATCH /users/{user_id}/titles/{title_id})
|
||||||
|
UpdateUserTitle(ctx context.Context, request UpdateUserTitleRequestObject) (UpdateUserTitleResponseObject, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type StrictHandlerFunc = strictgin.StrictGinHandlerFunc
|
type StrictHandlerFunc = strictgin.StrictGinHandlerFunc
|
||||||
|
|
@ -1360,18 +1445,18 @@ func (sh *strictHandler) UpdateUser(ctx *gin.Context, userId int64) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteUserTitle operation middleware
|
// GetUserTitles operation middleware
|
||||||
func (sh *strictHandler) DeleteUserTitle(ctx *gin.Context, userId int64, params DeleteUserTitleParams) {
|
func (sh *strictHandler) GetUserTitles(ctx *gin.Context, userId string, params GetUserTitlesParams) {
|
||||||
var request DeleteUserTitleRequestObject
|
var request GetUserTitlesRequestObject
|
||||||
|
|
||||||
request.UserId = userId
|
request.UserId = userId
|
||||||
request.Params = params
|
request.Params = params
|
||||||
|
|
||||||
handler := func(ctx *gin.Context, request interface{}) (interface{}, error) {
|
handler := func(ctx *gin.Context, request interface{}) (interface{}, error) {
|
||||||
return sh.ssi.DeleteUserTitle(ctx, request.(DeleteUserTitleRequestObject))
|
return sh.ssi.GetUserTitles(ctx, request.(GetUserTitlesRequestObject))
|
||||||
}
|
}
|
||||||
for _, middleware := range sh.middlewares {
|
for _, middleware := range sh.middlewares {
|
||||||
handler = middleware(handler, "DeleteUserTitle")
|
handler = middleware(handler, "GetUserTitles")
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := handler(ctx, request)
|
response, err := handler(ctx, request)
|
||||||
|
|
@ -1379,71 +1464,8 @@ func (sh *strictHandler) DeleteUserTitle(ctx *gin.Context, userId int64, params
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.Error(err)
|
ctx.Error(err)
|
||||||
ctx.Status(http.StatusInternalServerError)
|
ctx.Status(http.StatusInternalServerError)
|
||||||
} else if validResponse, ok := response.(DeleteUserTitleResponseObject); ok {
|
} else if validResponse, ok := response.(GetUserTitlesResponseObject); ok {
|
||||||
if err := validResponse.VisitDeleteUserTitleResponse(ctx.Writer); err != nil {
|
if err := validResponse.VisitGetUserTitlesResponse(ctx.Writer); err != nil {
|
||||||
ctx.Error(err)
|
|
||||||
}
|
|
||||||
} else if response != nil {
|
|
||||||
ctx.Error(fmt.Errorf("unexpected response type: %T", response))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUsersUserIdTitles operation middleware
|
|
||||||
func (sh *strictHandler) GetUsersUserIdTitles(ctx *gin.Context, userId string, params GetUsersUserIdTitlesParams) {
|
|
||||||
var request GetUsersUserIdTitlesRequestObject
|
|
||||||
|
|
||||||
request.UserId = userId
|
|
||||||
request.Params = params
|
|
||||||
|
|
||||||
handler := func(ctx *gin.Context, request interface{}) (interface{}, error) {
|
|
||||||
return sh.ssi.GetUsersUserIdTitles(ctx, request.(GetUsersUserIdTitlesRequestObject))
|
|
||||||
}
|
|
||||||
for _, middleware := range sh.middlewares {
|
|
||||||
handler = middleware(handler, "GetUsersUserIdTitles")
|
|
||||||
}
|
|
||||||
|
|
||||||
response, err := handler(ctx, request)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
ctx.Error(err)
|
|
||||||
ctx.Status(http.StatusInternalServerError)
|
|
||||||
} else if validResponse, ok := response.(GetUsersUserIdTitlesResponseObject); ok {
|
|
||||||
if err := validResponse.VisitGetUsersUserIdTitlesResponse(ctx.Writer); err != nil {
|
|
||||||
ctx.Error(err)
|
|
||||||
}
|
|
||||||
} else if response != nil {
|
|
||||||
ctx.Error(fmt.Errorf("unexpected response type: %T", response))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateUserTitle operation middleware
|
|
||||||
func (sh *strictHandler) UpdateUserTitle(ctx *gin.Context, userId int64) {
|
|
||||||
var request UpdateUserTitleRequestObject
|
|
||||||
|
|
||||||
request.UserId = userId
|
|
||||||
|
|
||||||
var body UpdateUserTitleJSONRequestBody
|
|
||||||
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.UpdateUserTitle(ctx, request.(UpdateUserTitleRequestObject))
|
|
||||||
}
|
|
||||||
for _, middleware := range sh.middlewares {
|
|
||||||
handler = middleware(handler, "UpdateUserTitle")
|
|
||||||
}
|
|
||||||
|
|
||||||
response, err := handler(ctx, request)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
ctx.Error(err)
|
|
||||||
ctx.Status(http.StatusInternalServerError)
|
|
||||||
} else if validResponse, ok := response.(UpdateUserTitleResponseObject); ok {
|
|
||||||
if err := validResponse.VisitUpdateUserTitleResponse(ctx.Writer); err != nil {
|
|
||||||
ctx.Error(err)
|
ctx.Error(err)
|
||||||
}
|
}
|
||||||
} else if response != nil {
|
} else if response != nil {
|
||||||
|
|
@ -1485,3 +1507,95 @@ func (sh *strictHandler) AddUserTitle(ctx *gin.Context, userId int64) {
|
||||||
ctx.Error(fmt.Errorf("unexpected response type: %T", response))
|
ctx.Error(fmt.Errorf("unexpected response type: %T", response))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteUserTitle operation middleware
|
||||||
|
func (sh *strictHandler) DeleteUserTitle(ctx *gin.Context, userId int64, titleId int64) {
|
||||||
|
var request DeleteUserTitleRequestObject
|
||||||
|
|
||||||
|
request.UserId = userId
|
||||||
|
request.TitleId = titleId
|
||||||
|
|
||||||
|
handler := func(ctx *gin.Context, request interface{}) (interface{}, error) {
|
||||||
|
return sh.ssi.DeleteUserTitle(ctx, request.(DeleteUserTitleRequestObject))
|
||||||
|
}
|
||||||
|
for _, middleware := range sh.middlewares {
|
||||||
|
handler = middleware(handler, "DeleteUserTitle")
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := handler(ctx, request)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(err)
|
||||||
|
ctx.Status(http.StatusInternalServerError)
|
||||||
|
} else if validResponse, ok := response.(DeleteUserTitleResponseObject); ok {
|
||||||
|
if err := validResponse.VisitDeleteUserTitleResponse(ctx.Writer); err != nil {
|
||||||
|
ctx.Error(err)
|
||||||
|
}
|
||||||
|
} else if response != nil {
|
||||||
|
ctx.Error(fmt.Errorf("unexpected response type: %T", response))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUserTitle operation middleware
|
||||||
|
func (sh *strictHandler) GetUserTitle(ctx *gin.Context, userId int64, titleId int64) {
|
||||||
|
var request GetUserTitleRequestObject
|
||||||
|
|
||||||
|
request.UserId = userId
|
||||||
|
request.TitleId = titleId
|
||||||
|
|
||||||
|
handler := func(ctx *gin.Context, request interface{}) (interface{}, error) {
|
||||||
|
return sh.ssi.GetUserTitle(ctx, request.(GetUserTitleRequestObject))
|
||||||
|
}
|
||||||
|
for _, middleware := range sh.middlewares {
|
||||||
|
handler = middleware(handler, "GetUserTitle")
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := handler(ctx, request)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(err)
|
||||||
|
ctx.Status(http.StatusInternalServerError)
|
||||||
|
} else if validResponse, ok := response.(GetUserTitleResponseObject); ok {
|
||||||
|
if err := validResponse.VisitGetUserTitleResponse(ctx.Writer); err != nil {
|
||||||
|
ctx.Error(err)
|
||||||
|
}
|
||||||
|
} else if response != nil {
|
||||||
|
ctx.Error(fmt.Errorf("unexpected response type: %T", response))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateUserTitle operation middleware
|
||||||
|
func (sh *strictHandler) UpdateUserTitle(ctx *gin.Context, userId int64, titleId int64) {
|
||||||
|
var request UpdateUserTitleRequestObject
|
||||||
|
|
||||||
|
request.UserId = userId
|
||||||
|
request.TitleId = titleId
|
||||||
|
|
||||||
|
var body UpdateUserTitleJSONRequestBody
|
||||||
|
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.UpdateUserTitle(ctx, request.(UpdateUserTitleRequestObject))
|
||||||
|
}
|
||||||
|
for _, middleware := range sh.middlewares {
|
||||||
|
handler = middleware(handler, "UpdateUserTitle")
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := handler(ctx, request)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
ctx.Error(err)
|
||||||
|
ctx.Status(http.StatusInternalServerError)
|
||||||
|
} else if validResponse, ok := response.(UpdateUserTitleResponseObject); ok {
|
||||||
|
if err := validResponse.VisitUpdateUserTitleResponse(ctx.Writer); err != nil {
|
||||||
|
ctx.Error(err)
|
||||||
|
}
|
||||||
|
} else if response != nil {
|
||||||
|
ctx.Error(fmt.Errorf("unexpected response type: %T", response))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@ paths:
|
||||||
$ref: "./paths/users-id.yaml"
|
$ref: "./paths/users-id.yaml"
|
||||||
/users/{user_id}/titles:
|
/users/{user_id}/titles:
|
||||||
$ref: "./paths/users-id-titles.yaml"
|
$ref: "./paths/users-id-titles.yaml"
|
||||||
|
/users/{user_id}/titles/{title_id}:
|
||||||
|
$ref: "./paths/users-id-titles-id.yaml"
|
||||||
|
|
||||||
components:
|
components:
|
||||||
parameters:
|
parameters:
|
||||||
|
|
|
||||||
107
api/paths/users-id-titles-id.yaml
Normal file
107
api/paths/users-id-titles-id.yaml
Normal file
|
|
@ -0,0 +1,107 @@
|
||||||
|
get:
|
||||||
|
summary: Get user title
|
||||||
|
operationId: getUserTitle
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: user_id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
- in: path
|
||||||
|
name: title_id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: User titles
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '../schemas/UserTitleMini.yaml'
|
||||||
|
'204':
|
||||||
|
description: No user title found
|
||||||
|
'400':
|
||||||
|
description: Request params are not correct
|
||||||
|
'404':
|
||||||
|
description: User or title not found
|
||||||
|
'500':
|
||||||
|
description: Unknown server error
|
||||||
|
|
||||||
|
patch:
|
||||||
|
summary: Update a usertitle
|
||||||
|
description: User updating title list of watched
|
||||||
|
operationId: updateUserTitle
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: user_id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
- in: path
|
||||||
|
name: title_id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
requestBody:
|
||||||
|
required: true
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
$ref: '../schemas/enums/UserTitleStatus.yaml'
|
||||||
|
rate:
|
||||||
|
type: integer
|
||||||
|
format: int32
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Title successfully updated
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '../schemas/UserTitleMini.yaml'
|
||||||
|
'400':
|
||||||
|
description: Invalid request body (missing fields, invalid types, etc.)
|
||||||
|
'401':
|
||||||
|
description: Unauthorized — missing or invalid auth token
|
||||||
|
'403':
|
||||||
|
description: Forbidden — user not allowed to update title
|
||||||
|
'404':
|
||||||
|
description: User or Title not found
|
||||||
|
'500':
|
||||||
|
description: Internal server error
|
||||||
|
|
||||||
|
delete:
|
||||||
|
summary: Delete a usertitle
|
||||||
|
description: User deleting title from list of watched
|
||||||
|
operationId: deleteUserTitle
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: user_id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
- in: path
|
||||||
|
name: title_id
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Title successfully deleted
|
||||||
|
'401':
|
||||||
|
description: Unauthorized — missing or invalid auth token
|
||||||
|
'403':
|
||||||
|
description: Forbidden — user not allowed to delete title
|
||||||
|
'404':
|
||||||
|
description: User or Title not found
|
||||||
|
'500':
|
||||||
|
description: Internal server error
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
get:
|
get:
|
||||||
summary: Get user titles
|
summary: Get user titles
|
||||||
|
operationId: getUserTitles
|
||||||
parameters:
|
parameters:
|
||||||
- $ref: '../parameters/cursor.yaml'
|
- $ref: '../parameters/cursor.yaml'
|
||||||
- $ref: "../parameters/title_sort.yaml"
|
- $ref: "../parameters/title_sort.yaml"
|
||||||
|
|
@ -138,88 +139,5 @@ post:
|
||||||
description: User or Title not found
|
description: User or Title not found
|
||||||
'409':
|
'409':
|
||||||
description: Conflict — title already assigned to user (if applicable)
|
description: Conflict — title already assigned to user (if applicable)
|
||||||
'500':
|
|
||||||
description: Internal server error
|
|
||||||
|
|
||||||
patch:
|
|
||||||
summary: Update a usertitle
|
|
||||||
description: User updating title list of watched
|
|
||||||
operationId: updateUserTitle
|
|
||||||
parameters:
|
|
||||||
- name: user_id
|
|
||||||
in: path
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
type: integer
|
|
||||||
format: int64
|
|
||||||
description: ID of the user to assign the title to
|
|
||||||
example: 123
|
|
||||||
requestBody:
|
|
||||||
required: true
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
type: object
|
|
||||||
required:
|
|
||||||
- title_id
|
|
||||||
properties:
|
|
||||||
title_id:
|
|
||||||
type: integer
|
|
||||||
format: int64
|
|
||||||
status:
|
|
||||||
$ref: '../schemas/enums/UserTitleStatus.yaml'
|
|
||||||
rate:
|
|
||||||
type: integer
|
|
||||||
format: int32
|
|
||||||
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: Title successfully updated
|
|
||||||
content:
|
|
||||||
application/json:
|
|
||||||
schema:
|
|
||||||
$ref: '../schemas/UserTitleMini.yaml'
|
|
||||||
'400':
|
|
||||||
description: Invalid request body (missing fields, invalid types, etc.)
|
|
||||||
'401':
|
|
||||||
description: Unauthorized — missing or invalid auth token
|
|
||||||
'403':
|
|
||||||
description: Forbidden — user not allowed to update title
|
|
||||||
'404':
|
|
||||||
description: User or Title not found
|
|
||||||
'500':
|
|
||||||
description: Internal server error
|
|
||||||
|
|
||||||
delete:
|
|
||||||
summary: Delete a usertitle
|
|
||||||
description: User deleting title from list of watched
|
|
||||||
operationId: deleteUserTitle
|
|
||||||
parameters:
|
|
||||||
- name: user_id
|
|
||||||
in: path
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
type: integer
|
|
||||||
format: int64
|
|
||||||
description: ID of the user to assign the title to
|
|
||||||
- in: query
|
|
||||||
name: title_id
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
type: integer
|
|
||||||
format: int64
|
|
||||||
|
|
||||||
|
|
||||||
responses:
|
|
||||||
'200':
|
|
||||||
description: Title successfully deleted
|
|
||||||
# '400':
|
|
||||||
# description: Invalid request body (missing fields, invalid types, etc.)
|
|
||||||
'401':
|
|
||||||
description: Unauthorized — missing or invalid auth token
|
|
||||||
'403':
|
|
||||||
description: Forbidden — user not allowed to delete title
|
|
||||||
'404':
|
|
||||||
description: User or Title not found
|
|
||||||
'500':
|
'500':
|
||||||
description: Internal server error
|
description: Internal server error
|
||||||
|
|
@ -116,9 +116,8 @@ type PostAuthSignInResponseObject interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
type PostAuthSignIn200JSONResponse struct {
|
type PostAuthSignIn200JSONResponse struct {
|
||||||
Error *string `json:"error"`
|
UserId int64 `json:"user_id"`
|
||||||
UserId *string `json:"user_id"`
|
UserName string `json:"user_name"`
|
||||||
UserName *string `json:"user_name"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (response PostAuthSignIn200JSONResponse) VisitPostAuthSignInResponse(w http.ResponseWriter) error {
|
func (response PostAuthSignIn200JSONResponse) VisitPostAuthSignInResponse(w http.ResponseWriter) error {
|
||||||
|
|
@ -148,9 +147,7 @@ type PostAuthSignUpResponseObject interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
type PostAuthSignUp200JSONResponse struct {
|
type PostAuthSignUp200JSONResponse struct {
|
||||||
Error *string `json:"error"`
|
UserId int64 `json:"user_id"`
|
||||||
Success *bool `json:"success,omitempty"`
|
|
||||||
UserId *string `json:"user_id"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (response PostAuthSignUp200JSONResponse) VisitPostAuthSignUpResponse(w http.ResponseWriter) error {
|
func (response PostAuthSignUp200JSONResponse) VisitPostAuthSignUpResponse(w http.ResponseWriter) error {
|
||||||
|
|
|
||||||
|
|
@ -30,16 +30,13 @@ paths:
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
|
required:
|
||||||
|
- user_id
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
success:
|
|
||||||
type: boolean
|
|
||||||
error:
|
|
||||||
type: string
|
|
||||||
nullable: true
|
|
||||||
user_id:
|
user_id:
|
||||||
type: string
|
type: integer
|
||||||
nullable: true
|
format: int64
|
||||||
|
|
||||||
/auth/sign-in:
|
/auth/sign-in:
|
||||||
post:
|
post:
|
||||||
|
|
@ -65,17 +62,16 @@ paths:
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
|
required:
|
||||||
|
- user_id
|
||||||
|
- user_name
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
error:
|
|
||||||
type: string
|
|
||||||
nullable: true
|
|
||||||
user_id:
|
user_id:
|
||||||
type: string
|
type: integer
|
||||||
nullable: true
|
format: int64
|
||||||
user_name:
|
user_name:
|
||||||
type: string
|
type: string
|
||||||
nullable: true
|
|
||||||
"401":
|
"401":
|
||||||
description: Access denied due to invalid credentials
|
description: Access denied due to invalid credentials
|
||||||
content:
|
content:
|
||||||
|
|
|
||||||
|
|
@ -11,20 +11,34 @@ services:
|
||||||
- "${POSTGRES_PORT}:5432"
|
- "${POSTGRES_PORT}:5432"
|
||||||
volumes:
|
volumes:
|
||||||
- postgres_data:/var/lib/postgresql
|
- postgres_data:/var/lib/postgresql
|
||||||
|
networks:
|
||||||
|
- nyanimedb-network
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
start_period: 10s
|
||||||
|
|
||||||
# pgadmin:
|
rabbitmq:
|
||||||
# image: dpage/pgadmin4:${PGADMIN_VERSION}
|
image: rabbitmq:3-management
|
||||||
# container_name: pgadmin
|
container_name: rabbitmq
|
||||||
# restart: always
|
ports:
|
||||||
# environment:
|
- "5672:5672"
|
||||||
# PGADMIN_DEFAULT_EMAIL: ${PGADMIN_EMAIL}
|
- "15672:15672"
|
||||||
# PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_PASSWORD}
|
environment:
|
||||||
# ports:
|
RABBITMQ_DEFAULT_USER: ${RABBITMQ_USER}
|
||||||
# - "${PGADMIN_PORT}:80"
|
RABBITMQ_DEFAULT_PASS: ${RABBITMQ_PASSWORD}
|
||||||
# depends_on:
|
volumes:
|
||||||
# - postgres
|
- rabbitmq_data:/var/lib/rabbitmq
|
||||||
# volumes:
|
networks:
|
||||||
# - pgadmin_data:/var/lib/pgadmin
|
- nyanimedb-network
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "rabbitmqctl", "status"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
start_period: 10s
|
||||||
|
|
||||||
nyanimedb-backend:
|
nyanimedb-backend:
|
||||||
image: meowgit.nekoea.red/nihonium/nyanimedb-backend:latest
|
image: meowgit.nekoea.red/nihonium/nyanimedb-backend:latest
|
||||||
|
|
@ -37,6 +51,9 @@ services:
|
||||||
- "8080:8080"
|
- "8080:8080"
|
||||||
depends_on:
|
depends_on:
|
||||||
- postgres
|
- postgres
|
||||||
|
- rabbitmq
|
||||||
|
networks:
|
||||||
|
- nyanimedb-network
|
||||||
|
|
||||||
nyanimedb-auth:
|
nyanimedb-auth:
|
||||||
image: meowgit.nekoea.red/nihonium/nyanimedb-auth:latest
|
image: meowgit.nekoea.red/nihonium/nyanimedb-auth:latest
|
||||||
|
|
@ -49,6 +66,8 @@ services:
|
||||||
- "8082:8082"
|
- "8082:8082"
|
||||||
depends_on:
|
depends_on:
|
||||||
- postgres
|
- postgres
|
||||||
|
networks:
|
||||||
|
- nyanimedb-network
|
||||||
|
|
||||||
nyanimedb-frontend:
|
nyanimedb-frontend:
|
||||||
image: meowgit.nekoea.red/nihonium/nyanimedb-frontend:latest
|
image: meowgit.nekoea.red/nihonium/nyanimedb-frontend:latest
|
||||||
|
|
@ -58,7 +77,12 @@ services:
|
||||||
- "8081:80"
|
- "8081:80"
|
||||||
depends_on:
|
depends_on:
|
||||||
- nyanimedb-backend
|
- nyanimedb-backend
|
||||||
|
networks:
|
||||||
|
- nyanimedb-network
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
postgres_data:
|
postgres_data:
|
||||||
pgadmin_data:
|
rabbitmq_data:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
nyanimedb-network:
|
||||||
|
|
|
||||||
2
go.mod
2
go.mod
|
|
@ -3,6 +3,7 @@ module nyanimedb
|
||||||
go 1.25.0
|
go 1.25.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/alexedwards/argon2id v1.0.0
|
||||||
github.com/gin-contrib/cors v1.7.6
|
github.com/gin-contrib/cors v1.7.6
|
||||||
github.com/gin-gonic/gin v1.11.0
|
github.com/gin-gonic/gin v1.11.0
|
||||||
github.com/golang-jwt/jwt/v5 v5.3.0
|
github.com/golang-jwt/jwt/v5 v5.3.0
|
||||||
|
|
@ -37,7 +38,6 @@ require (
|
||||||
github.com/quic-go/qpack v0.5.1 // indirect
|
github.com/quic-go/qpack v0.5.1 // indirect
|
||||||
github.com/quic-go/quic-go v0.54.0 // indirect
|
github.com/quic-go/quic-go v0.54.0 // indirect
|
||||||
github.com/rabbitmq/amqp091-go v1.10.0 // indirect
|
github.com/rabbitmq/amqp091-go v1.10.0 // indirect
|
||||||
github.com/streadway/amqp v1.1.0 // indirect
|
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.3.0 // indirect
|
github.com/ugorji/go/codec v1.3.0 // indirect
|
||||||
go.uber.org/mock v0.5.0 // indirect
|
go.uber.org/mock v0.5.0 // indirect
|
||||||
|
|
|
||||||
40
go.sum
40
go.sum
|
|
@ -1,4 +1,6 @@
|
||||||
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
|
github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk=
|
||||||
|
github.com/alexedwards/argon2id v1.0.0 h1:wJzDx66hqWX7siL/SRUmgz3F8YMrd/nfX/xHHcQQP0w=
|
||||||
|
github.com/alexedwards/argon2id v1.0.0/go.mod h1:tYKkqIjzXvZdzPvADMWOEZ+l6+BD6CtBXMj5fnJppiw=
|
||||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
|
github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ=
|
||||||
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
|
github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk=
|
||||||
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
|
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
|
||||||
|
|
@ -91,26 +93,64 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
|
github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
|
||||||
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||||
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU=
|
||||||
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM=
|
||||||
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
|
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
|
||||||
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||||
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
|
||||||
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
|
||||||
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
|
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
|
||||||
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
|
||||||
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
|
||||||
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
|
||||||
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
||||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
|
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||||
|
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
|
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
|
||||||
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||||
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
|
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
|
||||||
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
|
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
|
||||||
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
|
google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
|
||||||
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
|
|
||||||
|
|
@ -3,22 +3,21 @@ package handlers
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
auth "nyanimedb/auth"
|
auth "nyanimedb/auth"
|
||||||
sqlc "nyanimedb/sql"
|
sqlc "nyanimedb/sql"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/alexedwards/argon2id"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/golang-jwt/jwt/v5"
|
"github.com/golang-jwt/jwt/v5"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
var accessSecret = []byte("my_access_secret_key")
|
var accessSecret = []byte("my_access_secret_key")
|
||||||
var refreshSecret = []byte("my_refresh_secret_key")
|
var refreshSecret = []byte("my_refresh_secret_key")
|
||||||
|
|
||||||
var UserDb = make(map[string]string) // TEMP: stores passwords
|
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
db *sqlc.Queries
|
db *sqlc.Queries
|
||||||
}
|
}
|
||||||
|
|
@ -32,6 +31,22 @@ func parseInt64(s string) (int32, error) {
|
||||||
return int32(i), err
|
return int32(i), err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func HashPassword(password string) (string, error) {
|
||||||
|
params := &argon2id.Params{
|
||||||
|
Memory: 64 * 1024,
|
||||||
|
Iterations: 3,
|
||||||
|
Parallelism: 2,
|
||||||
|
SaltLength: 16,
|
||||||
|
KeyLength: 32,
|
||||||
|
}
|
||||||
|
|
||||||
|
return argon2id.CreateHash(password, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckPassword(password, hash string) (bool, error) {
|
||||||
|
return argon2id.ComparePasswordAndHash(password, hash)
|
||||||
|
}
|
||||||
|
|
||||||
func generateTokens(userID string) (accessToken string, refreshToken string, err error) {
|
func generateTokens(userID string) (accessToken string, refreshToken string, err error) {
|
||||||
accessClaims := jwt.MapClaims{
|
accessClaims := jwt.MapClaims{
|
||||||
"user_id": userID,
|
"user_id": userID,
|
||||||
|
|
@ -57,19 +72,27 @@ func generateTokens(userID string) (accessToken string, refreshToken string, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s Server) PostAuthSignUp(ctx context.Context, req auth.PostAuthSignUpRequestObject) (auth.PostAuthSignUpResponseObject, error) {
|
func (s Server) PostAuthSignUp(ctx context.Context, req auth.PostAuthSignUpRequestObject) (auth.PostAuthSignUpResponseObject, error) {
|
||||||
err := ""
|
passhash, err := HashPassword(req.Body.Pass)
|
||||||
success := true
|
if err != nil {
|
||||||
UserDb[req.Body.Nickname] = req.Body.Pass
|
log.Errorf("failed to hash password: %v", err)
|
||||||
|
// TODO: return 500
|
||||||
|
}
|
||||||
|
|
||||||
|
user_id, err := s.db.CreateNewUser(context.Background(), sqlc.CreateNewUserParams{
|
||||||
|
Passhash: passhash,
|
||||||
|
Nickname: req.Body.Nickname,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to create user %s: %v", req.Body.Nickname, err)
|
||||||
|
// TODO: check err and retyrn 400/500
|
||||||
|
}
|
||||||
|
|
||||||
return auth.PostAuthSignUp200JSONResponse{
|
return auth.PostAuthSignUp200JSONResponse{
|
||||||
Error: &err,
|
UserId: user_id,
|
||||||
Success: &success,
|
|
||||||
UserId: &req.Body.Nickname,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s Server) PostAuthSignIn(ctx context.Context, req auth.PostAuthSignInRequestObject) (auth.PostAuthSignInResponseObject, error) {
|
func (s Server) PostAuthSignIn(ctx context.Context, req auth.PostAuthSignInRequestObject) (auth.PostAuthSignInResponseObject, error) {
|
||||||
// ctx.SetCookie("122")
|
|
||||||
ginCtx, ok := ctx.Value(gin.ContextKey).(*gin.Context)
|
ginCtx, ok := ctx.Value(gin.ContextKey).(*gin.Context)
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Print("failed to get gin context")
|
log.Print("failed to get gin context")
|
||||||
|
|
@ -77,27 +100,38 @@ func (s Server) PostAuthSignIn(ctx context.Context, req auth.PostAuthSignInReque
|
||||||
return auth.PostAuthSignIn200JSONResponse{}, fmt.Errorf("failed to get gin.Context from context.Context")
|
return auth.PostAuthSignIn200JSONResponse{}, fmt.Errorf("failed to get gin.Context from context.Context")
|
||||||
}
|
}
|
||||||
|
|
||||||
err := ""
|
user, err := s.db.GetUserByNickname(context.Background(), req.Body.Nickname)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to get user by nickname %s: %v", req.Body.Nickname, err)
|
||||||
|
// TODO: return 400/500
|
||||||
|
}
|
||||||
|
|
||||||
pass, ok := UserDb[req.Body.Nickname]
|
ok, err = CheckPassword(req.Body.Pass, user.Passhash)
|
||||||
if !ok || pass != req.Body.Pass {
|
if err != nil {
|
||||||
e := "invalid credentials"
|
log.Errorf("failed to check password for user %s: %v", req.Body.Nickname, err)
|
||||||
|
// TODO: return 500
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
err_msg := "invalid credentials"
|
||||||
return auth.PostAuthSignIn401JSONResponse{
|
return auth.PostAuthSignIn401JSONResponse{
|
||||||
Error: &e,
|
Error: &err_msg,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
accessToken, refreshToken, _ := generateTokens(req.Body.Nickname)
|
accessToken, refreshToken, err := generateTokens(req.Body.Nickname)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to generate tokens for user %s: %v", req.Body.Nickname, err)
|
||||||
|
// TODO: return 500
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: check cookie settings carefully
|
||||||
ginCtx.SetSameSite(http.SameSiteStrictMode)
|
ginCtx.SetSameSite(http.SameSiteStrictMode)
|
||||||
ginCtx.SetCookie("access_token", accessToken, 604800, "/auth", "", true, true)
|
ginCtx.SetCookie("access_token", accessToken, 604800, "/auth", "", false, true)
|
||||||
ginCtx.SetCookie("refresh_token", refreshToken, 604800, "/api", "", true, true)
|
ginCtx.SetCookie("refresh_token", refreshToken, 604800, "/api", "", false, true)
|
||||||
|
|
||||||
// Return access token; refresh token can be returned in response or HttpOnly cookie
|
|
||||||
result := auth.PostAuthSignIn200JSONResponse{
|
result := auth.PostAuthSignIn200JSONResponse{
|
||||||
Error: &err,
|
UserId: user.ID,
|
||||||
UserId: &req.Body.Nickname,
|
UserName: user.Nickname,
|
||||||
UserName: &req.Body.Nickname,
|
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
auth "nyanimedb/auth"
|
auth "nyanimedb/auth"
|
||||||
|
|
@ -9,14 +12,22 @@ import (
|
||||||
|
|
||||||
"github.com/gin-contrib/cors"
|
"github.com/gin-contrib/cors"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/jackc/pgx/v5/pgxpool"
|
||||||
)
|
)
|
||||||
|
|
||||||
var AppConfig Config
|
var AppConfig Config
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
// TODO: env args
|
||||||
r := gin.Default()
|
r := gin.Default()
|
||||||
|
|
||||||
var queries *sqlc.Queries = nil
|
pool, err := pgxpool.New(context.Background(), os.Getenv("DATABASE_URL"))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Unable to connect to database: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
var queries *sqlc.Queries = sqlc.New(pool)
|
||||||
|
|
||||||
server := handlers.NewServer(queries)
|
server := handlers.NewServer(queries)
|
||||||
|
|
||||||
|
|
|
||||||
11
modules/auth/queries.sql
Normal file
11
modules/auth/queries.sql
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
-- name: GetUserByNickname :one
|
||||||
|
SELECT *
|
||||||
|
FROM users
|
||||||
|
WHERE nickname = sqlc.arg('nickname');
|
||||||
|
|
||||||
|
-- name: CreateNewUser :one
|
||||||
|
INSERT
|
||||||
|
INTO users (passhash, nickname)
|
||||||
|
VALUES (sqlc.arg(passhash), sqlc.arg(nickname))
|
||||||
|
RETURNING id;
|
||||||
|
|
||||||
|
|
@ -204,7 +204,7 @@ func (s Server) mapUsertitle(ctx context.Context, t sqlc.SearchUserTitlesRow) (o
|
||||||
return oapi_usertitle, nil
|
return oapi_usertitle, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s Server) GetUsersUserIdTitles(ctx context.Context, request oapi.GetUsersUserIdTitlesRequestObject) (oapi.GetUsersUserIdTitlesResponseObject, error) {
|
func (s Server) GetUserTitles(ctx context.Context, request oapi.GetUserTitlesRequestObject) (oapi.GetUserTitlesResponseObject, error) {
|
||||||
|
|
||||||
oapi_usertitles := make([]oapi.UserTitle, 0)
|
oapi_usertitles := make([]oapi.UserTitle, 0)
|
||||||
|
|
||||||
|
|
@ -213,7 +213,7 @@ func (s Server) GetUsersUserIdTitles(ctx context.Context, request oapi.GetUsersU
|
||||||
season, err := ReleaseSeason2sqlc(request.Params.ReleaseSeason)
|
season, err := ReleaseSeason2sqlc(request.Params.ReleaseSeason)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("%v", err)
|
log.Errorf("%v", err)
|
||||||
return oapi.GetUsersUserIdTitles400Response{}, err
|
return oapi.GetUserTitles400Response{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// var statuses_sort []string
|
// var statuses_sort []string
|
||||||
|
|
@ -227,19 +227,19 @@ func (s Server) GetUsersUserIdTitles(ctx context.Context, request oapi.GetUsersU
|
||||||
watch_status, err := UserTitleStatus2Sqlc(request.Params.WatchStatus)
|
watch_status, err := UserTitleStatus2Sqlc(request.Params.WatchStatus)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("%v", err)
|
log.Errorf("%v", err)
|
||||||
return oapi.GetUsersUserIdTitles400Response{}, err
|
return oapi.GetUserTitles400Response{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
title_statuses, err := TitleStatus2Sqlc(request.Params.Status)
|
title_statuses, err := TitleStatus2Sqlc(request.Params.Status)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("%v", err)
|
log.Errorf("%v", err)
|
||||||
return oapi.GetUsersUserIdTitles400Response{}, err
|
return oapi.GetUserTitles400Response{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
userID, err := parseInt64(request.UserId)
|
userID, err := parseInt64(request.UserId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("get user titles: %v", err)
|
log.Errorf("get user titles: %v", err)
|
||||||
return oapi.GetUsersUserIdTitles404Response{}, err
|
return oapi.GetUserTitles404Response{}, err
|
||||||
}
|
}
|
||||||
params := sqlc.SearchUserTitlesParams{
|
params := sqlc.SearchUserTitlesParams{
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
|
|
@ -265,7 +265,7 @@ func (s Server) GetUsersUserIdTitles(ctx context.Context, request oapi.GetUsersU
|
||||||
err := ParseCursorInto(string(*request.Params.Sort), string(*request.Params.Cursor), ¶ms)
|
err := ParseCursorInto(string(*request.Params.Sort), string(*request.Params.Cursor), ¶ms)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("%v", err)
|
log.Errorf("%v", err)
|
||||||
return oapi.GetUsersUserIdTitles400Response{}, nil
|
return oapi.GetUserTitles400Response{}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -273,10 +273,10 @@ func (s Server) GetUsersUserIdTitles(ctx context.Context, request oapi.GetUsersU
|
||||||
titles, err := s.db.SearchUserTitles(ctx, params)
|
titles, err := s.db.SearchUserTitles(ctx, params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("%v", err)
|
log.Errorf("%v", err)
|
||||||
return oapi.GetUsersUserIdTitles500Response{}, nil
|
return oapi.GetUserTitles500Response{}, nil
|
||||||
}
|
}
|
||||||
if len(titles) == 0 {
|
if len(titles) == 0 {
|
||||||
return oapi.GetUsersUserIdTitles204Response{}, nil
|
return oapi.GetUserTitles204Response{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var new_cursor oapi.CursorObj
|
var new_cursor oapi.CursorObj
|
||||||
|
|
@ -286,7 +286,7 @@ func (s Server) GetUsersUserIdTitles(ctx context.Context, request oapi.GetUsersU
|
||||||
t, err := s.mapUsertitle(ctx, title)
|
t, err := s.mapUsertitle(ctx, title)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("%v", err)
|
log.Errorf("%v", err)
|
||||||
return oapi.GetUsersUserIdTitles500Response{}, nil
|
return oapi.GetUserTitles500Response{}, nil
|
||||||
}
|
}
|
||||||
oapi_usertitles = append(oapi_usertitles, t)
|
oapi_usertitles = append(oapi_usertitles, t)
|
||||||
|
|
||||||
|
|
@ -303,7 +303,7 @@ func (s Server) GetUsersUserIdTitles(ctx context.Context, request oapi.GetUsersU
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return oapi.GetUsersUserIdTitles200JSONResponse{Cursor: new_cursor, Data: oapi_usertitles}, nil
|
return oapi.GetUserTitles200JSONResponse{Cursor: new_cursor, Data: oapi_usertitles}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func EmailToStringPtr(e *types.Email) *string {
|
func EmailToStringPtr(e *types.Email) *string {
|
||||||
|
|
@ -402,7 +402,7 @@ func (s Server) AddUserTitle(ctx context.Context, request oapi.AddUserTitleReque
|
||||||
func (s Server) DeleteUserTitle(ctx context.Context, request oapi.DeleteUserTitleRequestObject) (oapi.DeleteUserTitleResponseObject, error) {
|
func (s Server) DeleteUserTitle(ctx context.Context, request oapi.DeleteUserTitleRequestObject) (oapi.DeleteUserTitleResponseObject, error) {
|
||||||
params := sqlc.DeleteUserTitleParams{
|
params := sqlc.DeleteUserTitleParams{
|
||||||
UserID: request.UserId,
|
UserID: request.UserId,
|
||||||
TitleID: request.Params.TitleId,
|
TitleID: request.TitleId,
|
||||||
}
|
}
|
||||||
_, err := s.db.DeleteUserTitle(ctx, params)
|
_, err := s.db.DeleteUserTitle(ctx, params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -427,7 +427,7 @@ func (s Server) UpdateUserTitle(ctx context.Context, request oapi.UpdateUserTitl
|
||||||
Status: status,
|
Status: status,
|
||||||
Rate: request.Body.Rate,
|
Rate: request.Body.Rate,
|
||||||
UserID: request.UserId,
|
UserID: request.UserId,
|
||||||
TitleID: request.Body.TitleId,
|
TitleID: request.TitleId,
|
||||||
}
|
}
|
||||||
|
|
||||||
user_title, err := s.db.UpdateUserTitle(ctx, params)
|
user_title, err := s.db.UpdateUserTitle(ctx, params)
|
||||||
|
|
@ -455,3 +455,33 @@ func (s Server) UpdateUserTitle(ctx context.Context, request oapi.UpdateUserTitl
|
||||||
|
|
||||||
return oapi.UpdateUserTitle200JSONResponse(oapi_usertitle), nil
|
return oapi.UpdateUserTitle200JSONResponse(oapi_usertitle), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s Server) GetUserTitle(ctx context.Context, request oapi.GetUserTitleRequestObject) (oapi.GetUserTitleResponseObject, error) {
|
||||||
|
user_title, err := s.db.GetUserTitleByID(ctx, sqlc.GetUserTitleByIDParams{
|
||||||
|
TitleID: request.TitleId,
|
||||||
|
UserID: request.UserId,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
if err == pgx.ErrNoRows {
|
||||||
|
return oapi.GetUserTitle404Response{}, nil
|
||||||
|
} else {
|
||||||
|
log.Errorf("%v", err)
|
||||||
|
return oapi.GetUserTitle500Response{}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
oapi_status, err := sql2usertitlestatus(user_title.Status)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("%v", err)
|
||||||
|
return oapi.GetUserTitle500Response{}, nil
|
||||||
|
}
|
||||||
|
oapi_usertitle := oapi.UserTitleMini{
|
||||||
|
Ctime: &user_title.Ctime,
|
||||||
|
Rate: user_title.Rate,
|
||||||
|
ReviewId: user_title.ReviewID,
|
||||||
|
Status: oapi_status,
|
||||||
|
TitleId: user_title.TitleID,
|
||||||
|
UserId: user_title.UserID,
|
||||||
|
}
|
||||||
|
|
||||||
|
return oapi.GetUserTitle200JSONResponse(oapi_usertitle), nil
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ func main() {
|
||||||
|
|
||||||
r.Use(cors.New(cors.Config{
|
r.Use(cors.New(cors.Config{
|
||||||
AllowOrigins: []string{"*"}, // allow all origins, change to specific domains in production
|
AllowOrigins: []string{"*"}, // allow all origins, change to specific domains in production
|
||||||
AllowMethods: []string{"GET", "POST", "PUT", "DELETE"},
|
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "PATCH"},
|
||||||
AllowHeaders: []string{"Origin", "Content-Type", "Accept"},
|
AllowHeaders: []string{"Origin", "Content-Type", "Accept"},
|
||||||
ExposeHeaders: []string{"Content-Length"},
|
ExposeHeaders: []string{"Content-Length"},
|
||||||
AllowCredentials: true,
|
AllowCredentials: true,
|
||||||
|
|
|
||||||
|
|
@ -394,4 +394,10 @@ RETURNING *;
|
||||||
DELETE FROM usertitles
|
DELETE FROM usertitles
|
||||||
WHERE user_id = sqlc.arg('user_id')
|
WHERE user_id = sqlc.arg('user_id')
|
||||||
AND title_id = sqlc.arg('title_id')
|
AND title_id = sqlc.arg('title_id')
|
||||||
RETURNING *;
|
RETURNING *;
|
||||||
|
|
||||||
|
-- name: GetUserTitleByID :one
|
||||||
|
SELECT
|
||||||
|
ut.*
|
||||||
|
FROM usertitles as ut
|
||||||
|
WHERE ut.title_id = sqlc.arg('title_id')::bigint AND ut.user_id = sqlc.arg('user_id')::bigint;
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
|
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
|
||||||
import UsersIdPage from "./pages/UsersIdPage/UsersIdPage";
|
import UserPage from "./pages/UserPage/UserPage";
|
||||||
import TitlesPage from "./pages/TitlesPage/TitlesPage";
|
import TitlesPage from "./pages/TitlesPage/TitlesPage";
|
||||||
import TitlePage from "./pages/TitlePage/TitlePage";
|
import TitlePage from "./pages/TitlePage/TitlePage";
|
||||||
import { LoginPage } from "./pages/LoginPage/LoginPage";
|
import { LoginPage } from "./pages/LoginPage/LoginPage";
|
||||||
import { Header } from "./components/Header/Header";
|
import { Header } from "./components/Header/Header";
|
||||||
|
|
||||||
const App: React.FC = () => {
|
const App: React.FC = () => {
|
||||||
// Получаем username из localStorage
|
|
||||||
const username = localStorage.getItem("username") || undefined;
|
const username = localStorage.getItem("username") || undefined;
|
||||||
const userId = localStorage.getItem("userId");
|
const userId = localStorage.getItem("userId");
|
||||||
|
|
||||||
|
|
@ -15,17 +14,20 @@ const App: React.FC = () => {
|
||||||
<Router>
|
<Router>
|
||||||
<Header username={username} />
|
<Header username={username} />
|
||||||
<Routes>
|
<Routes>
|
||||||
|
{/* auth */}
|
||||||
<Route path="/login" element={<LoginPage />} />
|
<Route path="/login" element={<LoginPage />} />
|
||||||
<Route path="/signup" element={<LoginPage />} />
|
<Route path="/signup" element={<LoginPage />} />
|
||||||
|
{/*<Route path="/signup" element={<LoginPage />} />*/}
|
||||||
{/* /profile рендерит UsersIdPage с id из localStorage */}
|
|
||||||
|
{/* users */}
|
||||||
|
{/*<Route path="/users" element={<UsersPage />} />*/}
|
||||||
|
<Route path="/users/:id" element={<UserPage />} />
|
||||||
<Route
|
<Route
|
||||||
path="/profile"
|
path="/profile"
|
||||||
element={userId ? <UsersIdPage userId={userId} /> : <LoginPage />}
|
element={userId ? <UserPage userId={userId} /> : <LoginPage />}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Route path="/users/:id" element={<UsersIdPage />} />
|
{/* titles */}
|
||||||
|
|
||||||
<Route path="/titles" element={<TitlesPage />} />
|
<Route path="/titles" element={<TitlesPage />} />
|
||||||
<Route path="/titles/:id" element={<TitlePage />} />
|
<Route path="/titles/:id" element={<TitlePage />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ export type OpenAPIConfig = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const OpenAPI: OpenAPIConfig = {
|
export const OpenAPI: OpenAPIConfig = {
|
||||||
BASE: 'http://10.1.0.65:8081/api/v1',
|
BASE: '/api/v1',
|
||||||
VERSION: '1.0.0',
|
VERSION: '1.0.0',
|
||||||
WITH_CREDENTIALS: false,
|
WITH_CREDENTIALS: false,
|
||||||
CREDENTIALS: 'include',
|
CREDENTIALS: 'include',
|
||||||
|
|
|
||||||
|
|
@ -199,7 +199,7 @@ export class DefaultService {
|
||||||
* @returns any List of user titles
|
* @returns any List of user titles
|
||||||
* @throws ApiError
|
* @throws ApiError
|
||||||
*/
|
*/
|
||||||
public static getUsersTitles(
|
public static getUserTitles(
|
||||||
userId: string,
|
userId: string,
|
||||||
cursor?: string,
|
cursor?: string,
|
||||||
sort?: TitleSort,
|
sort?: TitleSort,
|
||||||
|
|
@ -278,27 +278,54 @@ export class DefaultService {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Get user title
|
||||||
|
* @param userId
|
||||||
|
* @param titleId
|
||||||
|
* @returns UserTitleMini User titles
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static getUserTitle(
|
||||||
|
userId: number,
|
||||||
|
titleId: number,
|
||||||
|
): CancelablePromise<UserTitleMini> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'GET',
|
||||||
|
url: '/users/{user_id}/titles/{title_id}',
|
||||||
|
path: {
|
||||||
|
'user_id': userId,
|
||||||
|
'title_id': titleId,
|
||||||
|
},
|
||||||
|
errors: {
|
||||||
|
400: `Request params are not correct`,
|
||||||
|
404: `User or title not found`,
|
||||||
|
500: `Unknown server error`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Update a usertitle
|
* Update a usertitle
|
||||||
* User updating title list of watched
|
* User updating title list of watched
|
||||||
* @param userId ID of the user to assign the title to
|
* @param userId
|
||||||
|
* @param titleId
|
||||||
* @param requestBody
|
* @param requestBody
|
||||||
* @returns UserTitleMini Title successfully updated
|
* @returns UserTitleMini Title successfully updated
|
||||||
* @throws ApiError
|
* @throws ApiError
|
||||||
*/
|
*/
|
||||||
public static updateUserTitle(
|
public static updateUserTitle(
|
||||||
userId: number,
|
userId: number,
|
||||||
|
titleId: number,
|
||||||
requestBody: {
|
requestBody: {
|
||||||
title_id: number;
|
|
||||||
status?: UserTitleStatus;
|
status?: UserTitleStatus;
|
||||||
rate?: number;
|
rate?: number;
|
||||||
},
|
},
|
||||||
): CancelablePromise<UserTitleMini> {
|
): CancelablePromise<UserTitleMini> {
|
||||||
return __request(OpenAPI, {
|
return __request(OpenAPI, {
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
url: '/users/{user_id}/titles',
|
url: '/users/{user_id}/titles/{title_id}',
|
||||||
path: {
|
path: {
|
||||||
'user_id': userId,
|
'user_id': userId,
|
||||||
|
'title_id': titleId,
|
||||||
},
|
},
|
||||||
body: requestBody,
|
body: requestBody,
|
||||||
mediaType: 'application/json',
|
mediaType: 'application/json',
|
||||||
|
|
@ -311,4 +338,31 @@ export class DefaultService {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Delete a usertitle
|
||||||
|
* User deleting title from list of watched
|
||||||
|
* @param userId
|
||||||
|
* @param titleId
|
||||||
|
* @returns any Title successfully deleted
|
||||||
|
* @throws ApiError
|
||||||
|
*/
|
||||||
|
public static deleteUserTitle(
|
||||||
|
userId: number,
|
||||||
|
titleId: number,
|
||||||
|
): CancelablePromise<any> {
|
||||||
|
return __request(OpenAPI, {
|
||||||
|
method: 'DELETE',
|
||||||
|
url: '/users/{user_id}/titles/{title_id}',
|
||||||
|
path: {
|
||||||
|
'user_id': userId,
|
||||||
|
'title_id': titleId,
|
||||||
|
},
|
||||||
|
errors: {
|
||||||
|
401: `Unauthorized — missing or invalid auth token`,
|
||||||
|
403: `Forbidden — user not allowed to delete title`,
|
||||||
|
404: `User or Title not found`,
|
||||||
|
500: `Internal server error`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ export type OpenAPIConfig = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const OpenAPI: OpenAPIConfig = {
|
export const OpenAPI: OpenAPIConfig = {
|
||||||
BASE: 'http://10.1.0.65:8081/auth',
|
BASE: '/auth',
|
||||||
VERSION: '1.0.0',
|
VERSION: '1.0.0',
|
||||||
WITH_CREDENTIALS: false,
|
WITH_CREDENTIALS: false,
|
||||||
CREDENTIALS: 'include',
|
CREDENTIALS: 'include',
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { DefaultService } from "../../api";
|
||||||
|
import type { UserTitleStatus } from "../../api";
|
||||||
|
import {
|
||||||
|
ClockIcon,
|
||||||
|
CheckCircleIcon,
|
||||||
|
PlayCircleIcon,
|
||||||
|
XCircleIcon,
|
||||||
|
} from "@heroicons/react/24/solid";
|
||||||
|
|
||||||
|
// Статусы с иконками и подписью
|
||||||
|
const STATUS_BUTTONS: { status: UserTitleStatus; icon: React.ReactNode; label: string }[] = [
|
||||||
|
{ status: "planned", icon: <ClockIcon className="w-5 h-5" />, label: "Planned" },
|
||||||
|
{ status: "finished", icon: <CheckCircleIcon className="w-5 h-5" />, label: "Finished" },
|
||||||
|
{ status: "in-progress", icon: <PlayCircleIcon className="w-5 h-5" />, label: "In Progress" },
|
||||||
|
{ status: "dropped", icon: <XCircleIcon className="w-5 h-5" />, label: "Dropped" },
|
||||||
|
];
|
||||||
|
|
||||||
|
export function TitleStatusControls({ titleId }: { titleId: number }) {
|
||||||
|
const [currentStatus, setCurrentStatus] = useState<UserTitleStatus | null>(null);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
|
const userIdStr = localStorage.getItem("userId");
|
||||||
|
const userId = userIdStr ? Number(userIdStr) : null;
|
||||||
|
|
||||||
|
// --- Load initial status ---
|
||||||
|
useEffect(() => {
|
||||||
|
if (!userId) return;
|
||||||
|
|
||||||
|
DefaultService.getUserTitle(userId, titleId)
|
||||||
|
.then((res) => setCurrentStatus(res.status))
|
||||||
|
.catch(() => setCurrentStatus(null)); // 404 = user title does not exist
|
||||||
|
}, [titleId, userId]);
|
||||||
|
|
||||||
|
// --- Handle click ---
|
||||||
|
const handleStatusClick = async (status: UserTitleStatus) => {
|
||||||
|
if (!userId || loading) return;
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1) Если кликнули на текущий статус — DELETE
|
||||||
|
if (currentStatus === status) {
|
||||||
|
await DefaultService.deleteUserTitle(userId, titleId);
|
||||||
|
setCurrentStatus(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) Если другой статус — POST или PATCH
|
||||||
|
if (!currentStatus) {
|
||||||
|
// ещё нет записи — POST
|
||||||
|
const added = await DefaultService.addUserTitle(userId, {
|
||||||
|
title_id: titleId,
|
||||||
|
status,
|
||||||
|
});
|
||||||
|
setCurrentStatus(added.status);
|
||||||
|
} else {
|
||||||
|
// уже есть запись — PATCH
|
||||||
|
const updated = await DefaultService.updateUserTitle(userId, titleId, { status });
|
||||||
|
setCurrentStatus(updated.status);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex gap-2 flex-wrap justify-center mt-2">
|
||||||
|
{STATUS_BUTTONS.map(btn => (
|
||||||
|
<button
|
||||||
|
key={btn.status}
|
||||||
|
onClick={() => handleStatusClick(btn.status)}
|
||||||
|
disabled={loading}
|
||||||
|
className={`
|
||||||
|
px-3 py-1 rounded-md border flex items-center gap-1 transition
|
||||||
|
${currentStatus === btn.status
|
||||||
|
? "bg-blue-600 text-white border-blue-700"
|
||||||
|
: "bg-gray-200 text-black border-gray-300 hover:bg-gray-300"}
|
||||||
|
`}
|
||||||
|
title={btn.label}
|
||||||
|
>
|
||||||
|
{btn.icon}
|
||||||
|
<span>{btn.label}</span>
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,20 +1,8 @@
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams, Link } from "react-router-dom";
|
||||||
import { DefaultService } from "../../api/services/DefaultService";
|
import { DefaultService } from "../../api/services/DefaultService";
|
||||||
import type { Title, UserTitleStatus } from "../../api";
|
import type { Title } from "../../api";
|
||||||
import {
|
import { TitleStatusControls } from "../../components/TitleStatusControls/TitleStatusControls";
|
||||||
ClockIcon,
|
|
||||||
CheckCircleIcon,
|
|
||||||
PlayCircleIcon,
|
|
||||||
XCircleIcon,
|
|
||||||
} from "@heroicons/react/24/solid";
|
|
||||||
|
|
||||||
const STATUS_BUTTONS: { status: UserTitleStatus; icon: React.ReactNode; label: string }[] = [
|
|
||||||
{ status: "planned", icon: <ClockIcon className="w-6 h-6" />, label: "Planned" },
|
|
||||||
{ status: "finished", icon: <CheckCircleIcon className="w-6 h-6" />, label: "Finished" },
|
|
||||||
{ status: "in-progress", icon: <PlayCircleIcon className="w-6 h-6" />, label: "In Progress" },
|
|
||||||
{ status: "dropped", icon: <XCircleIcon className="w-6 h-6" />, label: "Dropped" },
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function TitlePage() {
|
export default function TitlePage() {
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
|
|
@ -24,9 +12,9 @@ export default function TitlePage() {
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
const [userStatus, setUserStatus] = useState<UserTitleStatus | null>(null);
|
// ---------------------------
|
||||||
const [updatingStatus, setUpdatingStatus] = useState(false);
|
// LOAD TITLE INFO
|
||||||
|
// ---------------------------
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchTitle = async () => {
|
const fetchTitle = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
|
@ -44,30 +32,6 @@ export default function TitlePage() {
|
||||||
fetchTitle();
|
fetchTitle();
|
||||||
}, [titleId]);
|
}, [titleId]);
|
||||||
|
|
||||||
const handleStatusClick = async (status: UserTitleStatus) => {
|
|
||||||
if (updatingStatus || userStatus === status) return;
|
|
||||||
|
|
||||||
const userId = Number(localStorage.getItem("userId"));
|
|
||||||
if (!userId) {
|
|
||||||
alert("You must be logged in to set status.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setUpdatingStatus(true);
|
|
||||||
try {
|
|
||||||
await DefaultService.addUserTitle(userId, {
|
|
||||||
title_id: titleId,
|
|
||||||
status,
|
|
||||||
});
|
|
||||||
setUserStatus(status);
|
|
||||||
} catch (err: any) {
|
|
||||||
console.error(err);
|
|
||||||
alert(err?.message || "Failed to set status");
|
|
||||||
} finally {
|
|
||||||
setUpdatingStatus(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getTagsString = () =>
|
const getTagsString = () =>
|
||||||
title?.tags?.map(tag => tag.en).filter(Boolean).join(", ");
|
title?.tags?.map(tag => tag.en).filter(Boolean).join(", ");
|
||||||
|
|
||||||
|
|
@ -78,7 +42,7 @@ export default function TitlePage() {
|
||||||
return (
|
return (
|
||||||
<div className="w-full min-h-screen bg-gray-50 p-6 flex justify-center">
|
<div className="w-full min-h-screen bg-gray-50 p-6 flex justify-center">
|
||||||
<div className="flex flex-col md:flex-row bg-white shadow-lg rounded-xl max-w-4xl w-full p-6 gap-6">
|
<div className="flex flex-col md:flex-row bg-white shadow-lg rounded-xl max-w-4xl w-full p-6 gap-6">
|
||||||
{/* Постер */}
|
{/* Poster + status buttons */}
|
||||||
<div className="flex flex-col items-center">
|
<div className="flex flex-col items-center">
|
||||||
<img
|
<img
|
||||||
src={title.poster?.image_path || "/default-poster.png"}
|
src={title.poster?.image_path || "/default-poster.png"}
|
||||||
|
|
@ -86,48 +50,52 @@ export default function TitlePage() {
|
||||||
className="w-48 h-72 object-cover rounded-lg mb-4"
|
className="w-48 h-72 object-cover rounded-lg mb-4"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Статус кнопки с иконками */}
|
{/* Status buttons */}
|
||||||
<div className="flex gap-2 mt-2 flex-wrap justify-center">
|
<TitleStatusControls titleId={titleId} />
|
||||||
{STATUS_BUTTONS.map(btn => (
|
|
||||||
<button
|
|
||||||
key={btn.status}
|
|
||||||
onClick={() => handleStatusClick(btn.status)}
|
|
||||||
disabled={updatingStatus}
|
|
||||||
className={`p-2 rounded-lg transition flex items-center justify-center ${
|
|
||||||
userStatus === btn.status
|
|
||||||
? "bg-blue-600 text-white"
|
|
||||||
: "bg-gray-200 text-gray-700 hover:bg-gray-300"
|
|
||||||
}`}
|
|
||||||
title={btn.label}
|
|
||||||
>
|
|
||||||
{btn.icon}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Информация о тайтле */}
|
{/* Title info */}
|
||||||
<div className="flex-1 flex flex-col">
|
<div className="flex-1 flex flex-col">
|
||||||
<h1 className="text-3xl font-bold mb-2">
|
<h1 className="text-3xl font-bold mb-2">
|
||||||
{title.title_names?.en?.[0] || "Untitled"}
|
{title.title_names?.en?.[0] || "Untitled"}
|
||||||
</h1>
|
</h1>
|
||||||
{title.studio && <p className="text-gray-700 mb-1">Studio: {title.studio.name}</p>}
|
|
||||||
|
{title.studio && (
|
||||||
|
<p className="text-gray-700 mb-1">
|
||||||
|
Studio:{" "}
|
||||||
|
{title.studio.id ? (
|
||||||
|
<Link
|
||||||
|
to={`/studios/${title.studio.id}`}
|
||||||
|
className="text-blue-600 hover:underline"
|
||||||
|
>
|
||||||
|
{title.studio.name}
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
title.studio.name
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
{title.title_status && <p className="text-gray-700 mb-1">Status: {title.title_status}</p>}
|
{title.title_status && <p className="text-gray-700 mb-1">Status: {title.title_status}</p>}
|
||||||
|
|
||||||
{title.rating !== undefined && (
|
{title.rating !== undefined && (
|
||||||
<p className="text-gray-700 mb-1">
|
<p className="text-gray-700 mb-1">
|
||||||
Rating: {title.rating} ({title.rating_count} votes)
|
Rating: {title.rating} ({title.rating_count} votes)
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{title.release_year && (
|
{title.release_year && (
|
||||||
<p className="text-gray-700 mb-1">
|
<p className="text-gray-700 mb-1">
|
||||||
Released: {title.release_year} {title.release_season || ""}
|
Released: {title.release_year} {title.release_season || ""}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{title.episodes_aired !== undefined && (
|
{title.episodes_aired !== undefined && (
|
||||||
<p className="text-gray-700 mb-1">
|
<p className="text-gray-700 mb-1">
|
||||||
Episodes: {title.episodes_aired}/{title.episodes_all}
|
Episodes: {title.episodes_aired}/{title.episodes_all}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{title.tags && title.tags.length > 0 && (
|
{title.tags && title.tags.length > 0 && (
|
||||||
<p className="text-gray-700 mb-1">
|
<p className="text-gray-700 mb-1">
|
||||||
Tags: {getTagsString()}
|
Tags: {getTagsString()}
|
||||||
|
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
@import "tailwindcss";
|
|
||||||
|
|
@ -7,6 +7,7 @@ import { TitleCardSquare } from "../../components/cards/TitleCardSquare";
|
||||||
import { TitleCardHorizontal } from "../../components/cards/TitleCardHorizontal";
|
import { TitleCardHorizontal } from "../../components/cards/TitleCardHorizontal";
|
||||||
import type { CursorObj, Title, TitleSort } from "../../api";
|
import type { CursorObj, Title, TitleSort } from "../../api";
|
||||||
import { LayoutSwitch } from "../../components/LayoutSwitch/LayoutSwitch";
|
import { LayoutSwitch } from "../../components/LayoutSwitch/LayoutSwitch";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
const PAGE_SIZE = 10;
|
const PAGE_SIZE = 10;
|
||||||
|
|
||||||
|
|
@ -135,11 +136,11 @@ const handleLoadMore = async () => {
|
||||||
hasMore={!!cursor || nextPage.length > 1}
|
hasMore={!!cursor || nextPage.length > 1}
|
||||||
loadingMore={loadingMore}
|
loadingMore={loadingMore}
|
||||||
onLoadMore={handleLoadMore}
|
onLoadMore={handleLoadMore}
|
||||||
renderItem={(title, layout) =>
|
renderItem={(title, layout) => (
|
||||||
layout === "square"
|
<Link to={`/titles/${title.id}`} key={title.id} className="block">
|
||||||
? <TitleCardSquare title={title} />
|
{layout === "square" ? <TitleCardSquare title={title} /> : <TitleCardHorizontal title={title} />}
|
||||||
: <TitleCardHorizontal title={title} />
|
</Link>
|
||||||
}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{!cursor && nextPage.length == 0 && (
|
{!cursor && nextPage.length == 0 && (
|
||||||
|
|
|
||||||
|
|
@ -1,103 +0,0 @@
|
||||||
body,
|
|
||||||
html {
|
|
||||||
width: 100%;
|
|
||||||
margin: 0;
|
|
||||||
background-color: #777;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
html,
|
|
||||||
body,
|
|
||||||
#root {
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header {
|
|
||||||
width: 100vw;
|
|
||||||
padding: 30px 40px;
|
|
||||||
background: #f7f7f7;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 25px;
|
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
|
||||||
border-bottom: 1px solid #e5e5e5;
|
|
||||||
color: #000000;
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatarWrapper {
|
|
||||||
width: 120px;
|
|
||||||
height: 120px;
|
|
||||||
min-width: 120px;
|
|
||||||
border-radius: 50%;
|
|
||||||
overflow: hidden;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
background: #ddd;
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatarImg {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatarPlaceholder {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: #ccc;
|
|
||||||
font-size: 42px;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #555;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.userInfo {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.name {
|
|
||||||
font-size: 32px;
|
|
||||||
font-weight: 700;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nickname {
|
|
||||||
font-size: 18px;
|
|
||||||
color: #666;
|
|
||||||
margin-top: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
|
||||||
max-width: 100vw;
|
|
||||||
width: 100%;
|
|
||||||
position: absolute;
|
|
||||||
top: 0%;
|
|
||||||
/* margin: 25px auto; */
|
|
||||||
/* padding: 0 20px; */
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.desc {
|
|
||||||
font-size: 18px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.created {
|
|
||||||
font-size: 16px;
|
|
||||||
color: #888;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loader,
|
|
||||||
.error {
|
|
||||||
text-align: center;
|
|
||||||
margin-top: 40px;
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
|
|
@ -1,67 +1,184 @@
|
||||||
import React, { useEffect, useState } from "react";
|
// pages/UserPage/UserPage.tsx
|
||||||
import { useParams } from "react-router-dom"; // <-- import
|
import { useEffect, useState } from "react";
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
import { DefaultService } from "../../api/services/DefaultService";
|
import { DefaultService } from "../../api/services/DefaultService";
|
||||||
import type { User } from "../../api/models/User";
|
import { SearchBar } from "../../components/SearchBar/SearchBar";
|
||||||
import styles from "./UserPage.module.css";
|
import { TitlesSortBox } from "../../components/TitlesSortBox/TitlesSortBox";
|
||||||
|
import { LayoutSwitch } from "../../components/LayoutSwitch/LayoutSwitch";
|
||||||
|
import { ListView } from "../../components/ListView/ListView";
|
||||||
|
import { UserTitleCardSquare } from "../../components/cards/UserTitleCardSquare";
|
||||||
|
import { UserTitleCardHorizontal } from "../../components/cards/UserTitleCardHorizontal";
|
||||||
|
import type { User, UserTitle, CursorObj, TitleSort } from "../../api";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
const UserPage: React.FC = () => {
|
const PAGE_SIZE = 10;
|
||||||
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(() => {
|
type UserPageProps = {
|
||||||
if (!id) return;
|
userId?: string;
|
||||||
|
|
||||||
const getUserInfo = async () => {
|
|
||||||
try {
|
|
||||||
const userInfo = await DefaultService.getUsersId(id, "all"); // <-- use dynamic id
|
|
||||||
setUser(userInfo);
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
setError("Failed to fetch user info.");
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
getUserInfo();
|
|
||||||
}, [id]);
|
|
||||||
|
|
||||||
if (loading) return <div className={styles.loader}>Loading...</div>;
|
|
||||||
if (error) return <div className={styles.error}>{error}</div>;
|
|
||||||
if (!user) return <div className={styles.error}>User not found.</div>;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.container}>
|
|
||||||
<div className={styles.header}>
|
|
||||||
<div className={styles.avatarWrapper}>
|
|
||||||
{user.image?.image_path ? (
|
|
||||||
<img
|
|
||||||
src={`/images/${user.image.image_path}.png`}
|
|
||||||
alt="User Avatar"
|
|
||||||
className={styles.avatarImg}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<div className={styles.avatarPlaceholder}>
|
|
||||||
{user.disp_name?.[0] || "U"}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={styles.userInfo}>
|
|
||||||
<h1 className={styles.name}>{user.disp_name || user.nickname}</h1>
|
|
||||||
<p className={styles.nickname}>@{user.nickname}</p>
|
|
||||||
{/* <p className={styles.created}>
|
|
||||||
Joined: {new Date(user.creation_date).toLocaleDateString()}
|
|
||||||
</p> */}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={styles.content}>
|
|
||||||
{user.user_desc && <p className={styles.desc}>{user.user_desc}</p>}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default UserPage;
|
export default function UserPage({ userId }: UserPageProps) {
|
||||||
|
const params = useParams();
|
||||||
|
const id = userId || params?.id;
|
||||||
|
|
||||||
|
const [user, setUser] = useState<User | null>(null);
|
||||||
|
const [loadingUser, setLoadingUser] = useState(true);
|
||||||
|
const [errorUser, setErrorUser] = useState<string | null>(null);
|
||||||
|
|
||||||
|
// Для списка тайтлов
|
||||||
|
const [titles, setTitles] = useState<UserTitle[]>([]);
|
||||||
|
const [nextPage, setNextPage] = useState<UserTitle[]>([]);
|
||||||
|
const [cursor, setCursor] = useState<CursorObj | null>(null);
|
||||||
|
const [loadingTitles, setLoadingTitles] = useState(true);
|
||||||
|
const [loadingMore, setLoadingMore] = useState(false);
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
|
const [sort, setSort] = useState<TitleSort>("id");
|
||||||
|
const [sortForward, setSortForward] = useState(true);
|
||||||
|
const [layout, setLayout] = useState<"square" | "horizontal">("square");
|
||||||
|
|
||||||
|
// --- Получение данных пользователя ---
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchUser = async () => {
|
||||||
|
if (!id) return;
|
||||||
|
setLoadingUser(true);
|
||||||
|
try {
|
||||||
|
const result = await DefaultService.getUsersId(id, "all");
|
||||||
|
setUser(result);
|
||||||
|
setErrorUser(null);
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error(err);
|
||||||
|
setErrorUser(err?.message || "Failed to fetch user data");
|
||||||
|
} finally {
|
||||||
|
setLoadingUser(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fetchUser();
|
||||||
|
}, [id]);
|
||||||
|
|
||||||
|
// --- Получение списка тайтлов пользователя ---
|
||||||
|
const fetchPage = async (cursorObj: CursorObj | null) => {
|
||||||
|
if (!id) return { items: [], nextCursor: null };
|
||||||
|
const cursorStr = cursorObj
|
||||||
|
? btoa(JSON.stringify(cursorObj)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "")
|
||||||
|
: "";
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await DefaultService.getUserTitles(
|
||||||
|
id,
|
||||||
|
cursorStr,
|
||||||
|
sort,
|
||||||
|
sortForward,
|
||||||
|
search.trim() || undefined,
|
||||||
|
undefined, // status фильтр, можно добавить
|
||||||
|
undefined, // watchStatus
|
||||||
|
undefined, // rating
|
||||||
|
undefined, // myRate
|
||||||
|
undefined, // releaseYear
|
||||||
|
undefined, // releaseSeason
|
||||||
|
PAGE_SIZE,
|
||||||
|
"all"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!result?.data?.length) return { items: [], nextCursor: null };
|
||||||
|
|
||||||
|
return { items: result.data, nextCursor: result.cursor ?? null };
|
||||||
|
} catch (err: any) {
|
||||||
|
if (err.status === 204) return { items: [], nextCursor: null };
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Инициализация: загружаем сразу две страницы
|
||||||
|
useEffect(() => {
|
||||||
|
const initLoad = async () => {
|
||||||
|
setLoadingTitles(true);
|
||||||
|
setTitles([]);
|
||||||
|
setNextPage([]);
|
||||||
|
setCursor(null);
|
||||||
|
|
||||||
|
const firstPage = await fetchPage(null);
|
||||||
|
const secondPage = firstPage.nextCursor ? await fetchPage(firstPage.nextCursor) : { items: [], nextCursor: null };
|
||||||
|
|
||||||
|
setTitles(firstPage.items);
|
||||||
|
setNextPage(secondPage.items);
|
||||||
|
setCursor(secondPage.nextCursor);
|
||||||
|
setLoadingTitles(false);
|
||||||
|
};
|
||||||
|
initLoad();
|
||||||
|
}, [id, search, sort, sortForward]);
|
||||||
|
|
||||||
|
const handleLoadMore = async () => {
|
||||||
|
if (nextPage.length === 0) {
|
||||||
|
setLoadingMore(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setLoadingMore(true);
|
||||||
|
|
||||||
|
setTitles(prev => [...prev, ...nextPage]);
|
||||||
|
setNextPage([]);
|
||||||
|
|
||||||
|
if (cursor) {
|
||||||
|
try {
|
||||||
|
const next = await fetchPage(cursor);
|
||||||
|
if (next.items.length > 0) setNextPage(next.items);
|
||||||
|
setCursor(next.nextCursor);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoadingMore(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-full min-h-screen bg-gray-50 p-6 flex flex-col items-center">
|
||||||
|
|
||||||
|
{/* --- Карточка пользователя --- */}
|
||||||
|
{loadingUser && <div className="mt-10 text-xl font-medium">Loading user...</div>}
|
||||||
|
{errorUser && <div className="mt-10 text-red-600 font-medium">{errorUser}</div>}
|
||||||
|
{user && (
|
||||||
|
<div className="bg-white shadow-lg rounded-xl p-6 w-full max-w-sm flex flex-col items-center mb-8">
|
||||||
|
<img src={user.image?.image_path} alt={user.nickname} className="w-32 h-32 rounded-full object-cover mb-4" />
|
||||||
|
<h2 className="text-2xl font-bold mb-2">{user.disp_name || user.nickname}</h2>
|
||||||
|
{user.mail && <p className="text-gray-600 mb-2">{user.mail}</p>}
|
||||||
|
{user.user_desc && <p className="text-gray-700 text-center">{user.user_desc}</p>}
|
||||||
|
{user.creation_date && <p className="text-gray-400 mt-4 text-sm">Registered: {new Date(user.creation_date).toLocaleDateString()}</p>}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* --- Панель поиска, сортировки и лейаута --- */}
|
||||||
|
<div className="w-full sm:w-4/5 flex flex-col sm:flex-row gap-4 mb-6 items-center">
|
||||||
|
<SearchBar placeholder="Search titles..." search={search} setSearch={setSearch} />
|
||||||
|
<LayoutSwitch layout={layout} setLayout={setLayout} />
|
||||||
|
<TitlesSortBox sort={sort} setSort={setSort} sortForward={sortForward} setSortForward={setSortForward} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* --- Список тайтлов --- */}
|
||||||
|
{loadingTitles && <div className="mt-6 font-medium text-black">Loading titles...</div>}
|
||||||
|
{!loadingTitles && titles.length === 0 && <div className="mt-6 font-medium text-black">No titles found.</div>}
|
||||||
|
|
||||||
|
{titles.length > 0 && (
|
||||||
|
<>
|
||||||
|
<ListView<UserTitle>
|
||||||
|
items={titles}
|
||||||
|
layout={layout}
|
||||||
|
hasMore={!!cursor || nextPage.length > 1}
|
||||||
|
loadingMore={loadingMore}
|
||||||
|
onLoadMore={handleLoadMore}
|
||||||
|
renderItem={(title, layout) => (
|
||||||
|
<Link to={`/titles/${title.title?.id}`} key={title.title?.id} className="block">
|
||||||
|
{layout === "square" ? <UserTitleCardSquare title={title} /> : <UserTitleCardHorizontal title={title} />}
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{!cursor && nextPage.length === 0 && (
|
||||||
|
<div className="mt-6 font-medium text-black">
|
||||||
|
Результатов больше нет, было найдено {titles.length} тайтлов.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,183 +0,0 @@
|
||||||
// pages/UserPage/UserPage.tsx
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { useParams } from "react-router-dom";
|
|
||||||
import { DefaultService } from "../../api/services/DefaultService";
|
|
||||||
import { SearchBar } from "../../components/SearchBar/SearchBar";
|
|
||||||
import { TitlesSortBox } from "../../components/TitlesSortBox/TitlesSortBox";
|
|
||||||
import { LayoutSwitch } from "../../components/LayoutSwitch/LayoutSwitch";
|
|
||||||
import { ListView } from "../../components/ListView/ListView";
|
|
||||||
import { UserTitleCardSquare } from "../../components/cards/UserTitleCardSquare";
|
|
||||||
import { UserTitleCardHorizontal } from "../../components/cards/UserTitleCardHorizontal";
|
|
||||||
import type { User, UserTitle, CursorObj, TitleSort } from "../../api";
|
|
||||||
|
|
||||||
const PAGE_SIZE = 10;
|
|
||||||
|
|
||||||
type UsersIdPageProps = {
|
|
||||||
userId?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function UsersIdPage({ userId }: UsersIdPageProps) {
|
|
||||||
const params = useParams();
|
|
||||||
const id = userId || params?.id;
|
|
||||||
|
|
||||||
const [user, setUser] = useState<User | null>(null);
|
|
||||||
const [loadingUser, setLoadingUser] = useState(true);
|
|
||||||
const [errorUser, setErrorUser] = useState<string | null>(null);
|
|
||||||
|
|
||||||
// Для списка тайтлов
|
|
||||||
const [titles, setTitles] = useState<UserTitle[]>([]);
|
|
||||||
const [nextPage, setNextPage] = useState<UserTitle[]>([]);
|
|
||||||
const [cursor, setCursor] = useState<CursorObj | null>(null);
|
|
||||||
const [loadingTitles, setLoadingTitles] = useState(true);
|
|
||||||
const [loadingMore, setLoadingMore] = useState(false);
|
|
||||||
const [search, setSearch] = useState("");
|
|
||||||
const [sort, setSort] = useState<TitleSort>("id");
|
|
||||||
const [sortForward, setSortForward] = useState(true);
|
|
||||||
const [layout, setLayout] = useState<"square" | "horizontal">("square");
|
|
||||||
|
|
||||||
// --- Получение данных пользователя ---
|
|
||||||
useEffect(() => {
|
|
||||||
const fetchUser = async () => {
|
|
||||||
if (!id) return;
|
|
||||||
setLoadingUser(true);
|
|
||||||
try {
|
|
||||||
const result = await DefaultService.getUsersId(id, "all");
|
|
||||||
setUser(result);
|
|
||||||
setErrorUser(null);
|
|
||||||
} catch (err: any) {
|
|
||||||
console.error(err);
|
|
||||||
setErrorUser(err?.message || "Failed to fetch user data");
|
|
||||||
} finally {
|
|
||||||
setLoadingUser(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
fetchUser();
|
|
||||||
}, [id]);
|
|
||||||
|
|
||||||
// --- Получение списка тайтлов пользователя ---
|
|
||||||
const fetchPage = async (cursorObj: CursorObj | null) => {
|
|
||||||
if (!id) return { items: [], nextCursor: null };
|
|
||||||
const cursorStr = cursorObj
|
|
||||||
? btoa(JSON.stringify(cursorObj)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "")
|
|
||||||
: "";
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await DefaultService.getUsersTitles(
|
|
||||||
id,
|
|
||||||
cursorStr,
|
|
||||||
sort,
|
|
||||||
sortForward,
|
|
||||||
search.trim() || undefined,
|
|
||||||
undefined, // status фильтр, можно добавить
|
|
||||||
undefined, // watchStatus
|
|
||||||
undefined, // rating
|
|
||||||
undefined, // myRate
|
|
||||||
undefined, // releaseYear
|
|
||||||
undefined, // releaseSeason
|
|
||||||
PAGE_SIZE,
|
|
||||||
"all"
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!result?.data?.length) return { items: [], nextCursor: null };
|
|
||||||
|
|
||||||
return { items: result.data, nextCursor: result.cursor ?? null };
|
|
||||||
} catch (err: any) {
|
|
||||||
if (err.status === 204) return { items: [], nextCursor: null };
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Инициализация: загружаем сразу две страницы
|
|
||||||
useEffect(() => {
|
|
||||||
const initLoad = async () => {
|
|
||||||
setLoadingTitles(true);
|
|
||||||
setTitles([]);
|
|
||||||
setNextPage([]);
|
|
||||||
setCursor(null);
|
|
||||||
|
|
||||||
const firstPage = await fetchPage(null);
|
|
||||||
const secondPage = firstPage.nextCursor ? await fetchPage(firstPage.nextCursor) : { items: [], nextCursor: null };
|
|
||||||
|
|
||||||
setTitles(firstPage.items);
|
|
||||||
setNextPage(secondPage.items);
|
|
||||||
setCursor(secondPage.nextCursor);
|
|
||||||
setLoadingTitles(false);
|
|
||||||
};
|
|
||||||
initLoad();
|
|
||||||
}, [id, search, sort, sortForward]);
|
|
||||||
|
|
||||||
const handleLoadMore = async () => {
|
|
||||||
if (nextPage.length === 0) {
|
|
||||||
setLoadingMore(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setLoadingMore(true);
|
|
||||||
|
|
||||||
setTitles(prev => [...prev, ...nextPage]);
|
|
||||||
setNextPage([]);
|
|
||||||
|
|
||||||
if (cursor) {
|
|
||||||
try {
|
|
||||||
const next = await fetchPage(cursor);
|
|
||||||
if (next.items.length > 0) setNextPage(next.items);
|
|
||||||
setCursor(next.nextCursor);
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setLoadingMore(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
// const getAvatarUrl = (avatarId?: number) => (avatarId ? `/api/images/${avatarId}` : "/default-avatar.png");
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="w-full min-h-screen bg-gray-50 p-6 flex flex-col items-center">
|
|
||||||
|
|
||||||
{/* --- Карточка пользователя --- */}
|
|
||||||
{loadingUser && <div className="mt-10 text-xl font-medium">Loading user...</div>}
|
|
||||||
{errorUser && <div className="mt-10 text-red-600 font-medium">{errorUser}</div>}
|
|
||||||
{user && (
|
|
||||||
<div className="bg-white shadow-lg rounded-xl p-6 w-full max-w-sm flex flex-col items-center mb-8">
|
|
||||||
<img src={user.image?.image_path} alt={user.nickname} className="w-32 h-32 rounded-full object-cover mb-4" />
|
|
||||||
<h2 className="text-2xl font-bold mb-2">{user.disp_name || user.nickname}</h2>
|
|
||||||
{user.mail && <p className="text-gray-600 mb-2">{user.mail}</p>}
|
|
||||||
{user.user_desc && <p className="text-gray-700 text-center">{user.user_desc}</p>}
|
|
||||||
{user.creation_date && <p className="text-gray-400 mt-4 text-sm">Registered: {new Date(user.creation_date).toLocaleDateString()}</p>}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* --- Панель поиска, сортировки и лейаута --- */}
|
|
||||||
<div className="w-full sm:w-4/5 flex flex-col sm:flex-row gap-4 mb-6 items-center">
|
|
||||||
<SearchBar placeholder="Search titles..." search={search} setSearch={setSearch} />
|
|
||||||
<LayoutSwitch layout={layout} setLayout={setLayout} />
|
|
||||||
<TitlesSortBox sort={sort} setSort={setSort} sortForward={sortForward} setSortForward={setSortForward} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* --- Список тайтлов --- */}
|
|
||||||
{loadingTitles && <div className="mt-6 font-medium text-black">Loading titles...</div>}
|
|
||||||
{!loadingTitles && titles.length === 0 && <div className="mt-6 font-medium text-black">No titles found.</div>}
|
|
||||||
|
|
||||||
{titles.length > 0 && (
|
|
||||||
<>
|
|
||||||
<ListView<UserTitle>
|
|
||||||
items={titles}
|
|
||||||
layout={layout}
|
|
||||||
hasMore={!!cursor || nextPage.length > 1}
|
|
||||||
loadingMore={loadingMore}
|
|
||||||
onLoadMore={handleLoadMore}
|
|
||||||
renderItem={(title, layout) =>
|
|
||||||
layout === "square" ? <UserTitleCardSquare title={title} /> : <UserTitleCardHorizontal title={title} />
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{!cursor && nextPage.length === 0 && (
|
|
||||||
<div className="mt-6 font-medium text-black">
|
|
||||||
Результатов больше нет, было найдено {titles.length} тайтлов.
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -29,6 +29,25 @@ func (q *Queries) CreateImage(ctx context.Context, arg CreateImageParams) (Image
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const createNewUser = `-- name: CreateNewUser :one
|
||||||
|
INSERT
|
||||||
|
INTO users (passhash, nickname)
|
||||||
|
VALUES ($1, $2)
|
||||||
|
RETURNING id
|
||||||
|
`
|
||||||
|
|
||||||
|
type CreateNewUserParams struct {
|
||||||
|
Passhash string `json:"passhash"`
|
||||||
|
Nickname string `json:"nickname"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) CreateNewUser(ctx context.Context, arg CreateNewUserParams) (int64, error) {
|
||||||
|
row := q.db.QueryRow(ctx, createNewUser, arg.Passhash, arg.Nickname)
|
||||||
|
var id int64
|
||||||
|
err := row.Scan(&id)
|
||||||
|
return id, err
|
||||||
|
}
|
||||||
|
|
||||||
const deleteUserTitle = `-- name: DeleteUserTitle :one
|
const deleteUserTitle = `-- name: DeleteUserTitle :one
|
||||||
DELETE FROM usertitles
|
DELETE FROM usertitles
|
||||||
WHERE user_id = $1
|
WHERE user_id = $1
|
||||||
|
|
@ -262,6 +281,55 @@ func (q *Queries) GetUserByID(ctx context.Context, id int64) (GetUserByIDRow, er
|
||||||
return i, err
|
return i, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getUserByNickname = `-- name: GetUserByNickname :one
|
||||||
|
SELECT id, avatar_id, passhash, mail, nickname, disp_name, user_desc, creation_date, last_login
|
||||||
|
FROM users
|
||||||
|
WHERE nickname = $1
|
||||||
|
`
|
||||||
|
|
||||||
|
func (q *Queries) GetUserByNickname(ctx context.Context, nickname string) (User, error) {
|
||||||
|
row := q.db.QueryRow(ctx, getUserByNickname, nickname)
|
||||||
|
var i User
|
||||||
|
err := row.Scan(
|
||||||
|
&i.ID,
|
||||||
|
&i.AvatarID,
|
||||||
|
&i.Passhash,
|
||||||
|
&i.Mail,
|
||||||
|
&i.Nickname,
|
||||||
|
&i.DispName,
|
||||||
|
&i.UserDesc,
|
||||||
|
&i.CreationDate,
|
||||||
|
&i.LastLogin,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
|
const getUserTitleByID = `-- name: GetUserTitleByID :one
|
||||||
|
SELECT
|
||||||
|
ut.user_id, ut.title_id, ut.status, ut.rate, ut.review_id, ut.ctime
|
||||||
|
FROM usertitles as ut
|
||||||
|
WHERE ut.title_id = $1::bigint AND ut.user_id = $2::bigint
|
||||||
|
`
|
||||||
|
|
||||||
|
type GetUserTitleByIDParams struct {
|
||||||
|
TitleID int64 `json:"title_id"`
|
||||||
|
UserID int64 `json:"user_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (q *Queries) GetUserTitleByID(ctx context.Context, arg GetUserTitleByIDParams) (Usertitle, error) {
|
||||||
|
row := q.db.QueryRow(ctx, getUserTitleByID, arg.TitleID, arg.UserID)
|
||||||
|
var i Usertitle
|
||||||
|
err := row.Scan(
|
||||||
|
&i.UserID,
|
||||||
|
&i.TitleID,
|
||||||
|
&i.Status,
|
||||||
|
&i.Rate,
|
||||||
|
&i.ReviewID,
|
||||||
|
&i.Ctime,
|
||||||
|
)
|
||||||
|
return i, err
|
||||||
|
}
|
||||||
|
|
||||||
const insertStudio = `-- name: InsertStudio :one
|
const insertStudio = `-- name: InsertStudio :one
|
||||||
INSERT INTO studios (studio_name, illust_id, studio_desc)
|
INSERT INTO studios (studio_name, illust_id, studio_desc)
|
||||||
VALUES (
|
VALUES (
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ sql:
|
||||||
- engine: "postgresql"
|
- engine: "postgresql"
|
||||||
queries:
|
queries:
|
||||||
- "../modules/backend/queries.sql"
|
- "../modules/backend/queries.sql"
|
||||||
|
- "../modules/auth/queries.sql"
|
||||||
schema: "migrations"
|
schema: "migrations"
|
||||||
gen:
|
gen:
|
||||||
go:
|
go:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue