增加 Swagger API 文档页面
This commit is contained in:
10 files changed
+3284
-62
No files matched your search
+1089
File diff suppressed because it is too large.
Load diff
+1064
File diff suppressed because it is too large.
Load diff
@@ -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.html;OpenAPI 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 与 description,name 必填且唯一。
|
||||
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"
|
||||
@@ -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
|
||||
@@ -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
@@ -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
@@ -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(¬e, 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(¬e, 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
@@ -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 与 description,name 必填且唯一。
|
||||
// @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
@@ -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: "服务器内部错误"})
|
||||
}
|
||||
@@ -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.html;OpenAPI 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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user