diff --git a/.gitea/workflows/deploy.yaml b/.gitea/workflows/deploy.yaml index 00397cc..fbc3be4 100644 --- a/.gitea/workflows/deploy.yaml +++ b/.gitea/workflows/deploy.yaml @@ -5,9 +5,9 @@ on: branches: - master paths-ignore: - - ".gitignore" - - "README.md" - - ".vscode/**" + - .gitignore + - .vscode/** + - README.md env: docker_host: ${{ secrets.DOCKER_REGISTRY }} @@ -32,16 +32,16 @@ jobs: - name: 构建镜像 run: | - docker build -t ${{ env.docker_name }}:latest . - + docker build -t ${{ env.docker_name }}:latest . + - name: 登陆镜像 run: | - docker login -u "${{env.docker_user}}" -p "${{env.docker_pass}}" ${{env.docker_host}} + docker login -u "${{env.docker_user}}" -p "${{env.docker_pass}}" ${{env.docker_host}} - name: 推送镜像 shell: bash run: | - docker push ${{ env.docker_name }}:latest + docker push ${{ env.docker_name }}:latest - name: 更新服务 uses: http://git.dev.juetan.cn/mirror/ssh-action@v1.0.0 @@ -52,4 +52,4 @@ jobs: password: ${{ env.deploy_pass }} script: | docker service ls | grep -q ${{ env.deploy_name }} || exit 0 - docker service update --image ${{ env.docker_name }}:latest ${{ env.deploy_name }} \ No newline at end of file + docker service update --image ${{ env.docker_name }}:latest ${{ env.deploy_name }} diff --git a/Dockerfile b/Dockerfile index 27f7f54..252567f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,6 @@ FROM node:18-alpine As dev +RUN apk update && apk add sqlite +RUN apk add --no-cache --virtual .build-deps g++ gcc libgcc libstdc++ linux-headers make python3 WORKDIR /app COPY package*.json . RUN npm install @@ -16,4 +18,3 @@ COPY --from=dev /app/package.json ./ EXPOSE 3030 CMD [ "node", "./dist/main.js" ] - diff --git a/README.md b/README.md index 57b4219..182c257 100644 --- a/README.md +++ b/README.md @@ -26,4 +26,18 @@ - 角色模块 - 权限模块 - 上传模块 -- 文章模块 \ No newline at end of file +- 文章模块 + +## 部署 + +目前基于 Gitea 和 Gitea Actions 实现,大致流程是这样的:提交代码到 Gitea 仓库后,触发流水线任务进行构建并打包成 Docker 镜像,推送到 Gitea 自带的软件包仓库,然后登陆生产服务器执行更新命令。 + +使用 Github Actions 也是可以的,两者使用上是兼容的。本仓库有关部署的内容涉及三个地方,不需要的话可自行删除,如下: + +- Dockerfile 构建镜像的配置文件 +- .dockerignore 配置哪些文件应该被忽略掉 +- .gitea/workflows/depoy.yaml 流水线任务的配置文件,语法上与 Github Actions 一致 + +## 最后 + +如果你在使用过程中遇到问题,欢迎在 Issue 中提问。 \ No newline at end of file diff --git a/content/database/db.sqlite b/content/database/db.sqlite index 982a26d..2f42cb7 100644 Binary files a/content/database/db.sqlite and b/content/database/db.sqlite differ diff --git a/graph.json b/content/development/graph.json similarity index 100% rename from graph.json rename to content/development/graph.json diff --git a/content/development/openapi.json b/content/development/openapi.json new file mode 100644 index 0000000..9f172a9 --- /dev/null +++ b/content/development/openapi.json @@ -0,0 +1,2066 @@ +{ + "openapi": "3.0.0", + "paths": { + "/api/v1/users": { + "post": { + "operationId": "addUser", + "summary": "", + "description": "新增用户", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateUserDto" + } + } + } + }, + "responses": { + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "number" + } + }, + "required": [ + "data" + ] + } + ] + } + } + } + } + }, + "tags": [ + "user" + ] + }, + "get": { + "operationId": "getUsers", + "summary": "", + "description": "分页/条件查询用户", + "parameters": [ + { + "name": "nickname", + "required": false, + "in": "query", + "description": "用户昵称", + "example": "绝弹", + "schema": { + "type": "string" + } + }, + { + "name": "sort", + "required": false, + "in": "query", + "description": "排序规则", + "example": "id:desc", + "schema": { + "pattern": "/^(\\w+:\\w+,)*\\w+:\\w+$/", + "default": "id:desc", + "type": "string" + } + }, + { + "name": "page", + "required": false, + "in": "query", + "description": "页码", + "example": 1, + "schema": { + "minimum": 1, + "type": "number" + } + }, + { + "name": "size", + "required": false, + "in": "query", + "description": "每页条数", + "example": 10, + "schema": { + "minimum": 0, + "type": "number" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/User" + } + } + }, + "required": [ + "data" + ] + } + ] + } + } + } + } + }, + "tags": [ + "user" + ] + } + }, + "/api/v1/users/{id}": { + "get": { + "operationId": "getUser", + "summary": "", + "description": "根据ID查询用户", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "number" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/User" + } + }, + "required": [ + "data" + ] + } + ] + } + } + } + } + }, + "tags": [ + "user" + ] + }, + "patch": { + "operationId": "updateUser", + "summary": "", + "description": "根据ID更新用户", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "number" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateUserDto" + } + } + } + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Response" + } + } + } + } + }, + "tags": [ + "user" + ] + }, + "delete": { + "operationId": "delUser", + "summary": "", + "description": "根据ID删除用户", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "number" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Response" + } + } + } + } + }, + "tags": [ + "user" + ] + } + }, + "/api/v1/auth/login": { + "post": { + "operationId": "login", + "summary": "", + "description": "账号登陆", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AuthUserDto" + } + } + } + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/LoginedUserVo" + } + }, + "required": [ + "data" + ] + } + ] + } + } + } + } + }, + "tags": [ + "auth" + ] + } + }, + "/api/v1/logs": { + "post": { + "operationId": "addLog", + "summary": "", + "description": "新增日志管理", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateLogDto" + } + } + } + }, + "responses": { + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "number" + } + }, + "required": [ + "data" + ] + } + ] + } + } + } + } + }, + "tags": [ + "log" + ] + }, + "get": { + "operationId": "getLogs", + "summary": "", + "description": "根据分页/过滤参数查询日志管理", + "parameters": [ + { + "name": "nickname", + "required": false, + "in": "query", + "description": "用户名", + "example": "绝弹", + "schema": { + "type": "string" + } + }, + { + "name": "sort", + "required": false, + "in": "query", + "description": "排序规则", + "example": "id:desc", + "schema": { + "pattern": "/^(\\w+:\\w+,)*\\w+:\\w+$/", + "default": "id:desc", + "type": "string" + } + }, + { + "name": "page", + "required": false, + "in": "query", + "description": "页码", + "example": 1, + "schema": { + "minimum": 1, + "type": "number" + } + }, + { + "name": "size", + "required": false, + "in": "query", + "description": "每页条数", + "example": 10, + "schema": { + "minimum": 0, + "type": "number" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LoginLog" + } + } + }, + "required": [ + "data" + ] + } + ] + } + } + } + } + }, + "tags": [ + "log" + ] + } + }, + "/api/v1/logs/login": { + "get": { + "operationId": "getLoginLogs", + "summary": "", + "description": "分页查询登陆日志", + "parameters": [ + { + "name": "nickname", + "required": false, + "in": "query", + "description": "用户名", + "example": "绝弹", + "schema": { + "type": "string" + } + }, + { + "name": "sort", + "required": false, + "in": "query", + "description": "排序规则", + "example": "id:desc", + "schema": { + "pattern": "/^(\\w+:\\w+,)*\\w+:\\w+$/", + "default": "id:desc", + "type": "string" + } + }, + { + "name": "page", + "required": false, + "in": "query", + "description": "页码", + "example": 1, + "schema": { + "minimum": 1, + "type": "number" + } + }, + { + "name": "size", + "required": false, + "in": "query", + "description": "每页条数", + "example": 10, + "schema": { + "minimum": 0, + "type": "number" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LoginLog" + } + } + }, + "required": [ + "data" + ] + } + ] + } + } + } + } + }, + "tags": [ + "log" + ] + } + }, + "/api/v1/logs/{id}": { + "get": { + "operationId": "getLog", + "summary": "", + "description": "根据ID查询日志管理", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "number" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/LoginLog" + } + }, + "required": [ + "data" + ] + } + ] + } + } + } + } + }, + "tags": [ + "log" + ] + }, + "patch": { + "operationId": "updateLog", + "summary": "", + "description": "根据ID更新日志管理", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "number" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateLogDto" + } + } + } + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Response" + } + } + } + } + }, + "tags": [ + "log" + ] + }, + "delete": { + "operationId": "delLog", + "summary": "", + "description": "根据ID删除日志管理", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "number" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Response" + } + } + } + } + }, + "tags": [ + "log" + ] + } + }, + "/api/v1/roles": { + "post": { + "operationId": "addRole", + "summary": "", + "description": "创建角色", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateRoleDto" + } + } + } + }, + "responses": { + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "number" + } + }, + "required": [ + "data" + ] + } + ] + } + } + } + } + }, + "tags": [ + "role" + ] + }, + "get": { + "operationId": "getRoles", + "summary": "", + "description": "批量查询角色", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Response" + } + } + } + } + }, + "tags": [ + "role" + ] + } + }, + "/api/v1/roles/{id}": { + "get": { + "operationId": "getRole", + "summary": "", + "description": "查询角色", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "number" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "string" + } + }, + "required": [ + "data" + ] + } + ] + } + } + } + } + }, + "tags": [ + "role" + ] + }, + "patch": { + "operationId": "updateRole", + "summary": "", + "description": "更新角色", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "number" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateRoleDto" + } + } + } + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Response" + } + } + } + } + }, + "tags": [ + "role" + ] + }, + "delete": { + "operationId": "delRole", + "summary": "", + "description": "删除角色", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "number" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "string" + } + }, + "required": [ + "data" + ] + } + ] + } + } + } + } + }, + "tags": [ + "role" + ] + } + }, + "/api/v1/permissions": { + "post": { + "operationId": "addPermission", + "summary": "", + "description": "创建权限", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreatePermissionDto" + } + } + } + }, + "responses": { + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "number" + } + }, + "required": [ + "data" + ] + } + ] + } + } + } + } + }, + "tags": [ + "permission" + ] + }, + "get": { + "operationId": "getPermissions", + "summary": "", + "description": "批量查询权限", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Response" + } + } + } + } + }, + "tags": [ + "permission" + ] + } + }, + "/api/v1/permissions/{id}": { + "get": { + "operationId": "getPermission", + "summary": "", + "description": "查询权限", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "string" + } + }, + "required": [ + "data" + ] + } + ] + } + } + } + } + }, + "tags": [ + "permission" + ] + }, + "patch": { + "operationId": "setPermission", + "summary": "", + "description": "更新权限", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdatePermissionDto" + } + } + } + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Response" + } + } + } + } + }, + "tags": [ + "permission" + ] + }, + "delete": { + "operationId": "delPermission", + "summary": "", + "description": "删除权限", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "string" + } + }, + "required": [ + "data" + ] + } + ] + } + } + } + } + }, + "tags": [ + "permission" + ] + } + }, + "/api/v1/upload": { + "post": { + "operationId": "addFile", + "summary": "", + "description": "上传文件", + "parameters": [], + "requestBody": { + "required": true, + "description": "要上传的文件", + "content": { + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/CreateUploadDto" + } + } + } + }, + "responses": { + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "number" + } + }, + "required": [ + "data" + ] + } + ] + } + } + } + } + }, + "tags": [ + "upload" + ] + }, + "get": { + "operationId": "getUploads", + "summary": "", + "description": "批量查询", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Response" + } + } + } + } + }, + "tags": [ + "upload" + ] + } + }, + "/api/v1/upload/{id}": { + "get": { + "operationId": "getFile", + "summary": "", + "description": "查询", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "number" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/Upload" + } + }, + "required": [ + "data" + ] + } + ] + } + } + } + } + }, + "tags": [ + "upload" + ] + }, + "patch": { + "operationId": "updateFile", + "summary": "", + "description": "更新", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "string" + } + }, + "required": [ + "data" + ] + } + ] + } + } + } + } + }, + "tags": [ + "upload" + ] + }, + "delete": { + "operationId": "delFile", + "summary": "", + "description": "删除", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "number" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Response" + } + } + } + } + }, + "tags": [ + "upload" + ] + } + }, + "/api/v1/posts": { + "post": { + "operationId": "addPost", + "summary": "", + "description": "创建文章", + "parameters": [], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreatePostDto" + } + } + } + }, + "responses": { + "201": { + "description": "", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/Response" + }, + { + "type": "object", + "properties": { + "data": { + "type": "number" + } + }, + "required": [ + "data" + ] + } + ] + } + } + } + } + }, + "tags": [ + "post" + ] + }, + "get": { + "operationId": "getPosts", + "summary": "", + "description": "批量查询文章", + "parameters": [ + { + "name": "sort", + "required": false, + "in": "query", + "description": "排序规则", + "example": "id:desc", + "schema": { + "pattern": "/^(\\w+:\\w+,)*\\w+:\\w+$/", + "default": "id:desc", + "type": "string" + } + }, + { + "name": "page", + "required": false, + "in": "query", + "description": "页码", + "example": 1, + "schema": { + "minimum": 1, + "type": "number" + } + }, + { + "name": "size", + "required": false, + "in": "query", + "description": "每页条数", + "example": 10, + "schema": { + "minimum": 0, + "type": "number" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Response" + } + } + } + } + }, + "tags": [ + "post" + ] + } + }, + "/api/v1/posts/template.xlsx": { + "get": { + "operationId": "getPostTemplate", + "summary": "", + "description": "获取文章下载模板", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Response" + } + } + } + } + }, + "tags": [ + "post" + ] + } + }, + "/api/v1/posts/{id}": { + "get": { + "operationId": "getPost", + "summary": "", + "description": "查询文章", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "number" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/components/schemas/Post" + } + }, + "required": [ + "data" + ] + } + ] + } + } + } + } + }, + "tags": [ + "post" + ] + }, + "patch": { + "operationId": "updatePost", + "summary": "", + "description": "更新文章", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "number" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdatePostDto" + } + } + } + }, + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Response" + } + } + } + } + }, + "tags": [ + "post" + ] + }, + "delete": { + "operationId": "delPost", + "summary": "", + "description": "删除文章", + "parameters": [ + { + "name": "id", + "required": true, + "in": "path", + "schema": { + "type": "number" + } + } + ], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Response" + } + } + } + } + }, + "tags": [ + "post" + ] + } + } + }, + "info": { + "title": "Appnify接口文档", + "description": "Openapi 3.0文档", + "version": "1.0", + "contact": {} + }, + "tags": [ + { + "name": "user", + "description": "用户管理" + }, + { + "name": "auth", + "description": "认证管理" + }, + { + "name": "role", + "description": "角色管理" + }, + { + "name": "permission", + "description": "权限管理" + }, + { + "name": "post", + "description": "文章管理" + }, + { + "name": "upload", + "description": "文件上传" + } + ], + "servers": [], + "components": { + "securitySchemes": { + "bearer": { + "scheme": "bearer", + "bearerFormat": "JWT", + "type": "http" + } + }, + "schemas": { + "CreateUserDto": { + "type": "object", + "properties": { + "username": { + "type": "string", + "description": "登录账号", + "example": "juetan" + }, + "nickname": { + "type": "string", + "description": "用户昵称", + "example": "绝弹" + }, + "password": { + "type": "string", + "description": "用户密码", + "example": "password" + }, + "avatarId": { + "type": "number", + "description": "头像ID", + "example": 1 + }, + "roleIds": { + "description": "角色ID列表", + "example": [ + 1, + 2, + 3 + ], + "type": "array", + "items": { + "type": "number" + } + } + }, + "required": [ + "username", + "nickname" + ] + }, + "User": { + "type": "object", + "properties": { + "username": { + "type": "string", + "description": "登录账号", + "example": "juetan" + }, + "nickname": { + "type": "string", + "description": "用户昵称", + "example": "绝弹" + }, + "description": { + "type": "string", + "description": "用户介绍", + "example": "这个人很懒, 什么也没有留下!" + }, + "avatar": { + "type": "string", + "description": "用户头像(URL)", + "example": "./assets/222421415123.png " + }, + "password": { + "type": "string", + "description": "用户密码", + "example": "password" + }, + "email": { + "type": "string", + "description": "用户邮箱", + "example": "example@mail.com" + }, + "roleIds": { + "description": "用户角色ID", + "type": "array", + "items": { + "type": "number" + } + } + }, + "required": [ + "username", + "nickname", + "description", + "avatar", + "password", + "email", + "roleIds" + ] + }, + "UpdateUserDto": { + "type": "object", + "properties": { + "username": { + "type": "string", + "description": "登录账号", + "example": "juetan" + }, + "nickname": { + "type": "string", + "description": "用户昵称", + "example": "绝弹" + }, + "password": { + "type": "string", + "description": "用户密码", + "example": "password" + }, + "avatarId": { + "type": "number", + "description": "头像ID", + "example": 1 + }, + "roleIds": { + "description": "角色ID列表", + "example": [ + 1, + 2, + 3 + ], + "type": "array", + "items": { + "type": "number" + } + } + } + }, + "AuthUserDto": { + "type": "object", + "properties": { + "username": { + "type": "string", + "description": "用户名", + "example": "admin" + }, + "password": { + "type": "string", + "description": "用户密码", + "example": "123456" + } + }, + "required": [ + "username", + "password" + ] + }, + "LoginedUserVo": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "用户ID" + }, + "token": { + "type": "string", + "description": "访问令牌", + "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MjIsInVzZXJuYW1lIjoianVldGFuIiwiaWF0IjoxNjkxMTM5MjI3LCJleHAiOjE2OTExOTkyMjd9.6z7f-xfsHABbsyg401o2boKeqNQ1epPDYfEdavIcfYc" + }, + "username": { + "type": "string", + "description": "登录账号", + "example": "juetan" + }, + "nickname": { + "type": "string", + "description": "用户昵称", + "example": "绝弹" + }, + "description": { + "type": "string", + "description": "用户介绍", + "example": "这个人很懒, 什么也没有留下!" + }, + "avatar": { + "type": "string", + "description": "用户头像(URL)", + "example": "./assets/222421415123.png " + }, + "email": { + "type": "string", + "description": "用户邮箱", + "example": "example@mail.com" + }, + "roleIds": { + "description": "用户角色ID", + "type": "array", + "items": { + "type": "number" + } + } + }, + "required": [ + "id", + "token", + "username", + "nickname", + "description", + "avatar", + "email", + "roleIds" + ] + }, + "CreateLogDto": { + "type": "object", + "properties": { + "demo": { + "type": "string", + "description": "字段描述(Swagger用途)", + "example": "demo" + } + }, + "required": [ + "demo" + ] + }, + "LoginLog": { + "type": "object", + "properties": { + "nickname": { + "type": "string", + "description": "用户昵称", + "example": "绝弹" + }, + "description": { + "type": "string", + "description": "操作描述", + "example": "1" + }, + "status": { + "type": "boolean", + "description": "操作状态", + "example": true + }, + "ip": { + "type": "string", + "description": "登陆IP", + "example": "127.0.0.1" + }, + "addr": { + "type": "string", + "description": "登陆地址", + "example": "广东省深圳市" + }, + "browser": { + "type": "string", + "description": "浏览器", + "example": "chrome" + }, + "os": { + "type": "string", + "description": "操作系统", + "example": "windows 10" + } + }, + "required": [ + "nickname", + "description", + "status", + "ip", + "addr", + "browser", + "os" + ] + }, + "UpdateLogDto": { + "type": "object", + "properties": { + "demo": { + "type": "string", + "description": "字段描述(Swagger用途)", + "example": "demo" + } + } + }, + "Permission": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "权限名称", + "example": "文章列表" + }, + "slug": { + "type": "string", + "description": "权限标识", + "example": "post:list" + }, + "type": { + "type": "string", + "description": "权限类型", + "example": "menu", + "enum": [ + "menu", + "api" + ] + }, + "description": { + "type": "string", + "description": "权限描述", + "example": "文章列表" + } + }, + "required": [ + "name", + "slug", + "type", + "description" + ] + }, + "CreateRoleDto": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "description": { + "type": "string" + }, + "permissions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Permission" + } + } + }, + "required": [ + "name", + "slug" + ] + }, + "UpdateRoleDto": { + "type": "object", + "properties": { + "permissionIds": { + "type": "array", + "items": { + "type": "number" + } + }, + "name": { + "type": "string" + }, + "slug": { + "type": "string" + }, + "description": { + "type": "string" + }, + "permissions": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Permission" + } + } + } + }, + "CreatePermissionDto": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "权限名称", + "example": "权限名称" + }, + "slug": { + "type": "string", + "description": "权限标识", + "example": "permission:permission" + }, + "description": { + "type": "string", + "description": "权限描述" + } + }, + "required": [ + "name", + "slug" + ] + }, + "UpdatePermissionDto": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "权限名称", + "example": "权限名称" + }, + "slug": { + "type": "string", + "description": "权限标识", + "example": "permission:permission" + }, + "description": { + "type": "string", + "description": "权限描述" + } + } + }, + "CreateUploadDto": { + "type": "object", + "properties": { + "file": { + "type": "string", + "format": "binary" + } + }, + "required": [ + "file" + ] + }, + "Upload": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "文件名", + "example": "xxx.jpg" + }, + "size": { + "type": "number", + "description": "文件大小", + "example": 1024 + }, + "mimetype": { + "type": "string", + "description": "文件类型", + "example": "image/jpeg" + }, + "path": { + "type": "string", + "description": "文件路径", + "example": "/upload/2021/10/01/xxx.jpg" + }, + "hash": { + "type": "string", + "description": "文件哈希", + "example": "xxx" + }, + "extension": { + "type": "string", + "description": "文件后缀", + "example": ".jpg" + } + }, + "required": [ + "name", + "size", + "mimetype", + "path", + "hash", + "extension" + ] + }, + "CreatePostDto": { + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "文章标题" + }, + "description": { + "type": "string", + "description": "文章描述" + }, + "content": { + "type": "string", + "description": "文章内容" + } + }, + "required": [ + "title", + "description", + "content" + ] + }, + "Post": { + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "文章标题", + "example": "文章标题" + }, + "description": { + "type": "string", + "description": "文章描述", + "example": "文章描述" + }, + "content": { + "type": "string", + "description": "文章内容", + "example": "文章内容" + }, + "author": { + "description": "文章作者", + "example": "文章作者", + "allOf": [ + { + "$ref": "#/components/schemas/User" + } + ] + } + }, + "required": [ + "title", + "description", + "content", + "author" + ] + }, + "UpdatePostDto": { + "type": "object", + "properties": { + "title": { + "type": "string", + "description": "文章标题" + }, + "description": { + "type": "string", + "description": "文章描述" + }, + "content": { + "type": "string", + "description": "文章内容" + } + } + }, + "Response": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "description": "状态码", + "example": 2000, + "format": "int32" + }, + "message": { + "type": "string", + "description": "提示信息", + "example": "请求成功" + } + }, + "required": [ + "code", + "message" + ] + } + } + }, + "externalDocs": { + "description": "JSON数据", + "url": "/openapi.json" + } +} \ No newline at end of file diff --git a/package.json b/package.json index da4cbff..3f8c002 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "@nestjs/config": "^2.3.4", "@nestjs/jwt": "^10.1.1", "@nestjs/platform-express": "^9.4.3", + "@nestjs/cache-manager": "^2.1.0", "axios": "^1.5.0", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", @@ -45,10 +46,8 @@ "typeorm-naming-strategies": "^4.1.0", "ua-parser-js": "^1.0.36", "uuid": "^9.0.1", - "winston": "^3.10.0", "nodemailer": "^6.9.5", "mysql2": "^3.6.1", - "@nestjs/cache-manager": "^2.1.0", "cache-manager": "^5.2.3", "cache-manager-redis-store": "^3.0.1", "lodash": "^4.17.21", @@ -57,6 +56,7 @@ "multer": "1.4.5-lts.1", "redis": "^4.6.8", "sqlite3": "^5.1.6", + "winston": "^3.10.0", "winston-daily-rotate-file": "^4.7.1" }, "devDependencies": { @@ -107,4 +107,4 @@ "coverageDirectory": "../coverage", "testEnvironment": "node" } -} +} \ No newline at end of file diff --git a/src/app.module.ts b/src/app.module.ts index a7c21c7..6827351 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,7 +1,7 @@ import { Module } from '@nestjs/common'; -import { PostModule } from '@/modules/post'; +import { PostModule } from '@/content/post'; import { RoleModule } from '@/modules/role'; -import { UploadModule } from '@/modules/upload'; +import { UploadModule } from '@/storage/upload'; import { PermissionModule } from '@/modules/permission'; import { ConfigModule } from '@/config'; import { LoggerModule } from '@/common/logger'; @@ -12,8 +12,9 @@ import { AuthModule } from '@/modules/auth'; import { UserModule } from '@/modules/user'; import { ResponseModule } from '@/common/response'; import { SerializationModule } from '@/common/serialization'; -import { CacheModule } from './common/cache'; -import { ScanModule } from './utils/scan.module'; +import { CacheModule } from '@/storage/cache'; +import { ScanModule } from '@/utils/scan.module'; +import { ContentModule } from '@/content/content.module'; @Module({ imports: [ @@ -61,6 +62,8 @@ import { ScanModule } from './utils/scan.module'; * @description 用于连接数据库 */ DatabaseModule, + + /** * 用户模块 */ @@ -77,6 +80,8 @@ import { ScanModule } from './utils/scan.module'; * 权限模块 */ PermissionModule, + + /** * 上传模块 */ @@ -85,6 +90,7 @@ import { ScanModule } from './utils/scan.module'; * 文章模块 */ PostModule, + ContentModule ], }) export class AppModule {} diff --git a/src/common/response/notcaptured.filter.ts b/src/common/response/notcaptured.filter.ts index 05a1a98..b6216b8 100644 --- a/src/common/response/notcaptured.filter.ts +++ b/src/common/response/notcaptured.filter.ts @@ -22,7 +22,7 @@ export class AllExecptionFilter implements ExceptionFilter { return response.status(exception.status).json( Response.create({ code: ResponseCode.ERROR, - message: '访问的路径不存在', + message: '路径不存在', }), ); } @@ -32,7 +32,7 @@ export class AllExecptionFilter implements ExceptionFilter { return response.status(HttpStatus.UNAUTHORIZED).json( Response.create({ code: ResponseCode.TOKEN_EXPIRED, - message: '登陆已过期' + message: '登陆令牌已过期' }) ) } diff --git a/src/content/category/category.controller.ts b/src/content/category/category.controller.ts new file mode 100644 index 0000000..398af38 --- /dev/null +++ b/src/content/category/category.controller.ts @@ -0,0 +1,59 @@ +import { BaseController } from '@/common/base'; +import { Respond, RespondType } from '@/common/response'; +import { Body, Controller, Delete, Get, Param, Patch, Post, Query, ParseIntPipe } from '@nestjs/common'; +import { ApiOkResponse, ApiTags } from '@nestjs/swagger'; +import { CreateCategoryDto } from './dto/create-category.dto'; +import { FindCategoryDto } from './dto/find-category.dto'; +import { UpdateCategoryDto } from './dto/update-category.dto'; +import { Category } from './entities/category.entity'; +import { CategoryService } from './category.service'; + +@ApiTags('category') +@Controller('categories') +export class CategoryController extends BaseController { + constructor(private categoryService: CategoryService) { + super(); + } + + /** + * 新增分类 + */ + @Post() + addCategory(@Body() createCategoryDto: CreateCategoryDto) { + return this.categoryService.create(createCategoryDto); + } + + /** + * 根据分页/过滤参数查询分类 + */ + @Get() + @Respond(RespondType.PAGINATION) + @ApiOkResponse({ isArray: true, type: Category }) + getCategorys(@Query() query: FindCategoryDto) { + return this.categoryService.findMany(query); + } + + /** + * 根据ID查询分类 + */ + @Get(':id') + getCategory(@Param('id', ParseIntPipe) id: number): Promise { + return this.categoryService.findOne(id); + } + + /** + * 根据ID更新分类 + */ + @Patch(':id') + updateCategory(@Param('id', ParseIntPipe) id: number, @Body() updateCategoryDto: UpdateCategoryDto) { + return this.categoryService.update(+id, updateCategoryDto); + } + + /** + * 根据ID删除分类 + */ + @Delete(':id') + delCategory(@Param('id', ParseIntPipe) id: number) { + return this.categoryService.remove(+id); + } +} diff --git a/src/content/category/category.module.ts b/src/content/category/category.module.ts new file mode 100644 index 0000000..5d60e22 --- /dev/null +++ b/src/content/category/category.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { Category } from './entities/category.entity'; +import { CategoryController } from './category.controller'; +import { CategoryService } from './category.service'; + +@Module({ + imports: [TypeOrmModule.forFeature([Category])], + controllers: [CategoryController], + providers: [CategoryService], + exports: [CategoryService], +}) +export class CategoryModule {} diff --git a/src/content/category/category.service.ts b/src/content/category/category.service.ts new file mode 100644 index 0000000..b38ecdb --- /dev/null +++ b/src/content/category/category.service.ts @@ -0,0 +1,55 @@ +import { BaseService } from '@/common/base'; +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Like, Repository } from 'typeorm'; +import { CreateCategoryDto } from './dto/create-category.dto'; +import { FindCategoryDto } from './dto/find-category.dto'; +import { UpdateCategoryDto } from './dto/update-category.dto'; +import { Category } from './entities/category.entity'; + +@Injectable() +export class CategoryService extends BaseService { + constructor(@InjectRepository(Category) private categoryRepository: Repository) { + super(); + } + + /** + * 新增分类 + */ + async create(createCategoryDto: CreateCategoryDto) { + const category = this.categoryRepository.create(createCategoryDto); + await this.categoryRepository.save(category); + return category.id; + } + + /** + * 条件/分页查询 + */ + async findMany(findCategorydto: FindCategoryDto) { + const { page, size } = findCategorydto; + const { skip, take } = this.formatPagination(page, size, true); + return this.categoryRepository.findAndCount({ skip, take }); + } + + /** + * 根据ID查询 + */ + findOne(idOrOptions: number | Partial) { + const where = typeof idOrOptions === 'number' ? { id: idOrOptions } : (idOrOptions as any); + return this.categoryRepository.findOne({ where }); + } + + /** + * 根据ID更新 + */ + update(id: number, updateCategoryDto: UpdateCategoryDto) { + return this.categoryRepository.update(id, updateCategoryDto); + } + + /** + * 根据ID删除(软删除) + */ + remove(id: number) { + return this.categoryRepository.softDelete(id); + } +} diff --git a/src/content/category/dto/create-category.dto.ts b/src/content/category/dto/create-category.dto.ts new file mode 100644 index 0000000..37dc045 --- /dev/null +++ b/src/content/category/dto/create-category.dto.ts @@ -0,0 +1,51 @@ +import { IsEnum, IsNumber, IsOptional, IsString } from 'class-validator'; + +export class CreateCategoryDto { + /** + * 分类名称 + * @example '待分类' + */ + @IsString() + title: string; + /** + * 分类别名 + * @example 'default' + */ + @IsString() + slug: string; + /** + * 分类描述 + * @example '默认分类' + */ + @IsString() + @IsOptional() + description?: string; + /** + * 分类图标 + * @example 'default' + */ + @IsString() + @IsOptional() + icon?: string; + /** + * 分类排序 + * @example 0 + */ + @IsNumber() + @IsOptional() + sort?: number; + /** + * 分类类型 + * @example 'category' + */ + @IsEnum(['category', 'tag']) + @IsOptional() + type: 'category' | 'tag'; + /** + * 父级分类ID + * @example 0 + */ + @IsNumber() + @IsOptional() + parentId?: number; +} diff --git a/src/content/category/dto/find-category.dto.ts b/src/content/category/dto/find-category.dto.ts new file mode 100644 index 0000000..c46d63e --- /dev/null +++ b/src/content/category/dto/find-category.dto.ts @@ -0,0 +1,13 @@ +import { PaginationDto } from '@/common/response'; +import { IntersectionType } from '@nestjs/swagger'; +import { IsOptional, IsString } from 'class-validator'; + +export class FindCategoryDto extends IntersectionType(PaginationDto) { + /** + * 字段描述(Swagger用途) + * @example '示例值' + */ + @IsOptional() + @IsString() + demo?: string; +} diff --git a/src/content/category/dto/update-category.dto.ts b/src/content/category/dto/update-category.dto.ts new file mode 100644 index 0000000..d713b9b --- /dev/null +++ b/src/content/category/dto/update-category.dto.ts @@ -0,0 +1,4 @@ +import { PartialType } from '@nestjs/swagger'; +import { CreateCategoryDto } from './create-category.dto'; + +export class UpdateCategoryDto extends PartialType(CreateCategoryDto) {} diff --git a/src/content/category/entities/category.entity.ts b/src/content/category/entities/category.entity.ts new file mode 100644 index 0000000..fa71eb2 --- /dev/null +++ b/src/content/category/entities/category.entity.ts @@ -0,0 +1,48 @@ +import { BaseEntity } from '@/database'; +import { Column, Entity, ManyToOne, OneToMany } from 'typeorm'; + +@Entity({ orderBy: { id: 'DESC' } }) +export class Category extends BaseEntity { + /** + * 分类名称 + * @example '待分类' + */ + @Column() + title: string; + /** + * 分类别名 + * @example 'default' + */ + @Column() + slug: string; + /** + * 分类描述 + * @example '默认分类' + */ + @Column({ nullable: true }) + description?: string; + /** + * 分类图标 + * @example 'default' + */ + @Column({ nullable: true }) + icon?: string; + /** + * 分类排序 + * @example 0 + */ + @Column({ default: 0 }) + sort?: number; + /** + * 分类类型 + * @example 'category' + */ + @Column({ default: 'category' }) + type?: 'category' | 'tag'; + /** + * 父级分类ID + * @example 0 + */ + @Column({ default: 0, nullable: true }) + parentId?: number; +} diff --git a/src/content/category/index.ts b/src/content/category/index.ts new file mode 100644 index 0000000..bdabbf6 --- /dev/null +++ b/src/content/category/index.ts @@ -0,0 +1,4 @@ +export * from './entities/category.entity'; +export * from './category.controller'; +export * from './category.module'; +export * from './category.service'; diff --git a/src/content/content.module.ts b/src/content/content.module.ts new file mode 100644 index 0000000..1f2e5fc --- /dev/null +++ b/src/content/content.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { CategoryModule } from './category'; + +@Module({ + imports: [ + /** + * 分类模块 + */ + CategoryModule + ], +}) +export class ContentModule {} diff --git a/src/modules/post/dto/create-post.dto.ts b/src/content/post/dto/create-post.dto.ts similarity index 100% rename from src/modules/post/dto/create-post.dto.ts rename to src/content/post/dto/create-post.dto.ts diff --git a/src/modules/post/dto/find-post.dto.ts b/src/content/post/dto/find-post.dto.ts similarity index 100% rename from src/modules/post/dto/find-post.dto.ts rename to src/content/post/dto/find-post.dto.ts diff --git a/src/modules/post/dto/update-post.dto.ts b/src/content/post/dto/update-post.dto.ts similarity index 100% rename from src/modules/post/dto/update-post.dto.ts rename to src/content/post/dto/update-post.dto.ts diff --git a/src/modules/post/entities/post.entity.ts b/src/content/post/entities/post.entity.ts similarity index 100% rename from src/modules/post/entities/post.entity.ts rename to src/content/post/entities/post.entity.ts diff --git a/src/modules/post/index.ts b/src/content/post/index.ts similarity index 100% rename from src/modules/post/index.ts rename to src/content/post/index.ts diff --git a/src/modules/post/post.controller.ts b/src/content/post/post.controller.ts similarity index 100% rename from src/modules/post/post.controller.ts rename to src/content/post/post.controller.ts diff --git a/src/modules/post/post.module.ts b/src/content/post/post.module.ts similarity index 100% rename from src/modules/post/post.module.ts rename to src/content/post/post.module.ts diff --git a/src/modules/post/post.service.ts b/src/content/post/post.service.ts similarity index 100% rename from src/modules/post/post.service.ts rename to src/content/post/post.service.ts diff --git a/src/database/suscribers/entify.subscriber.ts b/src/database/suscribers/entify.subscriber.ts index 63c9803..eda82ff 100644 --- a/src/database/suscribers/entify.subscriber.ts +++ b/src/database/suscribers/entify.subscriber.ts @@ -27,7 +27,7 @@ export class EntitySubscripber implements EntitySubscriberInterface { } beforeSoftRemove(event: SoftRemoveEvent): void | Promise { - event.entity.deletedBy = this.getUser(); + event.entity && (event.entity.deletedBy = this.getUser()); } getUser() { diff --git a/src/modules/user/entities/user.entity.ts b/src/modules/user/entities/user.entity.ts index 1268278..9e4c116 100644 --- a/src/modules/user/entities/user.entity.ts +++ b/src/modules/user/entities/user.entity.ts @@ -1,5 +1,5 @@ import { BaseEntity } from '@/database'; -import { Post } from '@/modules/post'; +import { Post } from '@/content/post'; import { Role } from '@/modules/role'; import { ApiHideProperty } from '@nestjs/swagger'; import { Exclude } from 'class-transformer'; diff --git a/src/common/cache/cache.controller.ts b/src/storage/cache/cache.controller.ts similarity index 100% rename from src/common/cache/cache.controller.ts rename to src/storage/cache/cache.controller.ts diff --git a/src/common/cache/cache.module.ts b/src/storage/cache/cache.module.ts similarity index 100% rename from src/common/cache/cache.module.ts rename to src/storage/cache/cache.module.ts diff --git a/src/common/cache/cache.service.ts b/src/storage/cache/cache.service.ts similarity index 100% rename from src/common/cache/cache.service.ts rename to src/storage/cache/cache.service.ts diff --git a/src/common/cache/index.ts b/src/storage/cache/index.ts similarity index 100% rename from src/common/cache/index.ts rename to src/storage/cache/index.ts diff --git a/src/modules/upload/dto/create-upload.dto.ts b/src/storage/upload/dto/create-upload.dto.ts similarity index 100% rename from src/modules/upload/dto/create-upload.dto.ts rename to src/storage/upload/dto/create-upload.dto.ts diff --git a/src/modules/upload/dto/update-upload.dto.ts b/src/storage/upload/dto/update-upload.dto.ts similarity index 100% rename from src/modules/upload/dto/update-upload.dto.ts rename to src/storage/upload/dto/update-upload.dto.ts diff --git a/src/modules/upload/entities/upload.entity.ts b/src/storage/upload/entities/upload.entity.ts similarity index 100% rename from src/modules/upload/entities/upload.entity.ts rename to src/storage/upload/entities/upload.entity.ts diff --git a/src/modules/upload/index.ts b/src/storage/upload/index.ts similarity index 100% rename from src/modules/upload/index.ts rename to src/storage/upload/index.ts diff --git a/src/modules/upload/upload.controller.ts b/src/storage/upload/upload.controller.ts similarity index 100% rename from src/modules/upload/upload.controller.ts rename to src/storage/upload/upload.controller.ts diff --git a/src/modules/upload/upload.module.ts b/src/storage/upload/upload.module.ts similarity index 100% rename from src/modules/upload/upload.module.ts rename to src/storage/upload/upload.module.ts diff --git a/src/modules/upload/upload.service.ts b/src/storage/upload/upload.service.ts similarity index 100% rename from src/modules/upload/upload.service.ts rename to src/storage/upload/upload.service.ts