v4.0.0: 회원·덱 API, 인증 UI, 프로필 및 레이아웃 통일

Express/SQLite API와 Docker 구성을 추가하고 로그인·덱 저장·공개 덱·관리자·프로필 화면을 구현했다. 결과 화면 저장 UX, 헤더 프로필, 애드센스용 app-shell 레이아웃(1280px) 및 문서를 반영했다.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-22 16:59:36 +09:00
parent f8c954f24b
commit dbf02241e2
56 changed files with 6732 additions and 183 deletions

View File

@@ -0,0 +1,65 @@
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
/**
* 아바타 이미지 저장 디렉터리
* @returns {string}
*/
export function getAvatarsDir() {
const dir = process.env.AVATARS_PATH || path.join(__dirname, '..', '..', 'data', 'avatars');
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
return dir;
}
/**
* @param {number|string} userId
* @returns {string}
*/
export function getAvatarFilePath(userId) {
const filePath = path.join(getAvatarsDir(), `${userId}.png`);
return fs.existsSync(filePath) ? filePath : null;
}
/**
* @param {number|string} userId
* @returns {string|null}
*/
export function getAvatarUrl(userId) {
return getAvatarFilePath(userId) ? `/api/v1/auth/avatar/${userId}` : null;
}
/**
* base64 data URL을 PNG로 저장
* @param {number|string} userId
* @param {string} dataUrl
* @returns {string|null}
*/
export function saveAvatarImage(userId, dataUrl) {
if (!dataUrl || !dataUrl.startsWith('data:image')) {
return null;
}
const base64 = dataUrl.replace(/^data:image\/\w+;base64,/, '');
if (!base64) {
return null;
}
const fileName = `${userId}.png`;
const filePath = path.join(getAvatarsDir(), fileName);
fs.writeFileSync(filePath, Buffer.from(base64, 'base64'));
return fs.existsSync(filePath) ? fileName : null;
}
/**
* 아바타 이미지 삭제
* @param {number|string} userId
*/
export function deleteAvatarImage(userId) {
const filePath = path.join(getAvatarsDir(), `${userId}.png`);
if (fs.existsSync(filePath)) {
fs.unlinkSync(filePath);
}
}