Skip to content

v0.2.0 — 학술·행사·기획안과 3앱 모노레포

Latest

Choose a tag to compare

@swthewhite swthewhite released this 02 Sep 13:43
d4d6e62

두 번째 후속 릴리스입니다. 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 mvapps/admin/으로 옮겼습니다 — 히스토리가 보존되고 내용은 한 줄도 바뀌지 않았습니다.
  • packages/form-renderer 추출 (#152). 폼 렌더러를 공유 패키지로 뽑았습니다 — 어드민의 미리보기, 공개 폼, 행사 신청서, 기획안 작성이 같은 렌더러를 씁니다. 두 벌이면 제출자가 본 화면과 검토자가 본 화면이 갈립니다.
  • Worker 이름을 wrangler.jsonc가 아니라 CLI --name으로 (#157). 앱이 늘 때마다 env.dev.name·env.prod.name을 파일마다 박아 넣지 않기 위해서입니다. 이름 규칙은 각 앱 package.json 스크립트 한 줄로 끝납니다.
  • apps/eventsapps/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) — 폼 편집에서 입력란을 숨기고 접수 시작을 잠급니다. 접수 기간의 주인이 두 곳이면 어느 쪽이 이겼는지 화면으로는 알 수 없습니다.

행사 (운영진)

  • 행사 관리 화면 (#136) — 목록·편집·상태 전이·분류 관리
  • 신청 심사·참가자 관리 (#145)
  • 본문 이미지 첨부 (#148) — R2 presigned 업로드

회원

  • 동아리 가입 시기 입력과 연도 기반 기수 자동 채움 (#214)
  • CSV 이관의 동아리 가입 시기 매핑 전환 (#215) — 안내표와 양식 CSV를 함께 갱신했습니다.
  • 학번 수정과 변경 이력 표시 (#237)

수정

  • 완료된 하위 업무가 '지연'으로 표기되던 문제 (#199)
  • datanull인 200을 오류로 세우지 않습니다 (#197) — 초안이 없는 첫 진입에서 신청이 막히고 있었습니다.
  • 회차 목록 상태 필드명(sttsCd)이 어긋나 활동 상세가 죽던 문제 (#212), 회차 목록 정렬 (#190)
  • 신청서 문항 편집을 같은 탭으로 (#207) — 새 탭이라 뒤로가기가 죽었습니다.
  • 사이드바 '기획안 검토'를 폼에서 학술 묶음으로 (#201), 공개 폼 작성 화면 가로 상한 720px → 860px (#203)
  • 서버 개명 반영 — 커리큘럼 계획일 planYmd (#170), 처리 구분 RSPNS_ 접두사 우회 제거 (#233 · #235), 행사 이미지 publicUrlimageUrl (#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을 서빙합니다. 서비스가 끊기는 것이 아니라 새 버전이 안 올라갈 뿐입니다.

고치는 순서:

  1. ssccops 프로젝트의 빌드 루트 디렉터리를 apps/admin으로 바꿉니다.
  2. 스크립트 이름 충돌을 확인합니다. apps/admindeploy:prod는 이제 --name admin-ssccops를 넘깁니다 — 빌드 프로젝트가 짓는 스크립트(ssccops)와 이름이 다릅니다. 둘 중 하나로 맞춰야 합니다:
    • 프로젝트를 admin-ssccops로 새로 만들고 라우트·커스텀 도메인을 옮긴 뒤 옛 ssccops Worker를 정리하거나(다른 두 앱과 이름 규칙이 같아집니다), 또는
    • apps/admin/package.json--namessccops로 되돌립니다(도메인 바인딩을 건드리지 않아도 됩니다).
  3. 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/lmslms.sscc.co.kr입니다.

3. 환경변수 — 앱마다 따로입니다

세 앱이 각각 .env.example을 갖습니다. 공통은 둘입니다.

NEXT_PUBLIC_API_BASE_URL        # 셋 다 같은 ssccops-server
NEXT_PUBLIC_SUPABASE_URL        # 셋 다 같은 Supabase 프로젝트
NEXT_PUBLIC_SUPABASE_ANON_KEY   #   ↳ 같은 계정이 같은 회원으로 식별돼야 한다

앱별로 더 필요한 것:

  • apps/adminNEXT_PUBLIC_PUBLIC_FORM_ORIGIN(공개 폼 링크의 오리진, 비우면 현재 오리진)
  • apps/www · apps/lmsNEXT_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-serverPROD_FRONTEND_URL쉼표로 여러 개를 받습니다. 세 앱의 오리진을 모두 넣으세요.

빠뜨리면 증상이 '서버가 꺼진 것'과 똑같이 보입니다 — 조회는 대부분 SSR이라 그대로 열리는데, 브라우저에서 직접 서버를 부르는 화면(기획안 자동 저장·제출, 행사 신청서 초안 저장·제출)만 CLIENT_NETWORK_ERROR로 떨어집니다.

6. 함께 배포

ssccops-server v0.2.0과 함께 배포해야 합니다. 학술·행사·기획안 화면은 이 릴리스의 API가 있어야 동작하고, 회원 응답의 systemJoinDate·검토 이력의 rvwPrcsSeCd·행사 이미지의 imageUrl은 옛 서버와 조합하면 빈칸이 됩니다.

Full Changelog: v0.1.1...v0.2.0