first commit

This commit is contained in:
Michaël
2026-01-29 14:13:11 -03:00
commit 2302748c87
105 changed files with 93301 additions and 0 deletions

224
backend/src/routes/users.ts Normal file
View File

@@ -0,0 +1,224 @@
import { Hono } from 'hono';
import { zValidator } from '@hono/zod-validator';
import { z } from 'zod';
import { db, users, tickets, events, payments } from '../db/index.js';
import { eq, desc, sql } from 'drizzle-orm';
import { requireAuth } from '../lib/auth.js';
import { getNow } from '../lib/utils.js';
interface UserContext {
id: string;
email: string;
name: string;
role: string;
}
const usersRouter = new Hono<{ Variables: { user: UserContext } }>();
const updateUserSchema = z.object({
name: z.string().min(2).optional(),
phone: z.string().optional(),
role: z.enum(['admin', 'organizer', 'staff', 'marketing', 'user']).optional(),
languagePreference: z.enum(['en', 'es']).optional(),
});
// Get all users (admin only)
usersRouter.get('/', requireAuth(['admin']), async (c) => {
const role = c.req.query('role');
let query = (db as any).select({
id: (users as any).id,
email: (users as any).email,
name: (users as any).name,
phone: (users as any).phone,
role: (users as any).role,
languagePreference: (users as any).languagePreference,
createdAt: (users as any).createdAt,
}).from(users);
if (role) {
query = query.where(eq((users as any).role, role));
}
const result = await query.orderBy(desc((users as any).createdAt)).all();
return c.json({ users: result });
});
// Get user by ID (admin or self)
usersRouter.get('/:id', requireAuth(['admin', 'organizer', 'staff', 'marketing', 'user']), async (c) => {
const id = c.req.param('id');
const currentUser = c.get('user');
// Users can only view their own profile unless admin
if (currentUser.role !== 'admin' && currentUser.id !== id) {
return c.json({ error: 'Forbidden' }, 403);
}
const user = await (db as any)
.select({
id: (users as any).id,
email: (users as any).email,
name: (users as any).name,
phone: (users as any).phone,
role: (users as any).role,
languagePreference: (users as any).languagePreference,
createdAt: (users as any).createdAt,
})
.from(users)
.where(eq((users as any).id, id))
.get();
if (!user) {
return c.json({ error: 'User not found' }, 404);
}
return c.json({ user });
});
// Update user (admin or self)
usersRouter.put('/:id', requireAuth(['admin', 'organizer', 'staff', 'marketing', 'user']), zValidator('json', updateUserSchema), async (c) => {
const id = c.req.param('id');
const data = c.req.valid('json');
const currentUser = c.get('user');
// Users can only update their own profile unless admin
if (currentUser.role !== 'admin' && currentUser.id !== id) {
return c.json({ error: 'Forbidden' }, 403);
}
// Only admin can change roles
if (data.role && currentUser.role !== 'admin') {
delete data.role;
}
const existing = await (db as any).select().from(users).where(eq((users as any).id, id)).get();
if (!existing) {
return c.json({ error: 'User not found' }, 404);
}
await (db as any)
.update(users)
.set({ ...data, updatedAt: getNow() })
.where(eq((users as any).id, id));
const updated = await (db as any)
.select({
id: (users as any).id,
email: (users as any).email,
name: (users as any).name,
phone: (users as any).phone,
role: (users as any).role,
languagePreference: (users as any).languagePreference,
})
.from(users)
.where(eq((users as any).id, id))
.get();
return c.json({ user: updated });
});
// Get user's ticket history
usersRouter.get('/:id/history', requireAuth(['admin', 'organizer', 'staff', 'marketing', 'user']), async (c) => {
const id = c.req.param('id');
const currentUser = c.get('user');
// Users can only view their own history unless admin/organizer
if (!['admin', 'organizer'].includes(currentUser.role) && currentUser.id !== id) {
return c.json({ error: 'Forbidden' }, 403);
}
const userTickets = await (db as any)
.select()
.from(tickets)
.where(eq((tickets as any).userId, id))
.orderBy(desc((tickets as any).createdAt))
.all();
// Get event details for each ticket
const history = await Promise.all(
userTickets.map(async (ticket: any) => {
const event = await (db as any)
.select()
.from(events)
.where(eq((events as any).id, ticket.eventId))
.get();
return {
...ticket,
event,
};
})
);
return c.json({ history });
});
// Delete user (admin only)
usersRouter.delete('/:id', requireAuth(['admin']), async (c) => {
const id = c.req.param('id');
const currentUser = c.get('user');
// Prevent self-deletion
if (currentUser.id === id) {
return c.json({ error: 'Cannot delete your own account' }, 400);
}
const existing = await (db as any).select().from(users).where(eq((users as any).id, id)).get();
if (!existing) {
return c.json({ error: 'User not found' }, 404);
}
// Prevent deleting admin users
if (existing.role === 'admin') {
return c.json({ error: 'Cannot delete admin users' }, 400);
}
try {
// Get all tickets for this user
const userTickets = await (db as any)
.select()
.from(tickets)
.where(eq((tickets as any).userId, id))
.all();
// Delete payments associated with user's tickets
for (const ticket of userTickets) {
await (db as any).delete(payments).where(eq((payments as any).ticketId, ticket.id));
}
// Delete user's tickets
await (db as any).delete(tickets).where(eq((tickets as any).userId, id));
// Delete the user
await (db as any).delete(users).where(eq((users as any).id, id));
return c.json({ message: 'User deleted successfully' });
} catch (error) {
console.error('Error deleting user:', error);
return c.json({ error: 'Failed to delete user. They may have related records.' }, 500);
}
});
// Get user statistics (admin)
usersRouter.get('/stats/overview', requireAuth(['admin']), async (c) => {
const totalUsers = await (db as any)
.select({ count: sql<number>`count(*)` })
.from(users)
.get();
const adminCount = await (db as any)
.select({ count: sql<number>`count(*)` })
.from(users)
.where(eq((users as any).role, 'admin'))
.get();
return c.json({
stats: {
total: totalUsers?.count || 0,
admins: adminCount?.count || 0,
},
});
});
export default usersRouter;