import { users, User, InsertUser, apprentices, Apprentice, InsertApprentice, companies, Company, InsertCompany, mentors, Mentor, InsertMentor, files, File, InsertFile, PipelineStage, documents, Document, InsertDocument, comments, Comment, InsertComment, activities, Activity, InsertActivity, settings, Settings, InsertSettings, invitations, Invitation, InsertInvitation } from "@shared/schema"; import { db } from "./db"; import { eq } from "drizzle-orm"; export interface IStorage { // User operations getUser(id: number): Promise; getUserByUsername(username: string): Promise; getUserByEmail(email: string): Promise; createUser(user: InsertUser): Promise; getUsers(): Promise; // Invitation operations getInvitation(id: number): Promise; getInvitations(): Promise; getInvitationByToken(token: string): Promise; getInvitationByEmail(email: string): Promise; createInvitation(invitation: InsertInvitation & { token: string, expiresAt: Date }): Promise; markInvitationAsUsed(id: number): Promise; deleteInvitation(id: number): Promise; // Apprentice operations getApprentice(id: number): Promise; getApprentices(): Promise; createApprentice(apprentice: InsertApprentice): Promise; updateApprentice(id: number, apprentice: Partial): Promise; deleteApprentice(id: number): Promise; // Company operations getCompany(id: number): Promise; getCompanies(): Promise; createCompany(company: InsertCompany): Promise; updateCompany(id: number, company: Partial): Promise; deleteCompany(id: number): Promise; // Mentor operations getMentor(id: number): Promise; getMentors(): Promise; getMentorsByCompany(companyId: number): Promise; createMentor(mentor: InsertMentor): Promise; updateMentor(id: number, mentor: Partial): Promise; deleteMentor(id: number): Promise; // File operations getFile(id: number): Promise; getFiles(): Promise; getFilesByStage(stage: PipelineStage): Promise; getFileDetails(id: number): Promise<{ file: File; apprentice: Apprentice; company: Company; mentor: Mentor; documents: Document[]; comments: Array; } | undefined>; createFile(file: InsertFile): Promise; updateFile(id: number, file: Partial): Promise; updateFileStage(id: number, stage: PipelineStage): Promise; deleteFile(id: number): Promise; // Document operations getDocument(id: number): Promise; getDocuments(): Promise; getDocumentsByFile(fileId: number): Promise; createDocument(document: InsertDocument): Promise; updateDocument(id: number, document: Partial): Promise; deleteDocument(id: number): Promise; // Comment operations getComment(id: number): Promise; getCommentsByFile(fileId: number): Promise>; createComment(comment: InsertComment): Promise; deleteComment(id: number): Promise; // Activity operations getActivities(limit?: number): Promise>; getActivitiesByFile(fileId: number): Promise>; createActivity(activity: InsertActivity): Promise; // Stats operations getStats(): Promise<{ totalFiles: number; filesByStage: Record; validatedFiles: number; averageProcessingTime: number; }>; // Pipeline operations getPipelineConfig(): Promise<{ key: string; name: string }[]>; updatePipelineConfig(stages: { key: string; name: string }[]): Promise; } export class MemStorage implements IStorage { private users: Map; private apprentices: Map; private companies: Map; private mentors: Map; private files: Map; private documents: Map; private comments: Map; private activities: Map; private invitations: Map; private currentUserId: number; private currentApprenticeId: number; private currentCompanyId: number; private currentMentorId: number; private currentFileId: number; private currentDocumentId: number; private currentCommentId: number; private currentActivityId: number; private currentInvitationId: number; private db: any; // Placeholder for database connection constructor(db: any) { // Added db parameter this.db = db; this.users = new Map(); this.apprentices = new Map(); this.companies = new Map(); this.mentors = new Map(); this.files = new Map(); this.documents = new Map(); this.comments = new Map(); this.activities = new Map(); this.invitations = new Map(); this.currentUserId = 1; this.currentApprenticeId = 1; this.currentCompanyId = 1; this.currentMentorId = 1; this.currentFileId = 1; this.currentDocumentId = 1; this.currentCommentId = 1; this.currentActivityId = 1; this.currentInvitationId = 1; // Add initial admin user this.createUser({ username: "admin", password: "admin123", fullName: "Admin User", role: "admin", email: "admin@example.com", avatarUrl: "https://randomuser.me/api/portraits/women/44.jpg" }); } // User operations async getUser(id: number): Promise { return this.users.get(id); } async getUserByUsername(username: string): Promise { return Array.from(this.users.values()).find( (user) => user.username === username, ); } async getUserByEmail(email: string): Promise { return Array.from(this.users.values()).find( (user) => user.email === email, ); } async createUser(insertUser: InsertUser): Promise { const id = this.currentUserId++; // Ensure role is a string and avatarUrl is not undefined const role = insertUser.role || "user"; const avatarUrl = insertUser.avatarUrl || null; const user: User = { ...insertUser, id, role, avatarUrl }; this.users.set(id, user); return user; } async getUsers(): Promise { return Array.from(this.users.values()); } // Invitation operations async getInvitation(id: number): Promise { return this.invitations.get(id); } async getInvitations(): Promise { return Array.from(this.invitations.values()); } async getInvitationByToken(token: string): Promise { return Array.from(this.invitations.values()).find( (invitation) => invitation.token === token, ); } async getInvitationByEmail(email: string): Promise { return Array.from(this.invitations.values()).find( (invitation) => invitation.email === email && !invitation.used, ); } async createInvitation(invitation: InsertInvitation & { token: string, expiresAt: Date }): Promise { const id = this.currentInvitationId++; const createdAt = new Date(); const role = invitation.role || "user"; const newInvitation: Invitation = { ...invitation, role, id, createdAt, used: false }; this.invitations.set(id, newInvitation); return newInvitation; } async markInvitationAsUsed(id: number): Promise { const invitation = this.invitations.get(id); if (!invitation) return undefined; const updatedInvitation: Invitation = { ...invitation, used: true }; this.invitations.set(id, updatedInvitation); return updatedInvitation; } async deleteInvitation(id: number): Promise { return this.invitations.delete(id); } // Apprentice operations async getApprentice(id: number): Promise { return this.apprentices.get(id); } async getApprentices(): Promise { return Array.from(this.apprentices.values()); } async createApprentice(insertApprentice: InsertApprentice): Promise { const id = this.currentApprenticeId++; const createdAt = new Date(); // Initialize optional fields to null if undefined const phone = insertApprentice.phone || null; const birthDay = insertApprentice.birthDay || null; const birthMonth = insertApprentice.birthMonth || null; const birthYear = insertApprentice.birthYear || null; const streetNumber = insertApprentice.streetNumber || null; const street = insertApprentice.street || null; const postalCode = insertApprentice.postalCode || null; const city = insertApprentice.city || null; const education = insertApprentice.education || null; const apprentice: Apprentice = { ...insertApprentice, id, createdAt, phone, birthDay, birthMonth, birthYear, streetNumber, street, postalCode, city, education }; this.apprentices.set(id, apprentice); return apprentice; } async updateApprentice(id: number, apprentice: Partial): Promise { const existingApprentice = this.apprentices.get(id); if (!existingApprentice) return undefined; const updatedApprentice: Apprentice = { ...existingApprentice, ...apprentice }; this.apprentices.set(id, updatedApprentice); return updatedApprentice; } async deleteApprentice(id: number): Promise { return this.apprentices.delete(id); } // Company operations async getCompany(id: number): Promise { return this.companies.get(id); } async getCompanies(): Promise { return Array.from(this.companies.values()); } async createCompany(insertCompany: InsertCompany): Promise { const id = this.currentCompanyId++; const createdAt = new Date(); // Initialize optional fields to null if undefined const email = insertCompany.email || null; const phone = insertCompany.phone || null; const streetNumber = insertCompany.streetNumber || null; const street = insertCompany.street || null; const postalCode = insertCompany.postalCode || null; const city = insertCompany.city || null; const siret = insertCompany.siret || null; const company: Company = { ...insertCompany, id, createdAt, email, phone, streetNumber, street, postalCode, city, siret }; this.companies.set(id, company); return company; } async updateCompany(id: number, company: Partial): Promise { const existingCompany = this.companies.get(id); if (!existingCompany) return undefined; const updatedCompany: Company = { ...existingCompany, ...company }; this.companies.set(id, updatedCompany); return updatedCompany; } async deleteCompany(id: number): Promise { return this.companies.delete(id); } // Mentor operations async getMentor(id: number): Promise { return this.mentors.get(id); } async getMentors(): Promise { return Array.from(this.mentors.values()); } async getMentorsByCompany(companyId: number): Promise { return Array.from(this.mentors.values()).filter( (mentor) => mentor.companyId === companyId, ); } async createMentor(insertMentor: InsertMentor): Promise { const id = this.currentMentorId++; const createdAt = new Date(); // Initialize optional fields to null if undefined const phone = insertMentor.phone || null; const birthDay = insertMentor.birthDay || null; const birthMonth = insertMentor.birthMonth || null; const birthYear = insertMentor.birthYear || null; const position = insertMentor.position || null; const experience = insertMentor.experience || null; const companyId = insertMentor.companyId || null; const mentor: Mentor = { ...insertMentor, id, createdAt, phone, birthDay, birthMonth, birthYear, position, experience, companyId }; this.mentors.set(id, mentor); return mentor; } async updateMentor(id: number, mentor: Partial): Promise { const existingMentor = this.mentors.get(id); if (!existingMentor) return undefined; const updatedMentor: Mentor = { ...existingMentor, ...mentor }; this.mentors.set(id, updatedMentor); return updatedMentor; } async deleteMentor(id: number): Promise { return this.mentors.delete(id); } // File operations async getFile(id: number): Promise { return this.files.get(id); } async getFiles(): Promise { return Array.from(this.files.values()); } async getFilesByStage(stage: PipelineStage): Promise { return Array.from(this.files.values()).filter( (file) => file.stage === stage, ); } async getFileDetails(id: number): Promise<{ file: File; apprentice: Apprentice; company: Company; mentor: Mentor; documents: Document[]; comments: Array; } | undefined> { const file = this.files.get(id); if (!file) return undefined; const apprentice = this.apprentices.get(file.apprenticeId); const company = this.companies.get(file.companyId); const mentor = this.mentors.get(file.mentorId); if (!apprentice || !company || !mentor) return undefined; const documents = Array.from(this.documents.values()).filter( (doc) => doc.fileId === id, ); const fileComments = Array.from(this.comments.values()) .filter((comment) => comment.fileId === id) .map((comment) => { const user = this.users.get(comment.userId); if (!user) throw new Error(`User not found for comment ${comment.id}`); return { ...comment, user }; }); return { file, apprentice, company, mentor, documents, comments: fileComments, }; } async createFile(insertFile: InsertFile): Promise { const id = this.currentFileId++; const now = new Date(); // Initialize optional fields to null if undefined const stage = insertFile.stage || "CREATED"; const startDate = insertFile.startDate || null; const endDate = insertFile.endDate || null; const duration = insertFile.duration || null; const salary = insertFile.salary || null; const workHours = insertFile.workHours || null; const file: File = { ...insertFile, id, createdAt: now, updatedAt: now, stage, startDate, endDate, duration, salary, workHours }; this.files.set(id, file); return file; } async updateFile(id: number, file: Partial): Promise { const existingFile = this.files.get(id); if (!existingFile) return undefined; const updatedFile: File = { ...existingFile, ...file, updatedAt: new Date() }; this.files.set(id, updatedFile); return updatedFile; } async updateFileStage(id: number, stage: PipelineStage): Promise { const existingFile = this.files.get(id); if (!existingFile) return undefined; const updatedFile: File = { ...existingFile, stage, updatedAt: new Date() }; this.files.set(id, updatedFile); return updatedFile; } async deleteFile(id: number): Promise { return this.files.delete(id); } // Document operations async getDocument(id: number): Promise { return this.documents.get(id); } async getDocuments(): Promise { return Array.from(this.documents.values()); } async getDocumentsByFile(fileId: number): Promise { return Array.from(this.documents.values()).filter( (document) => document.fileId === fileId, ); } async createDocument(insertDocument: InsertDocument): Promise { const id = this.currentDocumentId++; const uploadedAt = new Date(); // Initialize optional fields to null if undefined const extractedData = insertDocument.extractedData || null; const document: Document = { ...insertDocument, id, uploadedAt, extractedData }; this.documents.set(id, document); return document; } async updateDocument(id: number, document: Partial): Promise { const existingDocument = this.documents.get(id); if (!existingDocument) return undefined; const updatedDocument: Document = { ...existingDocument, ...document }; this.documents.set(id, updatedDocument); return updatedDocument; } async deleteDocument(id: number): Promise { return this.documents.delete(id); } // Comment operations async getComment(id: number): Promise { return this.comments.get(id); } async getCommentsByFile(fileId: number): Promise> { return Array.from(this.comments.values()) .filter((comment) => comment.fileId === fileId) .map((comment) => { const user = this.users.get(comment.userId); if (!user) throw new Error(`User not found for comment ${comment.id}`); return { ...comment, user }; }); } async createComment(insertComment: InsertComment): Promise { const id = this.currentCommentId++; const createdAt = new Date(); const comment: Comment = { ...insertComment, id, createdAt }; this.comments.set(id, comment); return comment; } async deleteComment(id: number): Promise { return this.comments.delete(id); } // Activity operations async getActivities(limit = 10): Promise> { return Array.from(this.activities.values()) .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()) .slice(0, limit) .map((activity) => { const user = this.users.get(activity.userId); if (!user) throw new Error(`User not found for activity ${activity.id}`); return { ...activity, user }; }); } async getActivitiesByFile(fileId: number): Promise> { return Array.from(this.activities.values()) .filter((activity) => activity.fileId === fileId) .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()) .map((activity) => { const user = this.users.get(activity.userId); if (!user) throw new Error(`User not found for activity ${activity.id}`); return { ...activity, user }; }); } async createActivity(insertActivity: InsertActivity): Promise { const id = this.currentActivityId++; const createdAt = new Date(); // Initialize optional fields to null if undefined const fileId = insertActivity.fileId || null; const activity: Activity = { ...insertActivity, id, createdAt, fileId }; this.activities.set(id, activity); return activity; } // Stats operations async getStats(): Promise<{ totalFiles: number; filesByStage: Record; validatedFiles: number; averageProcessingTime: number; }> { const allFiles = Array.from(this.files.values()); const totalFiles = allFiles.length; const filesByStage = allFiles.reduce((acc, file) => { const stage = file.stage as PipelineStage; acc[stage] = (acc[stage] || 0) + 1; return acc; }, {} as Record); const validatedFiles = allFiles.filter(file => file.stage === 'VALIDATED').length; // Calculate average processing time in days (for validated files) let totalDays = 0; let processedFiles = 0; for (const file of allFiles) { if (file.stage === 'VALIDATED') { const creationDate = new Date(file.createdAt); const updateDate = new Date(file.updatedAt); const diffTime = Math.abs(updateDate.getTime() - creationDate.getTime()); const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); totalDays += diffDays; processedFiles++; } } const averageProcessingTime = processedFiles > 0 ? totalDays / processedFiles : 0; return { totalFiles, filesByStage, validatedFiles, averageProcessingTime, }; } // Pipeline settings async getPipelineConfig(): Promise<{ key: string; name: string }[]> { try { // Check if the pipeline configuration exists in the database const pipelineConfig = await db.query.settings.findFirst({ where: eq(settings.key, "pipeline_config") }); if (pipelineConfig) { // If found, return the stored configuration return pipelineConfig.value as { key: string; name: string }[]; } else { // If not found, use default configuration and store it const defaultStages = [ { key: "REQUEST", name: "Demande de dossier" }, { key: "CREATED", name: "Dossier créé" }, { key: "VERIFICATION", name: "En cours de vérification" }, { key: "PROCESSING", name: "En traitement" }, { key: "VALIDATED", name: "Validé" } ]; // Store the default configuration await this.updatePipelineConfig(defaultStages); return defaultStages; } } catch (error) { console.error("Error getting pipeline config:", error); // Fallback to default stages return [ { key: "REQUEST", name: "Demande de dossier" }, { key: "CREATED", name: "Dossier créé" }, { key: "VERIFICATION", name: "En cours de vérification" }, { key: "PROCESSING", name: "En traitement" }, { key: "VALIDATED", name: "Validé" } ]; } } async updatePipelineConfig(stages: { key: string; name: string }[]): Promise { try { // Check if the pipeline configuration already exists const existingConfig = await db.query.settings.findFirst({ where: eq(settings.key, "pipeline_config") }); if (existingConfig) { // Update the existing record await db.update(settings) .set({ value: stages as any, updatedAt: new Date() }) .where(eq(settings.id, existingConfig.id)); } else { // Create a new record await db.insert(settings).values({ key: "pipeline_config", value: stages as any, updatedAt: new Date() }); } return true; } catch (error) { console.error("Error updating pipeline config:", error); return false; } } } export const storage = new MemStorage(db);