import { users, events, eventTypes, scheduleTemplates, scheduleTemplateSlots, socialLinks, availability, slotEligibility, removalRequests, invitations, type User, type UpsertUser, type InsertUser, type UpdateUser, type Event, type InsertEvent, type UpdateEvent, type EventType, type InsertEventType, type UpdateEventType, type ScheduleTemplate, type InsertScheduleTemplate, type UpdateScheduleTemplate, type ScheduleTemplateSlot, type InsertScheduleTemplateSlot, type SocialLink, type InsertSocialLink, type Availability, type InsertAvailability, type SlotEligibility, type InsertSlotEligibility, type RemovalRequest, type InsertRemovalRequest, type UpdateRemovalRequest, type Invitation, type InsertInvitation, } from "@shared/schema"; import { db } from "./db"; import { eq, and, gte, lte, desc, asc, sql, inArray } from "drizzle-orm"; export interface IStorage { // User operations (IMPORTANT: mandatory for Replit Auth) getUser(id: string): Promise; upsertUser(user: UpsertUser): Promise; updateUser(id: string, updates: UpdateUser): Promise; getAllDJs(): Promise; deactivateUser(id: string): Promise; reactivateUser(id: string): Promise; makeAdmin(id: string): Promise; removeAdmin(id: string): Promise; // Event operations createEvent(event: InsertEvent): Promise; getEvent(id: number): Promise; getEventsByDJ(djId: string): Promise; getUpcomingEvents(limit?: number): Promise; getAllEvents(): Promise; updateEvent(id: number, updates: UpdateEvent): Promise; deleteEvent(id: number): Promise; getEventsByDateRange(startDate: string, endDate: string): Promise; getEventsByMonth(year: number, month: number): Promise; // Event type operations createEventType(eventType: InsertEventType): Promise; getEventType(id: number): Promise; getAllEventTypes(): Promise; updateEventType(id: number, updates: UpdateEventType): Promise; deleteEventType(id: number): Promise; // Schedule template operations createScheduleTemplate(template: InsertScheduleTemplate): Promise; getScheduleTemplate(id: number): Promise; getAllScheduleTemplates(): Promise; updateScheduleTemplate(id: number, updates: UpdateScheduleTemplate): Promise; deleteScheduleTemplate(id: number): Promise; // Schedule template slot operations createScheduleTemplateSlot(slot: InsertScheduleTemplateSlot): Promise; getScheduleTemplateSlots(templateId: number): Promise; deleteScheduleTemplateSlot(id: number): Promise; // Social link operations createSocialLink(link: InsertSocialLink): Promise; getSocialLinksByDJ(djId: string): Promise; updateSocialLink(id: number, updates: Partial): Promise; deleteSocialLink(id: number): Promise; // Availability operations createAvailability(availability: InsertAvailability): Promise; getAvailabilityByDJ(djId: string): Promise; deleteAvailability(id: number): Promise; isAvailable(djId: string, date: string): Promise; // Slot eligibility operations createSlotEligibility(eligibility: InsertSlotEligibility): Promise; getSlotEligibilityByDJ(djId: string): Promise; updateSlotEligibility(id: number, updates: Partial): Promise; deleteSlotEligibility(id: number): Promise; // Removal request operations createRemovalRequest(request: InsertRemovalRequest): Promise; getRemovalRequest(id: number): Promise; getPendingRemovalRequests(): Promise; updateRemovalRequest(id: number, updates: UpdateRemovalRequest): Promise; approveRemovalRequest(id: number, reviewedBy: string): Promise; denyRemovalRequest(id: number, reviewedBy: string): Promise; // Invitation operations createInvitation(invitation: InsertInvitation): Promise; getInvitationByToken(token: string): Promise; markInvitationAsUsed(id: number): Promise; getActiveInvitations(): Promise; // Statistics getDashboardStats(djId: string): Promise<{ upcomingEvents: number; thisMonth: number; pendingRequests: number; totalEvents: number; }>; getAdminStats(): Promise<{ totalDJs: number; activeEvents: number; pendingRequests: number; thisMonth: number; }>; } export class DatabaseStorage implements IStorage { // User operations async getUser(id: string): Promise { const [user] = await db.select().from(users).where(eq(users.id, id)); return user; } async upsertUser(userData: UpsertUser): Promise { const [user] = await db .insert(users) .values(userData) .onConflictDoUpdate({ target: users.id, set: { ...userData, updatedAt: new Date(), }, }) .returning(); return user; } async updateUser(id: string, updates: UpdateUser): Promise { const [user] = await db .update(users) .set({ ...updates, updatedAt: new Date() }) .where(eq(users.id, id)) .returning(); return user; } async getAllDJs(): Promise { return await db.select().from(users).where(eq(users.role, "dj")).orderBy(asc(users.displayName)); } async deactivateUser(id: string): Promise { await db.update(users).set({ isActive: false, updatedAt: new Date() }).where(eq(users.id, id)); } async reactivateUser(id: string): Promise { await db.update(users).set({ isActive: true, updatedAt: new Date() }).where(eq(users.id, id)); } async makeAdmin(id: string): Promise { await db.update(users).set({ role: "admin", updatedAt: new Date() }).where(eq(users.id, id)); } async removeAdmin(id: string): Promise { await db.update(users).set({ role: "dj", updatedAt: new Date() }).where(eq(users.id, id)); } // Event operations async createEvent(event: InsertEvent): Promise { const [newEvent] = await db.insert(events).values(event).returning(); return newEvent; } async getEvent(id: number): Promise { const [event] = await db.select().from(events).where(eq(events.id, id)); return event; } async getEventsByDJ(djId: string): Promise { return await db .select() .from(events) .where(eq(events.djId, djId)) .orderBy(asc(events.date), asc(events.startTime)); } async getUpcomingEvents(limit = 50): Promise { const today = new Date().toISOString().split('T')[0]; return await db .select() .from(events) .where(gte(events.date, today)) .orderBy(asc(events.date), asc(events.startTime)) .limit(limit); } async getAllEvents(): Promise { return await db .select() .from(events) .orderBy(desc(events.date), desc(events.startTime)); } async updateEvent(id: number, updates: UpdateEvent): Promise { const [event] = await db .update(events) .set({ ...updates, updatedAt: new Date() }) .where(eq(events.id, id)) .returning(); return event; } async deleteEvent(id: number): Promise { await db.delete(events).where(eq(events.id, id)); } async getEventsByDateRange(startDate: string, endDate: string): Promise { return await db .select() .from(events) .where(and(gte(events.date, startDate), lte(events.date, endDate))) .orderBy(asc(events.date), asc(events.startTime)); } async getEventsByMonth(year: number, month: number): Promise { const startDate = `${year}-${month.toString().padStart(2, '0')}-01`; const endDate = `${year}-${month.toString().padStart(2, '0')}-31`; return await this.getEventsByDateRange(startDate, endDate); } // Event type operations async createEventType(eventType: InsertEventType): Promise { const [newEventType] = await db.insert(eventTypes).values(eventType).returning(); return newEventType; } async getEventType(id: number): Promise { const [eventType] = await db.select().from(eventTypes).where(eq(eventTypes.id, id)); return eventType; } async getAllEventTypes(): Promise { return await db.select().from(eventTypes).where(eq(eventTypes.isActive, true)).orderBy(asc(eventTypes.name)); } async updateEventType(id: number, updates: UpdateEventType): Promise { const [eventType] = await db .update(eventTypes) .set(updates) .where(eq(eventTypes.id, id)) .returning(); return eventType; } async deleteEventType(id: number): Promise { await db.update(eventTypes).set({ isActive: false }).where(eq(eventTypes.id, id)); } // Schedule template operations async createScheduleTemplate(template: InsertScheduleTemplate): Promise { const [newTemplate] = await db.insert(scheduleTemplates).values(template).returning(); return newTemplate; } async getScheduleTemplate(id: number): Promise { const [template] = await db.select().from(scheduleTemplates).where(eq(scheduleTemplates.id, id)); return template; } async getAllScheduleTemplates(): Promise { return await db.select().from(scheduleTemplates).where(eq(scheduleTemplates.isActive, true)).orderBy(asc(scheduleTemplates.name)); } async updateScheduleTemplate(id: number, updates: UpdateScheduleTemplate): Promise { const [template] = await db .update(scheduleTemplates) .set(updates) .where(eq(scheduleTemplates.id, id)) .returning(); return template; } async deleteScheduleTemplate(id: number): Promise { await db.update(scheduleTemplates).set({ isActive: false }).where(eq(scheduleTemplates.id, id)); } // Schedule template slot operations async createScheduleTemplateSlot(slot: InsertScheduleTemplateSlot): Promise { const [newSlot] = await db.insert(scheduleTemplateSlots).values(slot).returning(); return newSlot; } async getScheduleTemplateSlots(templateId: number): Promise { return await db .select() .from(scheduleTemplateSlots) .where(eq(scheduleTemplateSlots.templateId, templateId)) .orderBy(asc(scheduleTemplateSlots.dayOfWeek), asc(scheduleTemplateSlots.startTime)); } async deleteScheduleTemplateSlot(id: number): Promise { await db.delete(scheduleTemplateSlots).where(eq(scheduleTemplateSlots.id, id)); } // Social link operations async createSocialLink(link: InsertSocialLink): Promise { const [newLink] = await db.insert(socialLinks).values(link).returning(); return newLink; } async getSocialLinksByDJ(djId: string): Promise { return await db.select().from(socialLinks).where(eq(socialLinks.djId, djId)); } async updateSocialLink(id: number, updates: Partial): Promise { const [link] = await db .update(socialLinks) .set(updates) .where(eq(socialLinks.id, id)) .returning(); return link; } async deleteSocialLink(id: number): Promise { await db.delete(socialLinks).where(eq(socialLinks.id, id)); } // Availability operations async createAvailability(availabilityData: InsertAvailability): Promise { const [newAvailability] = await db.insert(availability).values(availabilityData).returning(); return newAvailability; } async getAvailabilityByDJ(djId: string): Promise { return await db .select() .from(availability) .where(eq(availability.djId, djId)) .orderBy(asc(availability.startDate)); } async deleteAvailability(id: number): Promise { await db.delete(availability).where(eq(availability.id, id)); } async isAvailable(djId: string, date: string): Promise { const [unavailable] = await db .select() .from(availability) .where( and( eq(availability.djId, djId), lte(availability.startDate, date), gte(availability.endDate, date) ) ) .limit(1); return !unavailable; } // Slot eligibility operations async createSlotEligibility(eligibility: InsertSlotEligibility): Promise { const [newEligibility] = await db.insert(slotEligibility).values(eligibility).returning(); return newEligibility; } async getSlotEligibilityByDJ(djId: string): Promise { return await db.select().from(slotEligibility).where(eq(slotEligibility.djId, djId)); } async updateSlotEligibility(id: number, updates: Partial): Promise { const [eligibility] = await db .update(slotEligibility) .set(updates) .where(eq(slotEligibility.id, id)) .returning(); return eligibility; } async deleteSlotEligibility(id: number): Promise { await db.delete(slotEligibility).where(eq(slotEligibility.id, id)); } // Removal request operations async createRemovalRequest(request: InsertRemovalRequest): Promise { const [newRequest] = await db.insert(removalRequests).values(request).returning(); return newRequest; } async getRemovalRequest(id: number): Promise { const [request] = await db.select().from(removalRequests).where(eq(removalRequests.id, id)); return request; } async getPendingRemovalRequests(): Promise { return await db .select() .from(removalRequests) .where(eq(removalRequests.status, "pending")) .orderBy(asc(removalRequests.createdAt)); } async updateRemovalRequest(id: number, updates: UpdateRemovalRequest): Promise { const [request] = await db .update(removalRequests) .set(updates) .where(eq(removalRequests.id, id)) .returning(); return request; } async approveRemovalRequest(id: number, reviewedBy: string): Promise { await db .update(removalRequests) .set({ status: "approved", reviewedBy, reviewedAt: new Date(), }) .where(eq(removalRequests.id, id)); } async denyRemovalRequest(id: number, reviewedBy: string): Promise { await db .update(removalRequests) .set({ status: "denied", reviewedBy, reviewedAt: new Date(), }) .where(eq(removalRequests.id, id)); } // Invitation operations async createInvitation(invitation: InsertInvitation): Promise { const [newInvitation] = await db.insert(invitations).values(invitation).returning(); return newInvitation; } async getInvitationByToken(token: string): Promise { const [invitation] = await db.select().from(invitations).where(eq(invitations.token, token)); return invitation; } async markInvitationAsUsed(id: number): Promise { await db.update(invitations).set({ isUsed: true }).where(eq(invitations.id, id)); } async getActiveInvitations(): Promise { return await db .select() .from(invitations) .where(and(eq(invitations.isUsed, false), gte(invitations.expiresAt, new Date()))) .orderBy(desc(invitations.createdAt)); } // Statistics async getDashboardStats(djId: string): Promise<{ upcomingEvents: number; thisMonth: number; pendingRequests: number; totalEvents: number; }> { const today = new Date().toISOString().split('T')[0]; const currentMonth = new Date().getMonth() + 1; const currentYear = new Date().getFullYear(); const monthStart = `${currentYear}-${currentMonth.toString().padStart(2, '0')}-01`; const monthEnd = `${currentYear}-${currentMonth.toString().padStart(2, '0')}-31`; const [upcomingEvents] = await db .select({ count: sql`count(*)` }) .from(events) .where(and(eq(events.djId, djId), gte(events.date, today))); const [thisMonth] = await db .select({ count: sql`count(*)` }) .from(events) .where( and( eq(events.djId, djId), gte(events.date, monthStart), lte(events.date, monthEnd) ) ); const [pendingRequests] = await db .select({ count: sql`count(*)` }) .from(removalRequests) .where(and(eq(removalRequests.djId, djId), eq(removalRequests.status, "pending"))); const [totalEvents] = await db .select({ count: sql`count(*)` }) .from(events) .where(eq(events.djId, djId)); return { upcomingEvents: upcomingEvents.count, thisMonth: thisMonth.count, pendingRequests: pendingRequests.count, totalEvents: totalEvents.count, }; } async getAdminStats(): Promise<{ totalDJs: number; activeEvents: number; pendingRequests: number; thisMonth: number; }> { const today = new Date().toISOString().split('T')[0]; const currentMonth = new Date().getMonth() + 1; const currentYear = new Date().getFullYear(); const monthStart = `${currentYear}-${currentMonth.toString().padStart(2, '0')}-01`; const monthEnd = `${currentYear}-${currentMonth.toString().padStart(2, '0')}-31`; const [totalDJs] = await db .select({ count: sql`count(*)` }) .from(users) .where(and(eq(users.role, "dj"), eq(users.isActive, true))); const [activeEvents] = await db .select({ count: sql`count(*)` }) .from(events) .where(gte(events.date, today)); const [pendingRequests] = await db .select({ count: sql`count(*)` }) .from(removalRequests) .where(eq(removalRequests.status, "pending")); const [thisMonth] = await db .select({ count: sql`count(*)` }) .from(events) .where(and(gte(events.date, monthStart), lte(events.date, monthEnd))); return { totalDJs: totalDJs.count, activeEvents: activeEvents.count, pendingRequests: pendingRequests.count, thisMonth: thisMonth.count, }; } } export const storage = new DatabaseStorage();