ttipo 198ac8f4f2
CI (Flutter) / Flutter (analyze · test) (pull_request) Successful in 22m14s
CI / Backend (lint · type-check · test · e2e · build) (pull_request) Successful in 13m2s
CI / Frontend (lint · type-check · test · build) (pull_request) Successful in 13m25s
CI / Infra (compose · Dockerfile · nginx · 운영 스크립트) (pull_request) Successful in 2m6s
CI / 에러 코드 SSOT 동기화 검사 (pull_request) Successful in 11m25s
CI / Backend (lint · type-check · test · e2e · build) (push) Waiting to run
CI / Frontend (lint · type-check · test · build) (push) Waiting to run
CI / Infra (compose · Dockerfile · nginx · 운영 스크립트) (push) Waiting to run
CI / 에러 코드 SSOT 동기화 검사 (push) Waiting to run
CI (Flutter) / Flutter (analyze · test) (push) Has started running
chore(flutter): 앱 판을 1.0.1 로 올린다
읽기 화면의 빈 본문과 쓰기 · 검색 입력칸 고침을 직원 휴대폰에 내보낼 판이다.
최소 판(app-config 1.0.0)은 그대로라 1.0.0 을 쓰는 휴대폰도 막지 않는다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-12 08:11:02 +09:00
2026-10-26 12:28:48 +09:00
2026-10-26 12:28:48 +09:00

ARA SYSTEM

사내 그룹웨어다. 첫 모듈은 메일 — 웹메일(포털)과 휴대폰 앱(안드로이드)이다. 메일 엔진(Stalwart)이 메일을 주고받고, 우리 서버(NestJS)가 엔진을 감싸 웹과 앱에 같은 API 를 준다. 화면은 직접 만든다(Vue 3 · Flutter). @arasystem.it.kr 메일은 2026-11-05 옛 서버(mailcow)에서 옮겨 와 전 직원이 쓰고 있다.

지금

  • 운영: 메일 1~13단계. 메일 14단계(자체 검색 색인 · 같은 대화)와 앱 첫 판 · 휴대폰 앱 받기는 main 에 합쳤고 서버 배포를 기다린다.
  • 개발 중: 메일 15단계(메일 · 메일함 다루기). 다음은 16단계(개인 설정 — 주소록 가져오기 · 여러 서명 등).
  • 단계별 내용 · 다음 차례 · 미뤄 둔 후보는 docs/roadmap.md.

구성

이름 스택 하는 일 포트(개발)
backend/ NestJS · TypeORM 웹 · 앱이 함께 쓰는 REST API — 메일 엔진 중계, 로그인 세션, 검색 색인, 앱 알림, 대용량 첨부 3000
frontend/ Vue 3 · Vite 웹메일 화면. 운영은 nginx 가 화면과 같은 주소의 /api 를 백엔드로 넘긴다 5173
flutter/ Flutter · Riverpod 휴대폰 앱(안드로이드 — 스토어 없이 사내 배포) -
db PostgreSQL 15 로그인 세션 · 설정 · 검색 색인 · 대용량 첨부 기록 등 5432
mail Stalwart 메일 엔진(JMAP · IMAP · SMTP) — 직원 로그인 = 메일 계정 관리 8180(운영은 25 · 465 · 993 · 443)
mail-resolver unbound 메일 엔진 전용 DNS(스팸 차단 목록 · DNSSEC) -
clamav ClamAV 대용량 첨부 악성 검사(운영은 늘, 개발은 profile clamav) -
mailpit Mailpit 개발 전용 — 바깥 주소로 보낸 메일을 모은다 8025
redis Redis 기본 꺼짐 — 서버를 여러 대로 늘릴 때의 자리 6379
ops/ 셸 · systemd 운영 서버의 백업(restic) · 15분 점검 · 부팅 때 되살리기 -
ci/ Gitea Actions 러너 PR 마다 lint · 시험 · 빌드 · 인프라 검사 -

문서 지도

알고 싶은 것 문서
서버 · 웹 · 앱이 함께 지키는 API 약속 docs/mail/api-contract.md
웹 화면 디자인 · 화면 문구 규칙 · 단계별 화면 docs/design/webmail/README.md — 디자인 원본은 같은 폴더의 *.dc.html(캔버스 보드)
앱 화면 docs/design/app/README.md
메일 엔진 설정 · 운영 배포 · 백업 · 감시 · 전환 mail/README.md
앱 개발 실행 · 사내 배포(서명 키 · APK 올리기) flutter/README.md
CI 러너 ci/README.md
개발 규칙(공통 · 스택별 · 역할) CLAUDE.md · 아래 "개발 규칙"
로드맵 docs/roadmap.md
로고 · 색 docs/brand/

사전 요구사항

  • Docker / Docker Compose — 백엔드 · DB · 메일 엔진(컨테이너 안 Node 는 24 LTS 로 고정 — docker 스킬 "Node 버전")
  • Node.js 24 LTS — 호스트에서 웹 개발 서버 · 메일 setup 스크립트를 돌릴 때
  • Flutter 3.41.9(Dart 3.11.5) — 앱(compose 대상이 아니다 — flutter/README.md)

빠른 시작 (개발)

# 1) 환경변수 파일 — 값은 루트 .env.development 한 곳에 쓴다(SSOT)
cp .env.development.example .env.development
cp backend/.env.development.example backend/.env.development   # 있어야 한다(JWT 등 백엔드 고유 비밀)

# 2) 백엔드 스택 — backend + db, 기본 COMPOSE_PROFILES=mail 이면 mail · mail-resolver · mailpit 까지
docker compose -f docker-compose.yml -f docker-compose.dev.yml --env-file .env.development up -d

# 3) 메일 엔진 초기화 — 처음 한 번(여러 번 돌려도 안전하다). 개발 시험 계정도 여기서 만든다(mail/README.md)
node --env-file=.env.development mail/scripts/setup.mjs

# 4) 웹은 호스트에서 띄운다 — /api 는 vite 프록시가 백엔드 컨테이너로 넘긴다
cd frontend && npm install && npm run dev
  • 웹 http://localhost:5173 · API http://localhost:3000/api · Swagger http://localhost:3000/api-docs
  • 의존성을 바꿨을 때만 up -d --build(백엔드는 --renew-anon-volumes 도 — docker 스킬).
  • 백엔드를 처음 띄우면 watch 빌드에 수십 분이 걸릴 수 있다(Windows 바인드 마운트). 브랜치를 바꿨다고 다시 시작하지 않는다.
  • 웹을 컨테이너로 띄워야 하면 --profile frontend(느리다 — 호스트 vite 와 함께 띄우지 않는다). 운영(docker-compose.yml 단독)은 nginx 정적 호스팅이다.
  • 포트는 루트 .env.{env} 를 따른다(FRONTEND_PORT · BACKEND_PORT — vite.config.ts 가 직접 읽는다). 포트가 이미 쓰이면 다른 포트로 옮기지 않고 실패한다.
  • Redis 는 뜨지 않는다. 필요해지면 COMPOSE_PROFILES 에 redis 를 더한다.

전체 실행 · 배포 명령은 .claude/skills/docker/SKILL.md.

앱 실행 (Flutter)

cd flutter && cp .env.example .env && flutter pub get && flutter run

flutter/.env 의 API_URL 을 백엔드 주소로 둔다(Android 에뮬레이터는 http://10.0.2.2:3000/api). 실기기 · 새 메일 알림 · 사내 배포는 flutter/README.md.

환경변수

.env* 는 커밋하지 않는다. 각 자리의 *.example 을 복사해 쓴다. **루트 .env.{env} 가 단일 진실 공급원(SSOT)**이다 — backend · frontend 는 compose 가 루트 값을 넣어 준다 (구조는 .claude/rules/env-structure.md).

파일 용도 값을 쓰는 곳
.env.{env}.example 루트 — 모든 설정의 SSOT(compose 가 넣어 준다) 여기
backend/.env.{env}.example 백엔드 고유 비밀(JWT · 앱 알림 키 등)만 최소(파일은 있어야 한다)
flutter/.env.example 앱 — API_URL(compose 대상이 아니다) 따로

{env} = development | production. 운영 서버의 백업 · 점검 설정은 /etc/arasystem/(ops/ops.env.example — mail/README "백업").

표준 응답 포맷

// 성공
{ "success": true, "data": { /* ... */ } }

// 실패 — 사용자에게 보이는 모든 실패는 에러 코드를 가진다
{ "success": false, "error": { "code": "VAL_001", "message": "...", "traceId": "3f1c0a2e-..." } }

// 목록 — data 안의 꼴이 정해져 있다
{ "success": true, "data": { "items": [], "pageInfo": { "hasNext": false, "nextCursor": null, "total": 0 } } }

traceId 는 요청마다 붙는 추적 ID(x-request-id 헤더와 같다)라 서버 로그에 같은 값이 남는다 — 문의가 오면 이 값으로 로그를 찾는다. /api/health 는 예외다(모니터링 도구가 읽는 원본 꼴).

에러 코드는 한 곳에서

backend/src/common/errors/error-codes.ts   ← 여기만 고친다
        │  cd backend && npm run sync:error-codes
        ├─▶ frontend/src/shared/constants/error-codes.generated.ts   (자동 생성)
        └─▶ flutter/lib/core/api/error_lexicon.g.dart                (자동 생성)

생성 파일은 직접 고치지 않는다. CI 가 맞는지 본다 — .claude/rules/error-codes.md.

커밋 전 검사

# backend — 컨테이너 안에서(의존성 · lock 이 Linux 기준이라 호스트에서 npm install 하지 않는다)
docker exec arasystem-backend npm run lint
docker exec arasystem-backend npm run test                          # 메모리가 빠듯하면 -- --maxWorkers=2
docker exec arasystem-backend npx jest --config ./test/jest-e2e.json   # e2e — DB · 메일 엔진이 떠 있어야 한다
# frontend · flutter — 호스트에서
cd frontend && npm run lint && npm run type-check && npm run test:unit   # 시간대가 걸린 화면을 고쳤으면 TZ=UTC 로도
cd flutter  && flutter analyze && flutter test

컨테이너 이름은 <PROJECT_NAME>-backend(개발 기본 arasystem). lint 는 --max-warnings 0 이라 경고 하나도 실패다. 같은 검사를 CI(.github/workflows/ci.yml · ci-flutter.yml)가 PR 마다 한다.

⚠️ 백엔드 의존성은 컨테이너 안에서만 바꾼다 — Windows 에서 npm install 하면 Linux 전용 패키지가 lock 에서 빠져 Docker 빌드가 깨진다. docker compose -f docker-compose.yml -f docker-compose.dev.yml --env-file .env.development exec backend npm install <패키지>

개발 규칙

영역 문서
입구 · 공통 CLAUDE.md · .claude/rules/
응답 · 에러 response-format.md · error-codes.md
API 설계 api-design.md
로깅 · 보안 logging.md · security.md
점검 목록 checklist.md
NestJS .claude/skills/nestjs/SKILL.md
Vue 3 .claude/skills/vue3/SKILL.md
Flutter .claude/skills/flutter/SKILL.md
Docker · 운영 .claude/skills/docker/SKILL.md
역할 분담 .claude/agents/README.md

새 화면은 캔버스 보드로 먼저 확인받은 뒤 만든다(디자인 README). 화면 · 알림 글은 디자인 README 「화면 문구 — 사용자에게 말하듯」을 따른다.

디렉터리 구조

.
├── backend/      # NestJS API — modules/(auth · mail · contacts · file-storage · push · ops 등) · common · shared
├── frontend/     # Vue 3 웹메일 — pages · components · composables · stores · router, 운영 nginx 설정
├── flutter/      # 휴대폰 앱 — core · features · shared
├── mail/         # 메일 엔진 setup 스크립트 · DNS 리졸버 · ClamAV 설정 · 운영 문서(README)
├── ops/          # 운영 서버의 백업 · 점검 · 부팅 되살리기(systemd)
├── docs/         # API 계약 · 화면 디자인(캔버스 보드) · 브랜드 · 로드맵
├── ci/           # CI 러너(Gitea Actions) — 앱 스택과 따로 띄운다
├── data/         # 개발용 바인드 마운트 자리(내용은 커밋하지 않는다)
├── docker-compose.yml               # 운영 · 개발 공통 정의
├── docker-compose.dev.yml           # 개발 오버라이드(핫리로드 · 127.0.0.1 포트)
├── docker-compose.remote-proxy.yml  # 리버스 프록시가 다른 서버에 있을 때(운영 선택)
├── CLAUDE.md     # 개발 규칙의 입구(.claude/ 의 규칙을 불러온다)
├── .github/      # CI 워크플로(ci.yml · ci-flutter.yml) · PR 양식
└── .claude/      # rules(공통 규칙) · skills(스택별 규칙) · agents(역할) · 설정
S
Description
No description provided
Readme 29 MiB
Languages
TypeScript 69.1%
Dart 17%
Vue 10.4%
JavaScript 2.2%
Shell 0.5%
Other 0.7%