| |
| |
| |
|
|
| import { writeFile, readFile, mkdir, readdir, unlink } from 'fs/promises'; |
| import { existsSync } from 'fs'; |
| import path from 'path'; |
| import { v4 as uuidv4 } from 'uuid'; |
| import { StorageFile, SessionStorage, ChatMessage, Poll, Doubt } from './types'; |
|
|
| |
| const DATA_DIR = process.env.DATA_DIR || '/data'; |
| const SESSIONS_DIR = path.join(DATA_DIR, 'sessions'); |
| const UPLOADS_DIR = path.join(DATA_DIR, 'uploads'); |
|
|
| |
| async function ensureDirectories() { |
| if (!existsSync(DATA_DIR)) { |
| await mkdir(DATA_DIR, { recursive: true }); |
| } |
| if (!existsSync(SESSIONS_DIR)) { |
| await mkdir(SESSIONS_DIR, { recursive: true }); |
| } |
| if (!existsSync(UPLOADS_DIR)) { |
| await mkdir(UPLOADS_DIR, { recursive: true }); |
| } |
| } |
|
|
| |
| |
| |
|
|
| export class HFStorageService { |
| private baseUrl: string; |
|
|
| constructor() { |
| |
| this.baseUrl = process.env.HF_SPACE_URL || ''; |
| ensureDirectories(); |
| } |
|
|
| |
| |
| |
|
|
| async saveSessionData(sessionId: string, data: any): Promise<void> { |
| const sessionDir = path.join(SESSIONS_DIR, sessionId); |
| if (!existsSync(sessionDir)) { |
| await mkdir(sessionDir, { recursive: true }); |
| } |
| |
| const filePath = path.join(sessionDir, 'session.json'); |
| await writeFile(filePath, JSON.stringify(data, null, 2), 'utf-8'); |
| } |
|
|
| async loadSessionData(sessionId: string): Promise<any | null> { |
| const filePath = path.join(SESSIONS_DIR, sessionId, 'session.json'); |
| |
| if (!existsSync(filePath)) { |
| return null; |
| } |
| |
| const data = await readFile(filePath, 'utf-8'); |
| return JSON.parse(data); |
| } |
|
|
| async deleteSession(sessionId: string): Promise<void> { |
| const sessionDir = path.join(SESSIONS_DIR, sessionId); |
| if (existsSync(sessionDir)) { |
| const files = await readdir(sessionDir); |
| for (const file of files) { |
| await unlink(path.join(sessionDir, file)); |
| } |
| await unlink(sessionDir); |
| } |
| } |
|
|
| |
| |
| |
|
|
| async uploadFile( |
| sessionId: string, |
| fileName: string, |
| base64Data: string, |
| contentType: string = 'image/png' |
| ): Promise<StorageFile> { |
| const sessionDir = path.join(SESSIONS_DIR, sessionId, 'files'); |
| |
| if (!existsSync(sessionDir)) { |
| await mkdir(sessionDir, { recursive: true }); |
| } |
| |
| |
| const base64Content = base64Data.replace(/^data:[^;]+;base64,/, ''); |
| const buffer = Buffer.from(base64Content, 'base64'); |
| |
| const filePath = path.join(sessionDir, fileName); |
| await writeFile(filePath, buffer); |
| |
| return { |
| path: filePath, |
| url: `/api/storage/${sessionId}/${fileName}`, |
| size: buffer.length, |
| contentType, |
| uploadedAt: new Date().toISOString() |
| }; |
| } |
|
|
| async getFile(sessionId: string, fileName: string): Promise<Buffer | null> { |
| const filePath = path.join(SESSIONS_DIR, sessionId, 'files', fileName); |
| |
| if (!existsSync(filePath)) { |
| return null; |
| } |
| |
| return readFile(filePath); |
| } |
|
|
| |
| |
| |
|
|
| async saveChatMessage(sessionId: string, message: ChatMessage): Promise<void> { |
| const sessionDir = path.join(SESSIONS_DIR, sessionId); |
| if (!existsSync(sessionDir)) { |
| await mkdir(sessionDir, { recursive: true }); |
| } |
| |
| const chatFile = path.join(sessionDir, 'chat.json'); |
| let messages: ChatMessage[] = []; |
| |
| if (existsSync(chatFile)) { |
| const data = await readFile(chatFile, 'utf-8'); |
| messages = JSON.parse(data); |
| } |
| |
| messages.push(message); |
| await writeFile(chatFile, JSON.stringify(messages, null, 2), 'utf-8'); |
| } |
|
|
| async getChatHistory(sessionId: string): Promise<ChatMessage[]> { |
| const chatFile = path.join(SESSIONS_DIR, sessionId, 'chat.json'); |
| |
| if (!existsSync(chatFile)) { |
| return []; |
| } |
| |
| const data = await readFile(chatFile, 'utf-8'); |
| return JSON.parse(data); |
| } |
|
|
| |
| |
| |
|
|
| async savePoll(sessionId: string, poll: Poll): Promise<void> { |
| const sessionDir = path.join(SESSIONS_DIR, sessionId); |
| if (!existsSync(sessionDir)) { |
| await mkdir(sessionDir, { recursive: true }); |
| } |
| |
| const pollsFile = path.join(sessionDir, 'polls.json'); |
| let polls: Poll[] = []; |
| |
| if (existsSync(pollsFile)) { |
| const data = await readFile(pollsFile, 'utf-8'); |
| polls = JSON.parse(data); |
| } |
| |
| const existingIndex = polls.findIndex(p => p.id === poll.id); |
| if (existingIndex >= 0) { |
| polls[existingIndex] = poll; |
| } else { |
| polls.push(poll); |
| } |
| |
| await writeFile(pollsFile, JSON.stringify(polls, null, 2), 'utf-8'); |
| } |
|
|
| async getPolls(sessionId: string): Promise<Poll[]> { |
| const pollsFile = path.join(SESSIONS_DIR, sessionId, 'polls.json'); |
| |
| if (!existsSync(pollsFile)) { |
| return []; |
| } |
| |
| const data = await readFile(pollsFile, 'utf-8'); |
| return JSON.parse(data); |
| } |
|
|
| |
| |
| |
|
|
| async saveDoubt(sessionId: string, doubt: Doubt): Promise<void> { |
| const sessionDir = path.join(SESSIONS_DIR, sessionId); |
| if (!existsSync(sessionDir)) { |
| await mkdir(sessionDir, { recursive: true }); |
| } |
| |
| const doubtsFile = path.join(sessionDir, 'doubts.json'); |
| let doubts: Doubt[] = []; |
| |
| if (existsSync(doubtsFile)) { |
| const data = await readFile(doubtsFile, 'utf-8'); |
| doubts = JSON.parse(data); |
| } |
| |
| const existingIndex = doubts.findIndex(d => d.id === doubt.id); |
| if (existingIndex >= 0) { |
| doubts[existingIndex] = doubt; |
| } else { |
| doubts.push(doubt); |
| } |
| |
| await writeFile(doubtsFile, JSON.stringify(doubts, null, 2), 'utf-8'); |
| } |
|
|
| async getDoubts(sessionId: string): Promise<Doubt[]> { |
| const doubtsFile = path.join(SESSIONS_DIR, sessionId, 'doubts.json'); |
| |
| if (!existsSync(doubtsFile)) { |
| return []; |
| } |
| |
| const data = await readFile(doubtsFile, 'utf-8'); |
| return JSON.parse(data); |
| } |
|
|
| |
| |
| |
|
|
| async saveWhiteboardSnapshot( |
| sessionId: string, |
| slideNumber: number, |
| base64Data: string |
| ): Promise<StorageFile> { |
| const fileName = `whiteboard_slide_${slideNumber}_${Date.now()}.png`; |
| return this.uploadFile(sessionId, fileName, base64Data, 'image/png'); |
| } |
|
|
| |
| |
| |
|
|
| async exportSession(sessionId: string): Promise<SessionStorage> { |
| const sessionData = await this.loadSessionData(sessionId); |
| const chatHistory = await this.getChatHistory(sessionId); |
| const polls = await this.getPolls(sessionId); |
| const doubts = await this.getDoubts(sessionId); |
| |
| const filesDir = path.join(SESSIONS_DIR, sessionId, 'files'); |
| const slides: StorageFile[] = []; |
| const whiteboardSnapshots: StorageFile[] = []; |
| |
| if (existsSync(filesDir)) { |
| const files = await readdir(filesDir); |
| for (const file of files) { |
| if (file.startsWith('slide_')) { |
| slides.push({ |
| path: path.join(filesDir, file), |
| url: `/api/storage/${sessionId}/${file}`, |
| size: 0, |
| contentType: 'image/png', |
| uploadedAt: new Date().toISOString() |
| }); |
| } else if (file.startsWith('whiteboard_')) { |
| whiteboardSnapshots.push({ |
| path: path.join(filesDir, file), |
| url: `/api/storage/${sessionId}/${file}`, |
| size: 0, |
| contentType: 'image/png', |
| uploadedAt: new Date().toISOString() |
| }); |
| } |
| } |
| } |
| |
| return { |
| sessionId, |
| slides, |
| whiteboardSnapshots, |
| doubts: doubts.map(d => ({ |
| path: '', |
| url: '', |
| size: 0, |
| contentType: 'application/json', |
| uploadedAt: d.createdAt |
| })), |
| chatHistory, |
| pollHistory: polls |
| }; |
| } |
| } |
|
|
| |
| export const storageService = new HFStorageService(); |
|
|