Files
DJ-Management-Tool/server/storage.ts
T
spliceboti 4e17b6d5c3 Enhance DJ management with password reset and deactivation features
This commit introduces the ability to reset user passwords, both temporary and permanent, within the DJ management interface. It also adds soft deactivation and hard deletion options for users. The InvitationManagement component has been updated to improve clarity and user experience during the user creation process. Backend changes include password hashing using bcrypt, new API endpoints for status checks and password management, and modifications to storage functions to handle sanitized user data and password verification.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 3a22ac80-cd1d-4441-9e36-f24fc2f4c3de
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/3478f7c3-db8c-4fca-9165-3adbdf1b5829/3a22ac80-cd1d-4441-9e36-f24fc2f4c3de/Yn4xAWn
2025-09-12 21:18:07 +00:00

736 lines
25 KiB
TypeScript

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";
import * as bcrypt from "bcrypt";
// Password hashing utilities
const SALT_ROUNDS = 12;
export async function hashPassword(password: string): Promise<string> {
return await bcrypt.hash(password, SALT_ROUNDS);
}
export async function verifyPassword(password: string, hashedPassword: string): Promise<boolean> {
return await bcrypt.compare(password, hashedPassword);
}
// Safe user type without password fields
export type SafeUser = Omit<User, 'password' | 'tempPassword'>;
// Helper function to remove password fields from user objects
export function sanitizeUser(user: User): SafeUser {
const { password, tempPassword, ...safeUser } = user;
return safeUser;
}
export function sanitizeUsers(users: User[]): SafeUser[] {
return users.map(sanitizeUser);
}
export interface IStorage {
// User operations (IMPORTANT: mandatory for Replit Auth)
getUser(id: string): Promise<SafeUser | undefined>;
getUserByEmail(email: string): Promise<SafeUser | undefined>;
upsertUser(user: UpsertUser): Promise<SafeUser>;
createUser(user: InsertUser): Promise<SafeUser>;
updateUser(id: string, updates: UpdateUser): Promise<SafeUser>;
getAllDJs(): Promise<(SafeUser & { derivedActive: boolean })[]>;
deactivateUser(id: string): Promise<void>;
reactivateUser(id: string): Promise<void>;
makeAdmin(id: string): Promise<void>;
removeAdmin(id: string): Promise<void>;
setUserPassword(id: string, password: string, isTemporary?: boolean): Promise<void>;
deactivateUserSoft(id: string): Promise<void>;
deleteUserHard(id: string): Promise<void>;
getUserActiveStatus(id: string): Promise<boolean>;
getUserStatus(id: string): Promise<{ hasPassword: boolean; isActiveFlag: boolean; derivedActive: boolean; needsPasswordChange: boolean }>;
createUserWithPassword(userData: InsertUser & { password: string, isTemporary?: boolean }): Promise<SafeUser>;
verifyUserPassword(id: string, password: string): Promise<boolean>;
// Event operations
createEvent(event: InsertEvent): Promise<Event>;
getEvent(id: number): Promise<Event | undefined>;
getEventsByDJ(djId: string): Promise<Event[]>;
getUpcomingEvents(limit?: number): Promise<Event[]>;
getAllEvents(): Promise<Event[]>;
updateEvent(id: number, updates: UpdateEvent): Promise<Event>;
deleteEvent(id: number): Promise<void>;
getEventsByDateRange(startDate: string, endDate: string): Promise<Event[]>;
getEventsByMonth(year: number, month: number): Promise<Event[]>;
// Event type operations
createEventType(eventType: InsertEventType): Promise<EventType>;
getEventType(id: number): Promise<EventType | undefined>;
getAllEventTypes(): Promise<EventType[]>;
updateEventType(id: number, updates: UpdateEventType): Promise<EventType>;
deleteEventType(id: number): Promise<void>;
// Schedule template operations
createScheduleTemplate(template: InsertScheduleTemplate): Promise<ScheduleTemplate>;
getScheduleTemplate(id: number): Promise<ScheduleTemplate | undefined>;
getAllScheduleTemplates(): Promise<ScheduleTemplate[]>;
updateScheduleTemplate(id: number, updates: UpdateScheduleTemplate): Promise<ScheduleTemplate>;
deleteScheduleTemplate(id: number): Promise<void>;
// Schedule template slot operations
createScheduleTemplateSlot(slot: InsertScheduleTemplateSlot): Promise<ScheduleTemplateSlot>;
getScheduleTemplateSlots(templateId: number): Promise<ScheduleTemplateSlot[]>;
deleteScheduleTemplateSlot(id: number): Promise<void>;
// Social link operations
createSocialLink(link: InsertSocialLink): Promise<SocialLink>;
getSocialLinksByDJ(djId: string): Promise<SocialLink[]>;
updateSocialLink(id: number, updates: Partial<InsertSocialLink>): Promise<SocialLink>;
deleteSocialLink(id: number): Promise<void>;
// Availability operations
createAvailability(availability: InsertAvailability): Promise<Availability>;
getAvailabilityByDJ(djId: string): Promise<Availability[]>;
deleteAvailability(id: number): Promise<void>;
isAvailable(djId: string, date: string): Promise<boolean>;
// Slot eligibility operations
createSlotEligibility(eligibility: InsertSlotEligibility): Promise<SlotEligibility>;
getSlotEligibilityByDJ(djId: string): Promise<SlotEligibility[]>;
updateSlotEligibility(id: number, updates: Partial<InsertSlotEligibility>): Promise<SlotEligibility>;
deleteSlotEligibility(id: number): Promise<void>;
// Removal request operations
createRemovalRequest(request: InsertRemovalRequest): Promise<RemovalRequest>;
getRemovalRequest(id: number): Promise<RemovalRequest | undefined>;
getPendingRemovalRequests(): Promise<RemovalRequest[]>;
updateRemovalRequest(id: number, updates: UpdateRemovalRequest): Promise<RemovalRequest>;
approveRemovalRequest(id: number, reviewedBy: string): Promise<void>;
denyRemovalRequest(id: number, reviewedBy: string): Promise<void>;
// Invitation operations
createInvitation(invitation: InsertInvitation): Promise<Invitation>;
getInvitation(id: number): Promise<Invitation | undefined>;
getInvitationByToken(token: string): Promise<Invitation | undefined>;
updateInvitation(id: number, updates: Partial<InsertInvitation>): Promise<Invitation>;
deleteInvitation(id: number): Promise<void>;
markInvitationAsUsed(id: number): Promise<void>;
getActiveInvitations(): Promise<Invitation[]>;
// 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<SafeUser | undefined> {
const [user] = await db.select().from(users).where(eq(users.id, id));
return user ? sanitizeUser(user) : undefined;
}
async getUserByEmail(email: string): Promise<SafeUser | undefined> {
const [user] = await db.select().from(users).where(eq(users.email, email));
return user ? sanitizeUser(user) : undefined;
}
async createUser(userData: InsertUser): Promise<SafeUser> {
const [user] = await db.insert(users).values(userData).returning();
return sanitizeUser(user);
}
async createUserWithPassword(userData: InsertUser & { password: string, isTemporary?: boolean }): Promise<SafeUser> {
const { password, isTemporary, ...userFields } = userData;
const hashedPassword = await hashPassword(password);
const [user] = await db.insert(users).values({
...userFields,
password: hashedPassword,
tempPassword: isTemporary ? hashedPassword : null,
needsPasswordChange: isTemporary || false,
isActive: true // Activate immediately when password is provided
}).returning();
return sanitizeUser(user);
}
async upsertUser(userData: UpsertUser): Promise<SafeUser> {
const [user] = await db
.insert(users)
.values(userData)
.onConflictDoUpdate({
target: users.id,
set: {
...userData,
updatedAt: new Date(),
},
})
.returning();
return sanitizeUser(user);
}
async updateUser(id: string, updates: UpdateUser): Promise<SafeUser> {
const [user] = await db
.update(users)
.set({ ...updates, updatedAt: new Date() })
.where(eq(users.id, id))
.returning();
return sanitizeUser(user);
}
async getAllDJs(): Promise<(SafeUser & { derivedActive: boolean })[]> {
const users_list = await db.select().from(users).where(eq(users.role, "dj")).orderBy(asc(users.displayName));
// Add derived active status based on password presence while sanitizing
return users_list.map(user => {
const sanitizedUser = sanitizeUser(user);
return {
...sanitizedUser,
derivedActive: user.isActive && !!user.password
};
});
}
async deactivateUser(id: string): Promise<void> {
await db.update(users).set({ isActive: false, updatedAt: new Date() }).where(eq(users.id, id));
}
async reactivateUser(id: string): Promise<void> {
await db.update(users).set({ isActive: true, updatedAt: new Date() }).where(eq(users.id, id));
}
async makeAdmin(id: string): Promise<void> {
await db.update(users).set({ role: "admin", updatedAt: new Date() }).where(eq(users.id, id));
}
async removeAdmin(id: string): Promise<void> {
await db.update(users).set({ role: "dj", updatedAt: new Date() }).where(eq(users.id, id));
}
async setUserPassword(id: string, password: string, isTemporary: boolean = false): Promise<void> {
const hashedPassword = await hashPassword(password);
await db.update(users).set({
password: hashedPassword,
tempPassword: isTemporary ? hashedPassword : null,
needsPasswordChange: isTemporary,
isActive: true, // Activate user when password is set
updatedAt: new Date()
}).where(eq(users.id, id));
}
async deactivateUserSoft(id: string): Promise<void> {
// Soft deactivation - hide from availability/scheduling but keep all data
console.log(`[DEBUG] Attempting to deactivate user with id: ${id}`);
const result = await db.update(users).set({ isActive: false, updatedAt: new Date() }).where(eq(users.id, id));
console.log(`[DEBUG] Deactivation result:`, result);
// Verify the update by reading the user back
const [updatedUser] = await db.select({ id: users.id, isActive: users.isActive }).from(users).where(eq(users.id, id));
console.log(`[DEBUG] User after update:`, updatedUser);
}
async deleteUserHard(id: string): Promise<void> {
// Hard deletion - remove user and all future schedules/availability
const currentDate = new Date().toISOString().split('T')[0];
// Delete future availability
await db.delete(availability)
.where(and(eq(availability.djId, id), gte(availability.startDate, currentDate)));
// Delete future events (keep past events for historical purposes)
await db.delete(events)
.where(and(eq(events.djId, id), gte(events.date, currentDate)));
// Delete slot eligibility
await db.delete(slotEligibility).where(eq(slotEligibility.djId, id));
// Delete social links
await db.delete(socialLinks).where(eq(socialLinks.djId, id));
// Delete removal requests
await db.delete(removalRequests).where(eq(removalRequests.djId, id));
// Finally delete the user
await db.delete(users).where(eq(users.id, id));
}
async getUserActiveStatus(id: string): Promise<boolean> {
const [user] = await db.select({ isActive: users.isActive, password: users.password })
.from(users)
.where(eq(users.id, id));
// User is considered active if they have isActive=true AND have a password set
return user ? user.isActive && !!user.password : false;
}
async getUserStatus(id: string): Promise<{ hasPassword: boolean; isActiveFlag: boolean; derivedActive: boolean; needsPasswordChange: boolean }> {
const [user] = await db.select({
isActive: users.isActive,
password: users.password,
needsPasswordChange: users.needsPasswordChange
})
.from(users)
.where(eq(users.id, id));
if (!user) {
return { hasPassword: false, isActiveFlag: false, derivedActive: false, needsPasswordChange: false };
}
const hasPassword = !!user.password;
const isActiveFlag = user.isActive;
const derivedActive = isActiveFlag && hasPassword;
const needsPasswordChange = user.needsPasswordChange || false;
return { hasPassword, isActiveFlag, derivedActive, needsPasswordChange };
}
async verifyUserPassword(id: string, password: string): Promise<boolean> {
const [user] = await db.select({ password: users.password })
.from(users)
.where(eq(users.id, id));
if (!user?.password) {
return false;
}
return await verifyPassword(password, user.password);
}
// Event operations
async createEvent(event: InsertEvent): Promise<Event> {
const [newEvent] = await db.insert(events).values(event).returning();
return newEvent;
}
async getEvent(id: number): Promise<Event | undefined> {
const [event] = await db.select().from(events).where(eq(events.id, id));
return event;
}
async getEventsByDJ(djId: string): Promise<Event[]> {
return await db
.select()
.from(events)
.where(eq(events.djId, djId))
.orderBy(asc(events.date), asc(events.startTime));
}
async getUpcomingEvents(limit = 50): Promise<Event[]> {
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<Event[]> {
return await db
.select()
.from(events)
.orderBy(desc(events.date), desc(events.startTime));
}
async updateEvent(id: number, updates: UpdateEvent): Promise<Event> {
const [event] = await db
.update(events)
.set({ ...updates, updatedAt: new Date() })
.where(eq(events.id, id))
.returning();
return event;
}
async deleteEvent(id: number): Promise<void> {
await db.delete(events).where(eq(events.id, id));
}
async getEventsByDateRange(startDate: string, endDate: string): Promise<Event[]> {
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<Event[]> {
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<EventType> {
const [newEventType] = await db.insert(eventTypes).values(eventType).returning();
return newEventType;
}
async getEventType(id: number): Promise<EventType | undefined> {
const [eventType] = await db.select().from(eventTypes).where(eq(eventTypes.id, id));
return eventType;
}
async getAllEventTypes(): Promise<EventType[]> {
return await db.select().from(eventTypes).where(eq(eventTypes.isActive, true)).orderBy(asc(eventTypes.name));
}
async updateEventType(id: number, updates: UpdateEventType): Promise<EventType> {
const [eventType] = await db
.update(eventTypes)
.set(updates)
.where(eq(eventTypes.id, id))
.returning();
return eventType;
}
async deleteEventType(id: number): Promise<void> {
await db.update(eventTypes).set({ isActive: false }).where(eq(eventTypes.id, id));
}
// Schedule template operations
async createScheduleTemplate(template: InsertScheduleTemplate): Promise<ScheduleTemplate> {
const [newTemplate] = await db.insert(scheduleTemplates).values(template).returning();
return newTemplate;
}
async getScheduleTemplate(id: number): Promise<ScheduleTemplate | undefined> {
const [template] = await db.select().from(scheduleTemplates).where(eq(scheduleTemplates.id, id));
return template;
}
async getAllScheduleTemplates(): Promise<ScheduleTemplate[]> {
return await db.select().from(scheduleTemplates).where(eq(scheduleTemplates.isActive, true)).orderBy(asc(scheduleTemplates.name));
}
async updateScheduleTemplate(id: number, updates: UpdateScheduleTemplate): Promise<ScheduleTemplate> {
const [template] = await db
.update(scheduleTemplates)
.set(updates)
.where(eq(scheduleTemplates.id, id))
.returning();
return template;
}
async deleteScheduleTemplate(id: number): Promise<void> {
await db.update(scheduleTemplates).set({ isActive: false }).where(eq(scheduleTemplates.id, id));
}
// Schedule template slot operations
async createScheduleTemplateSlot(slot: InsertScheduleTemplateSlot): Promise<ScheduleTemplateSlot> {
const [newSlot] = await db.insert(scheduleTemplateSlots).values(slot).returning();
return newSlot;
}
async getScheduleTemplateSlots(templateId: number): Promise<ScheduleTemplateSlot[]> {
return await db
.select()
.from(scheduleTemplateSlots)
.where(eq(scheduleTemplateSlots.templateId, templateId))
.orderBy(asc(scheduleTemplateSlots.dayOfWeek), asc(scheduleTemplateSlots.startTime));
}
async deleteScheduleTemplateSlot(id: number): Promise<void> {
await db.delete(scheduleTemplateSlots).where(eq(scheduleTemplateSlots.id, id));
}
// Social link operations
async createSocialLink(link: InsertSocialLink): Promise<SocialLink> {
const [newLink] = await db.insert(socialLinks).values(link).returning();
return newLink;
}
async getSocialLinksByDJ(djId: string): Promise<SocialLink[]> {
return await db.select().from(socialLinks).where(eq(socialLinks.djId, djId));
}
async updateSocialLink(id: number, updates: Partial<InsertSocialLink>): Promise<SocialLink> {
const [link] = await db
.update(socialLinks)
.set(updates)
.where(eq(socialLinks.id, id))
.returning();
return link;
}
async deleteSocialLink(id: number): Promise<void> {
await db.delete(socialLinks).where(eq(socialLinks.id, id));
}
// Availability operations
async createAvailability(availabilityData: InsertAvailability): Promise<Availability> {
const [newAvailability] = await db.insert(availability).values(availabilityData).returning();
return newAvailability;
}
async getAvailabilityByDJ(djId: string): Promise<Availability[]> {
return await db
.select()
.from(availability)
.where(eq(availability.djId, djId))
.orderBy(asc(availability.startDate));
}
async deleteAvailability(id: number): Promise<void> {
await db.delete(availability).where(eq(availability.id, id));
}
async isAvailable(djId: string, date: string): Promise<boolean> {
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<SlotEligibility> {
const [newEligibility] = await db.insert(slotEligibility).values(eligibility).returning();
return newEligibility;
}
async getSlotEligibilityByDJ(djId: string): Promise<SlotEligibility[]> {
return await db.select().from(slotEligibility).where(eq(slotEligibility.djId, djId));
}
async updateSlotEligibility(id: number, updates: Partial<InsertSlotEligibility>): Promise<SlotEligibility> {
const [eligibility] = await db
.update(slotEligibility)
.set(updates)
.where(eq(slotEligibility.id, id))
.returning();
return eligibility;
}
async deleteSlotEligibility(id: number): Promise<void> {
await db.delete(slotEligibility).where(eq(slotEligibility.id, id));
}
// Removal request operations
async createRemovalRequest(request: InsertRemovalRequest): Promise<RemovalRequest> {
const [newRequest] = await db.insert(removalRequests).values(request).returning();
return newRequest;
}
async getRemovalRequest(id: number): Promise<RemovalRequest | undefined> {
const [request] = await db.select().from(removalRequests).where(eq(removalRequests.id, id));
return request;
}
async getPendingRemovalRequests(): Promise<RemovalRequest[]> {
return await db
.select()
.from(removalRequests)
.where(eq(removalRequests.status, "pending"))
.orderBy(asc(removalRequests.createdAt));
}
async updateRemovalRequest(id: number, updates: UpdateRemovalRequest): Promise<RemovalRequest> {
const [request] = await db
.update(removalRequests)
.set(updates)
.where(eq(removalRequests.id, id))
.returning();
return request;
}
async approveRemovalRequest(id: number, reviewedBy: string): Promise<void> {
await db
.update(removalRequests)
.set({
status: "approved",
reviewedBy,
reviewedAt: new Date(),
})
.where(eq(removalRequests.id, id));
}
async denyRemovalRequest(id: number, reviewedBy: string): Promise<void> {
await db
.update(removalRequests)
.set({
status: "denied",
reviewedBy,
reviewedAt: new Date(),
})
.where(eq(removalRequests.id, id));
}
// Invitation operations
async createInvitation(invitation: InsertInvitation): Promise<Invitation> {
const [newInvitation] = await db.insert(invitations).values(invitation).returning();
return newInvitation;
}
async getInvitation(id: number): Promise<Invitation | undefined> {
const [invitation] = await db.select().from(invitations).where(eq(invitations.id, id));
return invitation;
}
async getInvitationByToken(token: string): Promise<Invitation | undefined> {
const [invitation] = await db.select().from(invitations).where(eq(invitations.token, token));
return invitation;
}
async updateInvitation(id: number, updates: Partial<InsertInvitation>): Promise<Invitation> {
const [invitation] = await db
.update(invitations)
.set(updates)
.where(eq(invitations.id, id))
.returning();
return invitation;
}
async deleteInvitation(id: number): Promise<void> {
await db.delete(invitations).where(eq(invitations.id, id));
}
async markInvitationAsUsed(id: number): Promise<void> {
await db.update(invitations).set({ isUsed: true }).where(eq(invitations.id, id));
}
async getActiveInvitations(): Promise<Invitation[]> {
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<number>`count(*)` })
.from(events)
.where(and(eq(events.djId, djId), gte(events.date, today)));
const [thisMonth] = await db
.select({ count: sql<number>`count(*)` })
.from(events)
.where(
and(
eq(events.djId, djId),
gte(events.date, monthStart),
lte(events.date, monthEnd)
)
);
const [pendingRequests] = await db
.select({ count: sql<number>`count(*)` })
.from(removalRequests)
.where(and(eq(removalRequests.djId, djId), eq(removalRequests.status, "pending")));
const [totalEvents] = await db
.select({ count: sql<number>`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<number>`count(*)` })
.from(users)
.where(and(eq(users.role, "dj"), eq(users.isActive, true)));
const [activeEvents] = await db
.select({ count: sql<number>`count(*)` })
.from(events)
.where(gte(events.date, today));
const [pendingRequests] = await db
.select({ count: sql<number>`count(*)` })
.from(removalRequests)
.where(eq(removalRequests.status, "pending"));
const [thisMonth] = await db
.select({ count: sql<number>`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();