From 3c9b3727c6a963cb64aadb86ce5e1cab28d00018 Mon Sep 17 00:00:00 2001 From: ttipo Date: Mon, 9 Nov 2026 20:19:20 +0900 Subject: [PATCH] =?UTF-8?q?docs:=20=EC=A0=80=EC=9E=A5=EC=86=8C=20=EC=86=8C?= =?UTF-8?q?=EA=B0=9C=EB=A5=BC=20ARA=20SYSTEM=20=EC=9D=98=20=EC=A7=80?= =?UTF-8?q?=EA=B8=88=20=EA=B5=AC=EC=84=B1=EC=97=90=20=EB=A7=9E=EA=B2=8C=20?= =?UTF-8?q?=EA=B3=A0=EC=B9=98=EA=B3=A0=20=EB=A1=9C=EB=93=9C=EB=A7=B5?= =?UTF-8?q?=EC=9D=84=20=EB=8D=94=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 루트 README · CLAUDE.md · 점검 목록이 아직 "풀스택 모노레포 템플릿"으로 소개하고 있었다 — 사내 그룹웨어(메일 운영 중)에 맞게 지금 상태 · 구성(메일 엔진 · 리졸버 · ClamAV · 운영 스크립트 포함) · 문서 지도 · 디렉터리 구조로 다시 쓰고, 백엔드를 호스트에서 npm ci 하라던 옛 안내(컨테이너 안에서만 — 규칙과 반대)를 뺐다. docs/roadmap.md 에 메일 1~16단계 상태(16단계 첫 항목: 주소록 가져오기 · 내보내기) · 공통 기반 · 2부 · 앱 · 추후 후보 모음을 둔다. backend · frontend README 는 프레임워크가 만든 영어 안내 대신 이 저장소의 실행 · 검사 · 참고 문서로, 규칙 · 스킬 · 역할 문서의 "템플릿" 표현("템플릿 기본 제공 골격" 등)은 "기본 골격"으로 바꿨다(nginx 설정 템플릿은 그대로). Co-Authored-By: Claude Opus 5.5 --- .claude/agents/backend-dev.md | 2 +- .claude/rules/checklist.md | 12 +- .claude/rules/env-structure.md | 2 +- .claude/rules/security.md | 2 +- .claude/rules/testing.md | 2 +- .claude/skills/flutter/SKILL.md | 6 +- .claude/skills/nestjs/SKILL.md | 4 +- .claude/skills/vue3/SKILL.md | 4 +- CLAUDE.md | 3 +- README.md | 251 ++++++++++++++------------------ backend/README.md | 106 +++----------- docs/roadmap.md | 73 ++++++++++ frontend/README.md | 56 +++---- 13 files changed, 242 insertions(+), 281 deletions(-) create mode 100644 docs/roadmap.md diff --git a/.claude/agents/backend-dev.md b/.claude/agents/backend-dev.md index 2188afa..7f0d906 100644 --- a/.claude/agents/backend-dev.md +++ b/.claude/agents/backend-dev.md @@ -26,7 +26,7 @@ tools: Read, Grep, Glob, Edit, Write, Bash, Skill ## 작업 순서 -1. 만들기 전에 `/nestjs` 의 "템플릿 기본 제공 골격"에서 재사용할 패턴을 찾는다. 같은 역할을 새로 만들지 않는다. +1. 만들기 전에 `/nestjs` 의 "기본 골격"에서 재사용할 패턴을 찾는다. 같은 역할을 새로 만들지 않는다. 2. 실패 경로마다 에러 코드를 정한다. 코드 없는 실패는 만들지 않는다 ([[error-codes]]). 3. 목록 API 면 페이지네이션을 적용한다 ([[response-format]]). 4. Swagger 데코레이터를 함께 쓴다 ([[api-design]]). diff --git a/.claude/rules/checklist.md b/.claude/rules/checklist.md index 8885e49..914f644 100644 --- a/.claude/rules/checklist.md +++ b/.claude/rules/checklist.md @@ -1,11 +1,11 @@ -# 작업 점검 & 신규 프로젝트 셋업 +# 작업 점검 & 새 환경 셋업 -> 이 저장소는 **재사용 템플릿**이다. 클론해 어떤 프로젝트든 시작할 수 있도록, 여기에는 **언제나 유효한 공용 규칙**만 둔다(특정 시점의 구현 상태는 두지 않는다). -> 각 스택이 기본 제공하는 골격과 확장 규칙은 [[nestjs]] · [[vue3]] · [[flutter]] · [[docker]] 스킬 참조. +> 여기에는 **언제나 유효한 점검 규칙**만 둔다(특정 시점의 구현 상태는 두지 않는다 — 지금 상태는 각 스킬 · API 계약 · [로드맵](../../docs/roadmap.md)). +> 각 스택에 이미 있는 골격과 확장 규칙은 [[nestjs]] · [[vue3]] · [[flutter]] · [[docker]] 스킬 참조. --- -## 1. 신규 프로젝트 셋업 (클론 직후 1회) +## 1. 새 개발 환경 · 새 서버 셋업 (처음 한 번) - [ ] 루트 `.env.{env}.example` → `.env.{env}` 복사 후 **placeholder 값 교체** (`change-me-*` 가 남아 있으면 안 된다, [[env-structure]]) 운영은 `BACKEND_API_URL`·`CORS_ORIGIN` 을 **실제 도메인**으로 — `localhost`/`example.com` 이 남으면 기동 실패한다 @@ -85,10 +85,10 @@ ## 6. 기능 확장 시 따르는 순서 -새 기능은 만들기 전에, 해당 스킬의 **"템플릿 기본 제공 골격"** 에서 재사용할 패턴을 먼저 찾는다(같은 역할을 새로 만들지 말 것). +새 기능은 만들기 전에, 해당 스킬의 **"기본 골격"** 에서 재사용할 패턴을 먼저 찾는다(같은 역할을 새로 만들지 말 것). 1. 해당 디렉터리 스킬 규칙 확인 ([[nestjs]] / [[vue3]] / [[flutter]]) -2. 기본 제공 골격(인터셉터·필터·에러 레지스트리·dio client·예제 모듈)에서 패턴 재사용 +2. 기본 골격(인터셉터·필터·에러 레지스트리·dio client·예제 모듈)에서 패턴 재사용 3. 표준 응답/에러 흐름 준수 ([[response-format]] · [[error-codes]] · [[api-design]]) 4. 실패 경로마다 에러 코드 지정 — `BusinessException('CODE')` 로 던진다 5. env 신규 값은 루트 `.env` 부터 ([[env-structure]]) diff --git a/.claude/rules/env-structure.md b/.claude/rules/env-structure.md index 6f0f15e..466976e 100644 --- a/.claude/rules/env-structure.md +++ b/.claude/rules/env-structure.md @@ -5,7 +5,7 @@ description: 환경변수 단일 진실 공급원(SSOT) 구조 — 루트 .env.{ # 환경변수 구조 (단일 진실 공급원: 루트 `.env`) -이 템플릿은 **루트 `.env.{APP_ENV}` 가 모든 설정의 단일 진실 공급원(SSOT)** 이다. +이 저장소는 **루트 `.env.{APP_ENV}` 가 모든 설정의 단일 진실 공급원(SSOT)** 이다. 값을 한 곳에서만 관리하기 위해, 서비스(backend/frontend)는 자체 env에 값을 중복 작성하지 않고 **루트 → docker-compose 가 주입한 값을 그대로 사용**한다. ``` diff --git a/.claude/rules/security.md b/.claude/rules/security.md index b14ac9b..823733b 100644 --- a/.claude/rules/security.md +++ b/.claude/rules/security.md @@ -64,7 +64,7 @@ `img onerror` 같은 처리기가 실행된다. CSP 가 막아도 그것에만 기대지 않는다 ([[vue3]] 체크리스트 "외부 HTML") ### 파일 업로드 -템플릿은 `modules/upload` 에 로컬 디스크 기준 골격을 제공한다. 새 업로드 엔드포인트는 이 패턴을 복제한다. +`modules/upload` 에 로컬 디스크 기준 골격이 있다. 새 업로드 엔드포인트는 이 패턴을 복제한다. - **확장자와 MIME 을 둘 다** 검사한다 (`upload.config.ts`). 확장자만 보면 위장이 쉽고, MIME 만 보면 클라이언트가 보낸 값이라 신뢰할 수 없다. diff --git a/.claude/rules/testing.md b/.claude/rules/testing.md index 5e8a33f..6fc1ea9 100644 --- a/.claude/rules/testing.md +++ b/.claude/rules/testing.md @@ -5,7 +5,7 @@ description: 세 스택 공통 테스트 규칙 — 테스트 위치/네이밍, # 테스트 규칙 -> 템플릿은 각 스택에 테스트 러너가 준비되어 있다 — backend `jest`, frontend `vitest`, flutter `flutter_test`. +> 각 스택에 테스트 러너가 준비되어 있다 — backend `jest`, frontend `vitest`, flutter `flutter_test`. > 새 로직을 추가하면 최소한의 테스트를 함께 작성한다. ## 공통 원칙 diff --git a/.claude/skills/flutter/SKILL.md b/.claude/skills/flutter/SKILL.md index 32395f1..dfb7c1a 100644 --- a/.claude/skills/flutter/SKILL.md +++ b/.claude/skills/flutter/SKILL.md @@ -10,9 +10,9 @@ description: Flutter 모바일 앱(flutter/) 개발 규칙 — Dart Sound Null S --- -## 템플릿 기본 제공 골격 +## 기본 골격 -> 이 템플릿은 네트워크 · 세션 · 라우터 · 모듈 틀을 **기본 제공**한다. 새 코드는 이 파일들의 패턴을 재사용한다(같은 역할을 새로 만들지 말 것). +> 네트워크 · 세션 · 라우터 · 모듈 틀이 이미 있다. 새 코드는 이 파일들의 패턴을 재사용한다(같은 역할을 새로 만들지 말 것). > 앱 이름 "ARA SYSTEM", 식별자 `kr.it.arasystem.app`(Android applicationId · iOS bundle id — 직원 휴대폰에 설치한 뒤에는 못 바꾼다: 식별자가 다르면 다른 앱이라 그 위에 설치되지 않는다), 패키지 `arasystem_app`. | 제공 항목 | 위치 | 비고 | @@ -42,7 +42,7 @@ description: Flutter 모바일 앱(flutter/) 개발 규칙 — Dart Sound Null S | **앱 설정 · 업데이트 화면** | `lib/core/app_config/`(`app_config.dart` — 판 비교 `AppVersion` · 모양, `app_config_repository.dart`, `app_config_controller.dart` — `appUpdateRequiredProvider` · `pushAvailableProvider`) · `core/router/session_redirect.dart`(`updateRequired` 가 세션보다 먼저) · `features/app_update/`(모듈이 아니다 — 화면 · 글) · `core/app_download/`([새 버전 받기] — 받기 화면 주소 `app_download_link.dart` · 런처 `AppDownloadLauncher` · 글, 업데이트 화면과 설정 › 앱 정보가 함께 쓴다) | `GET /api/app-config` 는 띄울 때 한 번 · 앞으로 올 때 마지막으로 받은 지 1시간이 지났으면(실패는 1분 간격으로 다시) — 알리지 않고 막지 않는다. 404 `RES_001` 은 옛 서버(최소 판 없음 · 알림 쓸 수 없음). 판은 숫자 1~3칸 · 칸마다 4자리까지(서버의 판 칸 `APP_VERSION_PATTERN` 과 같다 — `-` · `+` 뒤는 버린다), 이 앱의 판을 모르면 막지 않는다. 업데이트 화면으로 옮길 때 쓰기 화면은 마저 저장한다(`ComposeController.detach(saveRemaining: true)` — 화면이 밖에서 닫힌 때도). 앱은 스토어에 올리지 않고 사내에 직접 나눠 준다(2026-11-08 — 빌드 · 서명 · 올리기는 `flutter/README.md` "사내 배포"). [새 버전 받기]는 포털의 휴대폰 앱 받기 화면 `/app`(`appDownloadPageUrlOf` — `/api` 를 뗀 출처 + 경로, 짝: 웹 `mobile-app.ts` 의 `MOBILE_APP_PATH`)을 바깥 브라우저로(`LaunchMode.externalApplication` — `canLaunchUrl` 은 쓰지 않는다) 연다. 열지 못하면 스킴 없는 주소를 담아 알린다(`openAppDownloadPage` — 두 화면이 같은 알림). 앱 설정 계약은 받는 곳 주소를 싣지 않는다 | | **설정 탭 (`App1Shell` · `App1Push` ④)** | `lib/features/settings/` — `settings_routes.dart`(`/settings` · `/settings/licenses` · 모듈이 내놓은 절) · `presentation/pages/settings_page.dart` · `widgets/settings_notification_row.dart` · `settings_texts.dart` | 한 화면(내 정보 · 알림 · 모듈 절 · 앱 정보(앱 버전 · [새 버전 받기] — 끝 아이콘이 바깥으로 여는 아이콘, 업데이트 화면과 같은 런처 · 오픈소스 라이선스) · 문의 · [로그아웃]). 문의는 로그인 화면과 같은 저장소(`supportContactProvider`), [로그아웃]은 확인 창(`shared/widgets/confirm_dialog.dart` — 처음 초점 [취소]). 스위치 색은 테마의 `switchTheme`(보드 — 켬 청록 · 끔 테두리 · 누를 수 없음 회색) | | 자리 화면 | `lib/shared/widgets/placeholder_page.dart` | 화면은 캔버스로 확인받은 뒤 만든다 — 그때까지 이름(과 갈 곳)만 보이는 자리 화면. 확정되면 라우트 묶음의 builder 를 바꾼다 | -| 예제 feature(템플릿) | `lib/features/user/` | 목록 페이지네이션(`{items, pageInfo}` — 단건 + 커서 목록) 예제로만 남긴 템플릿. 앱 API 에 `/users` 는 없다 | +| 예제 feature(참조용) | `lib/features/user/` | 목록 페이지네이션(`{items, pageInfo}` — 단건 + 커서 목록) 예제로만 남겼다. 앱 API 에 `/users` 는 없다 | | 시험 도구 | `test/support/` — `fakes.dart` · `provider_harness.dart` | 가짜 서버(`FakeServer` — HttpClientAdapter) · 메모리 보안 저장소 · 손 시계(`ManualClock`) · 알림 기록(`RecordingNotifier`). 고정 대기 없이 Completer 로 차례를 맞춘다 ([[testing]]) | > **확장 시 따르는 규칙** (아래 "프로젝트 구조"는 목표 구조 — 기능 추가 시 점진적으로 채운다): diff --git a/.claude/skills/nestjs/SKILL.md b/.claude/skills/nestjs/SKILL.md index c134aec..fe8e325 100644 --- a/.claude/skills/nestjs/SKILL.md +++ b/.claude/skills/nestjs/SKILL.md @@ -9,9 +9,9 @@ description: NestJS 백엔드(backend/) 개발 규칙 — 컨트롤러/서비스 --- -## 템플릿 기본 제공 골격 +## 기본 골격 -> 이 템플릿은 아래 인프라를 **기본 제공**한다. 새 코드는 이 파일들의 패턴을 재사용한다(같은 역할을 새로 만들지 말 것). +> 아래 인프라가 이미 있다. 새 코드는 이 파일들의 패턴을 재사용한다(같은 역할을 새로 만들지 말 것). | 제공 항목 | 위치 | 비고 | | --- | --- | --- | diff --git a/.claude/skills/vue3/SKILL.md b/.claude/skills/vue3/SKILL.md index 440c8aa..543b291 100644 --- a/.claude/skills/vue3/SKILL.md +++ b/.claude/skills/vue3/SKILL.md @@ -9,9 +9,9 @@ description: Vue 3 프론트엔드(frontend/) 개발 규칙 — Composition API --- -## 템플릿 기본 제공 골격 +## 기본 골격 -> 이 템플릿은 아래를 **기본 제공**한다. 새 코드는 이 파일들의 패턴을 복제한다(같은 역할을 새로 만들지 말 것). +> 아래가 이미 있다. 새 코드는 이 파일들의 패턴을 복제한다(같은 역할을 새로 만들지 말 것). | 제공 항목 | 위치 | 비고 | | --- | --- | --- | diff --git a/CLAUDE.md b/CLAUDE.md index 668be15..5e3a9a0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,6 +1,7 @@ # 프로젝트 개요 -이 저장소는 **풀스택 모노레포 기본 템플릿**이다. 누구나 클론하여 바로 개발을 시작할 수 있도록 공통 규칙과 언어별 규칙을 정의한다. +이 저장소는 **ARA SYSTEM** — 사내 그룹웨어(첫 모듈은 메일: 웹메일 · 휴대폰 앱)다. 백엔드 · 웹 · 앱과 메일 엔진 · 운영 설정을 한 저장소에서 다루고, 여기에 공통 규칙과 스택별 규칙을 둔다. +프로젝트 소개 · 문서 지도는 [`README.md`](README.md), 작업 차례는 [`docs/roadmap.md`](docs/roadmap.md). | 디렉터리 | 스택 | 역할 | 개발 규칙 (스킬) | | ----------- | -------------- | ------------------------ | ----------------------------------------- | diff --git a/README.md b/README.md index 93ab551..eac22dc 100644 --- a/README.md +++ b/README.md @@ -1,109 +1,97 @@ -# 풀스택 모노레포 기본 템플릿 +# ARA SYSTEM -NestJS(백엔드) · Vue 3(웹) · Flutter(모바일)를 하나의 저장소에서 운영하는 풀스택 템플릿입니다. -세 클라이언트는 **동일한 표준 응답 포맷**과 **공통 에러 코드 맵**을 공유하며, Docker Compose로 한 번에 구동됩니다. +사내 그룹웨어다. 첫 모듈은 **메일** — 웹메일(포털)과 휴대폰 앱(안드로이드)이다. +메일 엔진(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 | 웹 프론트엔드 | 5173 | -| `flutter/` | Flutter + Riverpod | 모바일 앱 | - | -| `db` | PostgreSQL | 데이터베이스 | 5432 | -| `redis` | Redis | 캐시 / 세션 (**기본 비활성**) | 6379 | -| `mail` | Stalwart (JMAP) | 사내 메일 엔진 — 직원 로그인 = 메일 계정 (profile `mail`, [`mail/README.md`](./mail/README.md)) | 운영 25·465·993·443 / 개발 관리 8180 | -| `mailpit` | Mailpit | **개발 전용** — 사내 도메인 밖으로 보낸 메일 수집 | 8025 | +| 이름 | 스택 | 하는 일 | 포트(개발) | +| --- | --- | --- | --- | +| `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 — backend·frontend·db·redis 구동 (컨테이너 안 런타임은 Node 24 LTS — `node:24.22.1-alpine3.24` 고정, 갱신 규칙은 docker 스킬 "Node 버전") -- Node.js **24 LTS 권장** — 호스트에서 프론트엔드 dev 서버·메일 setup 스크립트를 실행할 때 필요하다. - 프론트 `engines` 는 `^20.19.0 || >=22.12.0` 이라 20.19 이상이면 돌아가지만, 이미지·CI(24)와 맞추는 것을 권장한다 - (메일 setup 스크립트는 `--env-file` 때문에 20.6 이상) -- Flutter SDK `^3.11` — 모바일 앱 실행용 (compose 대상 아님) +- 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)) -## 빠른 시작 (Docker) +## 빠른 시작 (개발) ```bash -# 1) 환경변수 파일 생성 (예제 복사) -# 실제 설정값은 루트 .env.development 한 곳에서 관리한다 (SSOT). -# backend/frontend 는 docker-compose 가 값을 주입하므로 예제만 복사하면 된다. -cp .env.development.example .env.development # ← 여기에만 값 입력 -cp backend/.env.development.example backend/.env.development # (존재 필요: JWT 등 고유 시크릿) -# frontend 는 compose 가 빌드 시 값을 주입하므로 자체 env 복사 불필요 +# 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 + mailpit 포함) +# 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 -# 2-1) 메일 엔진 초기화 — 처음 한 번 (여러 번 실행해도 안전, mail/README.md) +# 3) 메일 엔진 초기화 — 처음 한 번(여러 번 돌려도 안전하다). 개발 시험 계정도 여기서 만든다(mail/README.md) node --env-file=.env.development mail/scripts/setup.mjs -# 3) 의존성 변경 시에만 --build -docker compose -f docker-compose.yml -f docker-compose.dev.yml --env-file .env.development up -d --build - -# 4) 프론트엔드는 호스트에서 직접 띄운다 (컨테이너보다 수십 배 빠르다) +# 4) 웹은 호스트에서 띄운다 — /api 는 vite 프록시가 백엔드 컨테이너로 넘긴다 cd frontend && npm install && npm run dev ``` -기동 후: +- 웹 · API · Swagger +- 의존성을 바꿨을 때만 `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` 를 더한다. -- 웹: -- API: -- Swagger 문서: +전체 실행 · 배포 명령은 [`.claude/skills/docker/SKILL.md`](./.claude/skills/docker/SKILL.md). -> **개발 환경에서 프론트엔드는 컨테이너로 뜨지 않습니다.** -> vite 를 컨테이너에서 돌리면 소스가 Windows 바인드 마운트 너머에 있어 첫 서빙까지 수 분이 걸립니다. -> 호스트에서 `npm run dev` 로 띄우면 1~2초이고, `/api` 요청은 vite 프록시가 백엔드 컨테이너로 넘깁니다. -> 컨테이너로 띄워야 한다면 `--profile frontend` 를 붙이세요 (아래 "프론트엔드 실행 방식" 참조). -> 운영(`docker-compose.yml` 단독)은 그대로 nginx 정적 호스팅으로 동작합니다. - -> Redis 는 기본적으로 뜨지 않습니다. 필요해지면 루트 `.env.{env}` 에 `COMPOSE_PROFILES=redis` 를 설정하세요. - -### 프론트엔드 실행 방식 - -| 방식 | 명령 | 첫 서빙 | 용도 | -| --- | --- | --- | --- | -| **호스트 vite (개발 기본)** | `cd frontend && npm run dev` | 1~2초 (최초 의존성 사전번들 10초 내외) | 일상 개발 | -| 컨테이너 vite (선택) | `docker compose ... --profile frontend up -d` | 수 분 (Windows 바인드 마운트) | 호스트에 Node 를 둘 수 없을 때 | -| nginx 정적 (운영) | `docker compose --env-file .env.production up -d --build` | - | 배포 | - -> 컨테이너 방식을 상시 사용하려면 루트 `.env.development` 에 `COMPOSE_PROFILES=frontend` 를 넣습니다. -> (redis 와 함께 켤 때는 `COMPOSE_PROFILES=frontend,redis`) -> 호스트 vite 와 컨테이너 vite 는 같은 호스트 포트를 쓰므로 **동시에 띄우지 마세요.** - -> **포트는 루트 `.env.{env}` 를 따릅니다.** 호스트에서 띄우는 vite 도 `FRONTEND_PORT` 로 열리고, -> `/api` 프록시 대상은 `BACKEND_PORT` 에서 나옵니다 — `vite.config.ts` 가 루트 env 를 직접 읽습니다. -> 포트가 이미 사용 중이면 조용히 다른 포트로 옮기지 않고 **실패**합니다 -> (`CORS_ORIGIN` 과 compose 포트 매핑이 그 값에 묶여 있어, 말없이 옮겨가면 원인 모르게 어긋납니다). - -전체 실행/배포 명령어는 [`.claude/skills/docker/SKILL.md`](./.claude/skills/docker/SKILL.md) 참조. - -## 모바일 앱 실행 (Flutter) - -Flutter 는 Docker Compose 대상이 아니므로 로컬에서 직접 실행합니다. +## 앱 실행 (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/.env` 의 `API_URL` 을 백엔드 주소로 둔다(Android 에뮬레이터는 `http://10.0.2.2:3000/api`). 실기기 · 새 메일 알림 · 사내 배포는 [`flutter/README.md`](./flutter/README.md). ## 환경변수 -`.env*` 는 커밋하지 않습니다. 각 디렉터리의 `*.example` 파일을 복사해 사용하세요. +`.env*` 는 커밋하지 않는다. 각 자리의 `*.example` 을 복사해 쓴다. **루트 `.env.{env}` 가 단일 진실 공급원(SSOT)**이다 — backend · frontend 는 compose 가 루트 값을 넣어 준다 +(구조는 [`.claude/rules/env-structure.md`](./.claude/rules/env-structure.md)). -**루트 `.env.{env}` 가 단일 진실 공급원(SSOT)** 입니다. backend·frontend 는 docker-compose 가 -루트 값을 주입하므로 자체 env 에 값을 중복 작성하지 않습니다. (자세한 위임 구조는 [`CLAUDE.md`](./CLAUDE.md) 의 "환경변수 구조" 참조) +| 파일 | 용도 | 값을 쓰는 곳 | +| --- | --- | --- | +| `.env.{env}.example` | 루트 — 모든 설정의 SSOT(compose 가 넣어 준다) | 여기 | +| `backend/.env.{env}.example` | 백엔드 고유 비밀(JWT · 앱 알림 키 등)만 | 최소(파일은 있어야 한다) | +| `flutter/.env.example` | 앱 — `API_URL`(compose 대상이 아니다) | 따로 | -| 파일 | 용도 | 값 작성 위치 | -| ----------------------------- | ---------------------------------------- | --------------------- | -| `.env.{env}.example` | 루트 — 모든 설정의 SSOT (compose 주입) | ✅ 여기에 작성 | -| `backend/.env.{env}.example` | 백엔드 — 고유 시크릿(JWT 등)만 | 최소 (존재 필수) | -| `frontend/.env.{env}.example` | 프론트 — API 주소는 같은 출처의 `/api` 고정(빌드 arg) | 작성 불필요 | -| `flutter/.env.example` | 모바일 — `API_URL` (compose 비대상) | ✅ 독립 관리 | - -`{env}` = `development` | `production` +`{env}` = `development` | `production`. 운영 서버의 백업 · 점검 설정은 `/etc/arasystem/`(`ops/ops.env.example` — mail/README "백업"). ## 표준 응답 포맷 @@ -114,97 +102,74 @@ cd flutter && cp .env.example .env && flutter pub get && flutter run // 실패 — 사용자에게 보이는 모든 실패는 에러 코드를 가진다 { "success": false, "error": { "code": "VAL_001", "message": "...", "traceId": "3f1c0a2e-..." } } -// 목록 — data 안의 형태가 고정되어 있다 +// 목록 — data 안의 꼴이 정해져 있다 { "success": true, "data": { "items": [], "pageInfo": { "hasNext": false, "nextCursor": null, "total": 0 } } } ``` -`traceId` 는 요청마다 발급되는 추적 ID(`x-request-id` 헤더와 동일)로, 서버 로그에 같은 값이 남습니다. -사용자 문의가 들어오면 이 값으로 로그를 검색하면 됩니다. +`traceId` 는 요청마다 붙는 추적 ID(`x-request-id` 헤더와 같다)라 서버 로그에 같은 값이 남는다 — 문의가 오면 이 값으로 로그를 찾는다. +`/api/health` 는 예외다(모니터링 도구가 읽는 원본 꼴). -> `/api/health` 는 예외입니다. 모니터링 도구가 읽는 엔드포인트라 래퍼 없이 원본 형식을 반환합니다. - -### 에러 코드는 한 곳에서 관리됩니다 +### 에러 코드는 한 곳에서 ``` -backend/src/common/errors/error-codes.ts ← 여기만 수정 +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) 참조. +생성 파일은 직접 고치지 않는다. CI 가 맞는지 본다 — [`.claude/rules/error-codes.md`](./.claude/rules/error-codes.md). -## 로컬 개발 (Docker 없이) - -DB 는 컨테이너로 띄우고 앱만 호스트에서 실행하는 방식이 가장 간단합니다. +## 커밋 전 검사 ```bash -# 1) DB 만 컨테이너로 기동 (127.0.0.1:5432 로 노출된다) -docker compose -f docker-compose.yml -f docker-compose.dev.yml --env-file .env.development up -d db - -# 2) backend — DB_HOST 기본값이 localhost 라 그대로 붙는다 -cd backend && npm ci && npm run start:dev - -# 3) frontend — vite dev 서버가 /api 를 localhost:3000 으로 프록시한다 (개발 기본 방식) -cd frontend && npm ci && npm run dev - -# 4) flutter -cd flutter && flutter pub get && flutter run -``` - -> 프론트엔드의 API 요청은 `/api` 상대경로로 나가고 vite dev 프록시가 백엔드로 넘깁니다. -> 백엔드를 다른 주소에서 띄웠다면 `VITE_DEV_API_PROXY` 로 대상을 바꾸세요. - -### 커밋 전 검사 - -```bash -cd backend && npm run lint && npm run test && npm run build -cd frontend && npm run lint && npm run type-check && npm run test:unit +# 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 ``` -lint 는 `--max-warnings 0` 이라 경고 하나만 있어도 실패합니다. 자동 수정은 `npm run lint:fix`. -같은 검사를 CI([`.github/workflows/ci.yml`](./.github/workflows/ci.yml))가 PR 마다 수행합니다. +컨테이너 이름은 `-backend`(개발 기본 `arasystem`). lint 는 `--max-warnings 0` 이라 경고 하나도 실패다. 같은 검사를 CI(`.github/workflows/ci.yml` · `ci-flutter.yml`)가 PR 마다 한다. -> ⚠️ **의존성을 추가·제거할 때는 컨테이너 안에서** 실행하세요. -> Windows 에서 `npm install` 하면 Linux 전용 패키지가 lock 에서 잘려 Docker 빌드가 깨집니다. -> ```bash -> docker compose -f docker-compose.yml -f docker-compose.dev.yml --env-file .env.development exec backend npm install <패키지> -> ``` +> ⚠️ **백엔드 의존성은 컨테이너 안에서만 바꾼다** — 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) | -| 체크리스트 | [`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) | +| 영역 | 문서 | +| --- | --- | +| 입구 · 공통 | [`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 (Controller / Service / DTO / 공통 필터·인터셉터) -├── frontend/ # Vue 3 (pages / components / composables / stores / router) -├── flutter/ # Flutter (core / features / shared) -├── docker-compose.yml # 베이스 정의 -├── docker-compose.dev.yml # 개발 오버라이드 (핫리로드, 포트 노출) -├── CLAUDE.md # 프로젝트 개요 + 공통 규칙 @import -├── .github/ -│ ├── workflows/ci.yml # lint·test·build + 에러 코드 동기화 + 인프라(compose·Dockerfile·nginx) 검사 -│ └── pull_request_template.md -├── ci/ # CI 러너 (Gitea Actions) — 앱 스택과 분리 -└── .claude/ - ├── settings.json # Claude Code 권한·환경 설정 - ├── rules/ # 공통 규칙 (코딩·응답 포맷·에러 코드·API 설계·로깅·보안·환경변수·테스트) - ├── skills/ # 도메인별 개발 규칙 (nestjs·vue3·flutter·docker) - └── agents/ # 역할별 규칙 (개발 3종·테스터·리뷰어·인프라·사용자/관리자 관점) +├── 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(역할) · 설정 ``` diff --git a/backend/README.md b/backend/README.md index 8f0f65f..bddfea7 100644 --- a/backend/README.md +++ b/backend/README.md @@ -1,98 +1,36 @@ -

- Nest Logo -

+# backend — ARA SYSTEM API (NestJS) -[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456 -[circleci-url]: https://circleci.com/gh/nestjs/nest +웹 · 앱이 함께 쓰는 REST API 다(`/api`). 메일 엔진(Stalwart)을 JMAP 으로 감싸고, 로그인 세션 · 설정 · 검색 색인 · 앱 알림 · 대용량 첨부 · 운영 도구를 맡는다. -

A progressive Node.js framework for building efficient and scalable server-side applications.

-

-NPM Version -Package License -NPM Downloads -CircleCI -Discord -Backers on Open Collective -Sponsors on Open Collective - Donate us - Support us - Follow us on Twitter -

- +- 모듈 지도 · 개발 규칙: [`.claude/skills/nestjs/SKILL.md`](../.claude/skills/nestjs/SKILL.md) +- API 약속(웹 · 앱과 함께): [`docs/mail/api-contract.md`](../docs/mail/api-contract.md) +- 공통 규칙(응답 꼴 · 에러 코드 · 로그 · 보안): [`CLAUDE.md`](../CLAUDE.md) -## Description +## 실행 — 컨테이너로 -[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository. - -## Project setup +백엔드는 늘 컨테이너로 띄운다. 의존성 · lock 이 Linux 기준이라 **호스트에서 `npm install` 하지 않는다**(바꿀 때도 컨테이너 안에서 — nestjs 스킬 "의존성 추가/제거"). ```bash -$ npm install +# 저장소 루트에서 — 개발 스택(backend + db + 메일 엔진) +docker compose -f docker-compose.yml -f docker-compose.dev.yml --env-file .env.development up -d +docker compose -f docker-compose.yml -f docker-compose.dev.yml --env-file .env.development logs -f backend ``` -## Compile and run the project +- 처음 띄우면 마이그레이션을 한 번 돌리고 watch 빌드를 시작한다(Windows 바인드 마운트라 수십 분 걸릴 수 있다). 코드를 고치면 저절로 다시 뜬다. +- Swagger: + +## 시험 · 검사 ```bash -# development -$ npm run start - -# watch mode -$ npm run start:dev - -# production mode -$ npm run start:prod +docker exec arasystem-backend npm run lint # 경고 0 +docker exec arasystem-backend npm run test # 단위 — 메모리가 빠듯하면 -- --maxWorkers=2 +docker exec arasystem-backend npx jest --config ./test/jest-e2e.json # e2e — DB · 메일 엔진 필요 ``` -## Run tests +컨테이너 이름은 `-backend`(개발 기본 `arasystem`). Git Bash 에서 컨테이너 안 경로를 줄 때는 `MSYS_NO_PATHCONV=1` 을 붙인다. -```bash -# unit tests -$ npm run test +## 자주 하는 일 -# e2e tests -$ npm run test:e2e - -# test coverage -$ npm run test:cov -``` - -## Deployment - -When you're ready to deploy your NestJS application to production, there are some key steps you can take to ensure it runs as efficiently as possible. Check out the [deployment documentation](https://docs.nestjs.com/deployment) for more information. - -If you are looking for a cloud-based platform to deploy your NestJS application, check out [Mau](https://mau.nestjs.com), our official platform for deploying NestJS applications on AWS. Mau makes deployment straightforward and fast, requiring just a few simple steps: - -```bash -$ npm install -g @nestjs/mau -$ mau deploy -``` - -With Mau, you can deploy your application in just a few clicks, allowing you to focus on building features rather than managing infrastructure. - -## Resources - -Check out a few resources that may come in handy when working with NestJS: - -- Visit the [NestJS Documentation](https://docs.nestjs.com) to learn more about the framework. -- For questions and support, please visit our [Discord channel](https://discord.gg/G7Qnnhy). -- To dive deeper and get more hands-on experience, check out our official video [courses](https://courses.nestjs.com/). -- Deploy your application to AWS with the help of [NestJS Mau](https://mau.nestjs.com) in just a few clicks. -- Visualize your application graph and interact with the NestJS application in real-time using [NestJS Devtools](https://devtools.nestjs.com). -- Need help with your project (part-time to full-time)? Check out our official [enterprise support](https://enterprise.nestjs.com). -- To stay in the loop and get updates, follow us on [X](https://x.com/nestframework) and [LinkedIn](https://linkedin.com/company/nestjs). -- Looking for a job, or have a job to offer? Check out our official [Jobs board](https://jobs.nestjs.com). - -## Support - -Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support). - -## Stay in touch - -- Author - [Kamil Myśliwiec](https://twitter.com/kammysliwiec) -- Website - [https://nestjs.com](https://nestjs.com/) -- Twitter - [@nestframework](https://twitter.com/nestframework) - -## License - -Nest is [MIT licensed](https://github.com/nestjs/nest/blob/master/LICENSE). +- **에러 코드**: `src/common/errors/error-codes.ts` 만 고치고 호스트에서 `cd backend && npm run sync:error-codes`(웹 · 앱 사전이 함께 바뀐다). +- **DB 스키마**: 엔티티를 고친 뒤 `docker exec arasystem-backend npm run migration:generate -- src/migrations/<이름>` → 만든 SQL 을 눈으로 확인 → 엔티티와 함께 커밋(nestjs 스킬 "DB 마이그레이션"). +- **운영 도구**(운영 서버 안 명령 — 입사 · 비밀번호 재설정 · 휴대폰 앱 올리기 등): `sudo docker exec -it -backend node dist/cli/ops.js` — 계약 6절 · mail/README. diff --git a/docs/roadmap.md b/docs/roadmap.md new file mode 100644 index 0000000..abc7323 --- /dev/null +++ b/docs/roadmap.md @@ -0,0 +1,73 @@ +# ARA SYSTEM 로드맵 + +그룹웨어 전체의 작업 차례다. 단계 번호는 모듈마다 따로 센다 — 메일의 "N단계"는 API 계약([`docs/mail/api-contract.md`](./mail/api-contract.md))과 +화면 디자인([`docs/design/webmail/README.md`](./design/webmail/README.md))의 "N단계"와 같다. 단계마다 **조사 → 캔버스(화면 보드) → 확정 → 구현 → 배포** 차례로 한다. + +마지막 정리: 2026-11-09 + +## 지금 + +- **운영**: 메일 1~13단계. 2026-11-05 옛 메일 서버(mailcow)에서 전 직원이 옮겨 왔다(메일만 옮겼다 — 주소록 · 달력 · 필터 · 부재중 · 서명은 직원이 새로). +- **배포를 기다림**: 메일 14단계 · 앱 서버(앱 로그인 · 새 메일 알림) · 휴대폰 앱 받기 — main 에 합쳤고, 전환 뒤 안정화 1~2주를 지나 11월 셋째 주에 운영에 올린다. +- **개발 중**: 메일 15단계(메일 · 메일함 다루기). +- **다음**: 메일 16단계(개인 설정). + +## 1부 — 메일 + +| 단계 | 내용 | 상태 | +| --- | --- | --- | +| 1 | 웹메일 기본 — 로그인 · 메일함 · 목록 · 읽기 · 쓰기(서식 편집기 · 첨부) | 운영 | +| 2 | 임시 저장 · 서명 · 받는 사람 자동 완성 | 운영 | +| 3 | 새 메일 확인 · 검색 · 여러 통 한꺼번에(읽음 · 중요 · 이동 · 삭제) | 운영 | +| 4 | 메일함 관리 · 비밀번호 바꾸기 · 부재중 자동 응답 · 스팸 · 원문 | 운영 | +| 5 | 실시간 새 메일 · 바탕화면 알림 | 운영 | +| 6 | 메일 필터 | 운영 | +| 7 | 이미 받은 메일에 필터 적용 | 운영 | +| 8 | 주소록(내 연락처 · 그룹 · 사내 목록) | 운영 | +| 9 | 보내기 취소 · 예약 발송 · 외부 이미지 보기 · 인쇄 · 보낸 사람 차단 · 보내기 · 읽기 설정 | 운영 | +| 10 | 파일럿 공존 — 옛 서버와 같은 도메인을 함께 쓰며 먼저 써 보기 | 운영(전환으로 끝남) | +| 11 | 대용량 첨부 — 링크로 보내기 · 조각 올리기 · 30일 보관 · 로그인 없는 내려받기 화면 · 악성 검사 | 운영 | +| 12 | 계정 보안 — 2단계 인증 · 앱 비밀번호 · 로그인 기록 · 로그인된 기기 | 운영 | +| 13 | 전환 준비 — 새 비밀번호 · 2단계 필수 · 메일 프로그램 연결 안내 · 운영 도구 · 백업 · 감시 · 휴지통 · 스팸 30일 | 운영 | +| 14 | 자체 검색 색인 · 조건 검색 · 검색어 강조 · 같은 대화 · 대화로 묶어 보기 | main 에 합침 — 배포 기다림 | +| 15 | 메일 · 메일함 다루기 — 읽기 화면 [이동] · 끌어다 놓기 · 메일함 옮기기 · 하위까지 지우기 · 차단할 때 받은편지함 정리 · 예약 메일 [지금 보내기] · 같은 대화 [모두 펼치기] | 개발 중 | +| 16 | 개인 설정 — **주소록 가져오기 · 내보내기**(vCard · CSV) · 여러 서명(사내 · 대외 · 영문) · 부재중 응답의 서식 본문과 사내/외부 구분 · 2단계 인증 휴대폰 직접 바꾸기 | 다음 | + +- 15단계부터는 직원이 쓰며 바라는 것을 먼저 본다 — 16단계가 끝날 즈음 아래 "추후 후보" 가운데 요청이 실제로 있는 것을 골라 다음 단계로 넣는다. +- 2026-11-09 계획에서 뺀 것: 수신 확인 · 사내 발송 취소(사내 받는 사람에게만 되고, 흔한 실수는 보내기 취소가 막는다 — 아래 후보로). + +## 공통 기반 — 조직 · 권한 + +부서 · 관리자 화면 · 모듈 사이 권한. 파일 공유 · 일정 공유 · 사내 AI 의 데이터 범위가 모두 "누가 무엇을 볼 수 있나"에 달려 2부보다 먼저 한다. + +## 2부 — 다른 웹 모듈 + +파일 저장소 → 캘린더(일정 · 초대) → 사내 AI(메일 · 파일 · 일정을 쓰는 도우미) → 그 밖. 파일 저장소는 11단계 대용량 첨부의 저장 · 올리기를 바탕으로 한다. + +## 3부 — 휴대폰 앱 (웹과 나란히) + +| 판 | 내용 | 상태 | +| --- | --- | --- | +| 앱 1단계 | 로그인(코드 · 새 비밀번호 · 2단계 켜기) · 메일(목록 · 읽기 · 쓰기 · 첨부 · 검색 · 이동 · 삭제) · 새 메일 알림(안드로이드) | main 에 합침 — 서버 배포 뒤 직원 배포 | +| 사내 배포 | 스토어 없이 우리 키로 서명한 APK 를 포털의 휴대폰 앱 받기 화면에서 | main 에 합침 — 서명 키 준비됨 | +| 다음 판 후보 | 주소록 · 대용량 첨부 · 필터 · 서식 편집 · 예약 메일 [지금 보내기] | 첫 판 반응을 보고 고른다 | + +## 추후 후보 (필요해지면) + +단계마다 "처음 판에서 미룬 것" · "이 단계에서 하지 않는 것"을 한데 모은 것이다. 직원 요청이 있거나 다른 모듈을 만들 때 함께 본다. + +- **메일 쓰기 · 읽기**: 수신 확인 · 사내 발송 취소, 본문 안에 그림 넣기(지금은 붙여 넣으면 첨부), 메일 안의 배경 그림, 보낸 사람마다 "이미지 늘 보기", 탭을 모두 닫아도 오는 알림(웹 푸시), + 7일 넘는 예약(메일 엔진 한도라 어렵다). +- **검색**(14단계에서 미룸): 강조한 곳 사이 다음 · 이전 이동, 검색어 줄임말(`from:` 꼴), 첨부 파일 안의 글 찾기, 끝이 바뀐 말 찾기(형태소 분석). +- **메일함 정리**(15단계에서 미룸): 메일함을 끌어 옮기기 · 순서 바꾸기, 옮기기 단축키, 예약됨 목록 줄의 [지금 보내기], 차단할 때 옮긴 메일 한꺼번에 되돌리기 · 받은편지함 밖 메일함 정리. +- **대용량 첨부**(11단계에서 미룸 — 파일 저장소 때 함께): 보관 기간 고르기 · 연장, 다시 보내기, 받는 사람 확인 코드 · 비밀번호, 사내 직원만 받기, 받는 사람별 기록, + 처음 받음 · 만료 전 알림, 미리 보기, 모두 받기(zip), 폴더 올리기, 링크 → 메일에 담기 되돌리기. +- **계정 · 보안**(12 · 13단계에서 미룸 — 공통 기반 때 함께): 복구 코드, 이 브라우저 기억, 패스키, 새 기기 로그인 알림 메일, 계정 변경 기록, 로그인 기록의 나라 · 지역, + 앱 비밀번호 이름 바꾸기 · 만료, 비밀번호 스스로 재설정, 공용 주소로 보내기 · 공유 메일함, 웹메일 관리자 화면. +- **앱**: 앱 안에서 받아 바로 설치, 최소 판이 아닌 새 판 알림, 아이폰, Google 개발자 인증(2027년 전 세계 — 그 전에 등록할지 정한다). + +## 틈날 때 (기능이 아닌 것) + +- 작은 정리: 메일 첨부 받기 경로의 긴 한글 파일 이름(nginx 머리글 버퍼), 백엔드 대소문자 라우팅에 대한 옛 주석, 앱 APK 확인을 서명 지문 대조로. +- 기술 정리(9단계에서 나온 것): 외부 이미지 중계의 접근 로그 양, 실시간 알림에 발송 상태 싣기, 예약 시각 범위의 오류 코드, 모두 4xx 로 붙잡힌 발송의 엔진 동작 실측. +- 의존성 갱신(배포 2주가 지난 판 — 보안 릴리스는 바로)과 코드 정리(쓰기 화면 쪼개기, 운영 이미지의 시험 파일). diff --git a/frontend/README.md b/frontend/README.md index 40e015c..9ca2629 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -1,48 +1,32 @@ -# frontend +# frontend — ARA SYSTEM 웹메일 (Vue 3) -This template should help get you started developing with Vue 3 in Vite. +웹메일 화면이다. 개발에서는 호스트의 vite 로, 운영에서는 nginx 가 화면을 내주고 같은 주소의 `/api` 를 백엔드로 넘긴다. -## Recommended IDE Setup +- 구조 · 개발 규칙(골격 · 스토어 · 컴포저블 · 화면별 파일): [`.claude/skills/vue3/SKILL.md`](../.claude/skills/vue3/SKILL.md) +- 화면 디자인 · 화면 문구 규칙: [`docs/design/webmail/README.md`](../docs/design/webmail/README.md) +- API 약속: [`docs/mail/api-contract.md`](../docs/mail/api-contract.md) -[VS Code](https://code.visualstudio.com/) + [Vue (Official)](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur). +## 개발 서버 -## Recommended Browser Setup - -- Chromium-based browsers (Chrome, Edge, Brave, etc.): - - [Vue.js devtools](https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd) - - [Turn on Custom Object Formatter in Chrome DevTools](http://bit.ly/object-formatters) -- Firefox: - - [Vue.js devtools](https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/) - - [Turn on Custom Object Formatter in Firefox DevTools](https://fxdx.dev/firefox-devtools-custom-object-formatters/) - -## Type Support for `.vue` Imports in TS - -TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types. - -## Customize configuration - -See [Vite Configuration Reference](https://vite.dev/config/). - -## Project Setup - -```sh -npm install +```bash +cd frontend && npm install && npm run dev # http://localhost:5173 — /api 는 vite 프록시가 백엔드 컨테이너로 ``` -### Compile and Hot-Reload for Development +백엔드 · DB · 메일 엔진은 루트의 개발 스택으로 먼저 띄운다(루트 README "빠른 시작"). 포트는 루트 `.env.{mode}` 의 `FRONTEND_PORT` · `BACKEND_PORT` 를 따른다. +API 응답이 JSON 이 아니라 HTML 이면 vite 프록시가 백엔드에 닿지 못한 것이다(백엔드 컨테이너 · 포트를 본다). -```sh -npm run dev +## 검사 + +```bash +npm run lint # 경고 0 +npm run type-check +npm run test:unit # 시간대가 걸린 화면을 고쳤으면 Git Bash 에서 TZ=UTC npm run test:unit -- --run 도 ``` -### Type-Check, Compile and Minify for Production +## 운영 빌드 -```sh -npm run build -``` +운영은 이미지(nginx)로 만든다 — `docker compose --env-file .env.production up -d --build`(docker 스킬 "운영 환경" · "같은 주소 배포"). `VITE_API_BASE_URL` 은 `/api` 로 고정이다. -### Run Unit Tests with [Vitest](https://vitest.dev/) +## 편집기 -```sh -npm run test:unit -``` +VS Code + [Vue (Official)](https://marketplace.visualstudio.com/items?itemName=Vue.volar). 형 검사는 `vue-tsc` 다.