feat: 휴대폰 앱을 스토어 없이 사내에 나눠 주는 받기 화면 · API · 운영 도구 · 앱 서명 #22

Merged
ttipo merged 7 commits from feat/app-distribution into main 2026-11-09 09:45:52 +09:00
Owner

무엇을 · 왜

안드로이드 앱을 스토어(Play 내부 테스트)에 올리지 않고, 직접 빌드해 우리 키로 서명한 APK 를 사내에만 나눠 주기로 했습니다(2026-11-08).
받는 곳은 포털에 로그인한 직원만 여는 화면이고, 앱의 업데이트 화면과 설정의 [새 버전 받기]가 그 화면을 휴대폰 브라우저로 엽니다.
화면은 캔버스 보드로 먼저 확정했습니다(v68 — SettingsMobileApp 새 보드, App1Push ⑩ · ④, App1Shell 앱 정보 줄).

  • 서버
    • 로그인한 직원만 부르는 GET /api/app-releases/android/latest(판 정보) · …/latest/file(APK — 대용량 첨부 받기와 같은 머리글 · Range)
    • 최신은 가장 늦게 올린 판입니다. 옛 APK 를 다시 올리면 되돌릴 수 있습니다.
    • 올리기는 API 가 없고 서버 B 의 운영 도구 「휴대폰 앱 올리기」 · 「휴대폰 앱 목록」으로 합니다. 파일 검사 · SHA-256 · 임시 이름에 쓴 뒤 바꾸기 · 실행 기록을 남기고, app-config 의 최소 판보다 낮은 판은 막습니다.
    • 내려받기 머리글은 shared/utils/download-headers.ts 한 곳으로 모았습니다. 대용량 첨부 받기도 이것을 쓰고, 값과 차례는 그대로입니다.
  • 웹: 설정 › 계정 › 「휴대폰 앱」(PC — QR 로 휴대폰에 넘김)과 셸 밖 /app(휴대폰 받기 화면 — 로그인 필요)
  • 앱
    • [새 버전 받기]가 <API_URL 의 출처>/app 을 바깥 브라우저로 엽니다(업데이트 화면 · 설정 › 앱 정보). Play 런처는 지웠습니다.
    • release 빌드는 우리 서명 키(key.properties) · google-services.json · 운영 포털의 https API 주소가 모두 갖춰질 때만 됩니다. 하나라도 빠지면 Gradle 이 까닭을 알리고 멈춥니다(알리는 글에 주소 값은 싣지 않습니다).
  • 앞단 nginx: APK 받기 경로에 앱 안 대용량 받기와 같은 location 을 둡니다(대소문자를 가리지 않는 정규식 · 같은 동시 연결 그릇 IP 30 · 전체 200 · 버퍼링 끔 · X-Accel-Buffering 넘김). CI 받기 경로 시험에 변형을 더했습니다.
  • 문서: 계약 1절 "휴대폰 앱 받기" · 6절 운영 도구, mail/README "휴대폰 앱 올리기"(직원 안내는 링크 없이 메뉴 경로만), flutter/README "사내 배포"(서명 키 · 빌드 · 올리기 전 확인), 디자인 README, 스킬 표

리뷰는 두 번 받았습니다. 1차에서 중간 1 · 낮음 3 · 참고 1, 2차에서 낮음 3 · 참고 1 이 나왔고 모두 고쳤습니다.

확인 방법

  • backend(컨테이너): lint 0건, 단위 3,748, e2e 340 통과
    • 새 e2e 7개: 401 · 404 · 정보 200 · 파일 머리글 · Range 206/416/If-Range · 파일 없음 404 · 제한된 세션 403 AUTH_010
  • frontend: lint · type-check, test:unit 2,816 (KST · UTC). 바꾼 날짜 시험은 로스앤젤레스 시간대에서도 통과
  • flutter: analyze 0건, test 1,077, debug APK 빌드
    • key.properties 없이 flutter build apk --release 를 부르면 두 까닭(서명 키 · API 주소)을 알리고 멈춥니다(주소 값 없음).
  • 아직 못 한 것: 개발 PC 의 Docker 디스크(D:) 하드웨어 오류로 Docker 가 멈춰, nginx nginx -t · 가짜 백엔드 받기 시험을 로컬에서 돌리지 못했습니다. 이 PR 의 CI infra 잡이 같은 시험을 돕니다(러너도 같은 PC 라 Docker 가 돌아온 뒤).
  • 실기기(갤럭시)에서 설치 창 · Play 프로텍트 글을 맞추는 일과 첫 release 빌드는 서명 키를 만든 뒤에 합니다.

영향 범위

  • backend
  • frontend
  • flutter
  • docker / 인프라
  • 규칙 문서(.claude/)

공유 계약: 새 API 둘(계약 1절 "휴대폰 앱 받기")을 더했습니다. 새 오류 코드는 없습니다(404 RES_001 · 401 AUTH_001 · 403 AUTH_010 · 앞단 429 REQ_001).
웹은 이 API 를 쓰고, 앱은 쓰지 않고 /app 화면 주소만 웹과 짝으로 둡니다(MOBILE_APP_PATH ↔ kAppDownloadPagePath). app-config 계약은 그대로입니다.

체크리스트

  • 린트/분석 통과 (Warning 0) · 변경 영역 테스트 통과
  • 주석 한국어, 매직 넘버·하드코딩 문자열 없음
  • 사용자 노출 실패는 모두 에러 코드와 함께 처리 (BusinessException / error.code 분기)
  • 에러 코드를 바꿨다면 npm run sync:error-codes 실행 후 생성 파일까지 커밋 — 바꾸지 않음
  • 민감정보 미커밋 (*.example 값은 placeholder) — key.properties · keystore 는 git 이 무시
  • 로그에 비밀번호·토큰·요청 본문 전체가 남지 않음

🤖 Generated with Claude Code

## 무엇을 · 왜 안드로이드 앱을 스토어(Play 내부 테스트)에 올리지 않고, **직접 빌드해 우리 키로 서명한 APK 를 사내에만** 나눠 주기로 했습니다(2026-11-08). 받는 곳은 **포털에 로그인한 직원만** 여는 화면이고, 앱의 업데이트 화면과 설정의 [새 버전 받기]가 그 화면을 휴대폰 브라우저로 엽니다. 화면은 캔버스 보드로 먼저 확정했습니다(v68 — `SettingsMobileApp` 새 보드, `App1Push` ⑩ · ④, `App1Shell` 앱 정보 줄). - **서버** - 로그인한 직원만 부르는 `GET /api/app-releases/android/latest`(판 정보) · `…/latest/file`(APK — 대용량 첨부 받기와 같은 머리글 · Range) - 최신은 가장 늦게 올린 판입니다. 옛 APK 를 다시 올리면 되돌릴 수 있습니다. - 올리기는 API 가 없고 서버 B 의 운영 도구 「휴대폰 앱 올리기」 · 「휴대폰 앱 목록」으로 합니다. 파일 검사 · SHA-256 · 임시 이름에 쓴 뒤 바꾸기 · 실행 기록을 남기고, app-config 의 최소 판보다 낮은 판은 막습니다. - 내려받기 머리글은 `shared/utils/download-headers.ts` 한 곳으로 모았습니다. 대용량 첨부 받기도 이것을 쓰고, 값과 차례는 그대로입니다. - **웹**: 설정 › 계정 › 「휴대폰 앱」(PC — QR 로 휴대폰에 넘김)과 셸 밖 `/app`(휴대폰 받기 화면 — 로그인 필요) - **앱** - [새 버전 받기]가 `<API_URL 의 출처>/app` 을 바깥 브라우저로 엽니다(업데이트 화면 · 설정 › 앱 정보). Play 런처는 지웠습니다. - release 빌드는 우리 서명 키(`key.properties`) · `google-services.json` · 운영 포털의 https API 주소가 모두 갖춰질 때만 됩니다. 하나라도 빠지면 Gradle 이 까닭을 알리고 멈춥니다(알리는 글에 주소 값은 싣지 않습니다). - **앞단 nginx**: APK 받기 경로에 앱 안 대용량 받기와 같은 location 을 둡니다(대소문자를 가리지 않는 정규식 · 같은 동시 연결 그릇 IP 30 · 전체 200 · 버퍼링 끔 · `X-Accel-Buffering` 넘김). CI 받기 경로 시험에 변형을 더했습니다. - **문서**: 계약 1절 "휴대폰 앱 받기" · 6절 운영 도구, mail/README "휴대폰 앱 올리기"(직원 안내는 링크 없이 메뉴 경로만), flutter/README "사내 배포"(서명 키 · 빌드 · 올리기 전 확인), 디자인 README, 스킬 표 리뷰는 두 번 받았습니다. 1차에서 중간 1 · 낮음 3 · 참고 1, 2차에서 낮음 3 · 참고 1 이 나왔고 모두 고쳤습니다. ## 확인 방법 - backend(컨테이너): lint 0건, 단위 3,748, e2e 340 통과 - 새 e2e 7개: 401 · 404 · 정보 200 · 파일 머리글 · Range 206/416/If-Range · 파일 없음 404 · 제한된 세션 403 `AUTH_010` - frontend: lint · type-check, test:unit 2,816 (KST · UTC). 바꾼 날짜 시험은 로스앤젤레스 시간대에서도 통과 - flutter: analyze 0건, test 1,077, debug APK 빌드 - key.properties 없이 `flutter build apk --release` 를 부르면 두 까닭(서명 키 · API 주소)을 알리고 멈춥니다(주소 값 없음). - **아직 못 한 것**: 개발 PC 의 Docker 디스크(D:) 하드웨어 오류로 Docker 가 멈춰, nginx `nginx -t` · 가짜 백엔드 받기 시험을 로컬에서 돌리지 못했습니다. 이 PR 의 CI infra 잡이 같은 시험을 돕니다(러너도 같은 PC 라 Docker 가 돌아온 뒤). - 실기기(갤럭시)에서 설치 창 · Play 프로텍트 글을 맞추는 일과 첫 release 빌드는 서명 키를 만든 뒤에 합니다. ## 영향 범위 - [x] backend - [x] frontend - [x] flutter - [x] docker / 인프라 - [x] 규칙 문서(.claude/) > **공유 계약**: 새 API 둘(계약 1절 "휴대폰 앱 받기")을 더했습니다. 새 오류 코드는 없습니다(404 `RES_001` · 401 `AUTH_001` · 403 `AUTH_010` · 앞단 429 `REQ_001`). > 웹은 이 API 를 쓰고, 앱은 쓰지 않고 `/app` 화면 주소만 웹과 짝으로 둡니다(`MOBILE_APP_PATH` ↔ `kAppDownloadPagePath`). app-config 계약은 그대로입니다. ## 체크리스트 - [x] 린트/분석 통과 (Warning 0) · 변경 영역 테스트 통과 - [x] 주석 한국어, 매직 넘버·하드코딩 문자열 없음 - [x] 사용자 노출 실패는 모두 에러 코드와 함께 처리 (`BusinessException` / `error.code` 분기) - [x] 에러 코드를 바꿨다면 `npm run sync:error-codes` 실행 후 생성 파일까지 커밋 — 바꾸지 않음 - [x] 민감정보 미커밋 (`*.example` 값은 placeholder) — key.properties · keystore 는 git 이 무시 - [x] 로그에 비밀번호·토큰·요청 본문 전체가 남지 않음 🤖 Generated with [Claude Code](https://claude.com/claude-code)
ttipo added 7 commits 2026-11-09 08:26:21 +09:00
앱을 스토어에 올리지 않고 직접 빌드한 APK 를 사내에만 나눠 주기로 했다(2026-11-08).
포털에 로그인한 직원만 받는 화면(설정 › 계정 › 휴대폰 앱, 셸 밖 /app)을 새 보드로 두고,
앱 업데이트 화면의 [Play 스토어 열기]를 [새 버전 받기](받기 화면을 브라우저로 연다)로,
설정 › 앱 정보에는 「새 버전 받기」 줄을 더했다. 캔버스 v68 에서 확정했다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
휴대폰 앱 받기(다음 커밋)가 대용량 첨부 받기와 같은 머리글을 쓰게 하려고,
file-sharing 의 writeFileHeaders 가 만들던 머리글을 shared/utils/download-headers.ts 로 옮겼다.
머리글 이름 · 값 · 차례는 그대로다(새 단위 시험과 대용량 받기의 단위 · e2e 가 지킨다).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
앱을 스토어 없이 사내에만 나눠 주기로 해(2026-11-08), 로그인한 직원만 최신 안드로이드 APK 의
정보와 파일을 받게 한다(GET /api/app-releases/android/latest · /latest/file — 제한된 세션은 막힌다).
올리기는 API 없이 서버의 운영 도구로 한다(파일 검사 · SHA-256 · 임시 이름에 쓴 뒤 바꾸기 · 실행 기록).
최신은 가장 늦게 올린 판이라 옛 APK 를 다시 올려 되돌릴 수 있고, app-config 의 최소 판보다 낮은 판은 올리지 않는다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
확정 보드 SettingsMobileApp 대로 PC 에서는 QR 로 휴대폰에 넘기고, 셸 밖 /app(로그인 필요)에서 휴대폰으로 받는다.
내려받기 전에 세션을 확인하고, 올린 판이 없으면(404 RES_001) 알림 없이 안내와 문의 줄을 보인다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
스토어에 올리지 않으니 업데이트 화면의 Play 스토어 열기를 포털 /app 을 바깥 브라우저로 여는 것으로 바꾸고,
설정 › 앱 정보에도 같은 줄을 더했다. release 빌드는 우리 서명 키(key.properties) · google-services.json ·
운영 포털의 https API 주소가 갖춰지지 않으면 까닭을 알리고 멈춘다 — debug 키로 조용히 서명되거나
개발 주소가 든 판이 직원에게 나가지 않게(알리는 글에 주소 값은 싣지 않는다).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
APK 받기가 일반 /api 로 가면 X-Accel-Buffering 이 외부 프록시로 넘어가지 않아 받는 사람마다 임시 파일이 쌓이고,
버퍼링을 끈 받기에 둘 동시 연결 상한도 없다(리뷰). 앱 안 대용량 받기와 같은 location(대소문자를 가리지 않는 정규식 ·
같은 그릇)을 두고, CI 의 받기 경로 시험에 변형 · 정보 경로 · 그릇 공유를 더했다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
docs: 휴대폰 앱 사내 배포를 계약 · 운영 · 앱 · 디자인 문서와 스킬에 적는다
CI / Frontend (lint · type-check · test · build) (pull_request) Successful in 12m41s
CI (Flutter) / Flutter (analyze · test) (pull_request) Successful in 22m13s
CI / Backend (lint · type-check · test · e2e · build) (pull_request) Successful in 13m13s
CI / Infra (compose · Dockerfile · nginx · 운영 스크립트) (pull_request) Successful in 2m13s
CI / 에러 코드 SSOT 동기화 검사 (pull_request) Successful in 11m5s
CI (Flutter) / Flutter (analyze · test) (push) Successful in 21m49s
CI / Backend (lint · type-check · test · e2e · build) (push) Successful in 13m13s
CI / Frontend (lint · type-check · test · build) (push) Successful in 12m54s
CI / Infra (compose · Dockerfile · nginx · 운영 스크립트) (push) Successful in 1m47s
CI / 에러 코드 SSOT 동기화 검사 (push) Successful in 10m26s
ff1948b7df
계약 1절 "휴대폰 앱 받기"와 6절 운영 도구 명령, 서버 B 에 APK 올리는 차례와 직원 안내(링크 없이),
앱의 서명 키 · release 빌드 · 올리기 전 확인, 웹 화면 절, 스킬 표, App1Shell 보드의 앱 정보 줄.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ttipo merged commit ff1948b7df into main 2026-11-09 09:45:52 +09:00
ttipo deleted branch feat/app-distribution 2026-11-09 11:11:08 +09:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: ttipo/arasystem#22