diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 37e916bc4..b609e99ae 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -3,15 +3,15 @@ name: Build and push Docker image on: workflow_dispatch: push: - branches: - - master + tags: + - "v*.*.*" jobs: buildx: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: submodules: true @@ -19,7 +19,11 @@ jobs: uses: docker/setup-qemu-action@v2 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 + + - name: Extract tag name + id: tag + run: echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV - name: Cache Docker layers uses: actions/cache@v3 @@ -30,18 +34,20 @@ jobs: ${{ runner.os }}-buildx- - name: Login to DockerHub - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and push - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v6 with: context: . platforms: linux/amd64,linux/arm64 push: true - tags: ${{ secrets.DOCKER_USERNAME }}/filecodebox:beta + tags: | + ${{ secrets.DOCKERHUB_USERNAME }}/filecodebox:${{ env.TAG }} + ${{ secrets.DOCKERHUB_USERNAME }}/filecodebox:latest cache-from: type=local,src=/tmp/.buildx-cache cache-to: type=local,dest=/tmp/.buildx-cache-new,mode=max - name: Move cache diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml deleted file mode 100644 index d047b2b87..000000000 --- a/.github/workflows/docs.yml +++ /dev/null @@ -1,69 +0,0 @@ -# 构建 VitePress 站点并将其部署到 GitHub Pages 的示例工作流程 -# -name: Deploy VitePress site to Pages - -on: - # 在针对 `main` 分支的推送上运行。如果你 - # 使用 `master` 分支作为默认分支,请将其更改为 `master` - push: - branches: [ master ] - - # 允许你从 Actions 选项卡手动运行此工作流程 - workflow_dispatch: - -# 设置 GITHUB_TOKEN 的权限,以允许部署到 GitHub Pages -permissions: - contents: read - pages: write - id-token: write - -# 只允许同时进行一次部署,跳过正在运行和最新队列之间的运行队列 -# 但是,不要取消正在进行的运行,因为我们希望允许这些生产部署完成 -concurrency: - group: pages - cancel-in-progress: false - -jobs: - # 构建工作 - build: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - fetch-depth: 0 # 如果未启用 lastUpdated,则不需要 - - uses: pnpm/action-setup@v3 - with: - version: 9 - # - uses: oven-sh/setup-bun@v1 # 如果使用 Bun,请取消注释 - - name: Setup Node - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: 'pnpm' - cache-dependency-path: 'docs/pnpm-lock.yaml' - - name: Setup Pages - uses: actions/configure-pages@v4 - - name: Install dependencies - working-directory: docs - run: pnpm install - - name: Build with VitePress - working-directory: docs - run: pnpm run docs:build - - name: Upload artifact - uses: actions/upload-pages-artifact@v3 - with: - path: docs/.vitepress/dist - - # 部署工作 - deploy: - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - needs: build - runs-on: ubuntu-latest - name: Deploy - steps: - - name: Deploy to GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 diff --git a/.python-version b/.python-version new file mode 100644 index 000000000..1d4830ee0 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.10.17 diff --git a/apps/admin/views.py b/apps/admin/views.py index f5ed34c81..861d1d554 100644 --- a/apps/admin/views.py +++ b/apps/admin/views.py @@ -18,7 +18,7 @@ from apps.admin.dependencies import create_token from core.settings import settings -admin_api = APIRouter(prefix="/admin", tags=["管理"]) +admin_api = APIRouter(prefix="/api/admin", tags=["管理"]) @admin_api.post("/login") @@ -63,9 +63,9 @@ async def dashboard(admin: bool = Depends(admin_required)): @admin_api.delete("/file/delete") async def file_delete( - data: IDData, - file_service: FileService = Depends(get_file_service), - admin: bool = Depends(admin_required), + data: IDData, + file_service: FileService = Depends(get_file_service), + admin: bool = Depends(admin_required), ): await file_service.delete_file(data.id) return APIResponse() @@ -73,11 +73,11 @@ async def file_delete( @admin_api.get("/file/list") async def file_list( - page: int = 1, - size: int = 10, - keyword: str = "", - file_service: FileService = Depends(get_file_service), - admin: bool = Depends(admin_required), + page: int = 1, + size: int = 10, + keyword: str = "", + file_service: FileService = Depends(get_file_service), + admin: bool = Depends(admin_required), ): files, total = await file_service.list_files(page, size, keyword) return APIResponse( @@ -92,17 +92,17 @@ async def file_list( @admin_api.get("/config/get") async def get_config( - config_service: ConfigService = Depends(get_config_service), - admin: bool = Depends(admin_required), + config_service: ConfigService = Depends(get_config_service), + admin: bool = Depends(admin_required), ): return APIResponse(detail=config_service.get_config()) @admin_api.patch("/config/update") async def update_config( - data: dict, - config_service: ConfigService = Depends(get_config_service), - admin: bool = Depends(admin_required), + data: dict, + config_service: ConfigService = Depends(get_config_service), + admin: bool = Depends(admin_required), ): data.pop("themesChoices") await config_service.update_config(data) @@ -111,9 +111,9 @@ async def update_config( @admin_api.get("/file/download") async def file_download( - id: int, - file_service: FileService = Depends(get_file_service), - admin: bool = Depends(admin_required), + id: int, + file_service: FileService = Depends(get_file_service), + admin: bool = Depends(admin_required), ): file_content = await file_service.download_file(id) return file_content @@ -121,8 +121,8 @@ async def file_download( @admin_api.get("/local/lists") async def get_local_lists( - local_file_service: LocalFileService = Depends(get_local_file_service), - admin: bool = Depends(admin_required), + local_file_service: LocalFileService = Depends(get_local_file_service), + admin: bool = Depends(admin_required), ): files = await local_file_service.list_files() return APIResponse(detail=files) @@ -130,9 +130,9 @@ async def get_local_lists( @admin_api.delete("/local/delete") async def delete_local_file( - item: DeleteItem, - local_file_service: LocalFileService = Depends(get_local_file_service), - admin: bool = Depends(admin_required), + item: DeleteItem, + local_file_service: LocalFileService = Depends(get_local_file_service), + admin: bool = Depends(admin_required), ): result = await local_file_service.delete_file(item.filename) return APIResponse(detail=result) @@ -140,9 +140,9 @@ async def delete_local_file( @admin_api.post("/local/share") async def share_local_file( - item: ShareItem, - file_service: FileService = Depends(get_file_service), - admin: bool = Depends(admin_required), + item: ShareItem, + file_service: FileService = Depends(get_file_service), + admin: bool = Depends(admin_required), ): share_info = await file_service.share_local_file(item) return APIResponse(detail=share_info) @@ -150,8 +150,8 @@ async def share_local_file( @admin_api.patch("/file/update") async def update_file( - data: UpdateFileData, - admin: bool = Depends(admin_required), + data: UpdateFileData, + admin: bool = Depends(admin_required), ): file_code = await FileCodes.filter(id=data.id).first() if not file_code: diff --git a/apps/base/views.py b/apps/base/views.py index 2744c7031..29e7ae400 100644 --- a/apps/base/views.py +++ b/apps/base/views.py @@ -7,13 +7,18 @@ from apps.admin.dependencies import share_required_login from apps.base.models import FileCodes, UploadChunk from apps.base.schemas import SelectFileModel, InitChunkUploadModel, CompleteUploadModel -from apps.base.utils import get_expire_info, get_file_path_name, ip_limit, get_chunk_file_path_name +from apps.base.utils import ( + get_expire_info, + get_file_path_name, + ip_limit, + get_chunk_file_path_name, +) from core.response import APIResponse from core.settings import settings from core.storage import storages, FileStorageInterface from core.utils import get_select_token -share_api = APIRouter(prefix="/share", tags=["分享"]) +share_api = APIRouter(prefix="/api/share", tags=["分享"]) async def validate_file_size(file: UploadFile, max_size: int): @@ -30,10 +35,10 @@ async def create_file_code(code, **kwargs): @share_api.post("/text/", dependencies=[Depends(share_required_login)]) async def share_text( - text: str = Form(...), - expire_value: int = Form(default=1, gt=0), - expire_style: str = Form(default="day"), - ip: str = Depends(ip_limit["upload"]), + text: str = Form(...), + expire_value: int = Form(default=1, gt=0), + expire_style: str = Form(default="day"), + ip: str = Depends(ip_limit["upload"]), ): text_size = len(text.encode("utf-8")) max_txt_size = 222 * 1024 @@ -58,10 +63,10 @@ async def share_text( @share_api.post("/file/", dependencies=[Depends(share_required_login)]) async def share_file( - expire_value: int = Form(default=1, gt=0), - expire_style: str = Form(default="day"), - file: UploadFile = File(...), - ip: str = Depends(ip_limit["upload"]), + expire_value: int = Form(default=1, gt=0), + expire_style: str = Form(default="day"), + file: UploadFile = File(...), + ip: str = Depends(ip_limit["upload"]), ): await validate_file_size(file, settings.uploadSize) @@ -157,7 +162,7 @@ async def download_file(key: str, code: str, ip: str = Depends(ip_limit["error"] ) -chunk_api = APIRouter(prefix="/chunk", tags=["切片"]) +chunk_api = APIRouter(prefix="/api/chunk", tags=["切片"]) @chunk_api.post("/upload/init/", dependencies=[Depends(share_required_login)]) @@ -191,23 +196,27 @@ async def init_chunk_upload(data: InitChunkUploadModel): ) # 获取已上传的分片列表 uploaded_chunks = await UploadChunk.filter( - upload_id=upload_id, - completed=True - ).values_list('chunk_index', flat=True) - return APIResponse(detail={ - "existed": False, - "upload_id": upload_id, - "chunk_size": data.chunk_size, - "total_chunks": total_chunks, - "uploaded_chunks": uploaded_chunks - }) - - -@chunk_api.post("/upload/chunk/{upload_id}/{chunk_index}", dependencies=[Depends(share_required_login)]) + upload_id=upload_id, completed=True + ).values_list("chunk_index", flat=True) + return APIResponse( + detail={ + "existed": False, + "upload_id": upload_id, + "chunk_size": data.chunk_size, + "total_chunks": total_chunks, + "uploaded_chunks": uploaded_chunks, + } + ) + + +@chunk_api.post( + "/upload/chunk/{upload_id}/{chunk_index}", + dependencies=[Depends(share_required_login)], +) async def upload_chunk( - upload_id: str, - chunk_index: int, - chunk: UploadFile = File(...), + upload_id: str, + chunk_index: int, + chunk: UploadFile = File(...), ): # 获取上传会话信息 chunk_info = await UploadChunk.filter(upload_id=upload_id, chunk_index=-1).first() @@ -227,24 +236,30 @@ async def upload_chunk( upload_id=upload_id, chunk_index=chunk_index, defaults={ - 'chunk_hash': chunk_hash, - 'completed': True, - 'file_size': chunk_info.file_size, - 'total_chunks': chunk_info.total_chunks, - 'chunk_size': chunk_info.chunk_size, - 'file_name': chunk_info.file_name - } + "chunk_hash": chunk_hash, + "completed": True, + "file_size": chunk_info.file_size, + "total_chunks": chunk_info.total_chunks, + "chunk_size": chunk_info.chunk_size, + "file_name": chunk_info.file_name, + }, ) # 获取文件路径 - _, _, _, _, save_path = await get_chunk_file_path_name(chunk_info.file_name, upload_id) + _, _, _, _, save_path = await get_chunk_file_path_name( + chunk_info.file_name, upload_id + ) # 保存分片到存储 storage = storages[settings.file_storage]() await storage.save_chunk(upload_id, chunk_index, chunk_data, chunk_hash, save_path) return APIResponse(detail={"chunk_hash": chunk_hash}) -@chunk_api.post("/upload/complete/{upload_id}", dependencies=[Depends(share_required_login)]) -async def complete_upload(upload_id: str, data: CompleteUploadModel, ip: str = Depends(ip_limit["upload"])): +@chunk_api.post( + "/upload/complete/{upload_id}", dependencies=[Depends(share_required_login)] +) +async def complete_upload( + upload_id: str, data: CompleteUploadModel, ip: str = Depends(ip_limit["upload"]) +): # 获取上传基本信息 chunk_info = await UploadChunk.filter(upload_id=upload_id, chunk_index=-1).first() if not chunk_info: @@ -253,17 +268,20 @@ async def complete_upload(upload_id: str, data: CompleteUploadModel, ip: str = D storage = storages[settings.file_storage]() # 验证所有分片 completed_chunks = await UploadChunk.filter( - upload_id=upload_id, - completed=True + upload_id=upload_id, completed=True ).count() if completed_chunks != chunk_info.total_chunks: raise HTTPException(status.HTTP_400_BAD_REQUEST, detail="分片不完整") # 获取文件路径 - path, suffix, prefix, _, save_path = await get_chunk_file_path_name(chunk_info.file_name, upload_id) + path, suffix, prefix, _, save_path = await get_chunk_file_path_name( + chunk_info.file_name, upload_id + ) # 合并文件并计算哈希 await storage.merge_chunks(upload_id, chunk_info, save_path) # 创建文件记录 - expired_at, expired_count, used_count, code = await get_expire_info(data.expire_value, data.expire_style) + expired_at, expired_count, used_count, code = await get_expire_info( + data.expire_value, data.expire_style + ) await FileCodes.create( code=code, file_hash=chunk_info.chunk_hash, @@ -276,7 +294,7 @@ async def complete_upload(upload_id: str, data: CompleteUploadModel, ip: str = D file_path=path, uuid_file_name=f"{prefix}{suffix}", prefix=prefix, - suffix=suffix + suffix=suffix, ) # 清理临时文件 await storage.clean_chunks(upload_id, save_path) diff --git a/main.py b/main.py index a85da8022..f5235b87e 100644 --- a/main.py +++ b/main.py @@ -125,7 +125,7 @@ async def robots(): return HTMLResponse(content=settings.robotsText, media_type="text/plain") -@app.post("/") +@app.post("/api/config") async def get_config(): return APIResponse( detail={ @@ -134,7 +134,11 @@ async def get_config(): "explain": settings.page_explain, "uploadSize": settings.uploadSize, "expireStyle": settings.expireStyle, - "enableChunk": settings.enableChunk if settings.file_storage == "local" and settings.enableChunk else 0, + "enableChunk": ( + settings.enableChunk + if settings.file_storage == "local" and settings.enableChunk + else 0 + ), "openUpload": settings.openUpload, "notify_title": settings.notify_title, "notify_content": settings.notify_content,