generated from ttipo/project-base
feat(backend): 임시 저장·서명(그림)·받는 사람 추천 API와 MAIL_008
- /api/drafts: 발송과 같은 준비(정화·한도)로 초안을 만들고, 바꿔 저장은 새로 만들어 다시 읽은 뒤 옛것을 지운다. 만들기와 지우기를 한 요청에 넣지 않는다 — 엔진이 만들기가 실패해도 지우기를 해서 저장본이 사라졌다. 옛 초안이 이미 없으면 409 MAIL_008(새 코드), 초안이 아닌 id 는 404 RES_001 로 아무것도 지우지 않는다. - 사용자 메일의 영구 삭제(교체한 옛 초안 · 보낸 뒤 draftId · 휴지통 영구 삭제)는 대상인지 확인한 상태 그대로일 때만 ifInState 로 지운다 — 확인과 지우기 사이에 다른 탭이 옮긴 메일까지 지우지 않게 (Stalwart 0.16.27 에서 확인) - /api/mail-signature: 서명(DB)과 그림(업로드 볼륨). 그림은 확장자·MIME·매직 바이트·크기·가로세로를 보고, 보낼 때 multipart/related 의 cid 그림으로 싣는다. 서명 저장은 사용자별로 잠가 차례로 한다 - /api/recipient-suggestions: 로그인한 직원 기록(mail_directory)과 내 메일함의 최근 주소로 추천, 사용자별 10분 캐시 - 본문 그림은 첨부 표시에서 뺀다(hasAttachment 직접 계산 · isInline), 예외 필터 로그의 검색어(q)는 길이만 남긴다 - 마이그레이션 AddMailSignaturesAndDirectory: 새 표 3개만 만든다 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||
|
||||
/**
|
||||
* 메일 서명·서명 그림·사내 목록 표 (docs/mail/api-contract.md 2절 "서명" · "받는 사람 추천").
|
||||
* - mail_signatures: 사용자마다 서명 하나 (정화한 HTML·텍스트·넣을 메일)
|
||||
* - mail_signature_images: 서명 그림 정보 — 파일은 업로드 볼륨의 mail-signatures 폴더. 주인 조회용 인덱스(user_email),
|
||||
* 정리 작업용 부분 인덱스(attached_at 이 비어 있는 행의 created_at)
|
||||
* - mail_directory: 웹메일 로그인에 성공한 직원 (받는 사람 추천의 "사내")
|
||||
* 새 표만 만든다 — 기존 표(auth_sessions)는 건드리지 않는다.
|
||||
* (자동 생성: npm run migration:generate — 엔티티는 modules/mail-signature/*.entity.ts · modules/mail-directory/mail-directory.entity.ts)
|
||||
*/
|
||||
export class AddMailSignaturesAndDirectory1793084109734 implements MigrationInterface {
|
||||
name = 'AddMailSignaturesAndDirectory1793084109734';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE "mail_directory" ("email" character varying(254) NOT NULL, "name" character varying(200) NOT NULL, "last_login_at" TIMESTAMP WITH TIME ZONE NOT NULL, CONSTRAINT "PK_d79c6055eed1d875a53a13a71f8" PRIMARY KEY ("email"))`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE "mail_signature_images" ("id" uuid NOT NULL, "user_email" character varying(254) NOT NULL, "content_type" character varying(32) NOT NULL, "size" integer NOT NULL, "width" integer NOT NULL, "height" integer NOT NULL, "file_name" character varying(64) NOT NULL, "created_at" TIMESTAMP WITH TIME ZONE NOT NULL, "attached_at" TIMESTAMP WITH TIME ZONE, CONSTRAINT "PK_f52f2c8dbb00f2973088763a776" PRIMARY KEY ("id"))`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_mail_signature_images_unattached" ON "mail_signature_images" ("created_at") WHERE "attached_at" IS NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE INDEX "IDX_mail_signature_images_user_email" ON "mail_signature_images" ("user_email") `,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE TABLE "mail_signatures" ("user_email" character varying(254) NOT NULL, "body_html" text, "body_text" text, "is_for_new" boolean NOT NULL DEFAULT true, "is_for_reply" boolean NOT NULL DEFAULT true, "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL, CONSTRAINT "PK_4846a94ce96c5f4fe400f449a7f" PRIMARY KEY ("user_email"))`,
|
||||
);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP TABLE "mail_signatures"`);
|
||||
await queryRunner.query(
|
||||
`DROP INDEX "public"."IDX_mail_signature_images_user_email"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`DROP INDEX "public"."IDX_mail_signature_images_unattached"`,
|
||||
);
|
||||
await queryRunner.query(`DROP TABLE "mail_signature_images"`);
|
||||
await queryRunner.query(`DROP TABLE "mail_directory"`);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user