Files
DJ-Management-Tool/server/storage.ts
T
spliceboti 60b5735588 Add user management for activation, deactivation, and deletion
Introduce API endpoints and storage methods for setting user passwords, soft deactivation (hiding availability), and hard deletion (removing all associated future data).

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

658 lines
22 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";
export interface IStorage {
// User operations (IMPORTANT: mandatory for Replit Auth)
getUser(id: string): Promise<User | undefined>;
getUserByEmail(email: string): Promise<User | undefined>;
upsertUser(user: UpsertUser): Promise<User>;
createUser(user: InsertUser): Promise<User>;
updateUser(id: string, updates: UpdateUser): Promise<User>;
getAllDJs(): Promise<User[]>;
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>;
createUserWithPassword(userData: InsertUser & { password: string, isTemporary?: boolean }): Promise<User>;
// 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<User | undefined> {
const [user] = await db.select().from(users).where(eq(users.id, id));
return user;
}
async getUserByEmail(email: string): Promise<User | undefined> {
const [user] = await db.select().from(users).where(eq(users.email, email));
return user;
}
async createUser(userData: InsertUser): Promise<User> {
const [user] = await db.insert(users).values(userData).returning();
return user;
}
async createUserWithPassword(userData: InsertUser & { password: string, isTemporary?: boolean }): Promise<User> {
const { password, isTemporary, ...userFields } = userData;
const [user] = await db.insert(users).values({
...userFields,
password,
tempPassword: isTemporary ? password : null,
needsPasswordChange: isTemporary || false,
isActive: true // Activate immediately when password is provided
}).returning();
return user;
}
async upsertUser(userData: UpsertUser): Promise<User> {
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<User> {
const [user] = await db
.update(users)
.set({ ...updates, updatedAt: new Date() })
.where(eq(users.id, id))
.returning();
return user;
}
async getAllDJs(): Promise<User[]> {
return await db.select().from(users).where(eq(users.role, "dj")).orderBy(asc(users.displayName));
}
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> {
await db.update(users).set({
password,
tempPassword: isTemporary ? password : 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
await db.update(users).set({ isActive: false, updatedAt: new Date() }).where(eq(users.id, id));
}
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;
}
// 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();