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
161 lines
4.5 KiB
TypeScript
161 lines
4.5 KiB
TypeScript
import type { VercelRequest, VercelResponse } from "@vercel/node";
|
|
import { authenticateRequest, requireAuth } from "../_auth";
|
|
|
|
export default async function handler(req: VercelRequest, res: VercelResponse) {
|
|
const auth = await authenticateRequest(req);
|
|
if (!requireAuth(auth, res)) return;
|
|
|
|
const { userClient, user } = auth;
|
|
|
|
const { data: talentProfile } = await userClient
|
|
.from('nexus_talent_profiles')
|
|
.select('id, az_eligible')
|
|
.eq('user_id', user.id)
|
|
.single();
|
|
|
|
if (!talentProfile) {
|
|
return res.status(400).json({ error: 'Talent profile not found. Create one first.' });
|
|
}
|
|
|
|
if (req.method === 'GET') {
|
|
const { contract_id, start_date, end_date, status } = req.query;
|
|
|
|
let query = userClient
|
|
.from('nexus_time_logs')
|
|
.select('*')
|
|
.eq('talent_profile_id', talentProfile.id)
|
|
.order('log_date', { ascending: false });
|
|
|
|
if (contract_id) query = query.eq('contract_id', contract_id);
|
|
if (start_date) query = query.gte('log_date', start_date);
|
|
if (end_date) query = query.lte('log_date', end_date);
|
|
if (status) query = query.eq('submission_status', status);
|
|
|
|
const { data, error } = await query;
|
|
|
|
if (error) {
|
|
return res.status(500).json({ error: error.message });
|
|
}
|
|
|
|
return res.status(200).json({ data });
|
|
}
|
|
|
|
if (req.method === 'POST') {
|
|
const body = req.body;
|
|
|
|
const azEligibleHours = body.location_state === 'AZ' && talentProfile.az_eligible
|
|
? body.hours_worked
|
|
: 0;
|
|
|
|
const { data, error } = await userClient
|
|
.from('nexus_time_logs')
|
|
.insert({
|
|
talent_profile_id: talentProfile.id,
|
|
contract_id: body.contract_id,
|
|
milestone_id: body.milestone_id,
|
|
log_date: body.log_date,
|
|
start_time: body.start_time,
|
|
end_time: body.end_time,
|
|
hours_worked: body.hours_worked,
|
|
description: body.description,
|
|
task_category: body.task_category,
|
|
location_type: body.location_type || 'remote',
|
|
location_state: body.location_state,
|
|
location_city: body.location_city,
|
|
location_latitude: body.location_latitude,
|
|
location_longitude: body.location_longitude,
|
|
az_eligible_hours: azEligibleHours,
|
|
billable: body.billable !== false,
|
|
submission_status: 'draft'
|
|
})
|
|
.select()
|
|
.single();
|
|
|
|
if (error) {
|
|
return res.status(500).json({ error: error.message });
|
|
}
|
|
|
|
return res.status(201).json({ data });
|
|
}
|
|
|
|
if (req.method === 'PUT') {
|
|
const { id } = req.query;
|
|
const body = req.body;
|
|
|
|
if (!id) {
|
|
return res.status(400).json({ error: 'Time log ID required' });
|
|
}
|
|
|
|
const { data: existingLog } = await userClient
|
|
.from('nexus_time_logs')
|
|
.select('*')
|
|
.eq('id', id)
|
|
.eq('talent_profile_id', talentProfile.id)
|
|
.single();
|
|
|
|
if (!existingLog) {
|
|
return res.status(404).json({ error: 'Time log not found' });
|
|
}
|
|
|
|
if (existingLog.submission_status !== 'draft' && existingLog.submission_status !== 'rejected') {
|
|
return res.status(400).json({ error: 'Can only edit draft or rejected time logs' });
|
|
}
|
|
|
|
const azEligibleHours = (body.location_state || existingLog.location_state) === 'AZ' && talentProfile.az_eligible
|
|
? (body.hours_worked || existingLog.hours_worked)
|
|
: 0;
|
|
|
|
const { data, error } = await userClient
|
|
.from('nexus_time_logs')
|
|
.update({
|
|
...body,
|
|
az_eligible_hours: azEligibleHours,
|
|
updated_at: new Date().toISOString()
|
|
})
|
|
.eq('id', id)
|
|
.select()
|
|
.single();
|
|
|
|
if (error) {
|
|
return res.status(500).json({ error: error.message });
|
|
}
|
|
|
|
return res.status(200).json({ data });
|
|
}
|
|
|
|
if (req.method === 'DELETE') {
|
|
const { id } = req.query;
|
|
|
|
if (!id) {
|
|
return res.status(400).json({ error: 'Time log ID required' });
|
|
}
|
|
|
|
const { data: existingLog } = await userClient
|
|
.from('nexus_time_logs')
|
|
.select('submission_status')
|
|
.eq('id', id)
|
|
.eq('talent_profile_id', talentProfile.id)
|
|
.single();
|
|
|
|
if (!existingLog) {
|
|
return res.status(404).json({ error: 'Time log not found' });
|
|
}
|
|
|
|
if (existingLog.submission_status !== 'draft') {
|
|
return res.status(400).json({ error: 'Can only delete draft time logs' });
|
|
}
|
|
|
|
const { error } = await userClient
|
|
.from('nexus_time_logs')
|
|
.delete()
|
|
.eq('id', id);
|
|
|
|
if (error) {
|
|
return res.status(500).json({ error: error.message });
|
|
}
|
|
|
|
return res.status(204).end();
|
|
}
|
|
|
|
return res.status(405).json({ error: 'Method not allowed' });
|
|
}
|