Files
arasystem/README.md
T
ttipo 3c9b3727c6
CI / Backend (lint · type-check · test · e2e · build) (pull_request) Successful in 13m49s
CI / Frontend (lint · type-check · test · build) (pull_request) Successful in 13m45s
CI / Infra (compose · Dockerfile · nginx · 운영 스크립트) (pull_request) Successful in 1m55s
CI / 에러 코드 SSOT 동기화 검사 (pull_request) Successful in 11m12s
docs: 저장소 소개를 ARA SYSTEM 의 지금 구성에 맞게 고치고 로드맵을 더한다
루트 README · CLAUDE.md · 점검 목록이 아직 "풀스택 모노레포 템플릿"으로 소개하고 있었다 — 사내 그룹웨어(메일 운영 중)에 맞게
지금 상태 · 구성(메일 엔진 · 리졸버 · ClamAV · 운영 스크립트 포함) · 문서 지도 · 디렉터리 구조로 다시 쓰고,
백엔드를 호스트에서 npm ci 하라던 옛 안내(컨테이너 안에서만 — 규칙과 반대)를 뺐다.
docs/roadmap.md 에 메일 1~16단계 상태(16단계 첫 항목: 주소록 가져오기 · 내보내기) · 공통 기반 · 2부 · 앱 · 추후 후보 모음을 둔다.
backend · frontend README 는 프레임워크가 만든 영어 안내 대신 이 저장소의 실행 · 검사 · 참고 문서로,
규칙 · 스킬 · 역할 문서의 "템플릿" 표현("템플릿 기본 제공 골격" 등)은 "기본 골격"으로 바꿨다(nginx 설정 템플릿은 그대로).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-09 20:19:20 +09:00

176 lines
11 KiB
Markdown

# ARA SYSTEM
사내 그룹웨어다. 첫 모듈은 **메일** — 웹메일(포털)과 휴대폰 앱(안드로이드)이다.
메일 엔진(Stalwart)이 메일을 주고받고, 우리 서버(NestJS)가 엔진을 감싸 웹과 앱에 같은 API 를 준다. 화면은 직접 만든다(Vue 3 · Flutter).
`@arasystem.it.kr` 메일은 2026-11-05 옛 서버(mailcow)에서 옮겨 와 전 직원이 쓰고 있다.
## 지금
- 운영: 메일 1~13단계. 메일 14단계(자체 검색 색인 · 같은 대화)와 앱 첫 판 · 휴대폰 앱 받기는 main 에 합쳤고 서버 배포를 기다린다.
- 개발 중: 메일 15단계(메일 · 메일함 다루기). 다음은 16단계(개인 설정 — 주소록 가져오기 · 여러 서명 등).
- 단계별 내용 · 다음 차례 · 미뤄 둔 후보는 [`docs/roadmap.md`](./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/mail/api-contract.md) |
| 웹 화면 디자인 · 화면 문구 규칙 · 단계별 화면 | [`docs/design/webmail/README.md`](./docs/design/webmail/README.md) — 디자인 원본은 같은 폴더의 `*.dc.html`(캔버스 보드) |
| 앱 화면 | [`docs/design/app/README.md`](./docs/design/app/README.md) |
| 메일 엔진 설정 · 운영 배포 · 백업 · 감시 · 전환 | [`mail/README.md`](./mail/README.md) |
| 앱 개발 실행 · 사내 배포(서명 키 · APK 올리기) | [`flutter/README.md`](./flutter/README.md) |
| CI 러너 | [`ci/README.md`](./ci/README.md) |
| 개발 규칙(공통 · 스택별 · 역할) | [`CLAUDE.md`](./CLAUDE.md) · 아래 "개발 규칙" |
| 로드맵 | [`docs/roadmap.md`](./docs/roadmap.md) |
| 로고 · 색 | [`docs/brand/`](./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`](./flutter/README.md))
## 빠른 시작 (개발)
```bash
# 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`](./.claude/skills/docker/SKILL.md).
## 앱 실행 (Flutter)
```bash
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`](./flutter/README.md).
## 환경변수
`.env*` 는 커밋하지 않는다. 각 자리의 `*.example` 을 복사해 쓴다. **루트 `.env.{env}` 가 단일 진실 공급원(SSOT)**이다 — backend · frontend 는 compose 가 루트 값을 넣어 준다
(구조는 [`.claude/rules/env-structure.md`](./.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 "백업").
## 표준 응답 포맷
```jsonc
// 성공
{ "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`](./.claude/rules/error-codes.md).
## 커밋 전 검사
```bash
# 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.md) · [`.claude/rules/`](./.claude/rules/) |
| 응답 · 에러 | [`response-format.md`](./.claude/rules/response-format.md) · [`error-codes.md`](./.claude/rules/error-codes.md) |
| API 설계 | [`api-design.md`](./.claude/rules/api-design.md) |
| 로깅 · 보안 | [`logging.md`](./.claude/rules/logging.md) · [`security.md`](./.claude/rules/security.md) |
| 점검 목록 | [`checklist.md`](./.claude/rules/checklist.md) |
| NestJS | [`.claude/skills/nestjs/SKILL.md`](./.claude/skills/nestjs/SKILL.md) |
| Vue 3 | [`.claude/skills/vue3/SKILL.md`](./.claude/skills/vue3/SKILL.md) |
| Flutter | [`.claude/skills/flutter/SKILL.md`](./.claude/skills/flutter/SKILL.md) |
| Docker · 운영 | [`.claude/skills/docker/SKILL.md`](./.claude/skills/docker/SKILL.md) |
| 역할 분담 | [`.claude/agents/README.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(역할) · 설정
```