aethex-forge/api/studio/time-logs.ts
sirpiglr e60e71476f Add core architecture, API endpoints, and UI components for NEXUS
Introduces NEXUS Core architecture documentation, new API endpoints for escrow, payroll, talent profiles, time logs, and UI components for financial dashboards and compliance tracking.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 9203795e-937a-4306-b81d-b4d5c78c240e
Replit-Commit-Checkpoint-Type: intermediate_checkpoint
Replit-Commit-Event-Id: e82c1588-4c11-4961-b289-6ab581ed9691
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
2025-12-13 03:01:54 +00:00

142 lines
4.5 KiB
TypeScript

import type { VercelRequest, VercelResponse } from "@vercel/node";
import { getAdminClient } from "../_supabase";
const STUDIO_API_KEY = process.env.STUDIO_API_KEY;
export default async function handler(req: VercelRequest, res: VercelResponse) {
const supabase = getAdminClient();
const apiKey = req.headers['x-studio-api-key'] || req.headers['authorization']?.replace('Bearer ', '');
const authHeader = req.headers.authorization;
let userId: string | null = null;
let isServiceAuth = false;
if (apiKey === STUDIO_API_KEY && STUDIO_API_KEY) {
isServiceAuth = true;
} else if (authHeader?.startsWith('Bearer ')) {
const token = authHeader.split(' ')[1];
const { data: { user }, error } = await supabase.auth.getUser(token);
if (error || !user) {
return res.status(401).json({ error: 'Invalid token' });
}
userId = user.id;
} else {
return res.status(401).json({ error: 'Unauthorized - requires Bearer token or X-Studio-API-Key' });
}
if (req.method === 'POST') {
const body = req.body;
if (!body.user_id && !userId) {
return res.status(400).json({ error: 'user_id required for service auth' });
}
const targetUserId = body.user_id || userId;
const { data: talentProfile } = await supabase
.from('nexus_talent_profiles')
.select('id, az_eligible')
.eq('user_id', targetUserId)
.single();
if (!talentProfile) {
return res.status(400).json({ error: 'Talent profile not found for user' });
}
const azEligibleHours = body.location_state === 'AZ' && talentProfile.az_eligible
? body.hours_worked
: 0;
const { data, error } = await supabase
.from('nexus_time_logs')
.insert({
talent_profile_id: talentProfile.id,
contract_id: body.contract_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,
location_verified: !!body.location_latitude && !!body.location_longitude,
az_eligible_hours: azEligibleHours,
billable: body.billable !== false,
submission_status: 'submitted',
submitted_at: new Date().toISOString()
})
.select()
.single();
if (error) {
return res.status(500).json({ error: error.message });
}
await supabase.from('nexus_compliance_events').insert({
entity_type: 'time_log',
entity_id: data.id,
event_type: 'studio_time_log_created',
event_category: 'compliance',
actor_id: isServiceAuth ? null : userId,
actor_role: isServiceAuth ? 'api' : 'talent',
realm_context: 'studio',
description: 'Time log submitted via Studio API',
payload: {
source: 'studio_api',
location_verified: data.location_verified,
az_eligible_hours: azEligibleHours
}
});
return res.status(201).json({ data });
}
if (req.method === 'GET') {
const { contract_id, start_date, end_date, user_id: queryUserId } = req.query;
const targetUserId = queryUserId || userId;
if (!targetUserId && !isServiceAuth) {
return res.status(400).json({ error: 'user_id required' });
}
let query = supabase
.from('nexus_time_logs')
.select(`
id, log_date, start_time, end_time, hours_worked,
location_state, az_eligible_hours, submission_status,
contract_id
`);
if (targetUserId) {
const { data: talentProfile } = await supabase
.from('nexus_talent_profiles')
.select('id')
.eq('user_id', targetUserId)
.single();
if (talentProfile) {
query = query.eq('talent_profile_id', talentProfile.id);
}
}
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);
const { data, error } = await query.order('log_date', { ascending: false });
if (error) {
return res.status(500).json({ error: error.message });
}
return res.status(200).json({ data });
}
return res.status(405).json({ error: 'Method not allowed' });
}