Add ESLint + Prettier + EditorConfig tooling at repo root

v1.0 Phase 1.1 — repo-wide lint/format baseline.

- eslint.config.mjs (flat config) lints server, client, shared
- .prettierrc.json, .prettierignore, .editorconfig, .nvmrc
- Root package.json holds shared devDeps; per-package scripts keep
  their typecheck + test runners
- Fix 7 lint issues surfaced by the baseline run:
  - TicketDetail.tsx: replace ternary-with-side-effects with if/else
  - admin/Users.tsx: escape apostrophe in JSX
  - errorHandler.ts: typed err as unknown with ErrorLike refinement
  - users.ts: Prisma.UserUpdateInput instead of Record<string, any>
  - seed.ts: drop unused goddard binding
- Run prettier across tracked sources for a clean formatting baseline
This commit is contained in:
2026-04-18 14:47:34 -04:00
parent 2a6090e473
commit 27d2ab0f0d
48 changed files with 14460 additions and 1096 deletions
+32 -44
View File
@@ -1,69 +1,57 @@
import { Request, Response, NextFunction } from 'express'
import jwt from 'jsonwebtoken'
import prisma from '../lib/prisma'
import { Request, Response, NextFunction } from 'express';
import jwt from 'jsonwebtoken';
import prisma from '../lib/prisma';
export interface AuthRequest extends Request {
user?: {
id: string
role: string
username: string
}
id: string;
role: string;
username: string;
};
}
export const authenticate = async (
req: AuthRequest,
res: Response,
next: NextFunction
) => {
const apiKey = req.headers['x-api-key'] as string | undefined
export const authenticate = async (req: AuthRequest, res: Response, next: NextFunction) => {
const apiKey = req.headers['x-api-key'] as string | undefined;
if (apiKey) {
const user = await prisma.user.findUnique({ where: { apiKey } })
const user = await prisma.user.findUnique({ where: { apiKey } });
if (!user || user.role !== 'SERVICE') {
return res.status(401).json({ error: 'Invalid API key' })
return res.status(401).json({ error: 'Invalid API key' });
}
req.user = { id: user.id, role: user.role, username: user.username }
return next()
req.user = { id: user.id, role: user.role, username: user.username };
return next();
}
const authHeader = req.headers.authorization
const authHeader = req.headers.authorization;
if (!authHeader?.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Unauthorized' })
return res.status(401).json({ error: 'Unauthorized' });
}
const token = authHeader.split(' ')[1]
const token = authHeader.split(' ')[1];
try {
const payload = jwt.verify(token, process.env.JWT_SECRET!) as {
id: string
role: string
username: string
}
req.user = payload
next()
id: string;
role: string;
username: string;
};
req.user = payload;
next();
} catch {
return res.status(401).json({ error: 'Invalid or expired token' })
return res.status(401).json({ error: 'Invalid or expired token' });
}
}
};
export const requireAdmin = (
req: AuthRequest,
res: Response,
next: NextFunction
) => {
export const requireAdmin = (req: AuthRequest, res: Response, next: NextFunction) => {
if (req.user?.role !== 'ADMIN') {
return res.status(403).json({ error: 'Admin access required' })
return res.status(403).json({ error: 'Admin access required' });
}
next()
}
next();
};
// Blocks USER role — allows ADMIN, AGENT, SERVICE
export const requireAgent = (
req: AuthRequest,
res: Response,
next: NextFunction
) => {
export const requireAgent = (req: AuthRequest, res: Response, next: NextFunction) => {
if (req.user?.role === 'USER') {
return res.status(403).json({ error: 'Insufficient permissions' })
return res.status(403).json({ error: 'Insufficient permissions' });
}
next()
}
next();
};
+21 -19
View File
@@ -1,29 +1,31 @@
import { Request, Response, NextFunction } from 'express'
import { ZodError } from 'zod'
import { Request, Response, NextFunction } from 'express';
import { ZodError } from 'zod';
export function errorHandler(
err: any,
req: Request,
res: Response,
next: NextFunction
) {
console.error(err)
type ErrorLike = {
code?: string;
status?: number;
statusCode?: number;
message?: string;
};
export function errorHandler(err: unknown, _req: Request, res: Response, _next: NextFunction) {
console.error(err);
if (err instanceof ZodError) {
return res.status(400).json({ error: 'Validation error', details: err.flatten() })
return res.status(400).json({ error: 'Validation error', details: err.flatten() });
}
// Prisma unique constraint violation
if (err.code === 'P2002') {
return res.status(409).json({ error: 'A record with that value already exists' })
const e = (err ?? {}) as ErrorLike;
if (e.code === 'P2002') {
return res.status(409).json({ error: 'A record with that value already exists' });
}
// Prisma record not found
if (err.code === 'P2025') {
return res.status(404).json({ error: 'Record not found' })
if (e.code === 'P2025') {
return res.status(404).json({ error: 'Record not found' });
}
const status = err.status || err.statusCode || 500
const message = err.message || 'Internal server error'
res.status(status).json({ error: message })
const status = e.status || e.statusCode || 500;
const message = e.message || 'Internal server error';
res.status(status).json({ error: message });
}