add first files
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
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
|
||||
Reference in New Issue
Block a user