liuyuqi-dellpc 1 year ago
parent
commit
96a7c97c73
5 changed files with 96 additions and 10 deletions
  1. 18 4
      .github/workflows/deploy-docker.yml
  2. 35 0
      Dockerfile
  3. 22 1
      backend/apps/api/home.py
  4. 9 5
      backend/apps/api/v1/login.py
  5. 12 0
      deploy/entrypoint.sh

+ 18 - 4
.github/workflows/deploy-docker.yml

@@ -2,8 +2,7 @@ name: Release Docker Image
 
 on:
   push:
-    tags:
-      - '[0-9]+.[0-9]+.[0-9]+'
+    tags: ['v*']
 
 jobs:
   build:
@@ -25,14 +24,29 @@ jobs:
       - name: Login to GitHub Container Registry
         run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin
 
+      - name: Log in to Docker Hub
+        uses: docker/login-action@v3
+        with:
+          username: ${{ secrets.DOCKERHUB_USERNAME  }}
+          password: ${{ secrets.DOCKERHUB_TOKEN }}
+
       - name: Build and push Docker image
-        uses: docker/build-push-action@v3
+        uses: docker/build-push-action@v5
         with:
           context: .
           file: ./Dockerfile
           platforms: linux/amd64,linux/arm64
           push: true
-          tags: ghcr.io/${{ github.repository }}:${{ github.ref_name }},ghcr.io/${{ github.repository }}:latest
+          tags: ghcr.io/${{ github.repository }}:${{ github.ref_name }},ghcr.io/${{ github.repository }}:latest,jianboy/${{ github.repository }}:${{ github.ref_name }}
 
       - name: Create GitHub Release
         uses: softprops/action-gh-release@v2
+
+      - name: Notify team on failure
+        if: ${{ failure() }}
+        uses: fjogeleit/http-request-action@v1
+        with:
+          url: ${{ secrets.BUILD_NOTIFICATION_URL }}
+          method: 'POST'
+          customHeaders: '{"Content-Type": "application/json"}'
+          data: '{"msg_type": "text","content": {"text": "HeyForm docker image build failed"}}'

+ 35 - 0
Dockerfile

@@ -0,0 +1,35 @@
+FROM node:18.8.0-alpine3.16 as frontend
+
+WORKDIR /app
+COPY ./frontend .
+RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories \
+    && cd /app/frontend && npm i -g pnpm --registry=https://registry.npmmirror.com \
+    && pnpm i && pnpm run build
+
+
+FROM python:3.11-slim
+
+WORKDIR /app
+ADD ./backend .
+COPY ./deploy/entrypoint.sh .
+
+RUN sed -i s@/deb.debian.org/@/mirrors.aliyun.com/@g /etc/apt/sources.list.d/debian.sources \
+    && sed -i s@/security.debian.org/@/mirrors.aliyun.com/@g /etc/apt/sources.list.d/debian.sources
+
+RUN apt-get update \
+    && apt-get install -y --no-install-recommends gcc python3-dev bash nginx vim curl procps net-tools\
+    && ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
+
+RUN pip install poetry -i https://pypi.tuna.tsinghua.edu.cn/simple\
+    && poetry config virtualenvs.create false \
+    && poetry install
+
+COPY --from=frontend /app/frontend/dist /app/frontend/dist
+ADD /deploy/web.conf /etc/nginx/sites-available/web.conf
+RUN rm -f /etc/nginx/sites-enabled/default \ 
+    && ln -s /etc/nginx/sites-available/web.conf /etc/nginx/sites-enabled/ 
+
+ENV LANG=zh_CN.UTF-8
+EXPOSE 80
+
+ENTRYPOINT [ "sh", "entrypoint.sh" ]

+ 22 - 1
backend/apps/api/home.py

@@ -6,7 +6,16 @@
 @Desc    :
 """
 
-from fastapi import APIRouter
+from fastapi import APIRouter, FastAPI, Request, HTTPException, status
+from fastapi.responses import (
+    StreamingResponse,
+    JSONResponse,
+    HTMLResponse,
+    FileResponse,
+    RedirectResponse,
+    Response,
+)
+from pathlib import Path
 
 router = APIRouter()
 
@@ -14,3 +23,15 @@ router = APIRouter()
 @router.get("/")
 async def index():
     return {"code": 200, "message": "this is backend api"}
+
+@app.get("/{full_path:path}", include_in_schema=False)
+def spa(full_path: str):
+    dist_dir = Path(__file__).parent / "dist"
+    # TODO: hacky way to only serve index.html on root urls
+    files = [entry.name for entry in dist_dir.iterdir() if entry.is_file()]
+    if full_path in files:
+        return FileResponse(dist_dir / full_path)
+    if "." in full_path:
+        raise HTTPException(status_code=404, detail="Asset not found")
+    return HTMLResponse((dist_dir / "index.html").read_bytes())
+

+ 9 - 5
backend/apps/api/v1/login.py

@@ -19,7 +19,7 @@ from fastapi.security import OAuth2PasswordRequestForm
 router = APIRouter()
 
 
-@router.post("/login/access-token")
+@router.post("/login/access-token", summary="登录")
 def login_access_token(
     session: SessionDep, form_data: Annotated[OAuth2PasswordRequestForm, Depends()]
 ) -> Token:
@@ -41,7 +41,7 @@ def login_access_token(
     )
 
 
-@router.post("/login/test-token", response_model=UserOut)
+@router.post("/login/test-token", response_model=UserOut, summary="test")
 def test_token(current_user: CurrentUser) -> Any:
     """
     Test access token
@@ -49,7 +49,7 @@ def test_token(current_user: CurrentUser) -> Any:
     return current_user
 
 
-@router.post("/password-recovery/{email}")
+@router.post("/password-recovery/{email}", summary= "密码找回")
 def recover_password(email: str, session: SessionDep) -> Message:
     """
     Password Recovery
@@ -73,7 +73,7 @@ def recover_password(email: str, session: SessionDep) -> Message:
     return Message(message="Password recovery email sent")
 
 
-@router.post("/reset-password/")
+@router.post("/reset-password/", summary="重置密码")
 def reset_password(session: SessionDep, body: NewPassword) -> Message:
     """
     Reset password
@@ -101,6 +101,7 @@ def reset_password(session: SessionDep, body: NewPassword) -> Message:
     "/password-recovery-html-content/{email}",
     dependencies=[Depends(get_current_active_superuser)],
     response_class=HTMLResponse,
+    summary="HTML Content for Password Recovery",
 )
 def recover_password_html_content(email: str, session: SessionDep) -> Any:
     """
@@ -123,7 +124,7 @@ def recover_password_html_content(email: str, session: SessionDep) -> Any:
     )
 
 
-@router.get("/healthcheck", include_in_schema=False)
+@router.get("/healthcheck", include_in_schema=False, summary="健康检查")
 async def healthcheck() -> dict[str, str]:
     return {"status": "ok"}
 
@@ -138,3 +139,6 @@ async def healthcheck() -> dict[str, str]:
 #     admin.password =
 #     new_admin = await add_user(admin)
 #     return new_admin
+
+
+

+ 12 - 0
deploy/entrypoint.sh

@@ -0,0 +1,12 @@
+#!/bin/bash
+# @Contact :   liuyuqi.gov@msn.cn
+# @Time    :   2024/04/04 12:11:50
+# @License :   (C)Copyright 2022 liuyuqi.
+# @Desc    :   
+###############################################################################
+
+# 一旦出现错误,脚本就会停止运行
+set -e
+
+nginx
+python run.py