const sqlite3 = require('sqlite3').verbose(); const path = require('path'); const fs = require('fs'); class DatabaseHelper { constructor() { this.dbPath = path.join(__dirname, 'jaryo.db'); this.db = null; } // 데이터베이스 연결 connect() { return new Promise((resolve, reject) => { if (this.db) { resolve(this.db); return; } this.db = new sqlite3.Database(this.dbPath, sqlite3.OPEN_READWRITE, (err) => { if (err) { console.error('데이터베이스 연결 오류:', err.message); reject(err); } else { console.log('✅ SQLite 데이터베이스 연결됨'); resolve(this.db); } }); }); } // 데이터베이스 연결 종료 close() { return new Promise((resolve, reject) => { if (this.db) { this.db.close((err) => { if (err) { reject(err); } else { this.db = null; resolve(); } }); } else { resolve(); } }); } // 모든 파일 목록 가져오기 async getAllFiles(limit = 100, offset = 0) { await this.connect(); return new Promise((resolve, reject) => { // 더 간단한 쿼리로 변경 - 첨부파일은 별도 쿼리로 처리 const query = ` SELECT * FROM files ORDER BY created_at DESC LIMIT ? OFFSET ? `; this.db.all(query, [limit, offset], async (err, rows) => { if (err) { reject(err); return; } const files = []; for (const row of rows) { const file = { id: row.id, title: row.title, description: row.description, category: row.category, tags: row.tags ? JSON.parse(row.tags) : [], user_id: row.user_id, created_at: row.created_at, updated_at: row.updated_at, files: [] }; // 각 파일의 첨부파일을 별도로 조회 try { const attachments = await this.getFileAttachments(row.id); file.files = attachments; } catch (attachmentError) { console.warn('첨부파일 조회 오류:', attachmentError); file.files = []; } files.push(file); } resolve(files); }); }); } // 파일의 첨부파일 목록 가져오기 async getFileAttachments(fileId) { return new Promise((resolve, reject) => { const query = 'SELECT * FROM file_attachments WHERE file_id = ?'; this.db.all(query, [fileId], (err, rows) => { if (err) { reject(err); } else { const attachments = rows.map(row => ({ id: row.id, original_name: row.original_name, file_name: row.file_name, file_path: row.file_path, file_size: row.file_size, mime_type: row.mime_type, name: row.original_name, // 호환성을 위해 size: row.file_size // 호환성을 위해 })); resolve(attachments); } }); }); } // 파일 검색 async searchFiles(searchTerm, category = null, limit = 100) { await this.connect(); return new Promise((resolve, reject) => { let query = ` SELECT * FROM files WHERE (title LIKE ? OR description LIKE ? OR tags LIKE ?) `; const params = [`%${searchTerm}%`, `%${searchTerm}%`, `%${searchTerm}%`]; if (category) { query += ' AND category = ?'; params.push(category); } query += ' ORDER BY created_at DESC LIMIT ?'; params.push(limit); this.db.all(query, params, async (err, rows) => { if (err) { reject(err); return; } const files = []; for (const row of rows) { const file = { id: row.id, title: row.title, description: row.description, category: row.category, tags: row.tags ? JSON.parse(row.tags) : [], user_id: row.user_id, created_at: row.created_at, updated_at: row.updated_at, files: [] }; // 각 파일의 첨부파일을 별도로 조회 try { const attachments = await this.getFileAttachments(row.id); file.files = attachments; } catch (attachmentError) { console.warn('첨부파일 조회 오류:', attachmentError); file.files = []; } files.push(file); } resolve(files); }); }); } // 새 파일 추가 async addFile(fileData) { await this.connect(); return new Promise((resolve, reject) => { const query = ` INSERT INTO files (id, title, description, category, tags, user_id) VALUES (?, ?, ?, ?, ?, ?) `; const params = [ fileData.id || this.generateId(), fileData.title, fileData.description || '', fileData.category, JSON.stringify(fileData.tags || []), fileData.user_id || 'offline-user' ]; this.db.run(query, params, function(err) { if (err) { reject(err); } else { resolve({ id: params[0], changes: this.changes }); } }); }); } // 파일 정보 수정 async updateFile(id, updates) { await this.connect(); return new Promise((resolve, reject) => { const setClause = []; const params = []; if (updates.title !== undefined) { setClause.push('title = ?'); params.push(updates.title); } if (updates.description !== undefined) { setClause.push('description = ?'); params.push(updates.description); } if (updates.category !== undefined) { setClause.push('category = ?'); params.push(updates.category); } if (updates.tags !== undefined) { setClause.push('tags = ?'); params.push(JSON.stringify(updates.tags)); } setClause.push('updated_at = CURRENT_TIMESTAMP'); params.push(id); const query = `UPDATE files SET ${setClause.join(', ')} WHERE id = ?`; this.db.run(query, params, function(err) { if (err) { reject(err); } else { resolve({ changes: this.changes }); } }); }); } // 파일 삭제 async deleteFile(id) { await this.connect(); return new Promise((resolve, reject) => { // 첨부파일부터 삭제 (CASCADE가 있지만 명시적으로) this.db.run('DELETE FROM file_attachments WHERE file_id = ?', [id], (err) => { if (err) { reject(err); return; } // 파일 정보 삭제 this.db.run('DELETE FROM files WHERE id = ?', [id], function(err) { if (err) { reject(err); } else { resolve({ changes: this.changes }); } }); }); }); } // 첨부파일 추가 async addFileAttachment(fileId, attachmentData) { await this.connect(); return new Promise((resolve, reject) => { const query = ` INSERT INTO file_attachments (file_id, original_name, file_name, file_path, file_size, mime_type, file_data) VALUES (?, ?, ?, ?, ?, ?, ?) `; const params = [ fileId, attachmentData.original_name, attachmentData.file_name || attachmentData.original_name, attachmentData.file_path || '', attachmentData.file_size || 0, attachmentData.mime_type || '', attachmentData.file_data || null ]; this.db.run(query, params, function(err) { if (err) { reject(err); } else { resolve({ id: this.lastID, changes: this.changes }); } }); }); } // 첨부파일 삭제 async deleteFileAttachment(attachmentId) { await this.connect(); return new Promise((resolve, reject) => { const query = 'DELETE FROM file_attachments WHERE id = ?'; this.db.run(query, [attachmentId], function(err) { if (err) { reject(err); } else { resolve({ changes: this.changes }); } }); }); } // 카테고리 목록 가져오기 async getCategories() { await this.connect(); return new Promise((resolve, reject) => { const query = 'SELECT * FROM categories ORDER BY is_default DESC, name ASC'; this.db.all(query, [], (err, rows) => { if (err) { reject(err); } else { resolve(rows); } }); }); } // 카테고리 추가 async addCategory(name) { await this.connect(); return new Promise((resolve, reject) => { const query = 'INSERT INTO categories (name) VALUES (?)'; this.db.run(query, [name], function(err) { if (err) { reject(err); } else { resolve({ id: this.lastID, changes: this.changes }); } }); }); } // 카테고리 수정 async updateCategory(id, name) { await this.connect(); return new Promise((resolve, reject) => { const query = 'UPDATE categories SET name = ? WHERE id = ?'; this.db.run(query, [name, id], function(err) { if (err) { reject(err); } else { resolve({ changes: this.changes }); } }); }); } // 카테고리 삭제 async deleteCategory(id) { await this.connect(); return new Promise((resolve, reject) => { // 해당 카테고리를 사용하는 파일들을 '기타'로 변경 this.db.serialize(() => { this.db.run('UPDATE files SET category = "기타" WHERE category = (SELECT name FROM categories WHERE id = ?)', [id], (err) => { if (err) { reject(err); return; } // 카테고리 삭제 this.db.run('DELETE FROM categories WHERE id = ?', [id], function(err) { if (err) { reject(err); } else { resolve({ changes: this.changes }); } }); }); }); }); } // 통계 정보 가져오기 async getStats() { await this.connect(); return new Promise((resolve, reject) => { const queries = [ 'SELECT COUNT(*) as total_files FROM files', 'SELECT category, COUNT(*) as count FROM files GROUP BY category', 'SELECT COUNT(*) as total_attachments FROM file_attachments' ]; Promise.all(queries.map(query => new Promise((res, rej) => { this.db.all(query, [], (err, rows) => { if (err) rej(err); else res(rows); }); }) )).then(results => { resolve({ total_files: results[0][0].total_files, by_category: results[1], total_attachments: results[2][0].total_attachments }); }).catch(reject); }); } // 사용자 관련 메서드들 async getUserByEmail(email) { await this.connect(); return new Promise((resolve, reject) => { const query = 'SELECT * FROM users WHERE email = ? AND is_active = 1'; this.db.get(query, [email], (err, row) => { if (err) { reject(err); } else { resolve(row); } }); }); } async getUserById(id) { await this.connect(); return new Promise((resolve, reject) => { const query = 'SELECT * FROM users WHERE id = ? AND is_active = 1'; this.db.get(query, [id], (err, row) => { if (err) { reject(err); } else { resolve(row); } }); }); } async createUser(userData) { await this.connect(); return new Promise((resolve, reject) => { const query = ` INSERT INTO users (id, email, password_hash, name, role) VALUES (?, ?, ?, ?, ?) `; const userId = this.generateId(); const params = [ userId, userData.email, userData.password_hash, userData.name, userData.role || 'user' ]; this.db.run(query, params, function(err) { if (err) { reject(err); } else { resolve({ id: userId, changes: this.changes }); } }); }); } async updateUserLastLogin(userId) { await this.connect(); return new Promise((resolve, reject) => { const query = 'UPDATE users SET last_login = CURRENT_TIMESTAMP WHERE id = ?'; this.db.run(query, [userId], function(err) { if (err) { reject(err); } else { resolve({ changes: this.changes }); } }); }); } async createSession(userId, sessionId, expiresAt) { await this.connect(); return new Promise((resolve, reject) => { const query = ` INSERT INTO user_sessions (id, user_id, expires_at) VALUES (?, ?, ?) `; this.db.run(query, [sessionId, userId, expiresAt], function(err) { if (err) { reject(err); } else { resolve({ id: sessionId, changes: this.changes }); } }); }); } async getSession(sessionId) { await this.connect(); return new Promise((resolve, reject) => { const query = ` SELECT s.*, u.id as user_id, u.email, u.name, u.role FROM user_sessions s JOIN users u ON s.user_id = u.id WHERE s.id = ? AND s.expires_at > datetime('now') AND u.is_active = 1 `; this.db.get(query, [sessionId], (err, row) => { if (err) { reject(err); } else { resolve(row); } }); }); } async deleteSession(sessionId) { await this.connect(); return new Promise((resolve, reject) => { const query = 'DELETE FROM user_sessions WHERE id = ?'; this.db.run(query, [sessionId], function(err) { if (err) { reject(err); } else { resolve({ changes: this.changes }); } }); }); } async cleanExpiredSessions() { await this.connect(); return new Promise((resolve, reject) => { const query = 'DELETE FROM user_sessions WHERE expires_at <= datetime("now")'; this.db.run(query, [], function(err) { if (err) { reject(err); } else { resolve({ changes: this.changes }); } }); }); } // ID 생성 헬퍼 generateId() { return Date.now().toString(36) + Math.random().toString(36).substr(2, 9); } } module.exports = DatabaseHelper;