Files
carmanagement/team.ts
T
Administrator 65394f6436 project design
2026-04-30 18:28:05 +00:00

170 lines
5.1 KiB
TypeScript

import { Router, Request, Response, NextFunction } from 'express'
import { z } from 'zod'
import {
listEmployees,
inviteEmployee,
updateEmployeeRole,
deactivateEmployee,
reactivateEmployee,
removeEmployee,
} from '../services/teamService'
import { requireCompanyAuth } from '../middleware/requireCompanyAuth'
import { requireTenant } from '../middleware/requireTenant'
import { requireSubscription } from '../middleware/requireSubscription'
import { requireRole } from '../middleware/requireRole'
const router = Router()
// All team routes require a valid company employee session + active subscription
router.use(requireCompanyAuth, requireTenant, requireSubscription)
// ─── Validation schemas ───────────────────────────────────────
const inviteSchema = z.object({
firstName: z.string().min(1).max(64),
lastName: z.string().min(1).max(64),
email: z.string().email(),
role: z.enum(['MANAGER', 'AGENT']), // OWNER cannot be invited
})
const updateRoleSchema = z.object({
role: z.enum(['MANAGER', 'AGENT']),
})
// ─── GET /team ────────────────────────────────────────────────
// List all employees for this company.
// All roles can view the team list.
router.get('/', async (req: Request, res: Response, next: NextFunction) => {
try {
const members = await listEmployees(req.companyId)
res.json({ data: members })
} catch (err) {
next(err)
}
})
// ─── GET /team/stats ──────────────────────────────────────────
// Quick counts for dashboard stat cards.
router.get('/stats', async (req: Request, res: Response, next: NextFunction) => {
try {
const members = await listEmployees(req.companyId)
const stats = {
total: members.length,
active: members.filter((m) => m.isActive && m.invitationStatus === 'accepted').length,
pending: members.filter((m) => m.invitationStatus === 'pending').length,
inactive: members.filter((m) => !m.isActive && m.invitationStatus === 'accepted').length,
}
res.json({ data: stats })
} catch (err) {
next(err)
}
})
// ─── POST /team/invite ────────────────────────────────────────
// Invite a new employee by email. OWNER only.
router.post(
'/invite',
requireRole('OWNER'),
async (req: Request, res: Response, next: NextFunction) => {
try {
const body = inviteSchema.parse(req.body)
const result = await inviteEmployee(
req.companyId,
req.employee.id,
body
)
res.status(201).json({ data: result })
} catch (err) {
next(err)
}
}
)
// ─── PATCH /team/:id/role ─────────────────────────────────────
// Change an employee's role. OWNER only.
router.patch(
'/:id/role',
requireRole('OWNER'),
async (req: Request, res: Response, next: NextFunction) => {
try {
const body = updateRoleSchema.parse(req.body)
const updated = await updateEmployeeRole(
req.companyId,
req.employee.id,
req.employee.role,
req.params.id,
body
)
res.json({ data: updated })
} catch (err) {
next(err)
}
}
)
// ─── POST /team/:id/deactivate ────────────────────────────────
// Suspend access without deleting the record. OWNER only.
router.post(
'/:id/deactivate',
requireRole('OWNER'),
async (req: Request, res: Response, next: NextFunction) => {
try {
const updated = await deactivateEmployee(
req.companyId,
req.employee.role,
req.params.id
)
res.json({ data: updated })
} catch (err) {
next(err)
}
}
)
// ─── POST /team/:id/reactivate ────────────────────────────────
// Restore a deactivated employee's access. OWNER only.
router.post(
'/:id/reactivate',
requireRole('OWNER'),
async (req: Request, res: Response, next: NextFunction) => {
try {
const updated = await reactivateEmployee(
req.companyId,
req.employee.role,
req.params.id
)
res.json({ data: updated })
} catch (err) {
next(err)
}
}
)
// ─── DELETE /team/:id ─────────────────────────────────────────
// Permanently remove an employee + revoke their Clerk session/invite. OWNER only.
router.delete(
'/:id',
requireRole('OWNER'),
async (req: Request, res: Response, next: NextFunction) => {
try {
const result = await removeEmployee(
req.companyId,
req.employee.role,
req.params.id
)
res.json({ data: result })
} catch (err) {
next(err)
}
}
)
export default router