Provide admin tools to manage and resend DJ invitations easily
Implements invitation management features, including resending and manual user creation, via new API endpoints and React components. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 3a22ac80-cd1d-4441-9e36-f24fc2f4c3de Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/3478f7c3-db8c-4fca-9165-3adbdf1b5829/dbb6f90a-b277-4de1-a273-07d2fc2d56d1.jpg
This commit is contained in:
@@ -539,6 +539,90 @@ export async function registerRoutes(app: Express): Promise<Server> {
|
||||
}
|
||||
});
|
||||
|
||||
// Resend invitation
|
||||
app.post('/api/invitations/:id/resend', isAuthenticated, isAdmin, async (req, res) => {
|
||||
try {
|
||||
const invitationId = parseInt(req.params.id);
|
||||
const invitation = await storage.getInvitation(invitationId);
|
||||
|
||||
if (!invitation) {
|
||||
return res.status(404).json({ message: "Invitation not found" });
|
||||
}
|
||||
|
||||
// Generate new token and extend expiration
|
||||
const newToken = crypto.randomBytes(32).toString('hex');
|
||||
const newExpiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000); // 7 days
|
||||
|
||||
const updatedInvitation = await storage.updateInvitation(invitationId, {
|
||||
token: newToken,
|
||||
expiresAt: newExpiresAt,
|
||||
isUsed: false
|
||||
});
|
||||
|
||||
res.json({
|
||||
message: "Invitation resent successfully",
|
||||
invitation: updatedInvitation
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error resending invitation:", error);
|
||||
res.status(500).json({ message: "Failed to resend invitation" });
|
||||
}
|
||||
});
|
||||
|
||||
// Delete invitation
|
||||
app.delete('/api/invitations/:id', isAuthenticated, isAdmin, async (req, res) => {
|
||||
try {
|
||||
const invitationId = parseInt(req.params.id);
|
||||
await storage.deleteInvitation(invitationId);
|
||||
res.json({ message: "Invitation deleted successfully" });
|
||||
} catch (error) {
|
||||
console.error("Error deleting invitation:", error);
|
||||
res.status(500).json({ message: "Failed to delete invitation" });
|
||||
}
|
||||
});
|
||||
|
||||
// Manual activation - create user directly
|
||||
app.post('/api/users/create-manual', isAuthenticated, isAdmin, async (req, res) => {
|
||||
try {
|
||||
const { email, firstName, lastName, displayName, tempPassword } = req.body;
|
||||
|
||||
if (!email || !tempPassword) {
|
||||
return res.status(400).json({ message: "Email and temporary password are required" });
|
||||
}
|
||||
|
||||
// Check if user already exists
|
||||
const existingUser = await storage.getUserByEmail(email);
|
||||
if (existingUser) {
|
||||
return res.status(409).json({ message: "User with this email already exists" });
|
||||
}
|
||||
|
||||
// Create user with temporary ID (will be replaced when they log in via Replit Auth)
|
||||
const tempUserId = `temp_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
|
||||
const userData = {
|
||||
id: tempUserId,
|
||||
email,
|
||||
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
|
||||
};
|
||||
|
||||
const user = await storage.createUser(userData);
|
||||
|
||||
res.json({
|
||||
message: "User created successfully",
|
||||
user: { ...user, tempPassword: undefined } // Don't send password back
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Error creating user manually:", error);
|
||||
res.status(500).json({ message: "Failed to create user manually" });
|
||||
}
|
||||
});
|
||||
|
||||
// Statistics routes
|
||||
app.get('/api/stats/dashboard', isAuthenticated, async (req: any, res) => {
|
||||
try {
|
||||
|
||||
@@ -42,7 +42,9 @@ 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>;
|
||||
@@ -108,7 +110,10 @@ export interface IStorage {
|
||||
|
||||
// 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[]>;
|
||||
|
||||
@@ -134,6 +139,16 @@ export class DatabaseStorage implements IStorage {
|
||||
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 upsertUser(userData: UpsertUser): Promise<User> {
|
||||
const [user] = await db
|
||||
.insert(users)
|
||||
@@ -450,11 +465,29 @@ export class DatabaseStorage implements IStorage {
|
||||
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));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user