新增文件上传接口与用户头像裁剪上传
- 新增 storage 配置(存储目录、单文件大小上限),ConfigVersion 3→4 自动补全 - internal/file:上传(sha256 秒传去重)、删除(上传者或管理员,引用中 409)、公开查看;本地存储 + 操作日志 + 引用计数,仅安全类型内联防存储型 XSS - internal/avatar:PUT/DELETE /api/me/avatar,自动管理头像文件引用与旧头像解绑 - 前端引入 vue-advanced-cropper,个人中心支持上传/更换/删除头像,裁剪输出 512×512 JPEG;http 请求支持 FormData - 导出 auth.CurrentUser、新增 model.User.IsAdmin 与 testutil 多部件上传辅助,补充接口测试并重新生成 Swagger 文档
This commit is contained in:
27 files changed
+2478
-31
No files matched your search
@@ -14,6 +14,8 @@ internal/
|
||||
├── usergroup/ 用户组管理
|
||||
├── note/ 便签
|
||||
├── site/ 站点信息
|
||||
├── file/ 文件上传、删除、查看与本地存储
|
||||
├── avatar/ 当前用户头像
|
||||
├── httpx/ HTTP 公共能力:ErrorResponse、分页、ID 解析
|
||||
├── utils/ 通用工具(与业务无关的公共函数)
|
||||
├── model/ 数据库模型
|
||||
@@ -84,7 +86,7 @@ password, err := utils.RandomString(16)
|
||||
- **响应文案一律英文**,统一错误响应 `httpx.ErrorResponse`,如 `invalid request`、`record not found`、`internal server error`、`unauthorized or session expired`
|
||||
- 分页参数用 `httpx.ParsePagination(c)`;路径 ID 用 `httpx.ParseID(c)`(用户组用 `usergroup` 内部的 `parseGroupID`,允许 id 0)
|
||||
- Swagger 注解:`@Summary` / `@Description` / `@Tags`(按权限取 `public` / `user` / `admin`)/ `@Security BearerAuth` / 401、403、500 失败响应;注解修改后执行 `go generate ./...` 重新生成 `docs/`
|
||||
- 路由按权限挂载:公开(`/health`、`/swagger`、`/auth/*`、`GET /site`)、需登录、仅管理员
|
||||
- 路由按权限挂载:公开(`/health`、`/swagger`、`/auth/*`、`GET /site`、`GET /files/:id`)、需登录(个人资料、头像、文件上传删除、notes)、仅管理员
|
||||
|
||||
## 4. 数据库与迁移
|
||||
|
||||
|
||||
+339
@@ -125,6 +125,187 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/files": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Upload a file (multipart field file). Content is deduplicated by sha256; the returned file has ref_count 0 until a business reference is acquired. Size limit from storage.max_size_mb.",
|
||||
"consumes": [
|
||||
"multipart/form-data"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"user"
|
||||
],
|
||||
"summary": "Upload a file",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "file",
|
||||
"description": "File content",
|
||||
"name": "file",
|
||||
"in": "formData",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Created",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/model.File"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "invalid request or empty file",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "unauthorized or session expired",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "account disabled",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"413": {
|
||||
"description": "file too large",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/files/{id}": {
|
||||
"get": {
|
||||
"description": "Public file content. Images, videos, audio, PDF and plain text are served inline; other types are served as attachments. Disabled files return 404.",
|
||||
"produces": [
|
||||
"application/octet-stream"
|
||||
],
|
||||
"tags": [
|
||||
"public"
|
||||
],
|
||||
"summary": "Get file content",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"example": 1,
|
||||
"description": "File ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "file"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "invalid id",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "record not found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Delete a file physically and keep the record with status 0; uploader or admin only. Files still referenced (ref_count \u003e 0) return 409.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"user"
|
||||
],
|
||||
"summary": "Delete a file",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"example": 1,
|
||||
"description": "File ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "Deleted"
|
||||
},
|
||||
"400": {
|
||||
"description": "invalid id",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "unauthorized or session expired",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "permission denied or account disabled",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "record not found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"409": {
|
||||
"description": "file is in use",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/health": {
|
||||
"get": {
|
||||
"description": "Check service and database connectivity; returns 503 when the database is unavailable.",
|
||||
@@ -249,6 +430,114 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/me/avatar": {
|
||||
"put": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Upload an image as the authenticated user's avatar (multipart field file, image only). The avatar URL is stored on the user and the file reference count is managed automatically.",
|
||||
"consumes": [
|
||||
"multipart/form-data"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"user"
|
||||
],
|
||||
"summary": "Update current user avatar",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "file",
|
||||
"description": "Avatar image",
|
||||
"name": "file",
|
||||
"in": "formData",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/model.User"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "invalid request, empty file, or not an image",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "unauthorized or session expired",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "account disabled",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"413": {
|
||||
"description": "file too large",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Clear the authenticated user's avatar and release the file reference when it points to a local file.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"user"
|
||||
],
|
||||
"summary": "Delete current user avatar",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/model.User"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "unauthorized or session expired",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "account disabled",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/notes": {
|
||||
"get": {
|
||||
"security": [
|
||||
@@ -1422,6 +1711,56 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"model.File": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"created_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"extension": {
|
||||
"type": "string"
|
||||
},
|
||||
"hash": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"last_referenced_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"metadata": {
|
||||
"type": "string"
|
||||
},
|
||||
"mime_type": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"ref_count": {
|
||||
"type": "integer"
|
||||
},
|
||||
"size": {
|
||||
"type": "integer"
|
||||
},
|
||||
"status": {
|
||||
"type": "integer"
|
||||
},
|
||||
"storage": {
|
||||
"type": "string"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"uploader_id": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"model.Note": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -118,6 +118,187 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/files": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Upload a file (multipart field file). Content is deduplicated by sha256; the returned file has ref_count 0 until a business reference is acquired. Size limit from storage.max_size_mb.",
|
||||
"consumes": [
|
||||
"multipart/form-data"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"user"
|
||||
],
|
||||
"summary": "Upload a file",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "file",
|
||||
"description": "File content",
|
||||
"name": "file",
|
||||
"in": "formData",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "Created",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/model.File"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "invalid request or empty file",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "unauthorized or session expired",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "account disabled",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"413": {
|
||||
"description": "file too large",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/files/{id}": {
|
||||
"get": {
|
||||
"description": "Public file content. Images, videos, audio, PDF and plain text are served inline; other types are served as attachments. Disabled files return 404.",
|
||||
"produces": [
|
||||
"application/octet-stream"
|
||||
],
|
||||
"tags": [
|
||||
"public"
|
||||
],
|
||||
"summary": "Get file content",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"example": 1,
|
||||
"description": "File ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "file"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "invalid id",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "record not found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Delete a file physically and keep the record with status 0; uploader or admin only. Files still referenced (ref_count \u003e 0) return 409.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"user"
|
||||
],
|
||||
"summary": "Delete a file",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"example": 1,
|
||||
"description": "File ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "Deleted"
|
||||
},
|
||||
"400": {
|
||||
"description": "invalid id",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "unauthorized or session expired",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "permission denied or account disabled",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "record not found",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"409": {
|
||||
"description": "file is in use",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/health": {
|
||||
"get": {
|
||||
"description": "Check service and database connectivity; returns 503 when the database is unavailable.",
|
||||
@@ -242,6 +423,114 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/me/avatar": {
|
||||
"put": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Upload an image as the authenticated user's avatar (multipart field file, image only). The avatar URL is stored on the user and the file reference count is managed automatically.",
|
||||
"consumes": [
|
||||
"multipart/form-data"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"user"
|
||||
],
|
||||
"summary": "Update current user avatar",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "file",
|
||||
"description": "Avatar image",
|
||||
"name": "file",
|
||||
"in": "formData",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/model.User"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "invalid request, empty file, or not an image",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "unauthorized or session expired",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "account disabled",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"413": {
|
||||
"description": "file too large",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Clear the authenticated user's avatar and release the file reference when it points to a local file.",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"user"
|
||||
],
|
||||
"summary": "Delete current user avatar",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/model.User"
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "unauthorized or session expired",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"403": {
|
||||
"description": "account disabled",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/httpx.ErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/notes": {
|
||||
"get": {
|
||||
"security": [
|
||||
@@ -1415,6 +1704,56 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"model.File": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"created_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"extension": {
|
||||
"type": "string"
|
||||
},
|
||||
"hash": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"last_referenced_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"metadata": {
|
||||
"type": "string"
|
||||
},
|
||||
"mime_type": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": "string"
|
||||
},
|
||||
"ref_count": {
|
||||
"type": "integer"
|
||||
},
|
||||
"size": {
|
||||
"type": "integer"
|
||||
},
|
||||
"status": {
|
||||
"type": "integer"
|
||||
},
|
||||
"storage": {
|
||||
"type": "string"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"uploader_id": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"model.Note": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -72,6 +72,39 @@ definitions:
|
||||
example: record not found
|
||||
type: string
|
||||
type: object
|
||||
model.File:
|
||||
properties:
|
||||
created_at:
|
||||
type: string
|
||||
extension:
|
||||
type: string
|
||||
hash:
|
||||
type: string
|
||||
id:
|
||||
type: integer
|
||||
last_referenced_at:
|
||||
type: string
|
||||
metadata:
|
||||
type: string
|
||||
mime_type:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
path:
|
||||
type: string
|
||||
ref_count:
|
||||
type: integer
|
||||
size:
|
||||
type: integer
|
||||
status:
|
||||
type: integer
|
||||
storage:
|
||||
type: string
|
||||
updated_at:
|
||||
type: string
|
||||
uploader_id:
|
||||
type: integer
|
||||
type: object
|
||||
model.Note:
|
||||
properties:
|
||||
content:
|
||||
@@ -405,6 +438,129 @@ paths:
|
||||
summary: Register
|
||||
tags:
|
||||
- public
|
||||
/files:
|
||||
post:
|
||||
consumes:
|
||||
- multipart/form-data
|
||||
description: Upload a file (multipart field file). Content is deduplicated by
|
||||
sha256; the returned file has ref_count 0 until a business reference is acquired.
|
||||
Size limit from storage.max_size_mb.
|
||||
parameters:
|
||||
- description: File content
|
||||
in: formData
|
||||
name: file
|
||||
required: true
|
||||
type: file
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"201":
|
||||
description: Created
|
||||
schema:
|
||||
$ref: '#/definitions/model.File'
|
||||
"400":
|
||||
description: invalid request or empty file
|
||||
schema:
|
||||
$ref: '#/definitions/httpx.ErrorResponse'
|
||||
"401":
|
||||
description: unauthorized or session expired
|
||||
schema:
|
||||
$ref: '#/definitions/httpx.ErrorResponse'
|
||||
"403":
|
||||
description: account disabled
|
||||
schema:
|
||||
$ref: '#/definitions/httpx.ErrorResponse'
|
||||
"413":
|
||||
description: file too large
|
||||
schema:
|
||||
$ref: '#/definitions/httpx.ErrorResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/httpx.ErrorResponse'
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: Upload a file
|
||||
tags:
|
||||
- user
|
||||
/files/{id}:
|
||||
delete:
|
||||
description: Delete a file physically and keep the record with status 0; uploader
|
||||
or admin only. Files still referenced (ref_count > 0) return 409.
|
||||
parameters:
|
||||
- description: File ID
|
||||
example: 1
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"204":
|
||||
description: Deleted
|
||||
"400":
|
||||
description: invalid id
|
||||
schema:
|
||||
$ref: '#/definitions/httpx.ErrorResponse'
|
||||
"401":
|
||||
description: unauthorized or session expired
|
||||
schema:
|
||||
$ref: '#/definitions/httpx.ErrorResponse'
|
||||
"403":
|
||||
description: permission denied or account disabled
|
||||
schema:
|
||||
$ref: '#/definitions/httpx.ErrorResponse'
|
||||
"404":
|
||||
description: record not found
|
||||
schema:
|
||||
$ref: '#/definitions/httpx.ErrorResponse'
|
||||
"409":
|
||||
description: file is in use
|
||||
schema:
|
||||
$ref: '#/definitions/httpx.ErrorResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/httpx.ErrorResponse'
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: Delete a file
|
||||
tags:
|
||||
- user
|
||||
get:
|
||||
description: Public file content. Images, videos, audio, PDF and plain text
|
||||
are served inline; other types are served as attachments. Disabled files return
|
||||
404.
|
||||
parameters:
|
||||
- description: File ID
|
||||
example: 1
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: integer
|
||||
produces:
|
||||
- application/octet-stream
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
type: file
|
||||
"400":
|
||||
description: invalid id
|
||||
schema:
|
||||
$ref: '#/definitions/httpx.ErrorResponse'
|
||||
"404":
|
||||
description: record not found
|
||||
schema:
|
||||
$ref: '#/definitions/httpx.ErrorResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/httpx.ErrorResponse'
|
||||
summary: Get file content
|
||||
tags:
|
||||
- public
|
||||
/health:
|
||||
get:
|
||||
description: Check service and database connectivity; returns 503 when the database
|
||||
@@ -487,6 +643,78 @@ paths:
|
||||
summary: Update current user profile
|
||||
tags:
|
||||
- user
|
||||
/me/avatar:
|
||||
delete:
|
||||
description: Clear the authenticated user's avatar and release the file reference
|
||||
when it points to a local file.
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/model.User'
|
||||
"401":
|
||||
description: unauthorized or session expired
|
||||
schema:
|
||||
$ref: '#/definitions/httpx.ErrorResponse'
|
||||
"403":
|
||||
description: account disabled
|
||||
schema:
|
||||
$ref: '#/definitions/httpx.ErrorResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/httpx.ErrorResponse'
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: Delete current user avatar
|
||||
tags:
|
||||
- user
|
||||
put:
|
||||
consumes:
|
||||
- multipart/form-data
|
||||
description: Upload an image as the authenticated user's avatar (multipart field
|
||||
file, image only). The avatar URL is stored on the user and the file reference
|
||||
count is managed automatically.
|
||||
parameters:
|
||||
- description: Avatar image
|
||||
in: formData
|
||||
name: file
|
||||
required: true
|
||||
type: file
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/model.User'
|
||||
"400":
|
||||
description: invalid request, empty file, or not an image
|
||||
schema:
|
||||
$ref: '#/definitions/httpx.ErrorResponse'
|
||||
"401":
|
||||
description: unauthorized or session expired
|
||||
schema:
|
||||
$ref: '#/definitions/httpx.ErrorResponse'
|
||||
"403":
|
||||
description: account disabled
|
||||
schema:
|
||||
$ref: '#/definitions/httpx.ErrorResponse'
|
||||
"413":
|
||||
description: file too large
|
||||
schema:
|
||||
$ref: '#/definitions/httpx.ErrorResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/httpx.ErrorResponse'
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: Update current user avatar
|
||||
tags:
|
||||
- user
|
||||
/notes:
|
||||
get:
|
||||
description: List notes ordered by id DESC. page starts at 1; page_size is 1-100,
|
||||
|
||||
Reference in New Issue
Block a user