增加 Swagger API 文档页面

This commit is contained in:
2026-09-19 17:26:07 +08:00
parent cd18cb8673
commit 30edfac1b5
10 files changed
+3284 -62

No files matched your search

+1089
View File
File diff suppressed because it is too large. Load diff
+1064
View File
File diff suppressed because it is too large. Load diff
+723
View File
@@ -0,0 +1,723 @@
basePath: /api
definitions:
api.ErrorResponse:
properties:
error:
example: 记录不存在
type: string
type: object
api.HealthResponse:
properties:
error:
example: 数据库不可用
type: string
status:
example: ok
type: string
type: object
api.NoteListResponse:
properties:
items:
items:
$ref: '#/definitions/model.Note'
type: array
page:
example: 1
type: integer
page_size:
example: 20
type: integer
total:
example: 42
type: integer
type: object
api.NoteRequest:
properties:
content:
example: 牛奶、鸡蛋
type: string
title:
example: 购物清单
maxLength: 200
type: string
required:
- title
type: object
api.UserCreateRequest:
properties:
avatar:
example: https://example.com/avatar.png
maxLength: 255
type: string
email:
example: alice@example.com
maxLength: 255
type: string
group_ids:
example:
- 1
items:
type: integer
type: array
nickname:
example: Alice
maxLength: 50
type: string
password:
example: secret123
maxLength: 72
minLength: 6
type: string
status:
enum:
- 0
- 1
example: 1
type: integer
username:
example: alice
maxLength: 50
type: string
required:
- email
- password
- username
type: object
api.UserGroupListResponse:
properties:
items:
items:
$ref: '#/definitions/model.UserGroup'
type: array
page:
example: 1
type: integer
page_size:
example: 20
type: integer
total:
example: 42
type: integer
type: object
api.UserGroupRequest:
properties:
description:
example: 负责日常运营
maxLength: 255
type: string
name:
example: 运营组
maxLength: 50
type: string
required:
- name
type: object
api.UserListResponse:
properties:
items:
items:
$ref: '#/definitions/model.User'
type: array
page:
example: 1
type: integer
page_size:
example: 20
type: integer
total:
example: 42
type: integer
type: object
api.UserUpdateRequest:
properties:
avatar:
example: https://example.com/avatar.png
maxLength: 255
type: string
group_ids:
example:
- 1
items:
type: integer
type: array
nickname:
example: Alice
maxLength: 50
type: string
password:
example: secret123
maxLength: 72
minLength: 6
type: string
status:
enum:
- 0
- 1
example: 1
type: integer
type: object
model.Note:
properties:
content:
type: string
created_at:
type: string
id:
type: integer
title:
type: string
updated_at:
type: string
type: object
model.User:
properties:
avatar:
type: string
created_at:
type: string
email:
type: string
groups:
items:
$ref: '#/definitions/model.UserGroup'
type: array
id:
type: integer
nickname:
type: string
status:
type: integer
updated_at:
type: string
username:
type: string
type: object
model.UserGroup:
properties:
created_at:
type: string
description:
type: string
id:
type: integer
is_system:
type: boolean
name:
type: string
updated_at:
type: string
type: object
info:
contact: {}
description: |-
Rill 服务端 HTTP API 文档,所有接口以配置项 api.prefix(默认 /api)为前缀,请求与响应均为 JSON。
Swagger 页面:{prefix}/swagger/index.htmlOpenAPI JSON{prefix}/swagger/doc.json。
title: Rill API
version: "1.0"
paths:
/health:
get:
description: 检查服务与数据库连接状态;数据库不可用时返回 503。
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/api.HealthResponse'
"503":
description: Service Unavailable
schema:
$ref: '#/definitions/api.HealthResponse'
summary: Health check
tags:
- system
/notes:
get:
description: 分页查询便签列表,按 id 倒序返回。page 从 1 开始;page_size 取值 1-100,默认 20。
parameters:
- description: 页码,默认 1
example: 1
in: query
name: page
type: integer
- description: 每页数量,默认 20,最大 100
example: 20
in: query
name: page_size
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/api.NoteListResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/api.ErrorResponse'
summary: List notes
tags:
- notes
post:
consumes:
- application/json
description: 创建便签。title 必填且最长 200 字符,content 可选。
parameters:
- description: 便签内容
in: body
name: note
required: true
schema:
$ref: '#/definitions/api.NoteRequest'
produces:
- application/json
responses:
"201":
description: Created
schema:
$ref: '#/definitions/model.Note'
"400":
description: 参数无效
schema:
$ref: '#/definitions/api.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/api.ErrorResponse'
summary: Create a note
tags:
- notes
/notes/{id}:
delete:
description: 按 id 删除便签,成功时返回 204 且无响应体。
parameters:
- description: 便签 ID
example: 1
in: path
name: id
required: true
type: integer
produces:
- application/json
responses:
"204":
description: 删除成功
"400":
description: id 无效
schema:
$ref: '#/definitions/api.ErrorResponse'
"404":
description: 记录不存在
schema:
$ref: '#/definitions/api.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/api.ErrorResponse'
summary: Delete a note
tags:
- notes
get:
description: 按 id 查询单个便签。
parameters:
- description: 便签 ID
example: 1
in: path
name: id
required: true
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/model.Note'
"400":
description: id 无效
schema:
$ref: '#/definitions/api.ErrorResponse'
"404":
description: 记录不存在
schema:
$ref: '#/definitions/api.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/api.ErrorResponse'
summary: Get a note
tags:
- notes
put:
consumes:
- application/json
description: 全量更新便签的 title 与 content,字段校验规则同创建。
parameters:
- description: 便签 ID
example: 1
in: path
name: id
required: true
type: integer
- description: 便签内容
in: body
name: note
required: true
schema:
$ref: '#/definitions/api.NoteRequest'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/model.Note'
"400":
description: 参数无效或 id 无效
schema:
$ref: '#/definitions/api.ErrorResponse'
"404":
description: 记录不存在
schema:
$ref: '#/definitions/api.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/api.ErrorResponse'
summary: Update a note
tags:
- notes
/user-groups:
get:
description: 分页查询用户组列表,按 id 升序返回。page 从 1 开始;page_size 取值 1-100,默认 20。
parameters:
- description: 页码,默认 1
example: 1
in: query
name: page
type: integer
- description: 每页数量,默认 20,最大 100
example: 20
in: query
name: page_size
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/api.UserGroupListResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/api.ErrorResponse'
summary: List user groups
tags:
- user-groups
post:
consumes:
- application/json
description: 创建用户组。name 必填且唯一(最长 50 字符),description 可选(最长 255 字符);id 由服务端分配。
parameters:
- description: 用户组信息
in: body
name: group
required: true
schema:
$ref: '#/definitions/api.UserGroupRequest'
produces:
- application/json
responses:
"201":
description: Created
schema:
$ref: '#/definitions/model.UserGroup'
"400":
description: 参数无效
schema:
$ref: '#/definitions/api.ErrorResponse'
"409":
description: 用户组名称已存在
schema:
$ref: '#/definitions/api.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/api.ErrorResponse'
summary: Create a user group
tags:
- user-groups
/user-groups/{id}:
delete:
description: 按 id 删除用户组,成功时返回 204 且无响应体。系统内置组或组内仍有用户时返回 409。
parameters:
- description: 用户组 ID
example: 2
in: path
name: id
required: true
type: integer
produces:
- application/json
responses:
"204":
description: 删除成功
"400":
description: id 无效
schema:
$ref: '#/definitions/api.ErrorResponse'
"404":
description: 记录不存在
schema:
$ref: '#/definitions/api.ErrorResponse'
"409":
description: 系统内置组不可删除或用户组内仍有用户
schema:
$ref: '#/definitions/api.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/api.ErrorResponse'
summary: Delete a user group
tags:
- user-groups
get:
description: 按 id 查询单个用户组,id 0 为内置 admin 组。
parameters:
- description: 用户组 ID
example: 1
in: path
name: id
required: true
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/model.UserGroup'
"400":
description: id 无效
schema:
$ref: '#/definitions/api.ErrorResponse'
"404":
description: 记录不存在
schema:
$ref: '#/definitions/api.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/api.ErrorResponse'
summary: Get a user group
tags:
- user-groups
put:
consumes:
- application/json
description: 更新用户组的 name 与 descriptionname 必填且唯一。
parameters:
- description: 用户组 ID
example: 1
in: path
name: id
required: true
type: integer
- description: 用户组信息
in: body
name: group
required: true
schema:
$ref: '#/definitions/api.UserGroupRequest'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/model.UserGroup'
"400":
description: 参数无效或 id 无效
schema:
$ref: '#/definitions/api.ErrorResponse'
"404":
description: 记录不存在
schema:
$ref: '#/definitions/api.ErrorResponse'
"409":
description: 用户组名称已存在
schema:
$ref: '#/definitions/api.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/api.ErrorResponse'
summary: Update a user group
tags:
- user-groups
/users:
get:
description: 分页查询用户列表(含所属用户组),按 id 倒序返回。page 从 1 开始;page_size 取值 1-100,默认 20。
parameters:
- description: 页码,默认 1
example: 1
in: query
name: page
type: integer
- description: 每页数量,默认 20,最大 100
example: 20
in: query
name: page_size
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/api.UserListResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/api.ErrorResponse'
summary: List users
tags:
- users
post:
consumes:
- application/json
description: 创建用户并关联用户组。username、email 唯一,password 长度 6-72;不传 group_ids 时默认加入普通用户组(id
1)。
parameters:
- description: 用户信息
in: body
name: user
required: true
schema:
$ref: '#/definitions/api.UserCreateRequest'
produces:
- application/json
responses:
"201":
description: Created
schema:
$ref: '#/definitions/model.User'
"400":
description: 参数无效或用户组不存在
schema:
$ref: '#/definitions/api.ErrorResponse'
"409":
description: 用户名或邮箱已存在
schema:
$ref: '#/definitions/api.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/api.ErrorResponse'
summary: Create a user
tags:
- users
/users/{id}:
delete:
description: 按 id 删除用户及其用户组成员关系,成功时返回 204 且无响应体。
parameters:
- description: 用户 ID
example: 1
in: path
name: id
required: true
type: integer
produces:
- application/json
responses:
"204":
description: 删除成功
"400":
description: id 无效
schema:
$ref: '#/definitions/api.ErrorResponse'
"404":
description: 记录不存在
schema:
$ref: '#/definitions/api.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/api.ErrorResponse'
summary: Delete a user
tags:
- users
get:
description: 按 id 查询用户(含所属用户组)。
parameters:
- description: 用户 ID
example: 1
in: path
name: id
required: true
type: integer
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/model.User'
"400":
description: id 无效
schema:
$ref: '#/definitions/api.ErrorResponse'
"404":
description: 记录不存在
schema:
$ref: '#/definitions/api.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/api.ErrorResponse'
summary: Get a user
tags:
- users
put:
consumes:
- application/json
description: 更新用户信息,仅更新请求中提供的字段。传 group_ids 会整体替换用户组;password 非空时重置密码。
parameters:
- description: 用户 ID
example: 1
in: path
name: id
required: true
type: integer
- description: 待更新字段
in: body
name: user
required: true
schema:
$ref: '#/definitions/api.UserUpdateRequest'
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/model.User'
"400":
description: 参数无效、id 无效或用户组不存在
schema:
$ref: '#/definitions/api.ErrorResponse'
"404":
description: 记录不存在
schema:
$ref: '#/definitions/api.ErrorResponse'
"500":
description: Internal Server Error
schema:
$ref: '#/definitions/api.ErrorResponse'
summary: Update a user
tags:
- users
swagger: "2.0"
+23
View File
@@ -7,6 +7,9 @@ require (
github.com/gin-gonic/gin v1.12.0
github.com/glebarez/sqlite v1.11.0
github.com/goccy/go-yaml v1.19.2
github.com/swaggo/files v1.0.1
github.com/swaggo/gin-swagger v1.6.1
github.com/swaggo/swag v1.16.6
golang.org/x/crypto v0.55.0
gorm.io/driver/mysql v1.6.0
gorm.io/gorm v1.31.2
@@ -14,14 +17,22 @@ require (
require (
filippo.io/edwards25519 v1.1.0 // indirect
github.com/KyleBanks/depth v1.2.1 // indirect
github.com/PuerkitoBio/purell v1.1.1 // indirect
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect
github.com/bytedance/gopkg v0.1.4 // indirect
github.com/bytedance/sonic v1.15.2 // indirect
github.com/bytedance/sonic/loader v0.5.1 // indirect
github.com/cloudwego/base64x v0.1.7 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
github.com/gin-contrib/sse v1.1.1 // indirect
github.com/glebarez/go-sqlite v1.21.2 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.6 // indirect
github.com/go-openapi/spec v0.20.4 // indirect
github.com/go-openapi/swag v0.19.15 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.30.3 // indirect
@@ -30,9 +41,11 @@ require (
github.com/google/uuid v1.3.0 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.4.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mailru/easyjson v0.7.6 // indirect
github.com/mattn/go-isatty v0.0.23 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
@@ -40,16 +53,26 @@ require (
github.com/quic-go/qpack v0.6.0 // indirect
github.com/quic-go/quic-go v0.60.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/russross/blackfriday/v2 v2.0.1 // indirect
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.3.1 // indirect
github.com/urfave/cli/v2 v2.3.0 // indirect
go.mongodb.org/mongo-driver/v2 v2.8.0 // indirect
golang.org/x/arch v0.29.0 // indirect
golang.org/x/mod v0.38.0 // indirect
golang.org/x/net v0.57.0 // indirect
golang.org/x/sync v0.22.0 // indirect
golang.org/x/sys v0.47.0 // indirect
golang.org/x/text v0.41.0 // indirect
golang.org/x/tools v0.48.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
modernc.org/libc v1.22.5 // indirect
modernc.org/mathutil v1.5.0 // indirect
modernc.org/memory v1.5.0 // indirect
modernc.org/sqlite v1.23.1 // indirect
sigs.k8s.io/yaml v1.3.0 // indirect
)
tool github.com/swaggo/swag/cmd/swag
+98
View File
@@ -1,5 +1,12 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M=
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
github.com/bytedance/gopkg v0.1.4 h1:oZnQwnX82KAIWb7033bEwtxvTqXcYMxDBaQxo5JJHWM=
github.com/bytedance/gopkg v0.1.4/go.mod h1:v1zWfPm21Fb+OsyXN2VAHdL6TBb2L88anLQgdyje6R4=
github.com/bytedance/sonic v1.15.2 h1:90H+rcF/FwLXwfB1cudOLq/je83n683Utf4Cbp0xHCo=
@@ -8,6 +15,9 @@ github.com/bytedance/sonic/loader v0.5.1 h1:Ygpfa9zwRCCKSlrp5bBP/b/Xzc3VxsAW+5NI
github.com/bytedance/sonic/loader v0.5.1/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
github.com/cloudwego/base64x v0.1.7 h1:NppS+Fgzg5ovhn4NkUXaDT3x9jldgH5ToMCqzBSi2zI=
github.com/cloudwego/base64x v0.1.7/go.mod h1:Cu1PV9zfrSf7ET2tIbWbbEy7jO7HHJ13q4X2SQ8aWYg=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -17,6 +27,8 @@ github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9
github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/gin-contrib/cors v1.7.8 h1:U0jjyXlWXMEx27hFE6hmdg/TOZviYgsOGGT+kLmUZrc=
github.com/gin-contrib/cors v1.7.8/go.mod h1:u3nLI2pP1IlJn7tbvL7iiubDO9quBZ8FXP9LxmqpsPI=
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
github.com/gin-contrib/sse v1.1.1 h1:uGYpNwTacv5R68bSGMapo62iLTRa9l5zxGCps4hK6ko=
github.com/gin-contrib/sse v1.1.1/go.mod h1:QXzuVkA0YO7o/gun03UI1Q+FTI8ZV/n5t03kIQAI89s=
github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8=
@@ -25,6 +37,16 @@ github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9g
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs=
github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns=
github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M=
github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
@@ -50,12 +72,25 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.4.0 h1:S6Hrbc7+ywsr0r+RLapfGBHfyefhCTwEh3A0tV913Dw=
github.com/klauspost/cpuid/v2 v2.4.0/go.mod h1:19jmZ9mjzoF//ddRSUsv0zfBTJWh3QJh9FNxZTMrGxU=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-isatty v0.0.23 h1:cYwCQTQf3HB6xUC+BtyCLZNr7IzbOmoZbmssVNzSyiQ=
github.com/mattn/go-isatty v0.0.23/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A=
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
@@ -65,6 +100,7 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pelletier/go-toml/v2 v2.4.3 h1:GTRvJQutkOSftxIFD5xw9aepkYNuPWmVJpffdDPYVpY=
github.com/pelletier/go-toml/v2 v2.4.3/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -78,39 +114,99 @@ github.com/quic-go/quic-go v0.60.0/go.mod h1:wpKpjmPpftl30sL6pFh7REVpjbcCVy4zt2v
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=
github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg=
github.com/swaggo/gin-swagger v1.6.1 h1:Ri06G4gc9N4t4k8hekMigJ9zKTFSlqj/9paAQCQs7cY=
github.com/swaggo/gin-swagger v1.6.1/go.mod h1:LQ+hJStHakCWRiK/YNYtJOu4mR2FP+pxLnILT/qNiTw=
github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI=
github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.mongodb.org/mongo-driver/v2 v2.8.0 h1:CxWDGQYY8QQwNjAl/aq2sfWakdnWZynnqJ9F4DhHbP8=
go.mongodb.org/mongo-driver/v2 v2.8.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0=
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
golang.org/x/arch v0.29.0 h1:8sSET5wB0+exBm0FGmOtdHMqjlRdV2DRD3/IV6OZgho=
golang.org/x/arch v0.29.0/go.mod h1:0X+GdSIP+kL5wPmpK7sdkEVTt2XoYP0cSjQSbZBwOi8=
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.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M=
golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.38.0 h1:MECBjubtXD7yj4HrhIUcywNaGeNVUdfVnxmPajOk4yk=
golang.org/x/mod v0.38.0/go.mod h1:V6Xz0pq8TQ3dGqVQ1FVHuelZpAL0uNhSkk9ogYP3c40=
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-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE=
golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU=
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.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
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-20210420072515-93ed5bcd2bfe/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-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
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/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.6/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.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=
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.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE=
golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg=
@@ -127,3 +223,5 @@ modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
modernc.org/sqlite v1.23.1 h1:nrSBg4aRQQwq59JpvGEQ15tNxoO5pX/kUjcRNwSAGQM=
modernc.org/sqlite v1.23.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk=
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
+33 -4
View File
@@ -4,9 +4,12 @@ package api
import (
"errors"
"net/http"
"path"
"strconv"
"github.com/gin-gonic/gin"
swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger"
"gorm.io/gorm"
"rill/internal/database"
@@ -16,6 +19,14 @@ import (
func RegisterRoutes(rg *gin.RouterGroup, db *gorm.DB) {
rg.GET("/health", health(db))
swagger := rg.Group("/swagger")
{
swagger.GET("", func(c *gin.Context) {
c.Redirect(http.StatusFound, path.Join("/", rg.BasePath(), "swagger", "index.html"))
})
swagger.GET("/*any", ginSwagger.WrapHandler(swaggerFiles.Handler, ginSwagger.URL("doc.json")))
}
notes := rg.Group("/notes")
{
notes.GET("", listNotes(db))
@@ -59,7 +70,7 @@ func parsePagination(c *gin.Context) (int, int) {
// respondGetError 查询类错误:记录不存在返回 404,其余按数据库错误处理。
func respondGetError(c *gin.Context, err error) {
if errors.Is(err, gorm.ErrRecordNotFound) {
c.JSON(http.StatusNotFound, gin.H{"error": "记录不存在"})
c.JSON(http.StatusNotFound, ErrorResponse{Error: "记录不存在"})
return
}
respondDBError(c, err)
@@ -68,18 +79,36 @@ func respondGetError(c *gin.Context, err error) {
// respondDuplicateOrDBError 写入类错误:唯一约束冲突返回 409,其余按数据库错误处理。
func respondDuplicateOrDBError(c *gin.Context, err error, duplicateMsg string) {
if errors.Is(err, gorm.ErrDuplicatedKey) {
c.JSON(http.StatusConflict, gin.H{"error": duplicateMsg})
c.JSON(http.StatusConflict, ErrorResponse{Error: duplicateMsg})
return
}
respondDBError(c, err)
}
// ErrorResponse 统一错误响应。
type ErrorResponse struct {
Error string `json:"error" example:"记录不存在"`
}
// HealthResponse 健康检查响应。
type HealthResponse struct {
Status string `json:"status" example:"ok"`
Error string `json:"error,omitempty" example:"数据库不可用"`
}
// @Summary Health check
// @Description 检查服务与数据库连接状态;数据库不可用时返回 503。
// @Tags system
// @Produce json
// @Success 200 {object} api.HealthResponse
// @Failure 503 {object} api.HealthResponse
// @Router /health [get]
func health(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
if err := database.Ping(c.Request.Context(), db); err != nil {
c.JSON(http.StatusServiceUnavailable, gin.H{"status": "error", "error": "数据库不可用"})
c.JSON(http.StatusServiceUnavailable, HealthResponse{Status: "error", Error: "数据库不可用"})
return
}
c.JSON(http.StatusOK, gin.H{"status": "ok"})
c.JSON(http.StatusOK, HealthResponse{Status: "ok"})
}
}
+77 -17
View File
@@ -17,11 +17,29 @@ const (
maxPageSize = 100
)
type noteRequest struct {
Title string `json:"title" binding:"required,max=200"`
Content string `json:"content"`
// NoteRequest 创建/更新便签请求。
type NoteRequest struct {
Title string `json:"title" binding:"required,max=200" example:"购物清单"`
Content string `json:"content" example:"牛奶、鸡蛋"`
}
// NoteListResponse 便签分页列表响应。
type NoteListResponse struct {
Items []model.Note `json:"items"`
Total int64 `json:"total" example:"42"`
Page int `json:"page" example:"1"`
PageSize int `json:"page_size" example:"20"`
}
// @Summary List notes
// @Description 分页查询便签列表,按 id 倒序返回。page 从 1 开始;page_size 取值 1-100,默认 20。
// @Tags notes
// @Produce json
// @Param page query int false "页码,默认 1" example(1)
// @Param page_size query int false "每页数量,默认 20,最大 100" example(20)
// @Success 200 {object} api.NoteListResponse
// @Failure 500 {object} api.ErrorResponse
// @Router /notes [get]
func listNotes(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
page, pageSize := parsePagination(c)
@@ -43,20 +61,30 @@ func listNotes(db *gorm.DB) gin.HandlerFunc {
return
}
c.JSON(http.StatusOK, gin.H{
"items": notes,
"total": total,
"page": page,
"page_size": pageSize,
c.JSON(http.StatusOK, NoteListResponse{
Items: notes,
Total: total,
Page: page,
PageSize: pageSize,
})
}
}
// @Summary Create a note
// @Description 创建便签。title 必填且最长 200 字符,content 可选。
// @Tags notes
// @Accept json
// @Produce json
// @Param note body api.NoteRequest true "便签内容"
// @Success 201 {object} model.Note
// @Failure 400 {object} api.ErrorResponse "参数无效"
// @Failure 500 {object} api.ErrorResponse
// @Router /notes [post]
func createNote(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
var req noteRequest
var req NoteRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "参数无效: " + err.Error()})
c.JSON(http.StatusBadRequest, ErrorResponse{Error: "参数无效: " + err.Error()})
return
}
@@ -69,6 +97,16 @@ func createNote(db *gorm.DB) gin.HandlerFunc {
}
}
// @Summary Get a note
// @Description 按 id 查询单个便签。
// @Tags notes
// @Produce json
// @Param id path int true "便签 ID" example(1)
// @Success 200 {object} model.Note
// @Failure 400 {object} api.ErrorResponse "id 无效"
// @Failure 404 {object} api.ErrorResponse "记录不存在"
// @Failure 500 {object} api.ErrorResponse
// @Router /notes/{id} [get]
func getNote(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
id, ok := parseID(c)
@@ -79,7 +117,7 @@ func getNote(db *gorm.DB) gin.HandlerFunc {
var note model.Note
if err := db.WithContext(c.Request.Context()).First(&note, id).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
c.JSON(http.StatusNotFound, gin.H{"error": "记录不存在"})
c.JSON(http.StatusNotFound, ErrorResponse{Error: "记录不存在"})
return
}
respondDBError(c, err)
@@ -89,6 +127,18 @@ func getNote(db *gorm.DB) gin.HandlerFunc {
}
}
// @Summary Update a note
// @Description 全量更新便签的 title 与 content,字段校验规则同创建。
// @Tags notes
// @Accept json
// @Produce json
// @Param id path int true "便签 ID" example(1)
// @Param note body api.NoteRequest true "便签内容"
// @Success 200 {object} model.Note
// @Failure 400 {object} api.ErrorResponse "参数无效或 id 无效"
// @Failure 404 {object} api.ErrorResponse "记录不存在"
// @Failure 500 {object} api.ErrorResponse
// @Router /notes/{id} [put]
func updateNote(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
id, ok := parseID(c)
@@ -96,9 +146,9 @@ func updateNote(db *gorm.DB) gin.HandlerFunc {
return
}
var req noteRequest
var req NoteRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "参数无效: " + err.Error()})
c.JSON(http.StatusBadRequest, ErrorResponse{Error: "参数无效: " + err.Error()})
return
}
@@ -106,7 +156,7 @@ func updateNote(db *gorm.DB) gin.HandlerFunc {
var note model.Note
if err := db.WithContext(ctx).First(&note, id).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
c.JSON(http.StatusNotFound, gin.H{"error": "记录不存在"})
c.JSON(http.StatusNotFound, ErrorResponse{Error: "记录不存在"})
return
}
respondDBError(c, err)
@@ -123,6 +173,16 @@ func updateNote(db *gorm.DB) gin.HandlerFunc {
}
}
// @Summary Delete a note
// @Description 按 id 删除便签,成功时返回 204 且无响应体。
// @Tags notes
// @Produce json
// @Param id path int true "便签 ID" example(1)
// @Success 204 "删除成功"
// @Failure 400 {object} api.ErrorResponse "id 无效"
// @Failure 404 {object} api.ErrorResponse "记录不存在"
// @Failure 500 {object} api.ErrorResponse
// @Router /notes/{id} [delete]
func deleteNote(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
id, ok := parseID(c)
@@ -136,7 +196,7 @@ func deleteNote(db *gorm.DB) gin.HandlerFunc {
return
}
if result.RowsAffected == 0 {
c.JSON(http.StatusNotFound, gin.H{"error": "记录不存在"})
c.JSON(http.StatusNotFound, ErrorResponse{Error: "记录不存在"})
return
}
c.Status(http.StatusNoContent)
@@ -146,7 +206,7 @@ func deleteNote(db *gorm.DB) gin.HandlerFunc {
func parseID(c *gin.Context) (uint, bool) {
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
if err != nil || id == 0 {
c.JSON(http.StatusBadRequest, gin.H{"error": "id 无效"})
c.JSON(http.StatusBadRequest, ErrorResponse{Error: "id 无效"})
return 0, false
}
return uint(id), true
@@ -154,5 +214,5 @@ func parseID(c *gin.Context) (uint, bool) {
func respondDBError(c *gin.Context, err error) {
slog.ErrorContext(c.Request.Context(), "数据库操作失败", "err", err, "path", c.Request.URL.Path)
c.JSON(http.StatusInternalServerError, gin.H{"error": "服务器内部错误"})
c.JSON(http.StatusInternalServerError, ErrorResponse{Error: "服务器内部错误"})
}
+78 -15
View File
@@ -11,11 +11,29 @@ import (
"rill/internal/model"
)
type userGroupRequest struct {
Name string `json:"name" binding:"required,max=50"`
Description string `json:"description" binding:"max=255"`
// UserGroupRequest 创建/更新用户组请求。
type UserGroupRequest struct {
Name string `json:"name" binding:"required,max=50" example:"运营组"`
Description string `json:"description" binding:"max=255" example:"负责日常运营"`
}
// UserGroupListResponse 用户组分页列表响应。
type UserGroupListResponse struct {
Items []model.UserGroup `json:"items"`
Total int64 `json:"total" example:"42"`
Page int `json:"page" example:"1"`
PageSize int `json:"page_size" example:"20"`
}
// @Summary List user groups
// @Description 分页查询用户组列表,按 id 升序返回。page 从 1 开始;page_size 取值 1-100,默认 20。
// @Tags user-groups
// @Produce json
// @Param page query int false "页码,默认 1" example(1)
// @Param page_size query int false "每页数量,默认 20,最大 100" example(20)
// @Success 200 {object} api.UserGroupListResponse
// @Failure 500 {object} api.ErrorResponse
// @Router /user-groups [get]
func listUserGroups(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
page, pageSize := parsePagination(c)
@@ -37,20 +55,31 @@ func listUserGroups(db *gorm.DB) gin.HandlerFunc {
return
}
c.JSON(http.StatusOK, gin.H{
"items": groups,
"total": total,
"page": page,
"page_size": pageSize,
c.JSON(http.StatusOK, UserGroupListResponse{
Items: groups,
Total: total,
Page: page,
PageSize: pageSize,
})
}
}
// @Summary Create a user group
// @Description 创建用户组。name 必填且唯一(最长 50 字符),description 可选(最长 255 字符);id 由服务端分配。
// @Tags user-groups
// @Accept json
// @Produce json
// @Param group body api.UserGroupRequest true "用户组信息"
// @Success 201 {object} model.UserGroup
// @Failure 400 {object} api.ErrorResponse "参数无效"
// @Failure 409 {object} api.ErrorResponse "用户组名称已存在"
// @Failure 500 {object} api.ErrorResponse
// @Router /user-groups [post]
func createUserGroup(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
var req userGroupRequest
var req UserGroupRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "参数无效: " + err.Error()})
c.JSON(http.StatusBadRequest, ErrorResponse{Error: "参数无效: " + err.Error()})
return
}
@@ -76,6 +105,16 @@ func createUserGroup(db *gorm.DB) gin.HandlerFunc {
}
}
// @Summary Get a user group
// @Description 按 id 查询单个用户组,id 0 为内置 admin 组。
// @Tags user-groups
// @Produce json
// @Param id path int true "用户组 ID" example(1)
// @Success 200 {object} model.UserGroup
// @Failure 400 {object} api.ErrorResponse "id 无效"
// @Failure 404 {object} api.ErrorResponse "记录不存在"
// @Failure 500 {object} api.ErrorResponse
// @Router /user-groups/{id} [get]
func getUserGroup(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
id, ok := parseGroupID(c)
@@ -92,6 +131,19 @@ func getUserGroup(db *gorm.DB) gin.HandlerFunc {
}
}
// @Summary Update a user group
// @Description 更新用户组的 name 与 descriptionname 必填且唯一。
// @Tags user-groups
// @Accept json
// @Produce json
// @Param id path int true "用户组 ID" example(1)
// @Param group body api.UserGroupRequest true "用户组信息"
// @Success 200 {object} model.UserGroup
// @Failure 400 {object} api.ErrorResponse "参数无效或 id 无效"
// @Failure 404 {object} api.ErrorResponse "记录不存在"
// @Failure 409 {object} api.ErrorResponse "用户组名称已存在"
// @Failure 500 {object} api.ErrorResponse
// @Router /user-groups/{id} [put]
func updateUserGroup(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
id, ok := parseGroupID(c)
@@ -99,9 +151,9 @@ func updateUserGroup(db *gorm.DB) gin.HandlerFunc {
return
}
var req userGroupRequest
var req UserGroupRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "参数无效: " + err.Error()})
c.JSON(http.StatusBadRequest, ErrorResponse{Error: "参数无效: " + err.Error()})
return
}
@@ -122,6 +174,17 @@ func updateUserGroup(db *gorm.DB) gin.HandlerFunc {
}
}
// @Summary Delete a user group
// @Description 按 id 删除用户组,成功时返回 204 且无响应体。系统内置组或组内仍有用户时返回 409。
// @Tags user-groups
// @Produce json
// @Param id path int true "用户组 ID" example(2)
// @Success 204 "删除成功"
// @Failure 400 {object} api.ErrorResponse "id 无效"
// @Failure 404 {object} api.ErrorResponse "记录不存在"
// @Failure 409 {object} api.ErrorResponse "系统内置组不可删除或用户组内仍有用户"
// @Failure 500 {object} api.ErrorResponse
// @Router /user-groups/{id} [delete]
func deleteUserGroup(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
id, ok := parseGroupID(c)
@@ -136,7 +199,7 @@ func deleteUserGroup(db *gorm.DB) gin.HandlerFunc {
return
}
if group.IsSystem {
c.JSON(http.StatusConflict, gin.H{"error": "系统内置组不可删除"})
c.JSON(http.StatusConflict, ErrorResponse{Error: "系统内置组不可删除"})
return
}
@@ -148,7 +211,7 @@ func deleteUserGroup(db *gorm.DB) gin.HandlerFunc {
return
}
if members > 0 {
c.JSON(http.StatusConflict, gin.H{"error": "用户组内仍有用户,无法删除"})
c.JSON(http.StatusConflict, ErrorResponse{Error: "用户组内仍有用户,无法删除"})
return
}
@@ -164,7 +227,7 @@ func deleteUserGroup(db *gorm.DB) gin.HandlerFunc {
func parseGroupID(c *gin.Context) (uint, bool) {
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "id 无效"})
c.JSON(http.StatusBadRequest, ErrorResponse{Error: "id 无效"})
return 0, false
}
return uint(id), true
+88 -26
View File
@@ -15,24 +15,43 @@ import (
const userGroupMembersTable = "user_group_members"
type userCreateRequest struct {
Username string `json:"username" binding:"required,max=50"`
Email string `json:"email" binding:"required,email,max=255"`
Password string `json:"password" binding:"required,min=6,max=72"`
Nickname string `json:"nickname" binding:"max=50"`
Avatar string `json:"avatar" binding:"max=255"`
Status *int8 `json:"status" binding:"omitempty,oneof=0 1"`
GroupIDs []uint `json:"group_ids"`
// UserCreateRequest 创建用户请求。
type UserCreateRequest struct {
Username string `json:"username" binding:"required,max=50" example:"alice"`
Email string `json:"email" binding:"required,email,max=255" example:"alice@example.com"`
Password string `json:"password" binding:"required,min=6,max=72" example:"secret123"`
Nickname string `json:"nickname" binding:"max=50" example:"Alice"`
Avatar string `json:"avatar" binding:"max=255" example:"https://example.com/avatar.png"`
Status *int8 `json:"status" binding:"omitempty,oneof=0 1" example:"1"`
GroupIDs []uint `json:"group_ids" example:"1"`
}
type userUpdateRequest struct {
Nickname string `json:"nickname" binding:"max=50"`
Avatar string `json:"avatar" binding:"max=255"`
Status *int8 `json:"status" binding:"omitempty,oneof=0 1"`
Password string `json:"password" binding:"omitempty,min=6,max=72"`
GroupIDs *[]uint `json:"group_ids"`
// UserUpdateRequest 更新用户请求,仅更新请求中提供的字段。
type UserUpdateRequest struct {
Nickname string `json:"nickname" binding:"max=50" example:"Alice"`
Avatar string `json:"avatar" binding:"max=255" example:"https://example.com/avatar.png"`
Status *int8 `json:"status" binding:"omitempty,oneof=0 1" example:"1"`
Password string `json:"password" binding:"omitempty,min=6,max=72" example:"secret123"`
GroupIDs *[]uint `json:"group_ids" example:"1"`
}
// UserListResponse 用户分页列表响应。
type UserListResponse struct {
Items []model.User `json:"items"`
Total int64 `json:"total" example:"42"`
Page int `json:"page" example:"1"`
PageSize int `json:"page_size" example:"20"`
}
// @Summary List users
// @Description 分页查询用户列表(含所属用户组),按 id 倒序返回。page 从 1 开始;page_size 取值 1-100,默认 20。
// @Tags users
// @Produce json
// @Param page query int false "页码,默认 1" example(1)
// @Param page_size query int false "每页数量,默认 20,最大 100" example(20)
// @Success 200 {object} api.UserListResponse
// @Failure 500 {object} api.ErrorResponse
// @Router /users [get]
func listUsers(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
page, pageSize := parsePagination(c)
@@ -58,20 +77,31 @@ func listUsers(db *gorm.DB) gin.HandlerFunc {
return
}
c.JSON(http.StatusOK, gin.H{
"items": users,
"total": total,
"page": page,
"page_size": pageSize,
c.JSON(http.StatusOK, UserListResponse{
Items: users,
Total: total,
Page: page,
PageSize: pageSize,
})
}
}
// @Summary Create a user
// @Description 创建用户并关联用户组。username、email 唯一,password 长度 6-72;不传 group_ids 时默认加入普通用户组(id 1)。
// @Tags users
// @Accept json
// @Produce json
// @Param user body api.UserCreateRequest true "用户信息"
// @Success 201 {object} model.User
// @Failure 400 {object} api.ErrorResponse "参数无效或用户组不存在"
// @Failure 409 {object} api.ErrorResponse "用户名或邮箱已存在"
// @Failure 500 {object} api.ErrorResponse
// @Router /users [post]
func createUser(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
var req userCreateRequest
var req UserCreateRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "参数无效: " + err.Error()})
c.JSON(http.StatusBadRequest, ErrorResponse{Error: "参数无效: " + err.Error()})
return
}
@@ -94,7 +124,7 @@ func createUser(db *gorm.DB) gin.HandlerFunc {
groups, err := findGroups(ctx, db, groupIDs)
if err != nil {
if errors.Is(err, errGroupsNotFound) {
c.JSON(http.StatusBadRequest, gin.H{"error": errGroupsNotFound.Error()})
c.JSON(http.StatusBadRequest, ErrorResponse{Error: errGroupsNotFound.Error()})
return
}
respondDBError(c, err)
@@ -124,6 +154,16 @@ func createUser(db *gorm.DB) gin.HandlerFunc {
}
}
// @Summary Get a user
// @Description 按 id 查询用户(含所属用户组)。
// @Tags users
// @Produce json
// @Param id path int true "用户 ID" example(1)
// @Success 200 {object} model.User
// @Failure 400 {object} api.ErrorResponse "id 无效"
// @Failure 404 {object} api.ErrorResponse "记录不存在"
// @Failure 500 {object} api.ErrorResponse
// @Router /users/{id} [get]
func getUser(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
id, ok := parseID(c)
@@ -148,6 +188,18 @@ func getUser(db *gorm.DB) gin.HandlerFunc {
}
}
// @Summary Update a user
// @Description 更新用户信息,仅更新请求中提供的字段。传 group_ids 会整体替换用户组;password 非空时重置密码。
// @Tags users
// @Accept json
// @Produce json
// @Param id path int true "用户 ID" example(1)
// @Param user body api.UserUpdateRequest true "待更新字段"
// @Success 200 {object} model.User
// @Failure 400 {object} api.ErrorResponse "参数无效、id 无效或用户组不存在"
// @Failure 404 {object} api.ErrorResponse "记录不存在"
// @Failure 500 {object} api.ErrorResponse
// @Router /users/{id} [put]
func updateUser(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
id, ok := parseID(c)
@@ -155,9 +207,9 @@ func updateUser(db *gorm.DB) gin.HandlerFunc {
return
}
var req userUpdateRequest
var req UserUpdateRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "参数无效: " + err.Error()})
c.JSON(http.StatusBadRequest, ErrorResponse{Error: "参数无效: " + err.Error()})
return
}
@@ -171,7 +223,7 @@ func updateUser(db *gorm.DB) gin.HandlerFunc {
if req.GroupIDs != nil {
if _, err := findGroups(ctx, db, *req.GroupIDs); err != nil {
if errors.Is(err, errGroupsNotFound) {
c.JSON(http.StatusBadRequest, gin.H{"error": errGroupsNotFound.Error()})
c.JSON(http.StatusBadRequest, ErrorResponse{Error: errGroupsNotFound.Error()})
return
}
respondDBError(c, err)
@@ -225,6 +277,16 @@ func updateUser(db *gorm.DB) gin.HandlerFunc {
}
}
// @Summary Delete a user
// @Description 按 id 删除用户及其用户组成员关系,成功时返回 204 且无响应体。
// @Tags users
// @Produce json
// @Param id path int true "用户 ID" example(1)
// @Success 204 "删除成功"
// @Failure 400 {object} api.ErrorResponse "id 无效"
// @Failure 404 {object} api.ErrorResponse "记录不存在"
// @Failure 500 {object} api.ErrorResponse
// @Router /users/{id} [delete]
func deleteUser(db *gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
id, ok := parseID(c)
@@ -373,5 +435,5 @@ func hashPassword(password string) (string, error) {
func respondHashError(c *gin.Context, err error) {
slog.ErrorContext(c.Request.Context(), "生成密码哈希失败", "err", err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "服务器内部错误"})
c.JSON(http.StatusInternalServerError, ErrorResponse{Error: "服务器内部错误"})
}
+11
View File
@@ -19,11 +19,19 @@ import (
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
"rill/docs"
"rill/internal/api"
"rill/internal/config"
"rill/internal/database"
)
//go:generate go tool swag init -g main.go -o docs --parseInternal
// @title Rill API
// @version 1.0
// @description Rill 服务端 HTTP API 文档,所有接口以配置项 api.prefix(默认 /api)为前缀,请求与响应均为 JSON。
// @description Swagger 页面:{prefix}/swagger/index.htmlOpenAPI JSON{prefix}/swagger/doc.json。
// @BasePath /api
func main() {
configPath := flag.String("c", "data/config.yaml", "配置文件路径(不存在时自动生成)")
flag.Parse()
@@ -35,6 +43,9 @@ func main() {
}
slog.SetDefault(slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: cfg.LogLevel()})))
// 接口文档路径跟随 API 前缀
docs.SwaggerInfo.BasePath = cfg.API.Prefix
//设置gin运行模式
gin.SetMode(cfg.Server.Mode)