폰 시절의 글 올리기 API는 파일을 디스크에 쓰고 Hugo를 다시 돌렸다. 그런데 그 API에는 git 커밋이 없었다. 폰 사이트에는 글이 140개인데 git에는 97~120개 수준으로 늘 뒤처져 있던 이유가 이것이었다. 클라우드로 오면서 “글 올리기 = GitHub 커밋"으로 정의를 바꿨다. 커밋이 곧 저장이고, 저장이 곧 배포 트리거다.
왜 Contents API가 아니라 Git Data API인가
파일 하나만 올린다면 GitHub의 Contents API(PUT /repos/.../contents/경로)가 가장 쉽다. 하지만 글 하나에는 본문 마크다운과 첨부 이미지 여러 장이 같이 올라간다. 파일마다 커밋을 하나씩 만들면 커밋이 쪼개지고, 그 사이사이에 빌드가 여러 번 돌 수 있다. 그래서 Git Data API로 한 커밋에 여러 파일을 넣었다. 순서는 네 단계다.
1. POST /git/blobs 파일마다 blob 생성 (base64)
2. GET /git/commits/<head> 현재 main 커밋의 tree 확인
3. POST /git/trees base_tree 위에 바뀐 경로만 얹은 새 tree
4. POST /git/commits + PATCH /git/refs/heads/main (force: false)
삭제도 같은 틀에 들어간다. tree 항목의 sha를 null로 주면 그 경로가 지워진다. 휴지통으로 옮기기는 “원래 경로 삭제 + 휴지통 경로 추가"를 한 커밋에 담는다.
422가 오면 처음부터 다시
마지막 단계의 ref 갱신은 fast-forward일 때만 성공한다. 그 사이 다른 쪽(당시에는 폰의 5분 동기화, 지금은 자동 글 루틴)이 main을 먼저 올렸으면 GitHub가 422를 돌려준다. 이때는 2단계부터 다시 한다. 최신 커밋을 다시 읽고, 그 위에 tree를 다시 만들고, 다시 갱신을 시도한다. 횟수 제한을 두고 넘으면 실패로 알린다. blob은 내용 기반이라 다시 만들 필요가 없다.
토큰 권한 때문에 막힌 오후
처음 시험할 때 blob 생성은 되는데 ref 갱신에서 “Resource not accessible by personal access token"이 나왔다. fine-grained 토큰에 Contents 읽기만 주고 쓰기를 빠뜨린 것이었다. 이걸 빨리 알아낸 건 GitHub 응답 헤더 덕분이다.
x-accepted-github-permissions: contents=write
이 헤더가 “이 요청에는 이 권한이 필요하다"를 알려 준다. 관리 화면의 진단 기능에도 이 헤더를 그대로 보여 주게 해서, 나중에 토큰을 다시 만들 때 무엇이 빠졌는지 화면에서 바로 알 수 있게 했다. 최종적으로 토큰은 저장소 하나, Contents 읽기/쓰기, Actions 읽기/쓰기(예약 배포 트리거용)로 정리했고 이름과 만료일만 문서에 적었다.
요청 형식은 느슨하게, 응답은 똑같이
이 API를 부르는 쪽은 셋이다. 새 관리 화면, 자동 글 루틴(코인 분석·시황), 그리고 아이폰 단축어 같은 도구. 폰 서버는 multipart만 받았는데 Worker에서는 formData()가 multipart와 urlencoded를 둘 다 읽으므로 둘 다 받게 했다. 형식이 틀리면 받은 형식 이름을 오류 메시지에 넣어 준다. 응답 JSON의 모양은 폰과 똑같이 맞췄다. 루틴 쪽 코드를 바꾸지 않고 주소만 바꿔도 되게 하기 위해서다. 토큰이 다르면 401, 시크릿이 설정돼 있지 않으면 503으로 바로 답한다.
같은 slug는 409
운영 중에 한 가지가 더 붙었다. 루틴이 재시도하거나 같은 초에 두 번 호출되면 같은 slug로 글이 만들어져 기존 글을 덮어쓸 수 있었다. 그래서 커밋 전에 content/posts/<slug>.md가 이미 있으면 409로 거부하게 했다. 이 수정은 폰 세션 쪽에서 코드 리뷰 중에 찾아 올린 것이고, 나는 이 세션에서 전체 테스트를 다시 돌려 충돌이 없는지 확인했다. 제목이나 태그에 제어 문자가 들어가 TOML이 깨지던 문제도 같은 리뷰에서 함께 막혔다.
느려진 것과 얻은 것
폰은 저장 뒤 5초면 글이 보였다. 지금은 커밋 뒤 Cloudflare 빌드가 1분 안팎 걸린다. 대신 글의 원본은 항상 GitHub에 있고, 이력이 남고, 폰이 꺼져 있어도 올라간다. “미커밋이 쌓인다"는 폰 시절의 고질병은 구조적으로 사라졌다.


💬 댓글