diff --git a/server/routes.ts b/server/routes.ts index a4ce06e..e4e807b 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -605,13 +605,11 @@ export async function registerRoutes(app: Express): Promise { firstName: firstName || '', lastName: lastName || '', displayName: displayName || `${firstName || ''} ${lastName || ''}`.trim(), - role: 'dj' as const, - isActive: true, - tempPassword, // Store temporarily - will be removed after first login - needsPasswordChange: true + password: tempPassword, + isTemporary: true }; - const user = await storage.createUser(userData); + const user = await storage.createUserWithPassword(userData); res.json({ message: "User created successfully", @@ -623,6 +621,54 @@ export async function registerRoutes(app: Express): Promise { } }); + // Set/change user password + app.post('/api/users/:id/set-password', isAuthenticated, isAdmin, async (req, res) => { + try { + const { password, isTemporary } = req.body; + if (!password) { + return res.status(400).json({ message: "Password is required" }); + } + await storage.setUserPassword(req.params.id, password, isTemporary || false); + res.json({ message: "Password updated successfully" }); + } catch (error) { + console.error("Error setting user password:", error); + res.status(500).json({ message: "Failed to set user password" }); + } + }); + + // Soft deactivate user (hide from scheduling but keep data) + app.post('/api/users/:id/deactivate-soft', isAuthenticated, isAdmin, async (req, res) => { + try { + await storage.deactivateUserSoft(req.params.id); + res.json({ message: "User deactivated successfully (data preserved)" }); + } catch (error) { + console.error("Error deactivating user:", error); + res.status(500).json({ message: "Failed to deactivate user" }); + } + }); + + // Hard delete user (remove user and future data) + app.delete('/api/users/:id/delete-hard', isAuthenticated, isAdmin, async (req, res) => { + try { + await storage.deleteUserHard(req.params.id); + res.json({ message: "User and associated future data deleted successfully" }); + } catch (error) { + console.error("Error deleting user:", error); + res.status(500).json({ message: "Failed to delete user" }); + } + }); + + // Get user active status + app.get('/api/users/:id/active-status', isAuthenticated, isAdmin, async (req, res) => { + try { + const isActive = await storage.getUserActiveStatus(req.params.id); + res.json({ isActive }); + } catch (error) { + console.error("Error checking user active status:", error); + res.status(500).json({ message: "Failed to check user status" }); + } + }); + // Statistics routes app.get('/api/stats/dashboard', isAuthenticated, async (req: any, res) => { try { diff --git a/server/storage.ts b/server/storage.ts index 9a34808..7d4a4f6 100644 --- a/server/storage.ts +++ b/server/storage.ts @@ -51,6 +51,11 @@ export interface IStorage { reactivateUser(id: string): Promise; makeAdmin(id: string): Promise; removeAdmin(id: string): Promise; + setUserPassword(id: string, password: string, isTemporary?: boolean): Promise; + deactivateUserSoft(id: string): Promise; + deleteUserHard(id: string): Promise; + getUserActiveStatus(id: string): Promise; + createUserWithPassword(userData: InsertUser & { password: string, isTemporary?: boolean }): Promise; // Event operations createEvent(event: InsertEvent): Promise; @@ -149,6 +154,18 @@ export class DatabaseStorage implements IStorage { return user; } + async createUserWithPassword(userData: InsertUser & { password: string, isTemporary?: boolean }): Promise { + 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 { const [user] = await db .insert(users) @@ -193,6 +210,55 @@ export class DatabaseStorage implements IStorage { 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 { + 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 { + // 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 { + // 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 { + 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 { const [newEvent] = await db.insert(events).values(event).returning(); diff --git a/shared/schema.ts b/shared/schema.ts index bb01042..aae94d7 100644 --- a/shared/schema.ts +++ b/shared/schema.ts @@ -43,6 +43,7 @@ export const users = pgTable("users", { role: userRoleEnum("role").default("dj").notNull(), isActive: boolean("is_active").default(true).notNull(), maxEventsPerMonth: integer("max_events_per_month").default(2).notNull(), + password: varchar("password"), tempPassword: varchar("temp_password"), needsPasswordChange: boolean("needs_password_change").default(false), createdAt: timestamp("created_at").defaultNow(),