- 사용자 온라인 상태 표시 (온라인/자리비움/오프라인) 디스코드 스타일 - 채팅방별 알림 ON/OFF 토글 (Bell 아이콘, localStorage 저장) - 파일 업로드 실시간 소켓 브로드캐스트 및 한글 파일명 깨짐 수정 - FAB 읽지않음 배지 버그 수정 (메신저 닫혀있을 때 markAsRead 차단) - 타이핑 도트 애니메이션, 날짜 오늘/어제 표시 - 입력창 bordered box, DM 편집 버튼 숨김 - 메신저 설정 버튼 제거, 새 대화 시작하기 Empty state CTA - useMessengerSocket 소켓 중복 생성 방지 (MessengerModal로 이동) Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> [RAPID-micro] 추적 파일 정리 및 메신저 소소한 변경 - .omc/state/ 파일 git 추적 제거 (.gitignore 이미 설정됨) - db/checkpoints/ gitignore 추가 - globals.css: 메신저 메시지 시간 폰트 스타일 추가 - useMessenger.ts: fileMimeType 필드 및 API_BASE_URL import 추가 Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
221 lines
7.7 KiB
TypeScript
221 lines
7.7 KiB
TypeScript
import { Request, Response } from 'express';
|
|
import { messengerService } from '../services/messengerService';
|
|
import { AuthenticatedRequest } from '../types/auth';
|
|
import { getIo } from '../socket/socketManager';
|
|
import path from 'path';
|
|
|
|
class MessengerController {
|
|
async getRooms(req: Request, res: Response) {
|
|
try {
|
|
const user = (req as AuthenticatedRequest).user!;
|
|
const rooms = await messengerService.getRooms(user.userId, user.companyCode!);
|
|
res.json({ success: true, data: rooms });
|
|
} catch (error) {
|
|
const err = error as Error;
|
|
console.error('getRooms error:', err.message);
|
|
res.status(500).json({ success: false, message: err.message });
|
|
}
|
|
}
|
|
|
|
async createRoom(req: Request, res: Response) {
|
|
try {
|
|
const user = (req as AuthenticatedRequest).user!;
|
|
const room_type = req.body.room_type ?? req.body.type;
|
|
const room_name = req.body.room_name ?? req.body.name;
|
|
const participant_ids = req.body.participant_ids ?? req.body.participantIds;
|
|
|
|
if (!room_type || !participant_ids || !Array.isArray(participant_ids)) {
|
|
return res.status(400).json({ success: false, message: 'room_type and participant_ids are required.' });
|
|
}
|
|
|
|
const room = await messengerService.createRoom(user.userId, user.companyCode!, {
|
|
room_type,
|
|
room_name,
|
|
participant_ids,
|
|
});
|
|
res.json({ success: true, data: room });
|
|
} catch (error) {
|
|
const err = error as Error;
|
|
console.error('createRoom error:', err.message);
|
|
res.status(500).json({ success: false, message: err.message });
|
|
}
|
|
}
|
|
|
|
async getMessages(req: Request, res: Response) {
|
|
try {
|
|
const user = (req as AuthenticatedRequest).user!;
|
|
const roomId = parseInt(req.params.roomId, 10);
|
|
const limit = parseInt(req.query.limit as string, 10) || 50;
|
|
const before = req.query.before ? parseInt(req.query.before as string, 10) : undefined;
|
|
|
|
const messages = await messengerService.getMessages(roomId, user.userId, user.companyCode!, limit, before);
|
|
res.json({ success: true, data: messages });
|
|
} catch (error) {
|
|
const err = error as Error;
|
|
console.error('getMessages error:', err.message);
|
|
res.status(500).json({ success: false, message: err.message });
|
|
}
|
|
}
|
|
|
|
async sendMessage(req: Request, res: Response) {
|
|
try {
|
|
const user = (req as AuthenticatedRequest).user!;
|
|
const roomId = parseInt(req.params.roomId, 10);
|
|
const content = req.body.content;
|
|
const messageType = req.body.type ?? req.body.message_type ?? 'text';
|
|
const parentId = req.body.parentId ?? req.body.parent_message_id ?? null;
|
|
|
|
if (!content) {
|
|
return res.status(400).json({ success: false, message: 'content is required.' });
|
|
}
|
|
|
|
const message = await messengerService.sendMessage(roomId, user.userId, user.companyCode!, content, messageType, parentId);
|
|
|
|
// Broadcast to all room participants via Socket.IO
|
|
const io = getIo();
|
|
if (io) {
|
|
io.to(`${user.companyCode}:${roomId}`).emit('new_message', message);
|
|
}
|
|
|
|
res.json({ success: true, data: message });
|
|
} catch (error) {
|
|
const err = error as Error;
|
|
console.error('sendMessage error:', err.message);
|
|
res.status(500).json({ success: false, message: err.message });
|
|
}
|
|
}
|
|
|
|
async markAsRead(req: Request, res: Response) {
|
|
try {
|
|
const user = (req as AuthenticatedRequest).user!;
|
|
const roomId = parseInt(req.params.roomId, 10);
|
|
await messengerService.markAsRead(roomId, user.userId);
|
|
res.json({ success: true });
|
|
} catch (error) {
|
|
const err = error as Error;
|
|
console.error('markAsRead error:', err.message);
|
|
res.status(500).json({ success: false, message: err.message });
|
|
}
|
|
}
|
|
|
|
async uploadFile(req: Request, res: Response) {
|
|
try {
|
|
const user = (req as AuthenticatedRequest).user!;
|
|
const files = req.files as Express.Multer.File[];
|
|
|
|
if (!files || files.length === 0) {
|
|
return res.status(400).json({ success: false, message: 'No files uploaded.' });
|
|
}
|
|
|
|
const roomId = parseInt(req.body.room_id, 10);
|
|
if (!roomId) {
|
|
return res.status(400).json({ success: false, message: 'room_id is required.' });
|
|
}
|
|
|
|
const io = getIo();
|
|
const savedFiles = [];
|
|
for (const file of files) {
|
|
// Use a readable placeholder as content to avoid filename encoding issues
|
|
const isImage = file.mimetype.startsWith('image/');
|
|
const content = isImage ? '[이미지]' : '[파일]';
|
|
|
|
// Create a file message
|
|
const message = await messengerService.sendMessage(
|
|
roomId,
|
|
user.userId,
|
|
user.companyCode!,
|
|
content,
|
|
'file'
|
|
);
|
|
|
|
const savedFile = await messengerService.saveFile(message.id, {
|
|
originalName: file.originalname,
|
|
storedName: file.filename,
|
|
filePath: file.path,
|
|
fileSize: file.size,
|
|
mimeType: file.mimetype,
|
|
});
|
|
|
|
message.files = [savedFile];
|
|
|
|
// Broadcast to room so recipients receive it in real-time
|
|
io.to(`${user.companyCode}:${roomId}`).emit('new_message', message);
|
|
|
|
savedFiles.push({ message, file: savedFile });
|
|
}
|
|
|
|
res.json({ success: true, data: savedFiles });
|
|
} catch (error) {
|
|
const err = error as Error;
|
|
console.error('uploadFile error:', err.message);
|
|
res.status(500).json({ success: false, message: err.message });
|
|
}
|
|
}
|
|
|
|
async downloadFile(req: Request, res: Response) {
|
|
try {
|
|
const fileId = parseInt(req.params.fileId, 10);
|
|
const file = await messengerService.getFileById(fileId);
|
|
|
|
if (!file) {
|
|
return res.status(404).json({ success: false, message: 'File not found.' });
|
|
}
|
|
|
|
res.download(file.file_path, file.original_name);
|
|
} catch (error) {
|
|
const err = error as Error;
|
|
console.error('downloadFile error:', err.message);
|
|
res.status(500).json({ success: false, message: err.message });
|
|
}
|
|
}
|
|
|
|
async getCompanyUsers(req: Request, res: Response) {
|
|
try {
|
|
const user = (req as AuthenticatedRequest).user!;
|
|
const users = await messengerService.getCompanyUsers(user.companyCode!, user.userId);
|
|
res.json({ success: true, data: users });
|
|
} catch (error) {
|
|
const err = error as Error;
|
|
console.error('getCompanyUsers error:', err.message);
|
|
res.status(500).json({ success: false, message: err.message });
|
|
}
|
|
}
|
|
|
|
async updateRoom(req: Request, res: Response) {
|
|
try {
|
|
const user = (req as AuthenticatedRequest).user!;
|
|
const roomId = parseInt(req.params.roomId, 10);
|
|
const { room_name } = req.body;
|
|
|
|
if (!room_name) {
|
|
return res.status(400).json({ success: false, message: 'room_name is required.' });
|
|
}
|
|
|
|
const room = await messengerService.updateRoom(roomId, user.companyCode!, room_name);
|
|
if (!room) {
|
|
return res.status(404).json({ success: false, message: 'Room not found.' });
|
|
}
|
|
|
|
res.json({ success: true, data: room });
|
|
} catch (error) {
|
|
const err = error as Error;
|
|
console.error('updateRoom error:', err.message);
|
|
res.status(500).json({ success: false, message: err.message });
|
|
}
|
|
}
|
|
|
|
async getUnreadCount(req: Request, res: Response) {
|
|
try {
|
|
const user = (req as AuthenticatedRequest).user!;
|
|
const count = await messengerService.getUnreadCount(user.userId, user.companyCode!);
|
|
res.json({ success: true, data: { unread_count: count } });
|
|
} catch (error) {
|
|
const err = error as Error;
|
|
console.error('getUnreadCount error:', err.message);
|
|
res.status(500).json({ success: false, message: err.message });
|
|
}
|
|
}
|
|
}
|
|
|
|
export const messengerController = new MessengerController();
|