Releases: SoongSilComputingClub/ssccops-web
Release list
v0.2.0 — 학술·행사·기획안과 3앱 모노레포
두 번째 후속 릴리스입니다. develop에 쌓인 58개 커밋을 main으로 올렸습니다.
v0.1.x까지 이 저장소는 어드민 앱 하나였습니다. 이번 회차에 모노레포로 전환하고 앱이 셋으로 늘었습니다 — 운영진이 쓰는 어드민, 로그인 없이 열리는 공개 웹사이트, 회원·스터디장이 쓰는 학술 앱입니다.
| 워크스페이스 | 무엇 | 로그인 | Worker (prod) |
|---|---|---|---|
apps/admin |
운영관리 어드민 (기존 앱) | 전 화면 필수 | admin-ssccops |
apps/www |
공개 웹사이트 — 행사 목록·상세·참가 신청 | 신청·내 신청만 | www-ssccops |
apps/lms |
학술 — 대시보드·회차·출석·기획안 | 전 화면 필수 | lms-ssccops |
앱을 나눈 기준은 로그인입니다. 학술 앱은 전 화면이 로그인 필수라 공개 앱에 얹지 않고 도메인부터 갈랐고, 공개 앱의 본체(행사 목록·상세)는 비로그인 완전 공개입니다.
모노레포 전환
- pnpm workspace + Turborepo (#138). 어드민 앱 소속 파일 전부를
git mv로apps/admin/으로 옮겼습니다 — 히스토리가 보존되고 내용은 한 줄도 바뀌지 않았습니다. packages/form-renderer추출 (#152). 폼 렌더러를 공유 패키지로 뽑았습니다 — 어드민의 미리보기, 공개 폼, 행사 신청서, 기획안 작성이 같은 렌더러를 씁니다. 두 벌이면 제출자가 본 화면과 검토자가 본 화면이 갈립니다.- Worker 이름을
wrangler.jsonc가 아니라 CLI--name으로 (#157). 앱이 늘 때마다env.dev.name·env.prod.name을 파일마다 박아 넣지 않기 위해서입니다. 이름 규칙은 각 앱package.json스크립트 한 줄로 끝납니다. apps/events→apps/www로 개명 (#160). 행사만 담을 자리가 아니라 공개 웹사이트 전체의 자리가 됐기 때문입니다.
공개 웹사이트 (apps/www)
- 행사 목록·상세 SSR/OG (#141) — 검색·공유에 걸려야 하는 화면이라 서버 렌더링이고 OG 메타를 냅니다.
- 로그인 연동과 '내 신청' (#150), 참가 신청 흐름 (#154) — 가입 유도·폼 작성·제출까지. 간편 가입을 신청 흐름 안에 임베드해, 처음 온 사람이 어드민으로 튕겨 나갔다 돌아오지 않아도 됩니다.
- 셸 정비와 PWA (#167) — 제목·상단 바 재배치.
학술 (apps/lms · apps/admin)
스터디장이 쓰는 화면과 학술국장이 쓰는 화면을 앱으로 갈랐습니다.
스터디장 (apps/lms)
- 팀원 관리 (#131) · 회차 기록 작성과 출석 체크 (#128) · 출석부 — 회차별 참석 현황 행렬 (#172) · 내 활동 목록·상세 (#188) · 대시보드 (#126)
- 첫 화면은 신분에 따라 갈립니다 (#228) — 스터디장은 대시보드로, 일반 회원은 기획안 카드 두 장. 스터디장이 아니면 상단 바에 기획안 메뉴 둘만 보입니다 (#224).
학술국장 (apps/admin)
- 학술관리 도메인 골격 (#122) · 스터디·프로젝트 목록과 활동 상세 (#125) · 회차·출석 승인 (#129) · 회차 이력·상세·출석 통계 (#130) · 대시보드 (#126)
- 모집 관리 (#127) — 모집 시작과 신청자 선발. 선발은 참가 상태로 그리고 언제든 다시 저장할 수 있습니다 (#209). 신청서 문항 편집으로 바로 연결됩니다 (#193).
기획안
- 작성·제출과 제출 현황 (#163) · 신규 작성 (#185) · 재제출 (#171 — 수정요청 사유를 먼저 보여주고 이전 답을 프리필합니다)
- 검토 화면 (#164) — 커리큘럼 표와 승인·수정요청·반려. 검토자는 승인을 누르기 전에 이관 결과를 표로 봅니다.
- 잠금 사유 노출과 목록의 활동명 표시 (#204)
- 운영관리에서 회원용 기획안 화면을 걷어냈습니다 (#231) — lms로 옮겨 간 잔재였습니다.
폼
- 시스템 폼 잠금 표시와 문항 버전 안내 (#132) — 무엇을 고칠 수 있고 무엇이 잠겼는지 화면이 미리 말합니다.
- 응답 검토 처리 화면 (#133) — 수정요청·반려 사유와 처리 이력.
- 템플릿 관리 (#134), 다중 응답 설정과 응답 목록 반영 (#135)
- 계약 문항을 서버 값으로 잠급니다 (#156) — 웹이 문항 구성에서 계약을 역산하던 것을 걷어냈습니다. 역산은 서버가 계약을 바꾸는 순간 조용히 어긋납니다.
- 학술 모집 폼의 접수 기간은 모집 관리에서만 (#194 · #222) — 폼 편집에서 입력란을 숨기고 접수 시작을 잠급니다. 접수 기간의 주인이 두 곳이면 어느 쪽이 이겼는지 화면으로는 알 수 없습니다.
행사 (운영진)
회원
- 동아리 가입 시기 입력과 연도 기반 기수 자동 채움 (#214)
- CSV 이관의 동아리 가입 시기 매핑 전환 (#215) — 안내표와 양식 CSV를 함께 갱신했습니다.
- 학번 수정과 변경 이력 표시 (#237)
수정
- 완료된 하위 업무가 '지연'으로 표기되던 문제 (#199)
data가null인 200을 오류로 세우지 않습니다 (#197) — 초안이 없는 첫 진입에서 신청이 막히고 있었습니다.- 회차 목록 상태 필드명(
sttsCd)이 어긋나 활동 상세가 죽던 문제 (#212), 회차 목록 정렬 (#190) - 신청서 문항 편집을 같은 탭으로 (#207) — 새 탭이라 뒤로가기가 죽었습니다.
- 사이드바 '기획안 검토'를 폼에서 학술 묶음으로 (#201), 공개 폼 작성 화면 가로 상한 720px → 860px (#203)
- 서버 개명 반영 — 커리큘럼 계획일
planYmd(#170), 처리 구분RSPNS_접두사 우회 제거 (#233 · #235), 행사 이미지publicUrl→imageUrl(#218), 업로드가File.type에 의존하지 않게 (#220)
⚠️ 배포 시 주의
이 저장소의 워크플로는 배포하지 않습니다 — CI만 돕니다. 실제 배포는 Cloudflare Workers Builds가 GitHub를 직접 보고 수행하므로, 아래는 전부 Cloudflare 대시보드에서 사람이 해야 하는 일입니다.
1. 손댈 곳은 어드민 하나입니다 — 나머지 넷은 이미 붙어 있습니다
이 PR의 커밋에 붙은 Workers Builds 체크로 확인한 현재 연결 상태입니다.
| Workers Builds 프로젝트 | 상태 |
|---|---|
dev-admin-ssccops · dev-www-ssccops · dev-lms-ssccops |
✅ 연결됨 · 통과 |
www-ssccops · lms-ssccops (prod) |
✅ 연결됨 · 통과 |
admin-ssccops (prod) |
❌ 없습니다 |
ssccops (prod 어드민, 옛 이름) |
main에 묶여 있고, 저장소 루트를 빌드 루트로 봅니다 |
어드민 앱은 저장소 루트에서 apps/admin/으로 내려갔습니다. 그런데 prod 어드민을 짓는 ssccops 프로젝트는 아직 루트를 보고 있고, 그 자리에는 이제 Next 앱이 없습니다 — 이 릴리스가 main에 들어가는 순간 그 빌드는 실패합니다.
다행히 실패하는 쪽이 안전한 실패입니다. 빌드가 깨지면 배포도 없으므로, 고칠 때까지 prod 어드민은 계속 v0.1.1을 서빙합니다. 서비스가 끊기는 것이 아니라 새 버전이 안 올라갈 뿐입니다.
고치는 순서:
ssccops프로젝트의 빌드 루트 디렉터리를apps/admin으로 바꿉니다.- 스크립트 이름 충돌을 확인합니다.
apps/admin의deploy:prod는 이제--name admin-ssccops를 넘깁니다 — 빌드 프로젝트가 짓는 스크립트(ssccops)와 이름이 다릅니다. 둘 중 하나로 맞춰야 합니다:- 프로젝트를
admin-ssccops로 새로 만들고 라우트·커스텀 도메인을 옮긴 뒤 옛ssccopsWorker를 정리하거나(다른 두 앱과 이름 규칙이 같아집니다), 또는 apps/admin/package.json의--name을ssccops로 되돌립니다(도메인 바인딩을 건드리지 않아도 됩니다).
- 프로젝트를
- dev 어드민(
dev-admin-ssccops)은 이미 새 구조로 통과하고 있으므로 루트 디렉터리 설정이 맞는 예시로 참고하면 됩니다.
2. prod 프로젝트가 어느 브랜치를 보는지 확인하세요
www-ssccops·lms-ssccops(prod)의 빌드가 develop 커밋에서 돌고 있습니다. 프로덕션 브랜치 설정인지 프리뷰 빌드인지는 저장소 쪽에서 구별되지 않으므로, 두 프로젝트의 Production branch가 main인지 대시보드에서 확인하세요. develop으로 돼 있으면 이 릴리스와 무관하게 prod가 이미 develop을 따라가고 있다는 뜻입니다.
wrangler.jsonc·open-next.config.ts는 세 앱 모두 준비돼 있고, 도메인 계획은 apps/www가 루트 도메인(sscc.club), apps/lms가 lms.sscc.co.kr입니다.
3. 환경변수 — 앱마다 따로입니다
세 앱이 각각 .env.example을 갖습니다. 공통은 둘입니다.
NEXT_PUBLIC_API_BASE_URL # 셋 다 같은 ssccops-server
NEXT_PUBLIC_SUPABASE_URL # 셋 다 같은 Supabase 프로젝트
NEXT_PUBLIC_SUPABASE_ANON_KEY # ↳ 같은 계정이 같은 회원으로 식별돼야 한다
앱별로 더 필요한 것:
apps/admin—NEXT_PUBLIC_PUBLIC_FORM_ORIGIN(공개 폼 링크의 오리진, 비우면 현재 오리진)apps/www·apps/lms—NEXT_PUBLIC_ADMIN_ORIGIN(가입·계정 연결 화면이 있는 곳). 비워 두면 링크 없이 안내 문구만 나옵니다 — 없는 화면으로 보내지 않기 위한 기본값입니다.
NEXT_PUBLIC_*는 빌드 타임에 인라인됩니다. 값을 바꾸면 재배포해야 반영됩니다.
4. Supabase Redirect URLs에 새 오리진 둘을 등록해야 합니다
https://<www 도메인>/auth/callback
https://<lms 도메인>/auth/callback
빠뜨리면 오류가 나지 않고 조용히 어긋납니다. Supabase(GoTrue)는 등록되지 않은 redirect_to를 거부하는 대신 Site URL로 폴백하므로, 새 앱에서 시작한 로그인이 어드민 도메인에서 끝나고 그쪽에는 PKCE code_verifier 쿠키가 없어 exchange_failed로 죽습니다. 어드민이 예전에 실제로 밟은 함정이고(ssccops#84), 오리진이 셋으로 늘어 다시 밟기 쉽습니다.
등록이 맞는지는 밖에서 확인할 수 있습니다 — Location이 보낸 값 그대로면 통과, Site URL이면 폴백된 것입니다:
curl -sD - "$SUPABASE_URL/auth/v1/verify?type=magiclink&token=bogus&redirect_to=<URL인코딩>" | grep -i '^location:'5. 서버의 CORS 허용 오리진에도 새 오리진 둘을 더해야 합니다
ssccops-server의 PROD_FRONTEND_URL은 쉼표로 여러 개를 받습니다. 세 앱의 오리진을 모두 넣으세요.
빠뜨리면 증상이 '서버가 꺼진 것'과 똑같이 보입니다 — 조회는 대부분 SSR이라 그대로 열리는데, 브라우저에서 직접 서버를 부르는 화면(기획안 자동 저장·제출, 행사 신청서 초안 저장·제출)만 CLIENT_NETWORK_ERROR로 떨어집니다.
6. 함께 배포
ssccops-server v0.2.0과 함께 배포해야 합니다. 학술·행사·기획안 화면은 이 릴리스의 API가 있어야 동작하고, 회원 응답의 systemJoinDate·검토 이력의 rvwPrcsSeCd·행사 이미지의 imageUrl은 옛 서버와 조합하면 빈칸이 됩니다.
Full Changelog: v0.1.1...v0.2.0
v0.1.1 — 모바일 대응과 권한 기반 승인 표기
첫 프로덕션 릴리스(v0.1.0) 이후의 첫 후속 릴리스입니다. develop에 쌓인 21개 커밋을 main으로 올렸습니다.
이번 회차의 큰 줄기는 모바일 대응입니다. 예전에는 body { min-width: 1024px }가 데스크톱 전용을 강제하고 있어, 휴대폰으로 열면 화면이 가로로 밀린 채 조작해야 했습니다.
모바일 반응형
경계는 lg(1024px) 하나뿐입니다. 예전의 min-width 값을 그대로 브레이크포인트로 삼았으므로 lg 이상은 정의상 예전과 같은 화면입니다 — 데스크톱 사용자에게는 바뀐 것이 없습니다.
- 기반 구축 (#85) — 전역 뷰포트, 관리자 셸(좁은 화면에서는 사이드바 대신 드로어), 공용 컴포넌트, 대시보드. 메뉴 목차와 권한 판정은 한 벌만 두어 사이드바와 드로어가 갈리지 않게 했습니다.
- 화면별 대응 — 승인함 (#86) · 폼 응답 (#87) · 가입 흐름 (#91) · 운영 조회 (#92) · 회원 조회 (#93) · 폼·응답 조회 (#94) · 등록·수정 (#95) · 역할·권한·CSV 이관 위저드 (#96)
- 입력란 글자를 좁은 화면에서 16px로 (#105). iOS Safari는 16px 미만인 입력란에 포커스하면 화면을 자동 확대하고 그 확대가 스스로 돌아오지 않습니다 — 첫 칸에 입력하는 순간 폼 전체가 커진 채로 남았습니다. 미관이 아니라 동작 문제라 공용 입력 컴포넌트에 규칙을 걸고 AGENTS.md에 남겼습니다.
- 대시보드 '내 업무 목록' 머리줄이 좁은 화면에서 글자 단위로 깨지던 것 (#103)
열 수가 많은 표(GridTable)는 lg 미만에서 카드로 바뀝니다. 아직 전 화면이 대응된 것은 아닙니다 — min-width 제거로 드러난 나머지 화면은 후속 이슈로 남아 있습니다.
PWA
- 매니페스트와 iOS 설치 메타 (#108) — 홈 화면에 앱으로 설치할 수 있습니다
- 매니페스트를 인증 가드에서 제외 (#110). 가드에 걸려 로그인으로 리다이렉트되는 바람에 설치 자체가 되지 않았습니다
- 앱 아이콘 (#106) → 동아리 공식 로고로 교체 (#113)
서비스 워커·오프라인·푸시는 이번 범위 밖입니다.
권한 · 문구
- 승인자 표기를 서버 결재 권한 기반으로 전환하고 투표 버튼을 권한으로 잠금 (#115). 서버가 승인·투표 자격을 권한 시스템으로 통합(ssccops-server#123)하면서, 웹이 들고 있던 승인자 어휘 하드코딩(
AUTZR_ROLE_NM4종)을 걷어냈습니다 — 권한 이름은 화면에서 바뀌는 운영 데이터라 사전을 웹에 두면 개명 즉시 어긋납니다. 투표 버튼은 이제APPROVAL_VOTE권한으로 미리 잠깁니다(예전에는 눌러 403을 보고서야 자격이 없다는 것을 알았습니다). - 화면 문구 윤문 (#117). AI 번역투를 걷어내려 훑었는데 실제로 걸린 것은 잘못된 안내였습니다 — 시스템에 없는 역할명("최고운영자")을 안내하던 6곳, 시스템에 없는 판정 기준("국장 이상")을 설명하던 2곳, 요구 권한을 "운영진 권한"으로 뭉뚱그려 무엇을 받아야 하는지 알 수 없던 17곳. 회원 도메인이 쓰던 형식(
업무 관리(WORK_MANAGE) 권한이 필요합니다)으로 통일하고, 화면에 노출된 개발 용어(@RequireAuthority·PK·"회원 도메인")를 사용자의 말로 옮겼습니다. 톤·용어 기준은 AGENTS.md에 남겼습니다. - 권한 분리를 화면에 반영하고 팀 공유용 AGENTS.md 작성 (#82)
운영
- 업무·하위 업무·회의 소프트 삭제 연동 (#119)
⚠️ 배포 시 주의
ssccops-server v0.1.1과 함께 배포해야 합니다. #115가 서버의 승인자 응답 계약(직위 코드 → 결재 권한)에 맞춰져 있어, 옛 서버와 조합하면 하위 업무 유형 화면의 승인자 표시가 빈칸이 됩니다.
Full Changelog: v0.1.0...v0.1.1
v0.1.0 — 첫 프로덕션 릴리스
SSCC 지원서·운영 관리 프론트엔드의 첫 프로덕션 릴리스입니다. develop 에 쌓인 45개 커밋을 main 으로 올렸습니다.
Next.js 16 / React 19 / Tailwind CSS 4 / Zustand / Supabase Auth, Cloudflare Workers 에 OpenNext 로 배포합니다.
인증 · 권한
- Supabase Google OAuth 로그인 (#1) 과 라우트 가드
- 회원가입 화면 — 폼 검증과 오류 처리 (#3), 로그인 세션 서버 연동 (#4)
- 역할 기반 화면 권한 (#29) — 서버가 내려주는
capabilities로 기능 노출을 제어합니다. 화면이 역할을 보고 스스로 판정하지 않으므로 버튼과 실제 인가가 갈리지 않습니다. - 역할별 권한 관리 화면 (#32) — 권한 트리 부여·회수. 저장 전 미리 보기를 위해 서버와 같은 펼침 규칙을 트리 간선으로 재현합니다.
- 회원·CSV 이관 메뉴에
MEMBER_MANAGE권한 게이트 (#52) - 운영관리 접근 권한 수정 (#71)
- OAuth 복귀 목적지를 쿠키로 전달 (#77) —
redirectTo에서?next=쿼리를 제거했습니다. 쿼리를 붙이면 Supabase 에 등록한 Redirect URL 과 일치하지 않아 Site URL(보통 localhost)로 조용히 폴백하는 결함이 있었습니다.
폼
- 폼 목록·상세 (#7) — API 계층 분리와 필터의 URL 반영
- 폼 편집기와 자동 저장 (#8)
- 라벨 관리·지정 (#10), 접수 상태 전환·복제 (#9)
- 공개 폼 — 인증 게이트 (#11, 가입 후 원래 폼으로 복귀), 응답 제출·자동 저장 (#12)
- 응답 목록·상세·상태 변경 (#13) — 상세의 이전/다음 이동은 서버가 내려주는 이웃 식별자를 씁니다(URL 로 바로 열어도 이동이 죽지 않도록)
- 폼 도메인 서버 계약 감사와 잔재 정리 (#14)
회원
- 회원 목록·상세 (#46), 정보 수정·내 계정 프로필 수정 (#47)
- 등급·상태 변경 (#48), 역할 부여·종료 (#50), 변경 이력 화면 (#51)
- 역할 목록·수정·역할 분류 관리 (#49)
- CSV 회원 이관 위저드 (#57) — 클라이언트 시뮬레이션을 걷어내고 서버 검증에 연동했습니다. CSV 파싱은 서버 한 곳에만 두어, 따옴표로 감싼 헤더에서 해석이 갈리는 문제를 없앴습니다.
- 이관 회원 계정 연결 화면 (#58) — 가입 화면에서 기존 회원으로 연결하는 경로
- 회원·역할 목 스토어 제거 (#54)
운영
- 업무 목록·상세·등록 (#30), 하위 업무 등록 (#36)·상세·전이·체크리스트 (#39)·전체 조회 (#41)
- 하위 업무 유형 관리 (#34)
- 승인함 조회·투표·승인·반려 (#45)
- 회의 등록·조회 (#56)
- 운영 대시보드 (#60), 운영 통합 페이지 (#63)
- 운영 등록의 담당자를 서버 후보 목록에서 선택 (#53)
⚠️ 배포 시 주의
dev/prod 배포 환경이 분리됐습니다
이 릴리스에 Cloudflare Workers 의 dev/prod 분리가 포함됩니다. wrangler.jsonc 에 이름을 하드코딩하던 것을 Wrangler named environments 로 바꿨습니다.
"env": {
"dev": { "name": "dev-ssccops" }, // 기존 배포 유지
"prod": { "name": "ssccops" }
}환경 블록에서 name 을 명시하면 Wrangler 가 -<env> 접미사를 붙이지 않고 그대로 씁니다. main·assets·compatibility_* 는 상속되고, vars 와 바인딩류는 상속되지 않으므로 추가할 때는 각 env 블록에 따로 써야 합니다.
Workers Builds 의 Production/Preview 로는 이걸 대신할 수 없습니다 — Build variables 가 Worker 당 한 세트뿐이라 production/preview 로 나뉘지 않는데, NEXT_PUBLIC_* 값은 Next 가 빌드 타임에 인라인해 버리므로 dev/prod 가 서로 다른 백엔드를 가리킬 수 없습니다. 그래서 Worker 를 둘로 나눴습니다.
대시보드 설정이 필요합니다
dev-ssccops (기존, 설정 수정) |
ssccops (신규 생성) |
|
|---|---|---|
| Production branch | develop |
main |
| Build command | pnpm exec opennextjs-cloudflare build -e dev |
pnpm exec opennextjs-cloudflare build -e prod |
| Deploy command | pnpm exec opennextjs-cloudflare deploy -e dev |
pnpm exec opennextjs-cloudflare deploy -e prod |
| Non-production branch builds | 켤 경우 ... upload -e dev |
끄기 |
| Build variables | dev 용 NEXT_PUBLIC_* |
prod 용 NEXT_PUBLIC_* |
기존 dev-ssccops 에 -e dev 를 넣지 않으면 다음 빌드가 ssccops-web 이라는 의도치 않은 세 번째 Worker 로 나갑니다.
Supabase · CORS
Google 프로바이더의 Redirect URL 은 경로까지 포함해서 등록해야 합니다. 오리진만 등록하면 Supabase 가 Site URL(보통 localhost)로 조용히 폴백해, 배포 환경에서 로그인이 로컬로 튕깁니다.
https://dev-ssccops.sscc-webservice.workers.dev/auth/callback
https://ssccops.sscc-webservice.workers.dev/auth/callback
서버의 frontend.url(CORS 허용 오리진)에도 prod 오리진 추가가 필요합니다 — 쉼표로 여러 개 넣을 수 있습니다.
필요한 빌드 변수
NEXT_PUBLIC_SUPABASE_URL · NEXT_PUBLIC_SUPABASE_ANON_KEY · NEXT_PUBLIC_API_BASE_URL · NEXT_PUBLIC_PUBLIC_FORM_ORIGIN(선택)
NEXT_PUBLIC_API_BASE_URL 이 비면 인증부터 폼 도메인 전 화면까지 목 데이터 폴백 없이 빈 화면이 됩니다.
스크립트 변경
pnpm run deploy 가 deploy:dev / deploy:prod 로 나뉘었습니다. 겸사겸사 pnpm 내장 명령과 충돌하던 pnpm deploy 이름도 피했습니다.
pnpm run preview # build -e dev && preview -e dev
pnpm run deploy:dev
pnpm run deploy:prod
알려진 제약
- prod Worker(
ssccops)는 아직 생성돼 있지 않습니다 — 위 대시보드 설정이 선행돼야 프로덕션 배포가 시작됩니다. - 폼 응답 목록에 페이징이 없습니다(서버 결정에 맞춤).