import { Response } from 'express'; import { AuthenticatedRequest } from '../types/auth'; import { userMailAccountService } from '../services/userMailAccountService'; import { userMailImapService } from '../services/userMailImapService'; import { userMailSmtpService } from '../services/userMailSmtpService'; import { encryptionService } from '../services/encryptionService'; import { imapConnectionPool } from '../services/imapConnectionPool'; import { mailCache } from '../services/mailCache'; class UserMailController { async listAccounts(req: AuthenticatedRequest, res: Response) { try { const userId = (req as any).user?.userId; if (!userId) return res.status(401).json({ success: false, message: '인증 필요' }); const protocol = req.query.protocol as string | undefined; const accounts = await userMailAccountService.getAccountsByUserId(userId, protocol); // 비밀번호 제거 후 반환 const safe = accounts.map(({ password, ...rest }) => rest); res.json({ success: true, data: safe }); } catch (error) { res.status(500).json({ success: false, message: error instanceof Error ? error.message : '서버 오류' }); } } async createAccount(req: AuthenticatedRequest, res: Response) { try { const userId = (req as any).user?.userId; if (!userId) return res.status(401).json({ success: false, message: '인증 필요' }); // 저장 전 연결 테스트 (임시 account 객체 사용, 평문 비밀번호를 암호화해서 전달) const tempAccount = { ...req.body, id: 0, userId, status: 'active', password: encryptionService.encrypt(req.body.password) }; const service = userMailImapService; const testResult = await service.testConnection(tempAccount); if (!testResult.success) { return res.status(400).json({ success: false, message: `연결 테스트 실패: ${testResult.message}` }); } const account = await userMailAccountService.createAccount(userId, req.body); const { password, ...safe } = account; res.status(201).json({ success: true, data: safe }); } catch (error) { res.status(500).json({ success: false, message: error instanceof Error ? error.message : '서버 오류' }); } } async updateAccount(req: AuthenticatedRequest, res: Response) { try { const userId = (req as any).user?.userId; if (!userId) return res.status(401).json({ success: false, message: '인증 필요' }); const accountId = parseInt(req.params.accountId); // 비밀번호 변경이 포함된 경우 연결 테스트 if (req.body.password) { const existing = await userMailAccountService.getAccountById(accountId, userId); if (!existing) return res.status(404).json({ success: false, message: '계정을 찾을 수 없습니다.' }); const tempAccount = { ...existing, ...req.body, password: encryptionService.encrypt(req.body.password) }; const service = userMailImapService; const testResult = await service.testConnection(tempAccount); if (!testResult.success) { return res.status(400).json({ success: false, message: `연결 테스트 실패: ${testResult.message}` }); } } const account = await userMailAccountService.updateAccount(accountId, userId, req.body); if (!account) return res.status(404).json({ success: false, message: '계정을 찾을 수 없습니다.' }); imapConnectionPool.destroyByAccount(accountId); mailCache.invalidateByPrefix(`mailList:${accountId}:`); mailCache.invalidateByPrefix(`mailDetail:${accountId}:`); const { password, ...safe } = account; res.json({ success: true, data: safe }); } catch (error) { res.status(500).json({ success: false, message: error instanceof Error ? error.message : '서버 오류' }); } } async deleteAccount(req: AuthenticatedRequest, res: Response) { try { const userId = (req as any).user?.userId; if (!userId) return res.status(401).json({ success: false, message: '인증 필요' }); const accountId = parseInt(req.params.accountId); const deleted = await userMailAccountService.deleteAccount(accountId, userId); if (!deleted) return res.status(404).json({ success: false, message: '계정을 찾을 수 없습니다.' }); imapConnectionPool.destroyByAccount(accountId); mailCache.invalidateByPrefix(`mailList:${accountId}:`); mailCache.invalidateByPrefix(`mailDetail:${accountId}:`); res.json({ success: true, message: '계정이 삭제되었습니다.' }); } catch (error) { res.status(500).json({ success: false, message: error instanceof Error ? error.message : '서버 오류' }); } } async testConnectionDirect(req: AuthenticatedRequest, res: Response) { try { const userId = (req as any).user?.userId; if (!userId) return res.status(401).json({ success: false, message: '인증 필요' }); const { protocol, host, port, useTls, username, password } = req.body; if (!protocol || !host || !port || !username || !password) { return res.status(400).json({ success: false, message: '필수 항목 누락' }); } const tempAccount = { id: 0, userId, displayName: '', email: '', protocol, host, port, useTls: useTls ?? true, username, status: 'active', password: encryptionService.encrypt(password), createdAt: new Date(), updatedAt: new Date(), }; const service = userMailImapService; const result = await service.testConnection(tempAccount); res.json({ success: true, data: result }); } catch (error) { res.status(500).json({ success: false, message: error instanceof Error ? error.message : '서버 오류' }); } } async testConnection(req: AuthenticatedRequest, res: Response) { try { const userId = (req as any).user?.userId; if (!userId) return res.status(401).json({ success: false, message: '인증 필요' }); const accountId = parseInt(req.params.accountId); const account = await userMailAccountService.getAccountById(accountId, userId); if (!account) return res.status(404).json({ success: false, message: '계정을 찾을 수 없습니다.' }); const service = userMailImapService; const result = await service.testConnection(account); res.json({ success: true, data: result }); } catch (error) { res.status(500).json({ success: false, message: error instanceof Error ? error.message : '서버 오류' }); } } async listMails(req: AuthenticatedRequest, res: Response) { try { const userId = (req as any).user?.userId; if (!userId) return res.status(401).json({ success: false, message: '인증 필요' }); const accountId = parseInt(req.params.accountId); const account = await userMailAccountService.getAccountById(accountId, userId); if (!account) return res.status(404).json({ success: false, message: '계정을 찾을 수 없습니다.' }); const limit = parseInt(req.query.limit as string) || 50; const service = userMailImapService; const mails = await service.fetchMailList(account, limit); res.json({ success: true, data: mails }); } catch (error) { res.status(500).json({ success: false, message: error instanceof Error ? error.message : '서버 오류' }); } } async streamMails(req: AuthenticatedRequest, res: Response) { res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); res.setHeader('X-Accel-Buffering', 'no'); res.flushHeaders(); const userId = (req as any).user?.userId; const accountId = parseInt(req.params.accountId); const limit = parseInt(req.query.limit as string) || 20; const before = req.query.before ? parseInt(req.query.before as string) : null; const account = await userMailAccountService.getAccountById(accountId, userId); if (!account) { res.write(`event: error\ndata: ${JSON.stringify({ message: '계정을 찾을 수 없습니다.' })}\n\n`); return res.end(); } let ended = false; req.on('close', () => { ended = true; }); await userMailImapService.fetchMailListStream( account, limit, before, (mail) => { if (!ended) res.write(`data: ${JSON.stringify(mail)}\n\n`); }, () => { if (!ended) { res.write(`event: done\ndata: {}\n\n`); res.end(); } }, (err) => { if (!ended) { res.write(`event: error\ndata: ${JSON.stringify({ message: err.message })}\n\n`); res.end(); } } ); } async getMailDetail(req: AuthenticatedRequest, res: Response) { try { const userId = (req as any).user?.userId; if (!userId) return res.status(401).json({ success: false, message: '인증 필요' }); const accountId = parseInt(req.params.accountId); const account = await userMailAccountService.getAccountById(accountId, userId); if (!account) return res.status(404).json({ success: false, message: '계정을 찾을 수 없습니다.' }); const seqno = parseInt(req.params.seqno); const service = userMailImapService; const detail = await service.getMailDetail(account, seqno); if (!detail) return res.status(404).json({ success: false, message: '메일을 찾을 수 없습니다.' }); res.json({ success: true, data: detail }); } catch (error) { res.status(500).json({ success: false, message: error instanceof Error ? error.message : '서버 오류' }); } } async markAsRead(req: AuthenticatedRequest, res: Response) { try { const userId = (req as any).user?.userId; if (!userId) return res.status(401).json({ success: false, message: '인증 필요' }); const accountId = parseInt(req.params.accountId); const account = await userMailAccountService.getAccountById(accountId, userId); if (!account) return res.status(404).json({ success: false, message: '계정을 찾을 수 없습니다.' }); const seqno = parseInt(req.params.seqno); const service = userMailImapService; const result = await service.markAsRead(account, seqno); res.json({ success: true, data: result }); } catch (error) { res.status(500).json({ success: false, message: error instanceof Error ? error.message : '서버 오류' }); } } async deleteMail(req: AuthenticatedRequest, res: Response) { try { const userId = (req as any).user?.userId; if (!userId) return res.status(401).json({ success: false, message: '인증 필요' }); const accountId = parseInt(req.params.accountId); const account = await userMailAccountService.getAccountById(accountId, userId); if (!account) return res.status(404).json({ success: false, message: '계정을 찾을 수 없습니다.' }); const seqno = parseInt(req.params.seqno); const service = userMailImapService; const result = await service.deleteMail(account, seqno); res.json({ success: true, data: result }); } catch (error) { res.status(500).json({ success: false, message: error instanceof Error ? error.message : '서버 오류' }); } } async listFolders(req: AuthenticatedRequest, res: Response): Promise { try { const accountId = parseInt(req.params.accountId); const account = await userMailAccountService.getAccountById(accountId, (req as any).user!.userId); if (!account) { res.status(404).json({ success: false, message: '계정 없음' }); return; } const folders = await userMailImapService.listFolders(account); res.json({ success: true, data: folders }); } catch (err) { res.status(500).json({ success: false, message: err instanceof Error ? err.message : '오류' }); } } async streamFolderMails(req: AuthenticatedRequest, res: Response): Promise { const accountId = parseInt(req.params.accountId); const folder = decodeURIComponent(req.params.folder); const limit = parseInt(req.query.limit as string) || 20; const before = req.query.before ? parseInt(req.query.before as string) : null; const account = await userMailAccountService.getAccountById(accountId, (req as any).user!.userId); if (!account) { res.status(404).json({ success: false, message: '계정 없음' }); return; } res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); res.flushHeaders(); await userMailImapService.streamMailsByFolder( account, folder, limit, before, (mail) => { res.write(`event: message\ndata: ${JSON.stringify(mail)}\n\n`); }, () => { res.write(`event: done\ndata: {}\n\n`); res.end(); }, (err) => { res.write(`event: error\ndata: ${JSON.stringify({ message: err.message })}\n\n`); res.end(); } ); } async moveMail(req: AuthenticatedRequest, res: Response): Promise { try { const accountId = parseInt(req.params.accountId); const seqno = parseInt(req.params.seqno); const { targetFolder } = req.body; if (!targetFolder) { res.status(400).json({ success: false, message: 'targetFolder 필요' }); return; } const account = await userMailAccountService.getAccountById(accountId, (req as any).user!.userId); if (!account) { res.status(404).json({ success: false, message: '계정 없음' }); return; } const result = await userMailImapService.moveMail(account, seqno, targetFolder); res.json(result); } catch (err) { res.status(500).json({ success: false, message: err instanceof Error ? err.message : '오류' }); } } async getAttachments(req: AuthenticatedRequest, res: Response): Promise { try { const accountId = parseInt(req.params.accountId); const seqno = parseInt(req.params.seqno); const account = await userMailAccountService.getAccountById(accountId, (req as any).user!.userId); if (!account) { res.status(404).json({ success: false, message: '계정 없음' }); return; } const folder = (req.query.folder as string) || 'INBOX'; const attachments = await userMailImapService.getAttachmentList(account, seqno, folder); res.json({ success: true, data: attachments }); } catch (err) { res.status(500).json({ success: false, message: err instanceof Error ? err.message : '오류' }); } } async downloadAttachment(req: AuthenticatedRequest, res: Response): Promise { try { const accountId = parseInt(req.params.accountId); const seqno = parseInt(req.params.seqno); const partId = decodeURIComponent(req.params.partId); const account = await userMailAccountService.getAccountById(accountId, (req as any).user!.userId); if (!account) { res.status(404).json({ success: false, message: '계정 없음' }); return; } const folder = (req.query.folder as string) || 'INBOX'; const filenameHint = (req.params.filename as string | undefined) || (req.query.filename as string | undefined); await userMailImapService.downloadAttachment(account, seqno, partId, res, folder, filenameHint); } catch (err) { if (!res.headersSent) { res.status(500).json({ success: false, message: err instanceof Error ? err.message : '오류' }); } } } async sendMail(req: AuthenticatedRequest, res: Response): Promise { try { const accountId = parseInt(req.params.accountId); const account = await userMailAccountService.getAccountById(accountId, (req as any).user!.userId); if (!account) { res.status(404).json({ success: false, message: '계정 없음' }); return; } const result = await userMailSmtpService.sendMail(account, req.body); res.json(result); } catch (err) { res.status(500).json({ success: false, message: err instanceof Error ? err.message : '오류' }); } } } export const userMailController = new UserMailController();