diff --git a/.gitignore b/.gitignore index a7074d5..8b05e2c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ dist/ gen/ bin/ +data/ *.exe *.sqlite3 diff --git a/config/api.yaml b/config/bookmark.yaml similarity index 64% rename from config/api.yaml rename to config/bookmark.yaml index 9a5fea2..e53af61 100644 --- a/config/api.yaml +++ b/config/bookmark.yaml @@ -17,6 +17,51 @@ security: - ApiKeyAuth: [] paths: + /bookmarks/v1/folder/serial: + get: + summary: 导出文件夹树结构 + description: 递归导出整个文件夹树结构并压缩返回 + operationId: exportFolderTree + tags: [folder] + responses: + '200': + description: 压缩的文件夹树数据 + content: + application/octet-stream: + schema: + type: string + format: binary + '500': + $ref: '#/components/responses/ServerInternalError' + + post: + summary: 导入文件夹树结构 + description: 上传并解压文件夹树结构数据,重建文件系统 + operationId: importFolderTree + tags: [folder] + requestBody: + required: true + content: + application/octet-stream: + schema: + type: string + format: binary + responses: + '200': + description: 导入成功 + content: + application/json: + schema: + $ref: '#/components/schemas/ImportResponse' + '400': + description: 数据格式错误 + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '500': + $ref: '#/components/responses/ServerInternalError' + /bookmarks/v1/folder: post: summary: 创建文件夹 @@ -145,6 +190,139 @@ paths: '500': $ref: '#/components/responses/ServerInternalError' + /bookmarks/v1/folder/{id}/mount: + get: + summary: 获取文件夹挂载信息 + description: 获取指定文件夹的所有挂载信息 + operationId: getFolderMounts + tags: [folder] + parameters: + - name: id + in: path + required: true + schema: + type: integer + format: int64 + description: 文件夹ID + responses: + '200': + description: 挂载信息列表 + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/MountInfo' + '404': + description: 文件夹不存在 + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '500': + $ref: '#/components/responses/ServerInternalError' + + post: + summary: 挂载文件夹 + description: 将指定文件夹挂载到目标位置 + operationId: mountFolder + tags: [folder] + parameters: + - name: id + in: path + required: true + schema: + type: integer + format: int64 + description: 要挂载的文件夹ID + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + target_folder_id: + type: integer + format: int64 + description: 目标文件夹ID(挂载点) + mount_name: + type: string + description: 挂载后的显示名称(可选,默认使用原文件夹名) + required: + - target_folder_id + responses: + '200': + description: 挂载成功 + content: + application/json: + schema: + $ref: '#/components/schemas/MountResponse' + '400': + description: 请求参数错误 + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: 文件夹不存在 + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '409': + description: 挂载冲突(如循环挂载、重复挂载等) + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '500': + $ref: '#/components/responses/ServerInternalError' + + delete: + summary: 取消挂载文件夹 + description: 取消指定文件夹的挂载关系 + operationId: unmountFolder + tags: [folder] + parameters: + - name: id + in: path + required: true + schema: + type: integer + format: int64 + description: 要取消挂载的文件夹ID + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + mount_point_id: + type: integer + format: int64 + description: 挂载点ID(从哪个位置取消挂载) + required: + - mount_point_id + responses: + '200': + description: 取消挂载成功 + '400': + description: 请求参数错误 + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '404': + description: 挂载关系不存在 + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '500': + $ref: '#/components/responses/ServerInternalError' + /bookmarks/v1/folder/{id}/content: get: summary: 获取文件夹的内容 @@ -324,7 +502,7 @@ components: ApiKeyAuth: type: apiKey in: header - name: X-API-Key + name: X-BookMark-Token responses: ServerInternalError: description: 服务器内部错误 @@ -339,6 +517,81 @@ components: schema: $ref: '#/components/schemas/Error' schemas: + ImportResponse: + type: object + properties: + imported_folders: + type: integer + description: 导入的文件夹数量 + imported_bookmarks: + type: integer + description: 导入的书签数量 + duration: + type: string + description: 导入耗时 + required: + - imported_folders + - imported_bookmarks + - duration + + MountResponse: + type: object + properties: + id: + type: integer + format: int64 + description: 挂载关系ID + source_folder_id: + type: integer + format: int64 + description: 源文件夹ID + target_folder_id: + type: integer + format: int64 + description: 目标文件夹ID(挂载点) + mount_name: + type: string + description: 挂载显示名称 + created_at: + type: string + format: date-time + description: 挂载时间 + required: + - id + - source_folder_id + - target_folder_id + - mount_name + - created_at + + MountInfo: + type: object + properties: + id: + type: integer + format: int64 + description: 挂载关系ID + source_folder: + $ref: '#/components/schemas/FolderBriefResponse' + target_folder: + $ref: '#/components/schemas/FolderBriefResponse' + mount_name: + type: string + description: 挂载显示名称 + is_mounted_here: + type: boolean + description: true表示此文件夹被挂载到target_folder,false表示此文件夹挂载了source_folder + created_at: + type: string + format: date-time + description: 挂载时间 + required: + - id + - source_folder + - target_folder + - mount_name + - is_mounted_here + - created_at + FolderRequest: description: 文件夹请求结构体 type: object @@ -349,11 +602,11 @@ components: maxLength: 255 description: 文件夹名称 example: 测试名称 - parent_path_id: + parent_id: type: integer format: int64 description: 父文件夹ID 若为空则自动创建在用户根目录下 - example: 1 + example: null required: - name @@ -376,11 +629,11 @@ components: description: type: string description: 书签描述 - parent_path_id: + parent_id: type: integer format: int64 description: 父文件夹ID 若为空则自动创建在用户根目录下 - example: 1 + example: null required: - name @@ -395,7 +648,7 @@ components: name: type: string description: 文件夹名称 - parent_path_id: + parent_id: type: integer format: int64 description: 父文件夹ID @@ -416,7 +669,6 @@ components: required: - id - name - - parent_path_id - created_at - updated_at - sub_folder_count @@ -442,7 +694,7 @@ components: description: type: string description: 书签描述 - parent_path_id: + parent_id: type: integer format: int64 description: 父文件夹ID @@ -457,7 +709,7 @@ components: required: - id - name - - parent_path_id + - parent_id - created_at - updated_at diff --git a/config/bookmark_cfg.yaml b/config/bookmark_cfg.yaml new file mode 100644 index 0000000..033f57b --- /dev/null +++ b/config/bookmark_cfg.yaml @@ -0,0 +1,6 @@ +# yaml-language-server: ... +package: api +generate: + gin-server: true + models: true +output: ./gen/bookmarks/gen.go diff --git a/config/model.conf b/config/model.conf new file mode 100644 index 0000000..6aa1ae5 --- /dev/null +++ b/config/model.conf @@ -0,0 +1,14 @@ +[request_definition] +r = sub, obj, act + +[policy_definition] +p = sub, obj, act, eft + +[role_definition] +g = _, _ + +[policy_effect] +e = some(where (p.eft == allow)) && !some(where (p.eft == deny)) + +[matchers] +m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act diff --git a/config/user.yaml b/config/user.yaml new file mode 100644 index 0000000..e69de29 diff --git a/config/vfs.yaml b/config/vfs.yaml new file mode 100644 index 0000000..91c1321 --- /dev/null +++ b/config/vfs.yaml @@ -0,0 +1,199 @@ +openapi: '3.0.3' +info: + title: zzyxyz_vfs_api + description: 虚拟文件系统API服务 + version: '1.0' +servers: + - url: http://localhost:8080/api + description: 开发环境 + - url: https://api.zzyxyz.com/api + description: 生产环境 +tags: + - name: vfs + description: 虚拟文件系统相关操作 +security: + - ApiKeyAuth: [] + +paths: + /vfs/v1/files/{path}: + parameters: + - name: path + in: path + required: true + description: 文件系统路径,例如 "documents/readme.txt" 或 "services/mysql.service" 或 "folder/" + schema: + type: string + example: "readme.txt" + + get: + summary: 读取文件或列出目录 + description: 获取指定路径的文件内容或目录列表 + operationId: getVFSNode + tags: [vfs] + parameters: + - name: op + in: query + required: false + description: 操作类型, list表示列出目录内容, 不指定则读取文件内容 + schema: + type: string + enum: [list] + responses: + '200': + description: 文件内容或目录列表 + content: + application/json: + schema: + oneOf: + - type: string + description: 文件内容 + - type: array + items: + $ref: '#/components/schemas/VFSDirectoryEntry' + description: 目录条目列表 + + '404': + description: 路径不存在 + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + + '500': + $ref: '#/components/responses/ServerInternalError' + + post: + summary: 创建文件或目录 + description: 创建文件或目录 + operationId: createVFSNode + tags: [vfs] + requestBody: + required: true + content: + application/json: + schema: + type: string + description: 文件内容(仅当type为file时有效) + responses: + '201': + description: 创建成功 + '400': + description: 请求参数错误 + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '500': + $ref: '#/components/responses/ServerInternalError' + + put: + summary: 修改文件或修改目录 + description: 修改/移动/重命名 已存在的 文件/目录 + operationId: updateVFSNode + tags: [vfs] + requestBody: + required: true + content: + application/json: + schema: + type: object + properties: + new_name: + type: string + description: 新名称(用于重命名) + new_path: + type: string + description: 新路径(用于移动) + content: + type: string + description: 新内容(用于修改文件内容) + anyOf: + - required: [new_name] + - required: [new_path] + - required: [content] + responses: + '200': + description: 操作成功 + '400': + description: 请求参数错误 + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '500': + $ref: '#/components/responses/ServerInternalError' + + delete: + summary: 删除文件或目录 + description: 删除指定路径的文件或目录 + operationId: deleteVFSNode + tags: [vfs] + responses: + '204': + description: 删除成功 + '404': + description: 路径不存在 + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '500': + $ref: '#/components/responses/ServerInternalError' + +components: + securitySchemes: + ApiKeyAuth: + type: apiKey + in: header + name: X-VFS-Token + responses: + ServerInternalError: + description: 服务器内部错误 + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + Unauthorized: + description: 未授权 + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + schemas: + VFSDirectoryEntry: + type: object + properties: + name: + type: string + description: 条目名称 + type: + type: string + enum: [file, directory, service] + description: 条目类型 + modified: + type: string + format: date-time + description: 修改时间 + permissions: + type: string + description: 权限信息,如 "rw" + required: + - name + - type + - modified + + Error: + type: object + description: 错误信息 + properties: + errtype: + type: string + example: "ParameterError" + description: 错误类型 + message: + example: "传递的第一个参数错误" + type: string + description: 错误信息 + required: + - errtype + - message diff --git a/config/cfg.yaml b/config/vfs_cfg.yaml similarity index 77% rename from config/cfg.yaml rename to config/vfs_cfg.yaml index 2b20bd3..f22a237 100644 --- a/config/cfg.yaml +++ b/config/vfs_cfg.yaml @@ -3,4 +3,4 @@ package: api generate: gin-server: true models: true -output: ./gen/api/gen.go +output: ./gen/vfs/gen.go diff --git a/go.mod b/go.mod index d3a54e4..8cec322 100644 --- a/go.mod +++ b/go.mod @@ -14,15 +14,19 @@ require ( require ( github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect + github.com/bmatcuk/doublestar/v4 v4.6.1 // indirect github.com/bytedance/gopkg v0.1.3 // indirect github.com/bytedance/sonic v1.14.1 // indirect github.com/bytedance/sonic/loader v0.3.0 // indirect + github.com/casbin/casbin/v2 v2.127.0 // indirect + github.com/casbin/govaluate v1.3.0 // indirect github.com/cloudwego/base64x v0.1.6 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect github.com/gabriel-vasile/mimetype v1.4.10 // indirect github.com/getkin/kin-openapi v0.132.0 // indirect github.com/gin-contrib/sse v1.1.0 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-openapi/jsonpointer v0.22.0 // indirect github.com/go-openapi/swag/jsonname v0.24.0 // indirect github.com/go-playground/locales v0.14.1 // indirect @@ -36,6 +40,7 @@ require ( github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect + github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/mailru/easyjson v0.9.0 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect @@ -47,11 +52,16 @@ require ( github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/perimeterx/marshmallow v1.1.5 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect + github.com/shirou/gopsutil/v3 v3.24.5 // indirect github.com/speakeasy-api/jsonpath v0.6.0 // indirect github.com/speakeasy-api/openapi-overlay v0.10.2 // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.3.0 // indirect github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect golang.org/x/arch v0.21.0 // indirect golang.org/x/crypto v0.42.0 // indirect golang.org/x/mod v0.28.0 // indirect diff --git a/go.sum b/go.sum index 4fe2596..8d60da7 100644 --- a/go.sum +++ b/go.sum @@ -1,13 +1,20 @@ github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= +github.com/bmatcuk/doublestar v1.1.1 h1:YroD6BJCZBYx06yYFEWvUuKVWQn3vLLQAVmDmvTSaiQ= github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= +github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I= +github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= github.com/bytedance/sonic v1.14.1 h1:FBMC0zVz5XUmE4z9wF4Jey0An5FueFvOsTKKKtwIl7w= github.com/bytedance/sonic v1.14.1/go.mod h1:gi6uhQLMbTdeP0muCnrjHLeCUPyb70ujhnNlhOylAFc= github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA= github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= +github.com/casbin/casbin/v2 v2.127.0 h1:UGK3uO/8cOslnNqFUJ4xzm/bh+N+o45U7cSolaFk38c= +github.com/casbin/casbin/v2 v2.127.0/go.mod h1:n4uZK8+tCMvcD6EVQZI90zKAok8iHAvEypcMJVKhGF0= +github.com/casbin/govaluate v1.3.0 h1:VA0eSY0M2lA86dYd5kPPuNZMUD9QkWnOCnavGrw9myc= +github.com/casbin/govaluate v1.3.0/go.mod h1:G/UnbIjZk/0uMNaLwZZmFQrR72tYRZWQkO70si/iR7A= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= @@ -30,6 +37,8 @@ github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ= github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-openapi/jsonpointer v0.22.0 h1:TmMhghgNef9YXxTu1tOopo+0BGEytxA+okbry0HjZsM= github.com/go-openapi/jsonpointer v0.22.0/go.mod h1:xt3jV88UtExdIkkL7NloURjRQjbeUgcxFblMjq2iaiU= github.com/go-openapi/swag/jsonname v0.24.0 h1:2wKS9bgRV/xB8c62Qg16w4AUiIrqqiniJFtZGi3dg5k= @@ -47,6 +56,7 @@ github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= @@ -60,6 +70,9 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -88,6 +101,8 @@ 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/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg= github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= @@ -132,10 +147,16 @@ github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= +github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI= +github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk= +github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= +github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= github.com/speakeasy-api/jsonpath v0.6.0 h1:IhtFOV9EbXplhyRqsVhHoBmmYjblIRh5D1/g8DHMXJ8= github.com/speakeasy-api/jsonpath v0.6.0/go.mod h1:ymb2iSkyOycmzKwbEAYPJV/yi2rSmvBCLZJcyD+VVWw= github.com/speakeasy-api/openapi-overlay v0.10.2 h1:VOdQ03eGKeiHnpb1boZCGm7x8Haj6gST0P3SGTX95GU= @@ -144,14 +165,21 @@ github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKk 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.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 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.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.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/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= 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.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA= @@ -159,6 +187,8 @@ github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2W github.com/vmware-labs/yaml-jsonpath v0.3.2 h1:/5QKeCBGdsInyDCyVNLbXyilb61MXGi9NP674f9Hobk= github.com/vmware-labs/yaml-jsonpath v0.3.2/go.mod h1:U6whw1z03QyqgWdgXxvVnQ90zN1BWz5V+51Ewf8k+rQ= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= golang.org/x/arch v0.21.0 h1:iTC9o7+wP6cPWpDWkivCvQFGAHDQ59SrSxsLPcnkArw= golang.org/x/arch v0.21.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -170,6 +200,7 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= @@ -187,17 +218,22 @@ golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/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-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -209,6 +245,7 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= diff --git a/internal/handlers/bookmark.go b/internal/handlers/bookmark.go index 636da81..9c5011c 100644 --- a/internal/handlers/bookmark.go +++ b/internal/handlers/bookmark.go @@ -3,11 +3,16 @@ package handlers import ( + "crypto/rand" + "encoding/hex" "fmt" + "log" "net/http" + "net/url" - "git.zzyxyz.com/zzy/zzyxyz_go_api/gen/api" + api "git.zzyxyz.com/zzy/zzyxyz_go_api/gen/bookmarks" "git.zzyxyz.com/zzy/zzyxyz_go_api/internal/models" + "github.com/casbin/casbin/v2" "github.com/gin-gonic/gin" _ "github.com/mattn/go-sqlite3" "gorm.io/driver/sqlite" @@ -18,13 +23,79 @@ type BookMarksImpl struct { db *gorm.DB } +// GetFolderMounts implements api.ServerInterface. +func (b *BookMarksImpl) GetFolderMounts(c *gin.Context, id int64) { + panic("unimplemented") +} + +// MountFolder implements api.ServerInterface. +func (b *BookMarksImpl) MountFolder(c *gin.Context, id int64) { + panic("unimplemented") +} + +// UnmountFolder implements api.ServerInterface. +func (b *BookMarksImpl) UnmountFolder(c *gin.Context, id int64) { + panic("unimplemented") +} + const forlder_root_id = 1 +var enforcer *casbin.Enforcer +var adminToken *string + +func validateApiKey(url *url.URL, apiKey string) bool { + if adminToken != nil && apiKey == *adminToken { + return true + } + + return false +} + +func AuthMiddleware() api.MiddlewareFunc { + return func(c *gin.Context) { + // 检查当前请求是否需要认证 + if _, exists := c.Get(api.ApiKeyAuthScopes); exists { + // 提取 API Key + apiKey := c.GetHeader("X-BookMark-Token") + + // 验证 API Key(您需要实现这个逻辑) + if apiKey == "" || !validateApiKey(c.Request.URL, apiKey) { + c.JSON(http.StatusUnauthorized, api.Error{ + Errtype: "Unauthorized", + Message: "Invalid or missing API key", + }) + c.Abort() + return + } + } + c.Next() + } +} + +func NewBookMarkPermission() (*api.GinServerOptions, error) { + // 一行代码生成安全的随机token + token := make([]byte, 16) + rand.Read(token) // 忽略错误处理以简化代码(生产环境建议处理) + tokenStr := hex.EncodeToString(token) + adminToken = &tokenStr + log.Printf("Admin API Token (for Swagger testing): %s", *adminToken) + + if e, err := casbin.NewEnforcer("./config/model.conf", ".data/policy.csv"); err == nil { + log.Fatalf("Failed to create casbin enforcer: %v", err) + } else { + enforcer = e + } + + return &api.GinServerOptions{ + Middlewares: []api.MiddlewareFunc{AuthMiddleware()}, + }, nil +} + func (b *BookMarksImpl) GetFolderDefaultRoot(folderID *int64) (*models.Folder, error) { var db *gorm.DB = b.db var real_root_id int64 = forlder_root_id - // 设置默认父文件夹ID为根目录(1) + // 设置默认父文件夹ID为根目录 parentID := real_root_id if folderID != nil && *folderID != 0 { parentID = *folderID @@ -47,40 +118,40 @@ func (b *BookMarksImpl) GetFolderDefaultRoot(folderID *int64) (*models.Folder, e func bookmarkReq2Model(req api.BookmarkRequest, parentID int64) models.Bookmark { return models.Bookmark{ - Name: req.Name, - Link: req.Link, - Detail: req.Detail, - Description: req.Description, - ParentPathID: parentID, + Name: req.Name, + Link: req.Link, + Detail: req.Detail, + Description: req.Description, + ParentID: parentID, } } func bookmarkModel2Res(bookmark models.Bookmark) api.BookmarkResponse { return api.BookmarkResponse{ - Id: bookmark.ID, - Name: bookmark.Name, - Link: bookmark.Link, - Detail: bookmark.Detail, - Description: bookmark.Description, - ParentPathId: bookmark.ParentPathID, - CreatedAt: bookmark.CreatedAt, + Id: bookmark.ID, + Name: bookmark.Name, + Link: bookmark.Link, + Detail: bookmark.Detail, + Description: bookmark.Description, + ParentId: bookmark.ParentID, + CreatedAt: bookmark.CreatedAt, } } -func folderReq2Model(req api.FolderRequest, parentID int64) models.Folder { +func folderReq2Model(req api.FolderRequest, parentID *int64) models.Folder { return models.Folder{ - Name: req.Name, - ParentPathID: parentID, + Name: req.Name, + ParentID: parentID, } } func folderModel2Res(folder models.Folder) api.FolderResponse { return api.FolderResponse{ - Id: folder.ID, - Name: folder.Name, - ParentPathId: folder.ParentPathID, - CreatedAt: folder.CreatedAt, - UpdatedAt: folder.UpdatedAt, + Id: folder.ID, + Name: folder.Name, + ParentId: folder.ParentID, + CreatedAt: folder.CreatedAt, + UpdatedAt: folder.UpdatedAt, } } @@ -108,9 +179,9 @@ func NewBookMarks(dbPath string) (*BookMarksImpl, error) { if result.RowsAffected == 0 { // 根文件夹不存在,创建它 rootFolder = models.Folder{ - ID: forlder_root_id, - Name: "Root", - ParentPathID: -1, // 根目录指向自己 + ID: forlder_root_id, + Name: "Root", + ParentID: nil, // 根目录指向NULL } if err := db.Create(&rootFolder).Error; err != nil { return nil, fmt.Errorf("failed to create root folder: %w", err) @@ -133,7 +204,7 @@ func (b *BookMarksImpl) CreateBookmark(c *gin.Context) { } var parentID int64 - if folder, err := b.GetFolderDefaultRoot(req.ParentPathId); err != nil || folder == nil { + if folder, err := b.GetFolderDefaultRoot(req.ParentId); err != nil || folder == nil { c.JSON(http.StatusNotFound, api.Error{ Errtype: "NotFoundError", Message: "Parent folder not found", @@ -170,7 +241,7 @@ func (b *BookMarksImpl) CreateFolder(c *gin.Context) { } var parentID int64 - if folder, err := b.GetFolderDefaultRoot(req.ParentPathId); err != nil || folder == nil { + if folder, err := b.GetFolderDefaultRoot(req.ParentId); err != nil || folder == nil { c.JSON(http.StatusNotFound, api.Error{ Errtype: "NotFoundError", Message: "Parent folder not found", @@ -181,7 +252,7 @@ func (b *BookMarksImpl) CreateFolder(c *gin.Context) { } // 创建文件夹 - folder := folderReq2Model(req, parentID) + folder := folderReq2Model(req, &parentID) if err := db.Create(&folder).Error; err != nil { c.JSON(http.StatusInternalServerError, api.Error{ @@ -237,7 +308,7 @@ func (b *BookMarksImpl) DeleteFolder(c *gin.Context, id int64) { return } - if folder.ID == folder.ParentPathID { + if folder.ParentID == nil { c.JSON(http.StatusBadRequest, api.Error{ Errtype: "ParameterError", Message: "Cannot delete root folder", @@ -375,7 +446,7 @@ func (b *BookMarksImpl) GetFolderInfo(c *gin.Context, id int64) { response := api.FolderResponse{ Id: folder.ID, Name: folder.Name, - ParentPathId: folder.ParentPathID, + ParentId: folder.ParentID, CreatedAt: folder.CreatedAt, UpdatedAt: folder.UpdatedAt, SubFolderCount: int(subFolderCount), @@ -424,16 +495,16 @@ func (b *BookMarksImpl) UpdateBookmark(c *gin.Context, id int64) { } // 更新父文件夹ID(如果提供且有效) - if req.ParentPathId != nil && *req.ParentPathId != 0 { + if req.ParentId != nil && *req.ParentId != 0 { var parentFolder models.Folder - if err := db.First(&parentFolder, *req.ParentPathId).Error; err != nil { + if err := db.First(&parentFolder, *req.ParentId).Error; err != nil { c.JSON(http.StatusNotFound, api.Error{ Errtype: "NotFoundError", Message: "Parent folder not found", }) return } - bookmark.ParentPathID = *req.ParentPathId + bookmark.ParentID = *req.ParentId } // 保存更新 @@ -480,9 +551,9 @@ func (b *BookMarksImpl) UpdateFolder(c *gin.Context, id int64) { } // 更新父文件夹ID(如果提供且有效) - if req.ParentPathId != nil && *req.ParentPathId != 0 { + if req.ParentId != nil && *req.ParentId != 0 { // 不能将文件夹设置为自己的子文件夹 - if *req.ParentPathId == id { + if *req.ParentId == id { c.JSON(http.StatusBadRequest, api.Error{ Errtype: "ParameterError", Message: "Cannot set folder as its own parent", @@ -491,14 +562,14 @@ func (b *BookMarksImpl) UpdateFolder(c *gin.Context, id int64) { } var parentFolder models.Folder - if err := db.First(&parentFolder, *req.ParentPathId).Error; err != nil { + if err := db.First(&parentFolder, *req.ParentId).Error; err != nil { c.JSON(http.StatusNotFound, api.Error{ Errtype: "NotFoundError", Message: "Parent folder not found", }) return } - folder.ParentPathID = *req.ParentPathId + folder.ParentID = req.ParentId } // 保存更新 @@ -523,5 +594,15 @@ func (b *BookMarksImpl) UpdateFolder(c *gin.Context, id int64) { c.JSON(http.StatusOK, response) } +// ExportFolderTree implements api.ServerInterface. +func (b *BookMarksImpl) ExportFolderTree(c *gin.Context) { + panic("unimplemented") +} + +// ImportFolderTree implements api.ServerInterface. +func (b *BookMarksImpl) ImportFolderTree(c *gin.Context) { + panic("unimplemented") +} + // Make sure we conform to ServerInterface var _ api.ServerInterface = (*BookMarksImpl)(nil) diff --git a/internal/handlers/bookmark_test.go b/internal/handlers/bookmark_test.go index b26b8c7..4469231 100644 --- a/internal/handlers/bookmark_test.go +++ b/internal/handlers/bookmark_test.go @@ -9,7 +9,7 @@ import ( "net/http/httptest" "testing" - "git.zzyxyz.com/zzy/zzyxyz_go_api/gen/api" + api "git.zzyxyz.com/zzy/zzyxyz_go_api/gen/bookmarks" "git.zzyxyz.com/zzy/zzyxyz_go_api/internal/handlers" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" @@ -68,7 +68,7 @@ func (suite *BookmarkTestSuite) TestCreateBookmark() { assert.Equal(suite.T(), request.Name, response.Name) assert.Equal(suite.T(), *request.Detail, *response.Detail) assert.Equal(suite.T(), *request.Description, *response.Description) - assert.Equal(suite.T(), int64(1), response.ParentPathId) // 默认根目录 + assert.Equal(suite.T(), int64(1), response.ParentId) // 默认根目录 } func (suite *BookmarkTestSuite) TestGetBookmark() { @@ -130,7 +130,7 @@ func (suite *BookmarkTestSuite) TestCreateFolder() { err := json.Unmarshal(resp.Body.Bytes(), &response) assert.NoError(suite.T(), err) assert.Equal(suite.T(), request.Name, response.Name) - assert.Equal(suite.T(), int64(1), response.ParentPathId) // 默认根目录 + assert.Equal(suite.T(), int64(1), response.ParentId) // 默认根目录 } func (suite *BookmarkTestSuite) TestGetFolderInfo() { @@ -188,11 +188,11 @@ func (suite *BookmarkTestSuite) TestGetFolderContent() { description := "Test bookmark in folder" link := "https://example.com/folder" bookmarkRequest := api.BookmarkRequest{ - Name: "Folder Bookmark", - Detail: &detail, - Description: &description, - Link: &link, - ParentPathId: &createdFolder.Id, + Name: "Folder Bookmark", + Detail: &detail, + Description: &description, + Link: &link, + ParentId: &createdFolder.Id, } bookmarkJson, _ := json.Marshal(bookmarkRequest) diff --git a/internal/handlers/vfs.go b/internal/handlers/vfs.go new file mode 100644 index 0000000..5ac8282 --- /dev/null +++ b/internal/handlers/vfs.go @@ -0,0 +1 @@ +package handlers diff --git a/internal/models/bookmark.go b/internal/models/bookmark.go index c390119..51a5383 100644 --- a/internal/models/bookmark.go +++ b/internal/models/bookmark.go @@ -8,25 +8,25 @@ import ( // Bookmark 书签结构体 type Bookmark struct { - ID int64 `json:"id" gorm:"primaryKey"` - Name string `json:"name" gorm:"not null;index;size:255"` - Link *string `json:"link" gorm:"type:url"` - Detail *string `json:"detail" gorm:"type:text"` - Description *string `json:"description" gorm:"type:text"` - ParentPathID int64 `json:"parent_path_id" gorm:"index;not null;default:1"` - CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"` - UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"` - DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"` + ID int64 `json:"id" gorm:"primaryKey"` + Name string `json:"name" gorm:"not null;index;size:255"` + Link *string `json:"link" gorm:"type:url"` + Detail *string `json:"detail" gorm:"type:text"` + Description *string `json:"description" gorm:"type:text"` + ParentID int64 `json:"parent_id" gorm:"not null;index"` + CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"` + UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"` + DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"` } // Folder 文件夹结构体 type Folder struct { - ID int64 `json:"id" gorm:"primaryKey"` - Name string `json:"name" gorm:"not null;index;size:255"` - ParentPathID int64 `json:"parent_path_id" gorm:"index;not null;default:1"` - CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"` - UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"` - DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"` + ID int64 `json:"id" gorm:"primaryKey"` + Name string `json:"name" gorm:"not null;index;size:255"` + ParentID *int64 `json:"parent_id" gorm:"index"` + CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"` + UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"` + DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"` } // IsValidParent 检查父文件夹ID是否有效 diff --git a/internal/models/user.go b/internal/models/user.go index 2640e7f..0493026 100644 --- a/internal/models/user.go +++ b/internal/models/user.go @@ -1 +1,15 @@ package models + +type PasswordUser struct { + ID int64 + Username string + Password string + BookMarkUserID int64 +} + +type BookMarkUser struct { + ID int64 + RootFolderID int64 + Token string + Data *string +} diff --git a/main.go b/main.go index 0cd3948..158672f 100644 --- a/main.go +++ b/main.go @@ -5,15 +5,15 @@ import ( "log" "net/http" - "git.zzyxyz.com/zzy/zzyxyz_go_api/gen/api" + bookmarks "git.zzyxyz.com/zzy/zzyxyz_go_api/gen/bookmarks" "git.zzyxyz.com/zzy/zzyxyz_go_api/internal/handlers" "github.com/gin-gonic/gin" ) -//go:generate go tool oapi-codegen -config config/cfg.yaml config/api.yaml - -//go:embed config/api.yaml dist/* +//go:generate go tool oapi-codegen -config config/bookmark_cfg.yaml config/bookmark.yaml +//go:generate go tool oapi-codegen -config config/vfs_cfg.yaml config/vfs.yaml +//go:embed config/* dist/* var staticFiles embed.FS func main() { @@ -30,10 +30,12 @@ func main() { { // create a type that satisfies the `api.ServerInterface`, // which contains an implementation of every operation from the generated code - if server, err := handlers.NewBookMarks("user.sqlite3"); err != nil { + if server, err := handlers.NewBookMarks("./data/bookmark.sqlite3"); err != nil { log.Fatal("Failed to create bookmarks server:", err) + } else if permission, err := handlers.NewBookMarkPermission(); err != nil || permission == nil { + log.Fatal("Failed to create bookmarks permission:", err) } else { - api.RegisterHandlers(api_router, server) + bookmarks.RegisterHandlersWithOptions(api_router, server, *permission) } handlers.TodoHandler(api_router) @@ -41,9 +43,9 @@ func main() { // FIXME 可能有更好的方式实现这个代码 // 提供嵌入的静态文件访问 - OpenAPI YAML 文件和 dist 目录 - router.GET("/swagger.yaml", func(c *gin.Context) { - file, _ := staticFiles.ReadFile("config/api.yaml") - c.Data(http.StatusOK, "application/x-yaml", file) + router.GET("/config/*filepath", func(ctx *gin.Context) { + fs := http.FileServer(http.FS(staticFiles)) + fs.ServeHTTP(ctx.Writer, ctx.Request) }) router.GET("/swagger/*filepath", func(ctx *gin.Context) { // 直接修改请求路径实现映射