Fullstack 95

heo4·2026년 8월 31일

Fullstack

목록 보기
50/70

풀스택

코드로그 운영

코드로그 운영을 중단하였다.
이유는 aws 라이트세일 무료플랜이 만료되어 가는 것도 있고 aws저용량 버전에서
사용량에 따른 과금 플랜으로 업그레이드하려고 했으나 해킹이나 트래픽이 문제가 생겨
과다청구되거나 문제가 발생한다는것을 알게 되었다 물론 안 일어날수도 있지만 노파심에
위험 부담을 줄이고 새로운 방식을 채택하고자 하였다.

하지만 새로운 방식으로 배포를 하려고 찾아보니 프로젝트의 구성을 바꿔야 했고 이를
유지하기 위해서는 다른 클라우드형 서비스를 찾아야 했는데 가입이 안되는 문제나 인수되어
이전된경우 등 많은 문제가 발생 이에 서비스를 중단하고 내용을 기록하여 깃 레포에 작성하고
마무리 하는것으로 결정했다.

경험

경험으로 구성한 프로젝트이기 때문에 다른 부분은 아쉽지만 앞으로 개발에 방향성을
잘 찾은거 같아 만족한다.
다만 스프링을 기반으로한 프로젝트는 데이터베이스가 별도로 필요한 서비스들이 대부분인데
많은 클라우드 서비스를 무료로 이용하기에는 부적합 하여 가장 많이 개발에 사용한 방식인
next.js나 자바스크립트 클라우드 플레어를 기반으로 한 프로젝트 위주로 개발을 이어나갈
예정이다 운영이나 관리면에서 수월하고 비용부담도 없기 때문에 이 형태로 계속할듯 하다.

개인용 포트폴리오 사이트를 제작

링크는: https://ppnation.heohyuk.workers.dev/

ppnation 사이트 작업 정리 — 자료실 재설계, 소셜 로그인, 계정 페이지

1. 의존성 패치 업그레이드

npm outdated로 확인된 것이 전부 semver 범위 내 패치라 npm update 한 번으로 끝냈다.

  • astro 7.2.9 → 7.2.10
  • wrangler 4.127.0 → 4.127.1
  • @astrojs/cloudflare 14.2.5 → 14.2.6
  • @astrojs/react 6.0.4 → 6.0.5

package.json의 ^ 범위는 그대로고 package-lock.json만 바뀐다.
npm run build 통과 확인 후 커밋.


2. 자료실: D1 + R2 → GitHub 퍼블릭 레포

배경

원래 자료실(/resources)은 D1 테이블 resources + R2 버킷(파일 저장) 구조였다.
자료 하나 추가하려면 DB에 INSERT하거나 별도 관리 UI가 필요했고, R2도 계정에서
활성화해야 해서 아직 503만 뱉는 상태였다.

방침

퍼블릭 GitHub 레포 하나를 자료 저장소로 쓰고, 사이트는 요청 시점에 그 레포의
resources.json(매니페스트)을 읽어 목록을 렌더링한다.
자료 추가 = 레포에 커밋.

매니페스트 형식 (resources.json)

{
  "items": [
    {
      "title": "1강 - 오리엔테이션",
      "description": "강의 소개와 환경 세팅",
      "category": "강의노트",
      "file": "files/lecture-01.pdf"
    },
    {
      "title": "MDN JavaScript 가이드",
      "category": "참고자료",
      "url": "https://developer.mozilla.org/..."
    }
  ]
}
  • file = 레포 기준 상대경로(파일 자료), url = 외부 링크. 둘 중 하나 필수.
  • category 생략 시 기타.

사이트 쪽 코드 (src/lib/resources.ts)

요청 시점에 raw.githubusercontent.com/<repo>/main/resources.json을 fetch해서
정규화한다. 핵심은:

  • 경로 탈출(..)·절대경로·비 http(s) 링크 필터링
  • file → 다운로드 링크로 변환 (뒤 6번에서 프록시 경유로 바뀜)
  • 카테고리별 그룹핑

페이지(src/pages/resources/index.astro)는 prerender = false(SSR)로 두고
응답에 cache-control: public, max-age=300, stale-while-revalidate=600을 붙였다.

반영 시간

레포에 커밋하면 재배포 없이 GitHub CDN 캐시(~5분) + 우리 응답 캐시(5분)
안에 사이트에 반영된다. 실제로는 커밋 후 2분 내에 떴다.

기존 코드 처리

lib/db.ts의 listResources에 @deprecated만 달고, R2용 /api/files/[...key].ts도
되돌릴 때를 위해 코드만 남겼다(미사용).


3. Cloudflare 멀티 계정 API 토큰 꼬임 정리

증상

npm run deploy가 code 10000(인증 실패)로 죽었다. wrangler whoami를 찍어보니
다른 프로젝트(budge) 계정으로 로그인돼 있었다.

원인

~/.bashrc에 이렇게 전역 export가 박혀 있었다.

export CLOUDFLARE_API_TOKEN=<budge 계정 토큰>

budge 작업할 때 넣고 안 뺀 것. 이게 모든 WSL 셸에 상속되고,
wrangler 인증 우선순위가 CLOUDFLARE_API_TOKEN 환경변수 > OAuth 로그인이라,
wrangler login을 새로 해도 이 변수가 안 빠지면 계속 그 토큰을 썼다.

정리

  1. ~/.bashrc에서 그 줄 삭제 (백업 남김)
  2. 그 토큰은 budge 프로젝트에서 cron 워커 배포할 때만 필요하니
    budge/.envrc(gitignore)로 옮김 → 필요할 때 source .envrc
  3. ppnation 계정으로 OAuth 로그인:
    env -u CLOUDFLARE_API_TOKEN npx wrangler login   # 환경변수 잠깐 빼고
    WSL에서도 localhost:8976 콜백이 정상 동작했다(예전 메모엔 안 된다고 돼 있었음).

교훈

  • 계정이 여러 개면 토큰을 전역에 두지 말고 프로젝트별로 스코프한다(direnv .envrc 등).
  • wrangler OAuth 세션은 머신 전체에 하나뿐이라, 계정 번갈아 쓰는 프로젝트는
    토큰 방식이 오히려 덜 번거롭다.
  • 이미 열려 있던 터미널은 프로세스 환경에 옛 값이 남으므로 새 창을 열어야 한다.

4. 소셜 로그인(GitHub) 활성화

로그인 관련 코드(arctic v3 기반 OAuth, 세션 관리, 미들웨어, 콜백 라우트, /login)는
이미 다 짜여 있었고 시크릿만 없어서 비활성인 상태였다. 그래서 "만들기"가 아니라
"활성화"만 했다.

절차

  1. GitHub OAuth App 생성
    • Homepage: https://ppnation.heohyuk.workers.dev
    • Callback: https://ppnation.heohyuk.workers.dev/api/auth/callback/github
  2. 프로덕션 시크릿 등록
    npx wrangler secret put GITHUB_CLIENT_ID
    npx wrangler secret put GITHUB_CLIENT_SECRET
    • Client ID는 공개값이라 상관없지만, Client Secret은 채팅/기록에 남기지 말고
      바로 wrangler secret put으로 넣는다.
    • 프롬프트 입력이 안 먹는 셸(Git Bash 등)에서는 파이프로:
      read -rs S && printf %s "$S" | npx wrangler secret put GITHUB_CLIENT_SECRET && unset S
  3. 재배포 → /login에서 GitHub 로그인. 첫 로그인 사용자가 자동 관리자.

Google은 보류

안 쓰기로 해서, login/account에서 isConfigured()로 미설정 공급자를 아예 숨겼다.
나중에 Google 시크릿만 등록하면 코드 변경 없이 다시 나타난다.

삽질 포인트

  • npx wrangler가 아니라 wrangler로 쳐서 "명령을 찾을 수 없음" → Windows CMD가 아니라
    WSL에서
    npx 붙여 실행해야 함. wrangler 로그인도 WSL 쪽에 저장돼 있음.
  • curl로 /api/account POST가 403 → Astro의 Origin(CSRF) 검사. 실제 브라우저 폼
    전송은 같은 Origin 헤더가 붙어서 통과한다. 버그가 아니라 보호 기능.

5. 내 정보 / 설정 페이지 /account

로그인 사용자가 자기 계정을 관리하는 페이지. users 테이블(id, email, name,
avatar_url, is_admin, created_at) + oauth_accounts(소셜 연결) 기준.

기능

  • 프로필 요약: 아바타·이름·이메일·가입일·관리자 배지
  • 표시 이름 변경: 1~30자. 이메일/사진은 소셜에서 가져오므로 수정 불가
  • 연결된 계정: GitHub/Google 연결 상태 표시, 미연결 시 "연결하기",
    연결 해제(단, 마지막 로그인 수단은 해제 불가 — 계정 잠김 방지)
  • 세션: 로그아웃 / 모든 기기에서 로그아웃(해당 유저 sessions 전체 삭제)
  • 회원 탈퇴: 댓글·세션·소셜연결·users 삭제. 작성한 게시글이 있으면 차단
    (관리자가 쓴 글이 고아가 되는 걸 막음)

구조

  • src/lib/account.ts: listLinkedProviders, renameUser, deleteAllSessions,
    unlinkProvider, countAuthoredPosts, deleteAccount
  • src/pages/api/account.ts: HTML 폼이라 intent 필드로 동작 분기
    (rename | unlink | logout-all | delete)
  • 피드백은 ?ok= / ?err= 쿼리로 넘겨 페이지에서 문구 매핑
  • Nav의 사용자 이름 → /account 링크

삭제 순서 (FK 고려)

D1(SQLite)은 기본적으로 FK를 강제하지 않으므로 의존 순서대로 직접 지운다.
db.batch([...])로 묶어서:

DELETE FROM comments WHERE author_id = ?
DELETE FROM sessions WHERE user_id = ?
DELETE FROM oauth_accounts WHERE user_id = ?
DELETE FROM users WHERE id = ?

6. 자료실 저장소 분리 + 강제 다운로드 프록시

6-1. 저장소를 별도 레포로

자료실 데이터를 사이트 코드와 섞지 않으려고 전용 퍼블릭 레포
heo-hyuk/ShareTemp로 옮겼다. 사이트 쪽은 상수 한 줄만 교체:

export const RESOURCES_REPO = 'heo-hyuk/ShareTemp';

6-2. 강제 다운로드가 안 되는 문제

raw.githubusercontent.com은 Content-Disposition: attachment를 안 붙인다.
그래서 타입별로 제각각이었다:

파일브라우저 동작
.zip .xlsx .pptx그냥 다운로드
.pdf뷰어로 열림
.md .txt .csv화면에 텍스트로 표시 (Ctrl+S 해야 저장)

<a download> 속성은 교차 출처면 무시되므로 해결책이 안 된다.

6-3. Worker 프록시 라우트

src/pages/api/download/[...path].ts:

  1. 레포 경로 검증(..·절대경로 차단)
  2. raw.githubusercontent.com에서 fetch
  3. Content-Disposition: attachment 붙여 스트리밍 재전송
    • 한글 파일명은 RFC 5987 (filename*=UTF-8''<percent-encoded>) + ASCII 폴백
  4. cache-control: public, max-age=3600

compatibility_flags에 global_fetch_strictly_public이 있어도 raw는 퍼블릭이라 OK.

6-4. 테스트

한글 파일명(샘플-강의노트.txt)으로 검증:

content-disposition: attachment; filename="__-____.txt";
  filename*=UTF-8''%EC%83%98%ED%94%8C-...txt

→ 브라우저에서 샘플-강의노트.txt로 저장됨. 경로 탈출/없는 파일은 400/404.


7. 배포 방식 (참고)

이 프로젝트는 Cloudflare Workers(Pages 아님)라 GitHub push로 자동 배포되지 않는다.
로컬에서 직접:

npm run deploy   # = astro build && wrangler deploy

(멀티 계정 이슈로 한동안은 env -u CLOUDFLARE_API_TOKEN npm run deploy로 실행.
3번 정리 후 새 셸에서는 그냥 npm run deploy.)


8. 이번 세션 커밋 (ppnation)

8c3a506  chore(deps): 의존성 패치 업그레이드
c288533  feat(resources): 자료실을 GitHub 레포 매니페스트 기반으로 전환
d7173f1  docs(deploy): OAuth 로그인(asdf1378kk) 기준으로 갱신
07e245c  feat(account): 내 정보/설정 페이지 (/account)
997f297  feat(auth): 미설정 소셜 공급자 숨김 (현재 GitHub만 노출)
0df8eb2  refactor(resources): 자료실 저장소를 heo-hyuk/ShareTemp 로 변경
f2a8f57  feat(resources): 파일 자료 강제 다운로드 프록시 라우트

9. 정리하며 (한 줄씩)

  • 콘텐츠는 코드에서 분리한다. 자주 바뀌는 자료 목록을 별도 퍼블릭 레포 매니페스트로
    빼니 재배포 없이 커밋만으로 갱신된다.
  • 자격증명은 전역이 아니라 프로젝트별로. 계정 여러 개 쓰면 전역 env 토큰이
    조용히 다른 프로젝트를 깨뜨린다.
  • 이미 있는 코드부터 확인. 로그인은 "만들" 게 아니라 시크릿만 넣으면 되는
    상태였다.
  • 교차 출처 다운로드는 프록시가 답. <a download>도, raw URL도 강제 저장을
    못 하면 얇은 Worker 라우트 하나로 해결.

0개의 댓글