generated from ttipo/project-base
a79f0791d0
- /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>
76 lines
2.2 KiB
TypeScript
76 lines
2.2 KiB
TypeScript
/**
|
|
* 서명 그림 테스트용 그림 파일 머리 — 테스트 전용 도우미 (앱 코드에서 import 하지 않는다).
|
|
*
|
|
* 형식 확인(signature-image-format.ts)은 파일 앞부분만 본다 — 여기서는 그 앞부분과, 크기를 맞출 채움 바이트만 만든다
|
|
* (실제로 그릴 수 있는 그림일 필요는 없다).
|
|
*
|
|
* 위치: src 밖(test/) — cpu-time.ts 와 같은 이유로 빌드·커버리지에 들어가지 않는다.
|
|
* 단위 테스트는 `@/../test/image-fixtures`, e2e 는 `./image-fixtures` 로 부른다.
|
|
*/
|
|
|
|
/** PNG 서명 + IHDR(가로·세로·비트 깊이·색 형식) — CRC 는 보지 않으므로 비워 둔다. `size` 바이트가 되게 0 으로 채운다 */
|
|
export function pngBytes(width: number, height: number, size = 33): Buffer {
|
|
const data = Buffer.alloc(Math.max(size, 33));
|
|
Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]).copy(data);
|
|
data.writeUInt32BE(13, 8);
|
|
data.write('IHDR', 12, 'ascii');
|
|
data.writeUInt32BE(width, 16);
|
|
data.writeUInt32BE(height, 20);
|
|
data[24] = 8; // 비트 깊이
|
|
data[25] = 6; // 색 형식 RGBA
|
|
return data;
|
|
}
|
|
|
|
/** GIF 머리 (논리 화면의 가로·세로, 리틀 엔디언) */
|
|
export function gifBytes(
|
|
width: number,
|
|
height: number,
|
|
version = 'GIF89a',
|
|
): Buffer {
|
|
const data = Buffer.alloc(13);
|
|
data.write(version, 0, 'ascii');
|
|
data.writeUInt16LE(width, 6);
|
|
data.writeUInt16LE(height, 8);
|
|
return data;
|
|
}
|
|
|
|
/** SOI + APP0(JFIF) + (선택) 표지 앞 채움 바이트 + SOFn(세로·가로) + EOI */
|
|
export function jpegBytes(
|
|
width: number,
|
|
height: number,
|
|
options: { sof?: number; fill?: boolean } = {},
|
|
): Buffer {
|
|
const app0 = Buffer.from([
|
|
0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01, 0x01, 0x00,
|
|
0x00, 0x01, 0x00, 0x01, 0x00, 0x00,
|
|
]);
|
|
const sof = Buffer.from([
|
|
0xff,
|
|
options.sof ?? 0xc0,
|
|
0x00,
|
|
0x11,
|
|
0x08,
|
|
(height >> 8) & 0xff,
|
|
height & 0xff,
|
|
(width >> 8) & 0xff,
|
|
width & 0xff,
|
|
0x03,
|
|
0x01,
|
|
0x22,
|
|
0x00,
|
|
0x02,
|
|
0x11,
|
|
0x01,
|
|
0x03,
|
|
0x11,
|
|
0x01,
|
|
]);
|
|
return Buffer.concat([
|
|
Buffer.from([0xff, 0xd8]),
|
|
app0,
|
|
...(options.fill ? [Buffer.from([0xff, 0xff])] : []),
|
|
sof,
|
|
Buffer.from([0xff, 0xd9]),
|
|
]);
|
|
}
|