Skip to content

Commit 3a358b2

Browse files
authored
Merge pull request #281 from Pseudo-Lab/deploy/cert-develop
feat(cert): 수료증 개발 1μ°¨ μ™„λ£Œ
2 parents 46eaf94 + 9d3a2d4 commit 3a358b2

File tree

22 files changed

+887
-135
lines changed

22 files changed

+887
-135
lines changed
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
name: cert-system deploy (Production/Development, self-hosted)
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
- deploy/cert-develop
8+
paths:
9+
- 'cert/**'
10+
- '.github/workflows/certificate-system.yml'
11+
workflow_dispatch:
12+
13+
# 같은 브랜치 λ™μ‹œ μ‹€ν–‰ μ‹œ 이전 작 μ·¨μ†Œ(경쟁 배포 λ°©μ§€)
14+
concurrency:
15+
group: cert-${{ github.ref }}
16+
cancel-in-progress: true
17+
18+
jobs:
19+
deploy-prod:
20+
if: github.ref_name == 'main'
21+
name: πŸš€ Deploy cert-system (Production)
22+
runs-on: oracle
23+
environment: cert_prod_deploy
24+
defaults:
25+
run:
26+
working-directory: ./cert
27+
steps:
28+
- uses: actions/checkout@v4
29+
30+
- name: Write .env (prod)
31+
run: |
32+
cat > .env <<'EOF'
33+
APP_HOST=${{ vars.APP_HOST }}
34+
ENVIRONMENT=${{ vars.ENVIRONMENT }}
35+
NODE_ENV=${{ vars.NODE_ENV }}
36+
CERT_TEMPLATE_ARCHIVE_PASSWORD=${{ secrets.CERT_TEMPLATE_ARCHIVE_PASSWORD }}
37+
NOTION_API_KEY=${{ secrets.NOTION_API_KEY }}
38+
NOTION_CERT_DB_ID=${{ secrets.NOTION_CERT_DB_ID }}
39+
NOTION_PROJ_DB_ID=${{ secrets.NOTION_PROJ_DB_ID }}
40+
SMTP_HOST=${{ vars.SMTP_HOST }}
41+
SMTP_PORT=${{ vars.SMTP_PORT }}
42+
SMTP_USERNAME=${{ secrets.SMTP_USERNAME }}
43+
SMTP_PASSWORD=${{ secrets.SMTP_PASSWORD }}
44+
CORS_ORIGINS=${{ vars.CORS_ORIGINS }}
45+
FRONTEND_EXTERNAL_API_URL=${{ vars.FRONTEND_EXTERNAL_API_URL }}
46+
EOF
47+
48+
- name: Build & up (prod)
49+
run: |
50+
set -euxo pipefail
51+
docker compose -p cert-main config -q
52+
docker compose -p cert-main down --remove-orphans
53+
docker compose -p cert-main up -d --build --remove-orphans
54+
docker image prune -f --filter "label=org.pseudolab.project=cert"
55+
56+
deploy-dev:
57+
if: github.ref_name == 'deploy/cert-develop'
58+
name: πŸš€ Deploy cert-system (Development)
59+
runs-on: oracle
60+
environment: cert_dev_deploy
61+
defaults:
62+
run:
63+
working-directory: ./cert
64+
steps:
65+
- uses: actions/checkout@v4
66+
67+
- name: Write .env (dev)
68+
run: |
69+
cat > .env <<'EOF'
70+
APP_HOST=${{ vars.APP_HOST }}
71+
ENVIRONMENT=${{ vars.ENVIRONMENT }}
72+
NODE_ENV=${{ vars.NODE_ENV }}
73+
CERT_TEMPLATE_ARCHIVE_PASSWORD=${{ secrets.CERT_TEMPLATE_ARCHIVE_PASSWORD }}
74+
NOTION_API_KEY=${{ secrets.NOTION_API_KEY }}
75+
NOTION_CERT_DB_ID=${{ secrets.NOTION_CERT_DB_ID }}
76+
NOTION_PROJ_DB_ID=${{ secrets.NOTION_PROJ_DB_ID }}
77+
SMTP_HOST=${{ vars.SMTP_HOST }}
78+
SMTP_PORT=${{ vars.SMTP_PORT }}
79+
SMTP_USERNAME=${{ secrets.SMTP_USERNAME }}
80+
SMTP_PASSWORD=${{ secrets.SMTP_PASSWORD }}
81+
CORS_ORIGINS=${{ vars.CORS_ORIGINS }}
82+
FRONTEND_EXTERNAL_API_URL=${{ vars.FRONTEND_EXTERNAL_API_URL }}
83+
EOF
84+
85+
- name: Build & up (dev)
86+
run: |
87+
set -euxo pipefail
88+
docker compose -p cert-dev config -q
89+
docker compose -p cert-dev down --remove-orphans
90+
docker compose -p cert-dev up -d --build --remove-orphans
91+
docker image prune -f --filter "label=org.pseudolab.project=cert"

β€Žcert/.env.exampleβ€Ž

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
NOTION_API_KEY=api_key_here
2+
NOTION_CERT_DB_ID=notion_cert_db_id_here
3+
NOTION_PROJ_DB_ID=notion_proj_db_id_here
4+
5+
SMTP_HOST=example_smtp_host
6+
SMTP_PORT=port_number
7+
SMTP_USERNAME=example_username
8+
SMTP_PASSWORD=example_password
9+
10+
CORS_ORIGINS="https://example.com"
11+
12+
FRONTEND_EXTERNAL_API_URL=https://example.com
13+
14+
APP_HOST=example.com
15+
16+
ENVIRONMENT=dev
17+
NODE_ENV=development
18+
19+
CERT_TEMPLATE_ARCHIVE_PASSWORD=your_secure_password

β€Žcert/backend/Dockerfileβ€Ž

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Use an official Python runtime as a parent image
2+
FROM python:3.11-slim
3+
4+
# Set the working directory in the container
5+
WORKDIR /app
6+
7+
# Install tzdata and set timezone to Asia/Seoul
8+
RUN apt-get update && \
9+
apt-get install -y --no-install-recommends tzdata && \
10+
ln -snf /usr/share/zoneinfo/Asia/Seoul /etc/localtime && \
11+
echo "Asia/Seoul" > /etc/timezone && \
12+
apt-get clean && rm -rf /var/lib/apt/lists/*
13+
14+
ENV TZ=Asia/Seoul
15+
16+
# Copy the dependency files
17+
COPY requirements.txt .
18+
COPY uv.lock .
19+
20+
# Install any needed packages specified in requirements.txt
21+
# Using uv pip install for faster and more reliable dependency management
22+
RUN pip install uv && uv pip install --system -r requirements.txt
23+
24+
# Copy the rest of the application code
25+
COPY . .
26+
27+
# Run the application
28+
CMD ["gunicorn", "-w", "4", "-k", "uvicorn.workers.UvicornWorker", "-b", "0.0.0.0:8000", "src.main:app"]

β€Žcert/backend/run.pyβ€Ž

Lines changed: 0 additions & 15 deletions
This file was deleted.
-2.07 MB
Binary file not shown.
-2.06 MB
Binary file not shown.
-2.06 MB
Binary file not shown.
6.2 MB
Binary file not shown.

β€Žcert/backend/src/main.pyβ€Ž

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,26 @@
1+
import logging
2+
import os
3+
4+
from dotenv import load_dotenv
15
from fastapi import FastAPI
26
from fastapi.middleware.cors import CORSMiddleware
3-
from dotenv import load_dotenv
4-
import os
57

68
from .routers import certificate
79

810

9-
# .env 파일 λ‘œλ“œ
11+
def configure_logging() -> None:
12+
"""κΈ°λ³Έ λ‘œκΉ… 섀정을 κ΅¬μ„±ν•œλ‹€."""
13+
log_level = os.getenv("LOG_LEVEL", "INFO").upper()
14+
logging.basicConfig(
15+
level=log_level,
16+
format="%(asctime)s [%(levelname)s] %(name)s - %(message)s",
17+
)
18+
19+
20+
# .env 파일 λ‘œλ“œ 및 λ‘œκΉ… μ„€μ •
1021
load_dotenv()
22+
configure_logging()
23+
logger = logging.getLogger(__name__)
1124

1225
# FastAPI μ•± 생성
1326
app = FastAPI(
@@ -22,23 +35,27 @@
2235
origins = os.getenv("CORS_ORIGINS", "").split(",")
2336
app.add_middleware(
2437
CORSMiddleware,
25-
allow_origins=origins,
38+
allow_origins=["*"],
2639
allow_credentials=True,
2740
allow_methods=["*"],
2841
allow_headers=["*"],
2942
)
3043

31-
app.include_router(certificate.certificate_router)
44+
# λͺ¨λ“  ν™˜κ²½μ—μ„œ /api ν”„λ¦¬ν”½μŠ€ μ‚¬μš© (개발/ν”„λ‘œλ•μ…˜ 톡일)
45+
app.include_router(certificate.certificate_router, prefix="/api")
46+
logger.info("FastAPI app initialized", extra={"environment": os.getenv("ENVIRONMENT")})
47+
3248

3349
@app.get("/")
3450
async def read_root():
3551
"""루트 μ—”λ“œν¬μΈνŠΈ"""
3652
return {
3753
"message": "PseudoLab 수료증 λ°œκΈ‰ μ‹œμŠ€ν…œ API μ„œλ²„",
3854
"version": "1.0.0",
39-
"status": "running"
55+
"status": "running",
4056
}
4157

58+
4259
@app.get("/health")
4360
async def health_check():
4461
"""ν—¬μŠ€ 체크 μ—”λ“œν¬μΈνŠΈ"""

β€Žcert/backend/src/models/certificate.pyβ€Ž

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from typing import Optional
2+
13
from pydantic import BaseModel, Field
24
from enum import Enum
35

@@ -36,11 +38,11 @@ class CertificateResponse(BaseModel):
3638
"""수료증 응닡 λͺ¨λΈ"""
3739
status: str = Field(..., example="success", description="응닡 μƒνƒœ")
3840
message: str = Field(..., example="수료증이 μ„±κ³΅μ μœΌλ‘œ μƒμ„±λ˜μ—ˆμŠ΅λ‹ˆλ‹€.", description="응닡 λ©”μ‹œμ§€")
39-
data: CertificateData = Field(..., description="수료증 데이터")
41+
data: Optional[CertificateData] = Field(None, description="수료증 데이터")
4042

4143

4244
class ErrorResponse(BaseModel):
4345
"""μ—λŸ¬ 응닡 λͺ¨λΈ"""
4446
status: str = Field(..., example="fail", description="응닡 μƒνƒœ")
4547
error_code: str = Field(..., example="CS0002", description="μ—λŸ¬ μ½”λ“œ")
46-
message: str = Field(..., example="수료이λ ₯이 ν™•μΈλ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.", description="μ—λŸ¬ λ©”μ‹œμ§€")
48+
message: str = Field(..., example="수료이λ ₯이 ν™•μΈλ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.", description="μ—λŸ¬ λ©”μ‹œμ§€")

0 commit comments

Comments
Β (0)