Replaces direct Supabase client instantiation with a unified authentication and authorization helper, introducing role-based access control to sensitive endpoints like escrow and payroll, and standardizing compliance event logging. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 9203795e-937a-4306-b81d-b4d5c78c240e Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Event-Id: 5eb35c62-c5ab-4c7e-9552-8dc89efa29f3 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/7c94b7a0-29c7-4f2e-94ef-44b2153872b7/9203795e-937a-4306-b81d-b4d5c78c240e/aPpJgbb Replit-Helium-Checkpoint-Created: true
87 lines
2.8 KiB
TypeScript
87 lines
2.8 KiB
TypeScript
import type { VercelRequest, VercelResponse } from "@vercel/node";
|
|
import { authenticateRequest, requireAuth, logComplianceEvent } from "../_auth";
|
|
|
|
export default async function handler(req: VercelRequest, res: VercelResponse) {
|
|
if (req.method !== 'POST') {
|
|
return res.status(405).json({ error: 'Method not allowed' });
|
|
}
|
|
|
|
const auth = await authenticateRequest(req);
|
|
if (!requireAuth(auth, res)) return;
|
|
|
|
const { userClient, adminClient, user } = auth;
|
|
|
|
const { time_log_id, decision, notes } = req.body;
|
|
|
|
if (!time_log_id || !decision) {
|
|
return res.status(400).json({ error: 'time_log_id and decision required' });
|
|
}
|
|
|
|
if (!['approved', 'rejected', 'needs_correction'].includes(decision)) {
|
|
return res.status(400).json({ error: 'Invalid decision. Must be: approved, rejected, or needs_correction' });
|
|
}
|
|
|
|
const { data: timeLog } = await adminClient
|
|
.from('nexus_time_logs')
|
|
.select('*, nexus_contracts!inner(client_id)')
|
|
.eq('id', time_log_id)
|
|
.single();
|
|
|
|
if (!timeLog) {
|
|
return res.status(404).json({ error: 'Time log not found' });
|
|
}
|
|
|
|
const isClient = timeLog.nexus_contracts?.client_id === user.id;
|
|
const isAdmin = user.user_type === 'admin';
|
|
|
|
if (!isClient && !isAdmin) {
|
|
return res.status(403).json({ error: 'Only the contract client or admin can approve time logs' });
|
|
}
|
|
|
|
if (timeLog.submission_status !== 'submitted') {
|
|
return res.status(400).json({ error: 'Time log must be in submitted status to approve/reject' });
|
|
}
|
|
|
|
const newStatus = decision === 'approved' ? 'approved' :
|
|
decision === 'rejected' ? 'rejected' : 'rejected';
|
|
|
|
const { data, error } = await adminClient
|
|
.from('nexus_time_logs')
|
|
.update({
|
|
submission_status: newStatus,
|
|
approved_at: decision === 'approved' ? new Date().toISOString() : null,
|
|
approved_by: decision === 'approved' ? user.id : null,
|
|
updated_at: new Date().toISOString()
|
|
})
|
|
.eq('id', time_log_id)
|
|
.select()
|
|
.single();
|
|
|
|
if (error) {
|
|
return res.status(500).json({ error: error.message });
|
|
}
|
|
|
|
await adminClient.from('nexus_time_log_audits').insert({
|
|
time_log_id: time_log_id,
|
|
reviewer_id: user.id,
|
|
audit_type: decision === 'approved' ? 'approval' : 'rejection',
|
|
decision: decision,
|
|
notes: notes,
|
|
ip_address: req.headers['x-forwarded-for']?.toString() || req.socket?.remoteAddress,
|
|
user_agent: req.headers['user-agent']
|
|
});
|
|
|
|
await logComplianceEvent(adminClient, {
|
|
entity_type: 'time_log',
|
|
entity_id: time_log_id,
|
|
event_type: `time_log_${decision}`,
|
|
event_category: 'compliance',
|
|
actor_id: user.id,
|
|
actor_role: isAdmin ? 'admin' : 'client',
|
|
realm_context: 'nexus',
|
|
description: `Time log ${decision} by ${isAdmin ? 'admin' : 'client'}`,
|
|
payload: { decision, notes }
|
|
}, req);
|
|
|
|
return res.status(200).json({ data });
|
|
}
|