Commit Graph

4 Commits

Author SHA1 Message Date
ttipo 93154ad9a5 feat(backend): 대용량 첨부 — 조각 올리기 · 악성 검사 · 공개 링크 · 내려받기
- file-storage: /api/file-uploads(만들기 · 16MiB 조각 PUT — Content-Digest · datasync · 같은 조각은 하나만 · 끝내기 · 상태 · 지우기),
  /api/file-quota. 보관량(직원 · 서버 · 디스크 여유)은 pg_advisory_xact_lock 안에서 센다. 저장 포트 FILE_BLOB_STORE(지금은 로컬 볼륨).
  ClamAV INSTREAM 을 직접 구현했다 — 판정 글은 한 곳(암호 · 한도 → 검사하지 못함, 스트림 크기 한도 → 설정 오류로 검사 중 그대로 + error),
  다 보낸 뒤 판정 대기 30분 + 1MB 마다 1초 · TCP keepalive. 검사 차례는 DB pending(동시 2), 정리 cron.
- file-sharing · mail: 메일 한 통에 링크 하나(토큰 256비트 — DB 에는 SHA-256 만). 보낼 때 서버가 본문에 덩이를 넣고(표시는 서버만 —
  두 정화기가 사용자 HTML 의 data-ara-* · 주석을 지운다), 링크는 확실한 실패(SendNotStartedException)에서만 거둔다.
  공개 /api/public-links/{token} · 파일 받기(Range · 조건부 UPDATE 로 받은 수), 링크 끊기 · 목록 · 읽기 화면의 largeFiles.
- 공개 호스트 미들웨어는 원문 · 라우터 경로 · WHATWG URL · 퍼센트를 푼 원문을 모두 보고(리뷰 — 절대 주소 꼴 · \ 요청이 막기를 지났다),
  라우팅은 대소문자를 가린다(createHttpAdapter — /FILES/ 같은 변형이 앞단 상한을 비켜 갔다).
- env: SHARE_PUBLIC_URL(nginx 와 같은 모양 검사 · 운영에서 포털과 같은 호스트면 기동 중단) · MAIL_LARGE_FILE_* · FILE_STORAGE_* · CLAMAV_*.
- 에러 코드 FILE_003 ~ FILE_008 · MAIL_016 — 웹 · 앱 사전 다시 생성. 마이그레이션 둘.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 00:37:36 +09:00
ttipo 08cf31fbe6 feat(backend): 주소록 API — 연락처 · 그룹 · 사내 목록 · 되돌리기, 받는 사람 추천에 주소록
계약 3절. 내 주소록은 엔진의 JMAP Contacts(직원마다 기본 주소록)에, 사내 목록은 엔진 계정 목록을 사내 목록 계정으로만 읽는다.
엔진 검색은 낱말 단위이고 이름 정렬을 못 해서, 직원마다 주소록을 엔진 state 로 확인하는 캐시에 두고 서버가 찾고(초성 · 전화 숫자) 정렬한다.
쓰기는 직원마다 차례로 · ifInState 로 하고, 어긋나거나 엔진이 잠깐 거절하면 다시 읽어 세 번까지.

리뷰로 고친 것:
- 전화번호 정규식이 입력 길이의 제곱이었다(1MB 본문 하나로 약 24분 CPU) — 정규식을 나누고, DTO 검사를 applyDecorators 로 묶어 형식 → 길이 → 모양 차례로 돌린다.
- 고치기는 PATCH(보낸 칸만 검사) — 칸 안의 남의 속성(label · contexts · fax · pref · role · 둘째 조직)은 지금 카드와 합쳐 지킨다(CardDAV 와 함께 쓰려고).
- [되돌리기]는 지우기 직전에 읽은 카드 전체를 5분 기억했다가 같은 uid 로 다시 만든다(되돌리기 표 — 우리 규칙 밖의 값 · 다른 프로그램의 칸도 그대로).
- 큰 주소록이 캐시에 들지 못해 요청마다 엔진을 통째로 다시 읽었다 — 크기 한도 800만을 쓰기에 걸고 캐시의 한 사람 상한과 맞췄다.
- 받는 사람 추천의 q 를 화면과 같은 맞추기(초성 · 조합 중 글자)로 — 주소록이 크면 미리 받은 100줄이 주소록으로 차서 사내 · 최근 주소의 초성 찾기가 사라졌다.
- excludeGroupId · 사내 항목 하나 · q 254자, 판은 읽기 전에 물은 것으로, 한글 이름 조각은 붙여 잇는다.

에러 코드 CONTACT_001(같은 이름 그룹) · CONTACT_002(주소록 한도)를 더하고 클라이언트 사전을 생성했다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 14:09:59 +09:00
ttipo 71f2e01d83 feat(backend): 직원 인증(쿠키 JWT·DB 세션)과 웹메일 API(Stalwart JMAP 중계)
첫 사내 모듈인 웹메일의 백엔드다. 메일 엔진은 외부(Stalwart)에 두고 백엔드가 JMAP 으로 중계한다.

- 직원 로그인 = 메일 계정: 엔진으로 비밀번호를 확인한 뒤 HttpOnly 쿠키 JWT 를 발급한다.
  리프레시 세션은 DB 에 해시로 저장하고 1회용으로 회전한다 (이미 쓴 토큰이 오면 세션 폐기)
- 로그인 실패 한도는 엔진에 묻기 전에 원자적으로 차지하고, IPv6 는 /64 로 묶어 센다
- 상태를 바꾸는 요청은 출처(Sec-Fetch-Site·Origin)를 검사하고, 클라이언트 IP 는 TRUST_PROXY 로만 해석한다
- 메일함·목록·읽기·발송·첨부 API: 받은 HTML 은 정화해서 내보내고, 발송은 Idempotency-Key 로 중복을 막는다
- 메일 엔진 관리자 계정은 로그인·대리 접근 대상에서 막는다 (게이트웨이가 관리자까지 대리할 수 있다)
- 에러 코드 AUTH_004·MAIL_001~006 을 추가하고 클라이언트 에러 사전을 다시 생성했다

계약: docs/mail/api-contract.md

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 01:31:38 +09:00
ttipo 18f10cece4 Initial commit 2026-10-26 12:28:48 +09:00