Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions cert/backend/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# LOG_LEVEL=INFO
# ENVIRONMENT=dev

# CORS Origins (쉼표로 구분)
CORS_ORIGINS=https://cert.pseudo-lab.com,https://dev-cert.pseudolab-devfactory.com,http://localhost:5173
5 changes: 4 additions & 1 deletion cert/backend/src/constants/error_codes.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ class ErrorCodes:

class ErrorMessages:
"""에러 메시지 상수"""
NO_HISTORY = "수료이력이 확인되지 않습니다. 🥲\n디스코드를 통해 김찬란에게 문의해주세요."
NO_HISTORY = "수료 명단에 존재하지 않습니다. 🥲\n입력하신 이름 '{name}'(이)가 명단에 있는지 확인하거나, 스터디 빌더 혹은 질문게시판에 문의해주세요."
USER_DROPPED_OUT = "스터디를 수료하지 않았습니다. 🥲\n스터디 빌더 혹은 질문게시판에 문의해주세요."
STUDY_NOT_COMPLETED = "수료증은 스터디가 완료된 이후 발급 가능합니다.\n스터디 빌더 혹은 질문게시판에 문의해주세요."
PROJECT_NOT_FOUND = "해당 프로젝트가 검색되지 않습니다.\n기수와 스터디명을 다시 확인하거나, 스터디 빌더 혹은 질문게시판에 문의해주세요."
PIPELINE_ERROR = "발급 처리 중 오류가 발생했습니다."
CONTACT_INFO = "시스템 상의 오류로 수료증 발급에 실패했습니다. 🥲\n디스코드를 통해 김수현(kyopbi)에게 문의해주세요"

Expand Down
18 changes: 15 additions & 3 deletions cert/backend/src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,23 @@ def configure_logging() -> None:
# Access log middleware
app.middleware("http")(access_log_middleware)

# CORS 미들웨어 설정
origins = os.getenv("CORS_ORIGINS", "").split(",")
# CORS configuration
# Load allowed origins from CORS_ORIGINS environment variable (comma-separated)
cors_origins_str = os.getenv("CORS_ORIGINS", "")
if cors_origins_str:
origins = [origin.strip() for origin in cors_origins_str.split(",") if origin.strip()]
else:
# Default origins for local development and known production/dev domains
origins = [
"http://localhost:3000",
"http://localhost:5173",
"https://cert.pseudo-lab.com",
"https://dev-cert.pseudolab-devfactory.com",
]

app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
Expand Down
9 changes: 9 additions & 0 deletions cert/backend/src/services/certificate_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -589,6 +589,15 @@ async def _create_new_certificate(
)
)

except NotEligibleError as e:
# 수료 대상 아님 (명단에 없거나 이탈자 등)
logger.warning(f"수료 대상 아님: {str(e)}")
if request_id:
await notion_client.update_certificate_status(
page_id=request_id,
status=CertificateStatus.NOT_ELIGIBLE
)
raise e
except Exception as e:
# 시스템 오류
logger.exception("신규 수료증 발급 중 시스템 오류")
Expand Down
15 changes: 8 additions & 7 deletions cert/backend/src/utils/notion_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import aiohttp
from typing import Optional, Dict, Any, List

from ..constants.error_codes import NotEligibleError, ResponseStatus
from ..constants.error_codes import NotEligibleError, ResponseStatus, ErrorMessages
from ..models.certificate import CertificateStatus
from ..models.project import Project, SeasonGroup, ProjectsBySeasonResponse

Expand Down Expand Up @@ -156,14 +156,17 @@ async def verify_user_participation(
user_role = "BUILDER"
elif user_name in runner_names:
user_role = "RUNNER"
# '수료자' 필드에 이름이 포함되어 있는지 확인
elif any(user_name in c for c in completer_names):
user_role = "RUNNER"

# 2. 사용자가 이탈자에 있는지 확인
if user_name in dropout_names:
raise NotEligibleError(f"수료 명단에 존재하지 않습니다. 🥲\n디스코드를 통해 질문게시판에 문의해주세요.")
raise NotEligibleError(ErrorMessages.USER_DROPPED_OUT)

# 3. 사용자가 참여자 목록에 있는지 확인
if user_role is None:
raise NotEligibleError(f"수료 명단에 존재하지 않습니다. 🥲\n디스코드를 통해 질문게시판에 문의해주세요.")
raise NotEligibleError(ErrorMessages.NO_HISTORY.format(name=user_name))

study_status = properties.get("단계", {}).get("select", {})
period_raw = project.get("properties", {}).get("기간", {}).get("date", {}) or {}
Expand All @@ -174,9 +177,7 @@ async def verify_user_participation(
)

if study_status.get("name") != "완료":
raise NotEligibleError(
"수료증은 스터디가 완료된 이후 발급 가능합니다.\n디스코드를 통해 질문게시판에 문의해주세요."
)
raise NotEligibleError(ErrorMessages.STUDY_NOT_COMPLETED)

fallback_period = self.default_periods.get(str(season), {})
raw_start = period_raw.get("start")
Expand Down Expand Up @@ -273,7 +274,7 @@ async def verify_user_participation(
"course_name": course_name,
},
)
raise Exception("해당 프로젝트가 검색되지 않습니다. \n디스코드를 통해 질문게시판에 문의해주세요.")
raise NotEligibleError(ErrorMessages.PROJECT_NOT_FOUND)
except Exception as e:
raise e

Expand Down
Loading