     1→import { NextRequest, NextResponse } from "next/server";
     2→import OpenAI from "openai";
     3→import { createClient } from "@/lib/supabase/server";
     4→import { checkRateLimit, getClientIP, createRateLimitResponse } from "@/lib/rate-limit";
     5→import { withErrorHandler, APIError, ErrorCodes } from "@/lib/api-errors";
     6→import { sanitizeInput } from "@/lib/sanitize";
     7→import { validateCSRFToken, isSameOrigin } from "@/lib/csrf";
     8→
     9→// Cast helper to use NextRequest methods while satisfying withErrorHandler signature
    10→type Handler = (request: Request) => Promise<Response>;
    11→
    12→const openai = new OpenAI({
    13→  apiKey: process.env.OPENAI_API_KEY || "",
    14→});
    15→
    16→const AI_RATE_LIMIT = 10;
    17→const AI_WINDOW_MS = 15 * 60 * 1000;
    18→
    19→const VALID_TRADES = [
    20→  "Labourer", "Steel Fixer", "Formworker", "Carpenter", "Electrician",
    21→  "Plumber", "Concreter", "Bricklayer", "Painter", "Roofer",
    22→  "Scaffolder", "Tiler", "Plasterer", "Welder", "Crane Operator",
    23→  "Excavator Operator",
    24→];
    25→
    26→const VALID_AVAILABILITY = ["full-time", "casual", "weekends", "flexible"];
    27→
    28→async function handler(request: Request) {
    29→  const nextRequest = request as NextRequest;
    30→  // CSRF protection: validate same-origin and token
    31→  if (!isSameOrigin(request)) {
    32→    return NextResponse.json({ error: 'Cross-origin request not allowed' }, { status: 403 });
    33→  }
    34→  const csrfValid = await validateCSRFToken(request);
    35→  if (!csrfValid) {
    36→    return NextResponse.json({ error: 'Invalid or missing CSRF token' }, { status: 403 });
    37→  }
    38→
    39→  // Auth check
    40→  const supabase = await createClient();
    41→  const { data: { user } } = await supabase.auth.getUser();
    42→  if (!user) {
    43→    throw new APIError("Unauthorized", 401, ErrorCodes.UNAUTHORIZED);
    44→  }
    45→
    46→  // Rate limit
    47→  const clientIP = getClientIP(request);
    48→  const rateLimit = checkRateLimit(`parse-voice:${clientIP}`, AI_RATE_LIMIT, AI_WINDOW_MS);
    49→  if (!rateLimit.allowed) {
    50→    const retryAfter = Math.ceil((rateLimit.resetTime - Date.now()) / 1000);
    51→    return createRateLimitResponse(clientIP, retryAfter);
    52→  }
    53→
    54→  const body = await request.json();
    55→  const { transcript } = body;
    56→
    57→  if (!transcript || typeof transcript !== "string") {
    58→    throw new APIError("Missing transcript", 400, ErrorCodes.INVALID_INPUT);
    59→  }
    60→
    61→  // Sanitize transcript to prevent prompt injection
    62→  const sanitizedTranscript = sanitizeInput(transcript, { maxLength: 5000 });
    63→
    64→  try {
    65→    const completion = await openai.chat.completions.create({
    66→      model: "gpt-4o-mini",
    67→      messages: [
    68→        {
    69→          role: "system",
    70→          content: `You are a profile parser for an Australian construction hiring app. Extract structured data from a worker's spoken description.
    71→
    72→Valid trades: ${VALID_TRADES.join(", ")}
    73→Valid availability: ${VALID_AVAILABILITY.join(", ")}
    74→
    75→Return ONLY valid JSON in this exact format:
    76→{
    77→  "trades": ["array of valid trades mentioned, or empty array"],
    78→  "trade": "primary trade (first one mentioned) or null",
    79→  "experience_years": number or null,
    80→  "location": "suburb/area string or null",
    81→  "availability": "one of the valid availability options or null",
    82→  "additional_details": "any other details mentioned — specific skills, years per trade, preferences, work history, specialties. Capture everything not covered by the structured fields above. null if nothing extra."
    83→}
    84→
    85→Match trades loosely — e.g. "sparky" = "Electrician", "chippie" = "Carpenter", "bricky" = "Bricklayer", "steel fixer"/"steelfixer"/"reo" = "Steel Fixer", "formworker"/"formwork"/"shuttering"/"shutter hand" = "Formworker".
    86→IMPORTANT: Extract ALL trades mentioned, not just one. If someone says "steel fixing and shuttering", return both "Steel Fixer" and "Formworker".
    87→For experience, extract the number of years mentioned (use the highest if multiple are given for different trades).
    88→For location, extract any suburb, city, or area name mentioned.
    89→For availability, match to the closest option.
    90→If a field is not mentioned, set it to null (or empty array for trades).`,
    91→        },
    92→        {
    93→          role: "user",
    94→          content: sanitizedTranscript,
    95→        },
    96→      ],
    97→      temperature: 0.3,
    98→      response_format: { type: "json_object" },
    99→    });
   100→
   101→    const content = completion.choices[0]?.message?.content;
   102→    if (!content) {
   103→      throw new APIError("No response from AI", 500, ErrorCodes.EXTERNAL_SERVICE_ERROR);
   104→    }
   105→
   106→    const parsed = JSON.parse(content);
   107→
   108→    // Validate trades array
   109→    if (Array.isArray(parsed.trades)) {
   110→      parsed.trades = parsed.trades.filter((t: string) => VALID_TRADES.includes(t));
   111→    } else {
   112→      parsed.trades = [];
   113→    }
   114→
   115→    // Validate primary trade (use first from trades array if invalid)
   116→    if (parsed.trade && !VALID_TRADES.includes(parsed.trade)) {
   117→      parsed.trade = parsed.trades[0] || null;
   118→    } else if (!parsed.trade && parsed.trades.length > 0) {
   119→      parsed.trade = parsed.trades[0];
   120→    }
   121→
   122→    // Validate availability
   123→    if (parsed.availability && !VALID_AVAILABILITY.includes(parsed.availability)) {
   124→      parsed.availability = null;
   125→    }
   126→
   127→    // Validate experience_years
   128→    if (parsed.experience_years !== null && (typeof parsed.experience_years !== "number" || parsed.experience_years < 0)) {
   129→      parsed.experience_years = null;
   130→    }
   131→
   132→    return Response.json(parsed);
   133→  } catch (error) {
   134→    console.error("OpenAI parsing error:", error);
   135→    if (error instanceof APIError) {
   136→      throw error;
   137→    }
   138→    throw new APIError(
   139→      "Failed to parse voice input",
   140→      500,
   141→      ErrorCodes.EXTERNAL_SERVICE_ERROR
   142→    );
   143→  }
   144→}
   145→
   146→export const POST = withErrorHandler(handler);
   147→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


     1→import { NextRequest, NextResponse } from "next/server";
     2→import OpenAI from "openai";
     3→import { createClient } from "@/lib/supabase/server";
     4→import { checkRateLimit, getClientIP, createRateLimitResponse } from "@/lib/rate-limit";
     5→import { sanitizeInput } from "@/lib/sanitize";
     6→import { withCSRFProtection } from "@/lib/csrf";
     7→
     8→const openai = new OpenAI({
     9→  apiKey: process.env.OPENAI_API_KEY || "",
    10→});
    11→
    12→// Rate limit: 5 requests per 15 minutes per IP (expensive AI call)
    13→const AI_RATE_LIMIT = 5;
    14→const AI_WINDOW_MS = 15 * 60 * 1000;
    15→
    16→const handler = async (request: NextRequest) => {
    17→  try {
    18→    // --- Rate limiting ---
    19→    const clientIP = getClientIP(request);
    20→    const rateLimit = checkRateLimit(`ai-profile:${clientIP}`, AI_RATE_LIMIT, AI_WINDOW_MS);
    21→
    22→    if (!rateLimit.allowed) {
    23→      const retryAfter = Math.ceil((rateLimit.resetTime - Date.now()) / 1000);
    24→      return createRateLimitResponse(clientIP, retryAfter);
    25→    }
    26→
    27→    // --- Auth check ---
    28→    const supabase = await createClient();
    29→    const { data: { user }, error: authError } = await supabase.auth.getUser();
    30→    if (authError || !user) {
    31→      return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
    32→    }
    33→
    34→    const { trade, trades, experience, location, availability, voiceTranscript } = await request.json();
    35→
    36→    if (!trade || experience === undefined || !location || !availability) {
    37→      return NextResponse.json(
    38→        { error: "Missing required fields" },
    39→        { status: 400 }
    40→      );
    41→    }
    42→
    43→    // Sanitize inputs to prevent prompt injection
    44→    const sanitizedTrade = sanitizeInput(trade, { maxLength: 100 });
    45→    const sanitizedLocation = sanitizeInput(location, { maxLength: 200 });
    46→    const sanitizedAvailability = sanitizeInput(availability, { maxLength: 50 });
    47→    const sanitizedTranscript = voiceTranscript ? sanitizeInput(voiceTranscript, { maxLength: 2000 }) : null;
    48→
    49→    // Sanitize additional trades
    50→    const allTrades: string[] = Array.isArray(trades)
    51→      ? trades.map((t: string) => sanitizeInput(t, { maxLength: 100 }))
    52→      : [sanitizedTrade];
    53→    const tradesStr = allTrades.join(", ");
    54→
    55→    // Validate experience is a number
    56→    const experienceYears = parseInt(experience, 10);
    57→    if (isNaN(experienceYears) || experienceYears < 0 || experienceYears > 100) {
    58→      return NextResponse.json(
    59→        { error: "Invalid experience value" },
    60→        { status: 400 }
    61→      );
    62→    }
    63→
    64→    const transcriptSection = sanitizedTranscript
    65→      ? `\n\nOriginal voice description from the worker (use this to make the bio personal and unique — reference specific details they mentioned):\n"${sanitizedTranscript}"`
    66→      : "";
    67→
    68→    const prompt = `You are an expert construction industry recruiter writing a profile for an Australian construction worker.
    69→
    70→Worker Information:
    71→- Trade(s): ${tradesStr}
    72→- Years of Experience: ${experienceYears} years
    73→- Location: ${sanitizedLocation}
    74→- Availability: ${sanitizedAvailability}${transcriptSection}
    75→
    76→Generate a JSON response with:
    77→1. bio: A professional 2-3 sentence bio (50-80 words) written in FIRST PERSON ("I", "my", "me"). It's the worker's own bio on their profile. Make it unique — if voice details are provided, incorporate specific things they mentioned (multiple trades, specialties, work style). Do NOT use third person ("they", "their", "them").
    78→2. skills: An array of 6-10 relevant technical skills for their trade(s) (specific tools, techniques, materials). If multiple trades, include skills from each.
    79→3. certifications: An array of 3-5 common/recommended certifications for their trade(s) (actual Australian construction certifications)
    80→
    81→Make it sound professional but approachable. Focus on practical skills and experience.
    82→
    83→Return ONLY valid JSON in this exact format:
    84→{
    85→  "bio": "string",
    86→  "skills": ["skill1", "skill2", ...],
    87→  "certifications": ["cert1", "cert2", ...]
    88→}`;
    89→
    90→    const completion = await openai.chat.completions.create({
    91→      model: "gpt-4o-mini",
    92→      messages: [
    93→        {
    94→          role: "system",
    95→          content: "You are a construction industry expert. Always respond with valid JSON only.",
    96→        },
    97→        {
    98→          role: "user",
    99→          content: prompt,
   100→        },
   101→      ],
   102→      temperature: 0.7,
   103→      response_format: { type: "json_object" },
   104→    });
   105→
   106→    const content = completion.choices[0]?.message?.content;
   107→    if (!content) {
   108→      throw new Error("No response from AI");
   109→    }
   110→
   111→    const generated = JSON.parse(content);
   112→
   113→    return NextResponse.json({
   114→      bio: generated.bio || "",
   115→      skills: generated.skills || [],
   116→      certifications: generated.certifications || [],
   117→    });
   118→  } catch (error) {
   119→    console.error("AI generation error:", error);
   120→
   121→    // Return more specific error messages
   122→    if (error instanceof Error) {
   123→      if (error.message.includes("API key")) {
   124→        return NextResponse.json(
   125→          { error: "AI service configuration error" },
   126→          { status: 503 }
   127→        );
   128→      }
   129→      if (error.message.includes("rate limit") || error.message.includes("429")) {
   130→        return NextResponse.json(
   131→          { error: "AI service is busy. Please try again in a moment." },
   132→          { status: 429 }
   133→        );
   134→      }
   135→    }
   136→
   137→    return NextResponse.json(
   138→      { error: "Failed to generate profile. Please try again." },
   139→      { status: 500 }
   140→    );
   141→  }
   142→}
   143→
   144→// Apply CSRF protection to the handler
   145→export const POST = withCSRFProtection(handler);
   146→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


     1→import { NextRequest, NextResponse } from "next/server";
     2→import OpenAI from "openai";
     3→import { createClient } from "@/lib/supabase/server";
     4→import { checkRateLimit, getClientIP, createRateLimitResponse } from "@/lib/rate-limit";
     5→import { sanitizeInput } from "@/lib/sanitize";
     6→import { withCSRFProtection } from "@/lib/csrf";
     7→
     8→const openai = new OpenAI({
     9→  apiKey: process.env.OPENAI_API_KEY || "",
    10→});
    11→
    12→const AI_RATE_LIMIT = 10;
    13→const AI_WINDOW_MS = 15 * 60 * 1000;
    14→
    15→const handler = async (request: NextRequest) => {
    16→  try {
    17→    const clientIP = getClientIP(request);
    18→    const rateLimit = checkRateLimit(`ai-job:${clientIP}`, AI_RATE_LIMIT, AI_WINDOW_MS);
    19→
    20→    if (!rateLimit.allowed) {
    21→      const retryAfter = Math.ceil((rateLimit.resetTime - Date.now()) / 1000);
    22→      return createRateLimitResponse(clientIP, retryAfter);
    23→    }
    24→
    25→    const supabase = await createClient();
    26→    const { data: { user }, error: authError } = await supabase.auth.getUser();
    27→    if (authError || !user) {
    28→      return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
    29→    }
    30→
    31→    const { trade } = await request.json();
    32→
    33→    if (!trade) {
    34→      return NextResponse.json({ error: "Missing trade" }, { status: 400 });
    35→    }
    36→
    37→    const sanitizedTrade = sanitizeInput(trade, { maxLength: 100 });
    38→
    39→    const completion = await openai.chat.completions.create({
    40→      model: "gpt-4o-mini",
    41→      messages: [
    42→        {
    43→          role: "system",
    44→          content: "You are an Australian construction industry expert. Always respond with valid JSON only.",
    45→        },
    46→        {
    47→          role: "user",
    48→          content: `Generate a job posting for a ${sanitizedTrade} in the Australian construction industry.
    49→
    50→Return JSON with:
    51→1. title: A concise job title (e.g. "Experienced Steel Fixer Needed")
    52→2. description: A 3-4 sentence job description mentioning key duties, site expectations, and any specific requirements. Use Australian English. Mention PPE, early starts if relevant.
    53→3. certifications: Array of required certifications (e.g. "White Card", "Working at Heights", "Trade Certificate", "Scaffolding Licence"). Always include "White Card".
    54→4. rateMin: Minimum hourly rate in AUD (realistic 2026 Australian market rate)
    55→5. rateMax: Maximum hourly rate in AUD
    56→6. rateType: "hourly" or "daily"
    57→
    58→Return ONLY valid JSON:
    59→{
    60→  "title": "string",
    61→  "description": "string",
    62→  "certifications": ["string"],
    63→  "rateMin": number,
    64→  "rateMax": number,
    65→  "rateType": "hourly"
    66→}`,
    67→        },
    68→      ],
    69→      temperature: 0.7,
    70→      response_format: { type: "json_object" },
    71→    });
    72→
    73→    const content = completion.choices[0]?.message?.content;
    74→    if (!content) {
    75→      throw new Error("No response from AI");
    76→    }
    77→
    78→    const generated = JSON.parse(content);
    79→
    80→    return NextResponse.json({
    81→      title: generated.title || "",
    82→      description: generated.description || "",
    83→      certifications: generated.certifications || ["White Card"],
    84→      rateMin: generated.rateMin || 35,
    85→      rateMax: generated.rateMax || 55,
    86→      rateType: generated.rateType || "hourly",
    87→    });
    88→  } catch (error) {
    89→    console.error("AI job generation error:", error);
    90→
    91→    if (error instanceof Error) {
    92→      if (error.message.includes("API key")) {
    93→        return NextResponse.json({ error: "AI service configuration error" }, { status: 503 });
    94→      }
    95→      if (error.message.includes("rate limit") || error.message.includes("429")) {
    96→        return NextResponse.json({ error: "AI service is busy. Please try again." }, { status: 429 });
    97→      }
    98→    }
    99→
   100→    return NextResponse.json({ error: "Failed to generate job details" }, { status: 500 });
   101→  }
   102→};
   103→
   104→export const POST = withCSRFProtection(handler);
   105→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


     1→import { NextRequest, NextResponse } from "next/server";
     2→import { createClient, getSupabaseAdmin } from "@/lib/supabase/server";
     3→import { checkRateLimit, getClientIP, createRateLimitResponse } from "@/lib/rate-limit";
     4→
     5→// Rate limit: 10 requests per 15 minutes
     6→const RATE_LIMIT = 10;
     7→const WINDOW_MS = 15 * 60 * 1000;
     8→
     9→/**
    10→ * POST /api/match/find-matches
    11→ *
    12→ * Finds and creates suggested matches for a job using DB-side scoring.
    13→ * Body: { job_id: string, limit?: number, min_score?: number }
    14→ *
    15→ * Scoring weights (calculated in database):
    16→ * - Trade match: 40%
    17→ * - Availability: 20%
    18→ * - Experience: 15%
    19→ * - Certifications: 15%
    20→ * - Location proximity: 10%
    21→ * - Ratings: 10%
    22→ *
    23→ * NOTE: This uses the find_matching_workers() RPC function which scales
    24→ * efficiently to 10k+ workers by performing all scoring calculations
    25→ * database-side with proper indexes.
    26→ */
    27→export async function POST(request: NextRequest) {
    28→  try {
    29→    // Rate limiting
    30→    const clientIP = getClientIP(request);
    31→    const rateLimit = checkRateLimit(`match:${clientIP}`, RATE_LIMIT, WINDOW_MS);
    32→    if (!rateLimit.allowed) {
    33→      const retryAfter = Math.ceil((rateLimit.resetTime - Date.now()) / 1000);
    34→      return createRateLimitResponse(clientIP, retryAfter);
    35→    }
    36→
    37→    // Auth check
    38→    const supabase = await createClient();
    39→    const { data: { user }, error: authError } = await supabase.auth.getUser();
    40→    if (authError || !user) {
    41→      return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
    42→    }
    43→
    44→    const { job_id, limit = 20, min_score = 20 } = await request.json();
    45→    if (!job_id) {
    46→      return NextResponse.json({ error: "job_id is required" }, { status: 400 });
    47→    }
    48→
    49→    // Validate limit (prevent abuse)
    50→    const validLimit = Math.min(Math.max(parseInt(limit as string) || 20, 1), 50);
    51→    const validMinScore = Math.min(Math.max(parseInt(min_score as string) || 20, 0), 100);
    52→
    53→    // Use admin client to bypass RLS for matching operations
    54→    const admin = getSupabaseAdmin();
    55→
    56→    // Verify job exists and user owns it
    57→    const { data: job, error: jobError } = await admin
    58→      .from("jobs")
    59→      .select("id, status, companies!inner(profile_id)")
    60→      .eq("id", job_id)
    61→      .single();
    62→
    63→    if (jobError || !job) {
    64→      return NextResponse.json({ error: "Job not found" }, { status: 404 });
    65→    }
    66→
    67→    // Verify the requesting user owns this job's company
    68→    const companies = job.companies as unknown as { profile_id: string } | null;
    69→    if (!companies || companies.profile_id !== user.id) {
    70→      return NextResponse.json({ error: "Not authorized for this job" }, { status: 403 });
    71→    }
    72→
    73→    if (job.status !== "active") {
    74→      return NextResponse.json({ error: "Job is not active" }, { status: 400 });
    75→    }
    76→
    77→    // Call the DB-side scoring RPC function
    78→    // This is the key optimization - all scoring happens in the database
    79→    const { data: scoredWorkers, error: rpcError } = await admin.rpc(
    80→      "find_matching_workers",
    81→      {
    82→        p_job_id: job_id,
    83→        p_limit: validLimit,
    84→        p_min_score: validMinScore,
    85→      }
    86→    );
    87→
    88→    if (rpcError) {
    89→      console.error("RPC error finding matches:", rpcError);
    90→      return NextResponse.json(
    91→        { error: "Failed to find matches", details: rpcError.message },
    92→        { status: 500 }
    93→      );
    94→    }
    95→
    96→    if (!scoredWorkers || scoredWorkers.length === 0) {
    97→      return NextResponse.json({
    98→        matches: [],
    99→        message: "No matching workers found",
   100→        matches_created: 0,
   101→      });
   102→    }
   103→
   104→    // Prepare match rows for insertion
   105→    const matchRows = scoredWorkers.map((match: {
   106→      worker_id: string;
   107→      score: number;
   108→      summary: string;
   109→    }) => ({
   110→      job_id,
   111→      worker_id: match.worker_id,
   112→      status: "suggested" as const,
   113→      match_score: match.score,
   114→      ai_summary: match.summary,
   115→      contractor_action: "pending" as const,
   116→      worker_action: "pending" as const,
   117→    }));
   118→
   119→    // Insert matches into the database
   120→    const { error: insertError } = await admin
   121→      .from("matches")
   122→      .upsert(matchRows, { onConflict: "job_id,worker_id", ignoreDuplicates: true });
   123→
   124→    if (insertError) {
   125→      console.error("Error inserting matches:", insertError);
   126→      return NextResponse.json({ error: "Failed to save matches" }, { status: 500 });
   127→    }
   128→
   129→    return NextResponse.json({
   130→      matches: scoredWorkers.map((m: {
   131→        worker_id: string;
   132→        score: number;
   133→        summary: string;
   134→        reasons: string[];
   135→        profile: Record<string, unknown>;
   136→      }) => ({
   137→        worker_id: m.worker_id,
   138→        score: m.score,
   139→        summary: m.summary,
   140→        reasons: m.reasons,
   141→        profile: m.profile,
   142→      })),
   143→      matches_created: scoredWorkers.length,
   144→    });
   145→  } catch (error) {
   146→    console.error("Match finding error:", error);
   147→    return NextResponse.json(
   148→      { error: "Failed to find matches" },
   149→      { status: 500 }
   150→    );
   151→  }
   152→}
   153→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


     1→import { createClient } from "@/lib/supabase/server";
     2→import { NextResponse } from "next/server";
     3→
     4→export async function GET() {
     5→  try {
     6→    const supabase = await createClient();
     7→
     8→    // Check if user is authenticated
     9→    const { data: { user }, error: authError } = await supabase.auth.getUser();
    10→
    11→    if (authError || !user) {
    12→      return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
    13→    }
    14→
    15→    // Update last_seen_at
    16→    await supabase
    17→      .from('profiles')
    18→      .update({ last_seen_at: new Date().toISOString() })
    19→      .eq('id', user.id);
    20→
    21→    // Get conversation previews using the database function
    22→    const { data: conversations, error } = await supabase
    23→      .rpc('get_conversation_previews', { user_id: user.id });
    24→
    25→    if (error) {
    26→      console.error("Error fetching conversations:", error);
    27→      return NextResponse.json(
    28→        { error: "Failed to fetch conversations" },
    29→        { status: 500 }
    30→      );
    31→    }
    32→
    33→    return NextResponse.json({ conversations: conversations || [] });
    34→  } catch (error) {
    35→    console.error("Error in messages API:", error);
    36→    return NextResponse.json(
    37→      { error: "Internal server error" },
    38→      { status: 500 }
    39→    );
    40→  }
    41→}

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


Let me read the remaining message API routes and then start on the lib files.


     1→import { createClient } from "@/lib/supabase/server";
     2→import { NextResponse } from "next/server";
     3→
     4→export async function POST(request: Request) {
     5→  try {
     6→    const supabase = await createClient();
     7→
     8→    // Check if user is authenticated
     9→    const { data: { user }, error: authError } = await supabase.auth.getUser();
    10→
    11→    if (authError || !user) {
    12→      return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
    13→    }
    14→
    15→    const { recipientId, initialMessage } = await request.json();
    16→
    17→    if (!recipientId) {
    18→      return NextResponse.json(
    19→        { error: "Recipient ID is required" },
    20→        { status: 400 }
    21→      );
    22→    }
    23→
    24→    // Verify recipient exists
    25→    const { data: recipient, error: recipientError } = await supabase
    26→      .from('profiles')
    27→      .select('id, name')
    28→      .eq('id', recipientId)
    29→      .single();
    30→
    31→    if (recipientError || !recipient) {
    32→      return NextResponse.json(
    33→        { error: "Recipient not found" },
    34→        { status: 404 }
    35→      );
    36→    }
    37→
    38→    // Get or create conversation using the database function
    39→    const { data: conversationId, error: conversationError } = await supabase
    40→      .rpc('get_or_create_conversation', {
    41→        user1_id: user.id,
    42→        user2_id: recipientId
    43→      });
    44→
    45→    if (conversationError) {
    46→      console.error("Error creating conversation:", conversationError);
    47→      return NextResponse.json(
    48→        { error: "Failed to create conversation" },
    49→        { status: 500 }
    50→      );
    51→    }
    52→
    53→    // If there's an initial message, send it
    54→    if (initialMessage?.trim()) {
    55→      const { error: messageError } = await supabase
    56→        .from('messages')
    57→        .insert({
    58→          conversation_id: conversationId,
    59→          sender_id: user.id,
    60→          content: initialMessage.trim()
    61→        });
    62→
    63→      if (messageError) {
    64→        console.error("Error sending initial message:", messageError);
    65→        // Don't fail the whole request if message fails
    66→      }
    67→    }
    68→
    69→    return NextResponse.json({
    70→      conversationId,
    71→      message: "Conversation created successfully"
    72→    });
    73→  } catch (error) {
    74→    console.error("Error in start conversation API:", error);
    75→    return NextResponse.json(
    76→      { error: "Internal server error" },
    77→      { status: 500 }
    78→    );
    79→  }
    80→}
    81→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


     1→import { createClient } from "@/lib/supabase/server";
     2→import { NextResponse } from "next/server";
     3→
     4→export async function GET(
     5→  request: Request,
     6→  { params }: { params: Promise<{ id: string }> }
     7→) {
     8→  try {
     9→    const { id: conversationId } = await params;
    10→    const supabase = await createClient();
    11→
    12→    // Check if user is authenticated
    13→    const { data: { user }, error: authError } = await supabase.auth.getUser();
    14→
    15→    if (authError || !user) {
    16→      return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
    17→    }
    18→
    19→    // Verify user is a participant in this conversation
    20→    const { data: participant, error: participantError } = await supabase
    21→      .from('conversation_participants')
    22→      .select('id')
    23→      .eq('conversation_id', conversationId)
    24→      .eq('profile_id', user.id)
    25→      .single();
    26→
    27→    if (participantError || !participant) {
    28→      return NextResponse.json({ error: "Conversation not found" }, { status: 404 });
    29→    }
    30→
    31→    // Get conversation details with other participant info
    32→    const { data: conversationData, error: conversationError } = await supabase
    33→      .from('conversation_participants')
    34→      .select(`
    35→        profile_id,
    36→        profiles!inner(
    37→          id,
    38→          name,
    39→          avatar_url,
    40→          type
    41→        )
    42→      `)
    43→      .eq('conversation_id', conversationId)
    44→      .neq('profile_id', user.id)
    45→      .single();
    46→
    47→    if (conversationError || !conversationData) {
    48→      return NextResponse.json(
    49→        { error: "Failed to fetch conversation details" },
    50→        { status: 500 }
    51→      );
    52→    }
    53→
    54→    // Get messages in this conversation
    55→    const { data: messages, error: messagesError } = await supabase
    56→      .from('messages')
    57→      .select(`
    58→        id,
    59→        content,
    60→        sender_id,
    61→        status,
    62→        created_at,
    63→        attachments
    64→      `)
    65→      .eq('conversation_id', conversationId)
    66→      .order('created_at', { ascending: true });
    67→
    68→    if (messagesError) {
    69→      return NextResponse.json(
    70→        { error: "Failed to fetch messages" },
    71→        { status: 500 }
    72→      );
    73→    }
    74→
    75→    // Update last_read_at and last_seen_at
    76→    const now = new Date().toISOString();
    77→    await supabase
    78→      .from('conversation_participants')
    79→      .update({ last_read_at: now })
    80→      .eq('conversation_id', conversationId)
    81→      .eq('profile_id', user.id);
    82→
    83→    await supabase
    84→      .from('profiles')
    85→      .update({ last_seen_at: now })
    86→      .eq('id', user.id);
    87→
    88→    // Cast profiles to single object (inner join guarantees single row)
    89→    const profile = conversationData.profiles as unknown as {
    90→      id: string;
    91→      name: string;
    92→      avatar_url: string | null;
    93→      type: string;
    94→    };
    95→
    96→    // Check if other user was active in the last 5 minutes
    97→    const { data: otherProfile } = await supabase
    98→      .from('profiles')
    99→      .select('last_seen_at')
   100→      .eq('id', profile.id)
   101→      .single();
   102→
   103→    const fiveMinAgo = Date.now() - 5 * 60 * 1000;
   104→    const isOnline = otherProfile?.last_seen_at
   105→      ? new Date(otherProfile.last_seen_at).getTime() > fiveMinAgo
   106→      : false;
   107→
   108→    return NextResponse.json({
   109→      conversation: {
   110→        id: conversationId,
   111→        otherParticipant: {
   112→          id: profile.id,
   113→          name: profile.name,
   114→          avatar: profile.avatar_url,
   115→          type: profile.type,
   116→          isOnline,
   117→        },
   118→        messages: messages?.map(msg => ({
   119→          id: msg.id,
   120→          content: msg.content,
   121→          isOwn: msg.sender_id === user.id,
   122→          timestamp: msg.created_at,
   123→          status: msg.status,
   124→          attachments: msg.attachments
   125→        })) || []
   126→      }
   127→    });
   128→  } catch (error) {
   129→    console.error("Error in conversation API:", error);
   130→    return NextResponse.json(
   131→      { error: "Internal server error" },
   132→      { status: 500 }
   133→    );
   134→  }
   135→}

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


     1→import { createClient } from "@/lib/supabase/server";
     2→import { NextRequest, NextResponse } from "next/server";
     3→import { validateCSRFToken, isSameOrigin } from "@/lib/csrf";
     4→import { sanitizeInput } from "@/lib/sanitize";
     5→
     6→export async function POST(
     7→  request: NextRequest,
     8→  { params }: { params: Promise<{ id: string }> }
     9→) {
    10→  try {
    11→    // CSRF protection: validate same-origin and token
    12→    if (!isSameOrigin(request)) {
    13→      return NextResponse.json({ error: 'Cross-origin request not allowed' }, { status: 403 });
    14→    }
    15→    const csrfValid = await validateCSRFToken(request);
    16→    if (!csrfValid) {
    17→      return NextResponse.json({ error: 'Invalid or missing CSRF token' }, { status: 403 });
    18→    }
    19→
    20→    const { id: conversationId } = await params;
    21→    const supabase = await createClient();
    22→
    23→    // Check if user is authenticated
    24→    const { data: { user }, error: authError } = await supabase.auth.getUser();
    25→
    26→    if (authError || !user) {
    27→      return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
    28→    }
    29→    const { content, attachments = [] } = await request.json();
    30→
    31→    const sanitizedContent = content ? sanitizeInput(content, { maxLength: 5000 }) : '';
    32→
    33→    if (!sanitizedContent) {
    34→      return NextResponse.json(
    35→        { error: "Message content is required" },
    36→        { status: 400 }
    37→      );
    38→    }
    39→
    40→    // Verify user is a participant in this conversation
    41→    const { data: participant, error: participantError } = await supabase
    42→      .from('conversation_participants')
    43→      .select('id')
    44→      .eq('conversation_id', conversationId)
    45→      .eq('profile_id', user.id)
    46→      .single();
    47→
    48→    if (participantError || !participant) {
    49→      return NextResponse.json({ error: "Conversation not found" }, { status: 404 });
    50→    }
    51→
    52→    // Insert the message
    53→    const { data: message, error: messageError } = await supabase
    54→      .from('messages')
    55→      .insert({
    56→        conversation_id: conversationId,
    57→        sender_id: user.id,
    58→        content: sanitizedContent,
    59→        attachments: attachments
    60→      })
    61→      .select(`
    62→        id,
    63→        content,
    64→        sender_id,
    65→        status,
    66→        created_at,
    67→        attachments
    68→      `)
    69→      .single();
    70→
    71→    if (messageError) {
    72→      console.error("Error sending message:", messageError);
    73→      return NextResponse.json(
    74→        { error: "Failed to send message" },
    75→        { status: 500 }
    76→      );
    77→    }
    78→
    79→    // Get the other participant to send notification
    80→    const { data: otherParticipant } = await supabase
    81→      .from('conversation_participants')
    82→      .select('profile_id')
    83→      .eq('conversation_id', conversationId)
    84→      .neq('profile_id', user.id)
    85→      .single();
    86→
    87→    if (otherParticipant) {
    88→      // Create notification for the other participant
    89→      const { data: senderProfile } = await supabase
    90→        .from('profiles')
    91→        .select('name')
    92→        .eq('id', user.id)
    93→        .single();
    94→
    95→      const { error: notifError } = await supabase.from('notifications').insert({
    96→        profile_id: otherParticipant.profile_id,
    97→        type: 'system',
    98→        title: 'New message',
    99→        body: `${senderProfile?.name || 'Someone'} sent you a message`,
   100→        data: {
   101→          conversationId: conversationId,
   102→          senderId: user.id
   103→        }
   104→      });
   105→      if (notifError) {
   106→        console.error('Failed to create message notification:', notifError);
   107→      }
   108→    }
   109→
   110→    return NextResponse.json({
   111→      message: {
   112→        id: message.id,
   113→        content: message.content,
   114→        isOwn: true,
   115→        timestamp: message.created_at,
   116→        status: message.status,
   117→        attachments: message.attachments
   118→      }
   119→    });
   120→  } catch (error) {
   121→    console.error("Error in send message API:", error);
   122→    return NextResponse.json(
   123→      { error: "Internal server error" },
   124→      { status: 500 }
   125→    );
   126→  }
   127→}

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


     1→import { NextResponse } from "next/server";
     2→import { createServerClient } from "@supabase/ssr";
     3→import { cookies } from "next/headers";
     4→
     5→export async function GET(request: Request) {
     6→  const { searchParams, origin } = new URL(request.url);
     7→  const code = searchParams.get("code");
     8→  const rawNext = searchParams.get("next") ?? "/dashboard";
     9→  // Prevent open redirect — only allow relative paths starting with /
    10→  const next = rawNext.startsWith("/") && !rawNext.startsWith("//") ? rawNext : "/dashboard";
    11→
    12→  if (code) {
    13→    const cookieStore = await cookies();
    14→
    15→    const supabase = createServerClient(
    16→      process.env.NEXT_PUBLIC_SUPABASE_URL!,
    17→      process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    18→      {
    19→        cookies: {
    20→          getAll() {
    21→            return cookieStore.getAll();
    22→          },
    23→          setAll(cookiesToSet) {
    24→            try {
    25→              cookiesToSet.forEach(({ name, value, options }) =>
    26→                cookieStore.set(name, value, options)
    27→              );
    28→            } catch {
    29→              // Cookies can only be set in Server Actions or Route Handlers
    30→            }
    31→          },
    32→        },
    33→      }
    34→    );
    35→
    36→    const { error } = await supabase.auth.exchangeCodeForSession(code);
    37→
    38→    if (!error) {
    39→      return NextResponse.redirect(`${origin}${next}`);
    40→    }
    41→  }
    42→
    43→  return NextResponse.redirect(
    44→    `${origin}/auth/login?error=Could+not+authenticate`
    45→  );
    46→}
    47→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


Now let me read all the lib files systematically.


     1→import { createServerClient } from "@supabase/ssr";
     2→import { createClient as createSupabaseClient, SupabaseClient } from "@supabase/supabase-js";
     3→import { cookies } from "next/headers";
     4→
     5→/**
     6→ * Cookie-based server client WITH user context.
     7→ * Use this in Server Components, Server Actions, and Route Handlers
     8→ * where you need to know who the logged-in user is.
     9→ */
    10→export async function createClient() {
    11→  const cookieStore = await cookies();
    12→
    13→  return createServerClient(
    14→    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    15→    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    16→    {
    17→      cookies: {
    18→        getAll() {
    19→          return cookieStore.getAll();
    20→        },
    21→        setAll(cookiesToSet) {
    22→          try {
    23→            cookiesToSet.forEach(({ name, value, options }) =>
    24→              cookieStore.set(name, value, options)
    25→            );
    26→          } catch {
    27→            // Cookies can only be set in Server Actions or Route Handlers
    28→          }
    29→        },
    30→      },
    31→    }
    32→  );
    33→}
    34→
    35→// Lazy-initialized admin client to avoid build-time errors (H11 fix)
    36→let _supabaseAdmin: SupabaseClient | null = null;
    37→
    38→/**
    39→ * Admin client with service key — bypasses RLS.
    40→ * Use ONLY for admin operations (email verification, etc.)
    41→ * NEVER use this to read user data in server components.
    42→ *
    43→ * Throws if SUPABASE_SERVICE_KEY is not configured.
    44→ */
    45→export function getSupabaseAdmin(): SupabaseClient {
    46→  if (_supabaseAdmin) {
    47→    return _supabaseAdmin;
    48→  }
    49→
    50→  const serviceKey = process.env.SUPABASE_SERVICE_KEY;
    51→  const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
    52→
    53→  if (!serviceKey) {
    54→    throw new Error(
    55→      'SUPABASE_SERVICE_KEY is not configured. ' +
    56→      'Please add it to your .env.local file. ' +
    57→      'See SUPABASE-KEY-ROTATION-GUIDE.md for details.'
    58→    );
    59→  }
    60→
    61→  if (!supabaseUrl) {
    62→    throw new Error(
    63→      'NEXT_PUBLIC_SUPABASE_URL is not configured. ' +
    64→      'Please add it to your .env.local file.'
    65→    );
    66→  }
    67→
    68→  _supabaseAdmin = createSupabaseClient(supabaseUrl, serviceKey, {
    69→    auth: {
    70→      autoRefreshToken: false,
    71→      persistSession: false,
    72→    },
    73→  });
    74→
    75→  return _supabaseAdmin;
    76→}
    77→
    78→/**
    79→ * @deprecated Use getSupabaseAdmin() instead. This export will be removed.
    80→ * Admin client with service key — bypasses RLS.
    81→ */
    82→export const supabaseAdmin = new Proxy({} as SupabaseClient, {
    83→  get(target, prop) {
    84→    const admin = getSupabaseAdmin();
    85→    return admin[prop as keyof SupabaseClient];
    86→  },
    87→});
    88→
    89→/**
    90→ * Helper to verify email addresses (admin operation)
    91→ */
    92→export async function verifyEmail(userId: string) {
    93→  const admin = getSupabaseAdmin();
    94→  const { data, error } = await admin.auth.admin.updateUserById(
    95→    userId,
    96→    { email_confirm: true }
    97→  );
    98→
    99→  if (error) throw error;
   100→  return data;
   101→}
   102→
   103→/**
   104→ * Helper to check if user has verified their email (admin operation)
   105→ */
   106→export async function isEmailVerified(userId: string): Promise<boolean> {
   107→  const admin = getSupabaseAdmin();
   108→  const { data, error } = await admin.auth.admin.getUserById(userId);
   109→
   110→  if (error) return false;
   111→  return !!data.user?.email_confirmed_at;
   112→}
   113→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


     1→/**
     2→ * Client-side Supabase configuration with enhanced security
     3→ *
     4→ * IMPORTANT: This module does NOT export a singleton instance.
     5→ * Always use createClient() to get a fresh client instance.
     6→ * This prevents stale auth credentials on shared devices.
     7→ */
     8→
     9→import { createBrowserClient } from '@supabase/ssr'
    10→import type { SupabaseClient } from '@supabase/supabase-js'
    11→
    12→const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!
    13→const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
    14→
    15→/**
    16→ * Creates a Supabase client for use in browser/Client Components.
    17→ * Uses @supabase/ssr to properly sync cookies with the server.
    18→ */
    19→export function createClient(): SupabaseClient {
    20→  return createBrowserClient(supabaseUrl, supabaseAnonKey)
    21→}
    22→
    23→/**
    24→ * @deprecated Use createClient() instead. This export will be removed.
    25→ * Direct supabase export is disabled to prevent stale auth on shared devices.
    26→ * Always call createClient() to get a fresh instance.
    27→ */
    28→export const supabase = new Proxy({} as SupabaseClient, {
    29→  get() {
    30→    throw new Error(
    31→      'Direct supabase import is deprecated and disabled for security. ' +
    32→      'Use createClient() instead to prevent stale auth on shared devices.'
    33→    )
    34→  },
    35→})
    36→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


     1→import { createServerClient } from "@supabase/ssr";
     2→import { NextResponse, type NextRequest } from "next/server";
     3→
     4→export async function updateSession(request: NextRequest) {
     5→  let supabaseResponse = NextResponse.next({
     6→    request,
     7→  });
     8→
     9→  const supabase = createServerClient(
    10→    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    11→    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    12→    {
    13→      cookies: {
    14→        getAll() {
    15→          return request.cookies.getAll();
    16→        },
    17→        setAll(cookiesToSet) {
    18→          cookiesToSet.forEach(({ name, value }) =>
    19→            request.cookies.set(name, value)
    20→          );
    21→          supabaseResponse = NextResponse.next({
    22→            request,
    23→          });
    24→          cookiesToSet.forEach(({ name, value, options }) =>
    25→            supabaseResponse.cookies.set(name, value, options)
    26→          );
    27→        },
    28→      },
    29→    }
    30→  );
    31→
    32→  // Use getUser() — the only safe server-side auth check (validates JWT with Supabase).
    33→  // Do NOT use getSession() here — it reads unverified cookie data.
    34→  const {
    35→    data: { user },
    36→  } = await supabase.auth.getUser();
    37→
    38→  const pathname = request.nextUrl.pathname;
    39→
    40→  // Route classification
    41→  const protectedPaths = ["/dashboard", "/contractor", "/worker", "/messages", "/profile"];
    42→  const isProtected = protectedPaths.some((p) => pathname.startsWith(p));
    43→  const isSignup = pathname.includes("/signup");
    44→  const isVerifyPage = pathname === "/auth/verify-email";
    45→  const isAuthPage = pathname.startsWith("/auth/login") || pathname.startsWith("/auth/signup");
    46→
    47→  // ── Not logged in ──
    48→  if (!user) {
    49→    // Redirect unauthenticated users away from protected routes (but allow signup pages)
    50→    if (isProtected && !isSignup) {
    51→      const url = request.nextUrl.clone();
    52→      url.pathname = "/auth/login";
    53→      return NextResponse.redirect(url);
    54→    }
    55→    return supabaseResponse;
    56→  }
    57→
    58→  // ── Logged in ──
    59→  const emailVerified = !!user.email_confirmed_at;
    60→
    61→  if (!emailVerified) {
    62→    // Unverified user trying to access protected routes → send to verify page
    63→    // (but let them stay on signup flows to finish onboarding after verification)
    64→    if (isProtected && !isSignup && !isVerifyPage) {
    65→      const url = request.nextUrl.clone();
    66→      url.pathname = "/auth/verify-email";
    67→      return NextResponse.redirect(url);
    68→    }
    69→  } else {
    70→    // Verified user on auth pages → send to dashboard
    71→    if (isAuthPage || isVerifyPage) {
    72→      const url = request.nextUrl.clone();
    73→      url.pathname = "/dashboard";
    74→      return NextResponse.redirect(url);
    75→    }
    76→  }
    77→
    78→  return supabaseResponse;
    79→}
    80→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


     1→export type UserType = "worker" | "contractor";
     2→
     3→export type Availability = "full-time" | "casual" | "weekends" | "flexible";
     4→
     5→export type RateType = "hourly" | "daily";
     6→
     7→export type JobStatus = "draft" | "active" | "filled" | "closed";
     8→
     9→export type MatchStatus =
    10→  | "suggested"
    11→  | "applied"
    12→  | "accepted"
    13→  | "rejected"
    14→  | "hired";
    15→
    16→export type ActionStatus = "pending" | "accepted" | "rejected";
    17→
    18→export type PaymentStatus = "pending" | "charged" | "refunded";
    19→
    20→export type NotificationType = "match" | "hire_request" | "hire_confirmed" | "rating" | "job_posted" | "system";
    21→
    22→export interface Profile {
    23→  id: string;
    24→  type: UserType;
    25→  name: string | null;
    26→  phone: string | null;
    27→  email: string | null;
    28→  avatar_url: string | null;
    29→  /**
    30→   * Location as { lat, lng } object
    31→   * Note: Database stores this as PostgreSQL point type
    32→   * Use postgresPointToJson() when reading from DB
    33→   * Use jsonToPostgresPoint() when writing to DB
    34→   */
    35→  location: { lat: number; lng: number } | null;
    36→  suburb: string | null;
    37→  verified: boolean;
    38→  onboarding_completed: boolean;
    39→  created_at: string;
    40→  updated_at: string;
    41→  ai_generated: boolean;
    42→}
    43→
    44→export interface Company {
    45→  id: string;
    46→  profile_id: string;
    47→  name: string;
    48→  abn: string | null;
    49→  address: string | null;
    50→  industry: string | null;
    51→  size: string | null;
    52→  website: string | null;
    53→  logo_url: string | null;
    54→  ai_research: Record<string, unknown> | null;
    55→  created_at: string;
    56→  updated_at: string;
    57→}
    58→
    59→export interface WorkerProfile {
    60→  id: string;
    61→  profile_id: string;
    62→  trades: string[];
    63→  experience_years: number | null;
    64→  certifications: string[];
    65→  white_card_number: string | null;
    66→  white_card_verified: boolean;
    67→  availability: Availability | null;
    68→  languages: string[];
    69→  bio: string | null;
    70→  voice_note_url: string | null;
    71→  ai_extracted: Record<string, unknown> | null;
    72→  created_at: string;
    73→  updated_at: string;
    74→}
    75→
    76→export interface Job {
    77→  id: string;
    78→  company_id: string;
    79→  title: string;
    80→  description: string | null;
    81→  trade: string;
    82→  /**
    83→   * Location as { lat, lng } object
    84→   * Note: Database stores this as PostgreSQL point type
    85→   * Use postgresPointToJson() when reading from DB
    86→   * Use jsonToPostgresPoint() when writing to DB
    87→   */
    88→  location: { lat: number; lng: number } | null;
    89→  suburb: string | null;
    90→  site_address: string | null;
    91→  rate_min: number | null;
    92→  rate_max: number | null;
    93→  rate_type: RateType;
    94→  start_date: string | null;
    95→  duration: string | null;
    96→  workers_needed: number;
    97→  certifications_required: string[];
    98→  status: JobStatus;
    99→  ai_generated: boolean;
   100→  created_at: string;
   101→  updated_at: string;
   102→}
   103→
   104→export interface Match {
   105→  id: string;
   106→  job_id: string;
   107→  worker_id: string;
   108→  status: MatchStatus;
   109→  match_score: number | null;
   110→  ai_summary: string | null;
   111→  contractor_action: ActionStatus;
   112→  worker_action: ActionStatus;
   113→  hired_at: string | null;
   114→  fee_charged: boolean;
   115→  created_at: string;
   116→  updated_at: string;
   117→}
   118→
   119→export interface Rating {
   120→  id: string;
   121→  match_id: string;
   122→  from_id: string;
   123→  to_id: string;
   124→  score: number;
   125→  comment: string | null;
   126→  created_at: string;
   127→}
   128→
   129→export interface Payment {
   130→  id: string;
   131→  match_id: string;
   132→  company_id: string;
   133→  amount: number;
   134→  status: PaymentStatus;
   135→  stripe_payment_id: string | null;
   136→  created_at: string;
   137→}
   138→
   139→export interface Notification {
   140→  id: string;
   141→  profile_id: string;
   142→  type: NotificationType;
   143→  title: string;
   144→  body: string | null;
   145→  data: Record<string, unknown> | null;
   146→  read: boolean;
   147→  created_at: string;
   148→}
   149→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


     1→import { clsx, type ClassValue } from "clsx"
     2→import { twMerge } from "tailwind-merge"
     3→
     4→export function cn(...inputs: ClassValue[]) {
     5→  return twMerge(clsx(inputs))
     6→}
     7→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


     1→import { NextRequest, NextResponse } from 'next/server';
     2→import { randomBytes } from 'crypto';
     3→
     4→// CSRF token configuration
     5→const CSRF_TOKEN_LENGTH = 32;
     6→const CSRF_COOKIE_NAME = 'csrf-token';
     7→const CSRF_HEADER_NAME = 'x-csrf-token';
     8→const CSRF_FORM_FIELD_NAME = 'csrf_token';
     9→
    10→/**
    11→ * Generate a cryptographically secure CSRF token
    12→ */
    13→export function generateCSRFToken(): string {
    14→  return randomBytes(CSRF_TOKEN_LENGTH).toString('hex');
    15→}
    16→
    17→/**
    18→ * Create a CSRF token and set it in a cookie
    19→ */
    20→export function createCSRFToken(): { token: string; cookie: string } {
    21→  const token = generateCSRFToken();
    22→  let cookie = `${CSRF_COOKIE_NAME}=${token}; Path=/; SameSite=Strict; Max-Age=86400`; // 24 hours
    23→
    24→  // Add secure flag in production
    25→  if (process.env.NODE_ENV === 'production' && process.env.NEXT_PUBLIC_APP_URL?.startsWith('https://')) {
    26→    cookie += '; Secure';
    27→  }
    28→
    29→  return { token, cookie };
    30→}
    31→
    32→/**
    33→ * Get CSRF token from cookie
    34→ */
    35→export function getCSRFCookieToken(request: Request | NextRequest): string | null {
    36→  // Check if it's a NextRequest with cookies property
    37→  if ('cookies' in request && typeof request.cookies.get === 'function') {
    38→    const cookie = request.cookies.get(CSRF_COOKIE_NAME);
    39→    return cookie?.value || null;
    40→  }
    41→
    42→  // Fall back to parsing Cookie header for standard Request
    43→  const cookieHeader = request.headers.get('cookie');
    44→  if (!cookieHeader) return null;
    45→
    46→  const cookies = cookieHeader.split(';');
    47→  for (const cookie of cookies) {
    48→    const [name, value] = cookie.trim().split('=');
    49→    if (name === CSRF_COOKIE_NAME) {
    50→      return value;
    51→    }
    52→  }
    53→  return null;
    54→}
    55→
    56→/**
    57→ * Get CSRF token from request (header or form data)
    58→ */
    59→export function getCSRFRequestToken(request: Request | NextRequest): string | null {
    60→  // Check header first
    61→  const headerToken = request.headers.get(CSRF_HEADER_NAME);
    62→  if (headerToken) return headerToken;
    63→
    64→  // For form data, check in the request body (will need to parse appropriately)
    65→  // This is handled in the validation function for JSON requests
    66→  return null;
    67→}
    68→
    69→/**
    70→ * Validate CSRF token from request
    71→ * For JSON requests: checks x-csrf-token header
    72→ * For form requests: checks form field
    73→ */
    74→export async function validateCSRFToken(request: Request | NextRequest): Promise<boolean> {
    75→  const cookieToken = getCSRFCookieToken(request);
    76→
    77→  if (!cookieToken) {
    78→    console.error('CSRF validation failed: No token in cookie');
    79→    return false;
    80→  }
    81→
    82→  let requestToken: string | null = null;
    83→
    84→  // For JSON requests
    85→  if (request.headers.get('content-type')?.includes('application/json')) {
    86→    requestToken = request.headers.get(CSRF_HEADER_NAME);
    87→
    88→    // If no header, check in JSON body
    89→    if (!requestToken) {
    90→      try {
    91→        const body = await request.clone().json();
    92→        requestToken = body[CSRF_FORM_FIELD_NAME] || null;
    93→      } catch {
    94→        // Ignore JSON parsing errors
    95→      }
    96→    }
    97→  }
    98→
    99→  // For form data requests
   100→  else if (request.headers.get('content-type')?.includes('multipart/form-data')) {
   101→    try {
   102→      const formData = await request.clone().formData();
   103→      requestToken = formData.get(CSRF_FORM_FIELD_NAME) as string;
   104→    } catch {
   105→      // Ignore form parsing errors
   106→    }
   107→  }
   108→
   109→  if (!requestToken) {
   110→    console.error('CSRF validation failed: No token in request');
   111→    return false;
   112→  }
   113→
   114→  // Constant-time comparison to prevent timing attacks
   115→  const tokensMatch = timingSafeEqual(cookieToken, requestToken);
   116→
   117→  if (!tokensMatch) {
   118→    console.error('CSRF validation failed: Token mismatch');
   119→    return false;
   120→  }
   121→
   122→  return true;
   123→}
   124→
   125→/**
   126→ * Timing-safe string comparison
   127→ */
   128→function timingSafeEqual(a: string, b: string): boolean {
   129→  if (a.length !== b.length) return false;
   130→
   131→  let result = 0;
   132→  for (let i = 0; i < a.length; i++) {
   133→    result |= a.charCodeAt(i) ^ b.charCodeAt(i);
   134→  }
   135→
   136→  return result === 0;
   137→}
   138→
   139→/**
   140→ * Check if request is same-origin
   141→ */
   142→export function isSameOrigin(request: Request | NextRequest): boolean {
   143→  const origin = request.headers.get('origin');
   144→  const referer = request.headers.get('referer');
   145→
   146→  if (!origin && !referer) {
   147→    // No origin/referer headers - reject the request to prevent CSRF bypass
   148→    return false;
   149→  }
   150→
   151→  const checkUrl = origin || referer;
   152→  if (!checkUrl) return false;
   153→
   154→  try {
   155→    const parsedUrl = new URL(checkUrl);
   156→    const appUrl = new URL(process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000');
   157→
   158→    return parsedUrl.origin === appUrl.origin;
   159→  } catch {
   160→    return false;
   161→  }
   162→}
   163→
   164→/**
   165→ * CSRF protection middleware
   166→ * Use this for routes that need CSRF protection
   167→ */
   168→export function withCSRFProtection<T extends NextRequest>(
   169→  handler: (request: T) => Promise<NextResponse | Response>,
   170→  options?: {
   171→    requireSameOrigin?: boolean;
   172→    skipValidation?: boolean;
   173→  }
   174→): (request: T) => Promise<NextResponse | Response> {
   175→  return async (request: T) => {
   176→    // Skip CSRF for GET, HEAD, OPTIONS requests
   177→    if (['GET', 'HEAD', 'OPTIONS'].includes(request.method)) {
   178→      return handler(request);
   179→    }
   180→
   181→    // Check if request is same-origin (optional, but recommended)
   182→    if (options?.requireSameOrigin !== false && !isSameOrigin(request)) {
   183→      return NextResponse.json(
   184→        { error: 'Cross-origin request not allowed' },
   185→        { status: 403 }
   186→      );
   187→    }
   188→
   189→    // Validate CSRF token (unless explicitly skipped)
   190→    if (!options?.skipValidation) {
   191→      const isValid = await validateCSRFToken(request);
   192→      if (!isValid) {
   193→        return NextResponse.json(
   194→          { error: 'Invalid or missing CSRF token' },
   195→          { status: 403 }
   196→        );
   197→      }
   198→    }
   199→
   200→    return handler(request);
   201→  };
   202→}
   203→
   204→/**
   205→ * Create a CSRF token endpoint
   206→ * This should be called when a user session starts
   207→ */
   208→export async function createCSRTEndpoint(request: NextRequest): Promise<NextResponse> {
   209→  if (request.method !== 'GET') {
   210→    return NextResponse.json({ error: 'Method not allowed' }, { status: 405 });
   211→  }
   212→
   213→  const { token, cookie } = createCSRFToken();
   214→
   215→  const response = NextResponse.json({ token });
   216→  response.headers.append('Set-Cookie', cookie);
   217→
   218→  return response;
   219→}

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


Let me continue reading the remaining lib files.


     1→/**
     2→ * Simple in-memory rate limiting for API endpoints
     3→ * In production, use Redis or a proper rate limiting service
     4→ *
     5→ * SECURITY NOTES:
     6→ * - This is an in-memory store, so rate limits are per-instance
     7→ * - For multi-instance deployments, use Redis or a shared cache
     8→ * - IP-based rate limiting can be bypassed by users with rotating IPs
     9→ * - Consider adding user ID-based limiting for authenticated routes
    10→ */
    11→
    12→interface RateLimitEntry {
    13→  count: number;
    14→  resetTime: number;
    15→}
    16→
    17→const rateLimitStore = new Map<string, RateLimitEntry>();
    18→const WINDOW_MS = 15 * 60 * 1000; // 15 minutes
    19→const MAX_REQUESTS = 100; // 100 requests per window (more generous for general use)
    20→
    21→// Cleanup interval to prevent memory leaks (H14 fix)
    22→const CLEANUP_INTERVAL_MS = 5 * 60 * 1000; // Run cleanup every 5 minutes
    23→let cleanupInterval: NodeJS.Timeout | null = null;
    24→
    25→/**
    26→ * Start the cleanup interval to prevent memory leaks
    27→ * This should be called once when the app starts
    28→ */
    29→export function startRateLimitCleanup(): void {
    30→  if (cleanupInterval) return; // Already running
    31→
    32→  cleanupInterval = setInterval(() => {
    33→    const now = Date.now();
    34→    let cleaned = 0;
    35→
    36→    for (const [key, entry] of rateLimitStore.entries()) {
    37→      if (now > entry.resetTime) {
    38→        rateLimitStore.delete(key);
    39→        cleaned++;
    40→      }
    41→    }
    42→
    43→    if (cleaned > 0) {
    44→      console.log(`[RateLimit] Cleaned up ${cleaned} expired entries`);
    45→    }
    46→  }, CLEANUP_INTERVAL_MS);
    47→
    48→  // Ensure cleanup doesn't prevent process exit (for serverless)
    49→  if (cleanupInterval.unref) {
    50→    cleanupInterval.unref();
    51→  }
    52→}
    53→
    54→/**
    55→ * Stop the cleanup interval (useful for testing)
    56→ */
    57→export function stopRateLimitCleanup(): void {
    58→  if (cleanupInterval) {
    59→    clearInterval(cleanupInterval);
    60→    cleanupInterval = null;
    61→  }
    62→}
    63→
    64→/**
    65→ * Get client IP address from request
    66→ * Handles various proxy scenarios and falls back to 'unknown'
    67→ */
    68→export function getClientIP(request: Request): string {
    69→  // Try X-Forwarded-For first (common proxy header)
    70→  const forwarded = request.headers.get('x-forwarded-for');
    71→  if (forwarded) {
    72→    // X-Forwarded-For can be a comma-separated list; use the first (original client)
    73→    return forwarded.split(',')[0].trim();
    74→  }
    75→
    76→  // Try other common headers
    77→  const realIP = request.headers.get('x-real-ip');
    78→  if (realIP) return realIP;
    79→
    80→  // Fallback to a default (for serverless/edge environments)
    81→  return 'unknown';
    82→}
    83→
    84→/**
    85→ * Check if request is allowed based on rate limit
    86→ * @param identifier - Unique identifier (IP address, user ID, etc.)
    87→ * @param maxRequests - Maximum requests allowed in the window (optional, uses default if not provided)
    88→ * @param windowMs - Time window in milliseconds (optional, uses default if not provided)
    89→ * @returns Object with allowed status and remaining requests
    90→ */
    91→export function checkRateLimit(
    92→  identifier: string,
    93→  maxRequests?: number,
    94→  windowMs?: number
    95→): {
    96→  allowed: boolean;
    97→  remaining: number;
    98→  resetTime: number;
    99→  totalLimit: number;
   100→} {
   101→  const limit = maxRequests ?? MAX_REQUESTS;
   102→  const window = windowMs ?? WINDOW_MS;
   103→  const now = Date.now();
   104→  const entry = rateLimitStore.get(identifier);
   105→
   106→  if (!entry || now > entry.resetTime) {
   107→    // New window or expired
   108→    rateLimitStore.set(identifier, {
   109→      count: 1,
   110→      resetTime: now + window,
   111→    });
   112→    return {
   113→      allowed: true,
   114→      remaining: limit - 1,
   115→      resetTime: now + window,
   116→      totalLimit: limit,
   117→    };
   118→  }
   119→
   120→  if (entry.count >= limit) {
   121→    // Rate limit exceeded
   122→    return {
   123→      allowed: false,
   124→      remaining: 0,
   125→      resetTime: entry.resetTime,
   126→      totalLimit: limit,
   127→    };
   128→  }
   129→
   130→  // Within limit
   131→  entry.count++;
   132→  return {
   133→    allowed: true,
   134→    remaining: limit - entry.count,
   135→    resetTime: entry.resetTime,
   136→    totalLimit: limit,
   137→  };
   138→}
   139→
   140→/**
   141→ * Get rate limit headers for response
   142→ */
   143→export function getRateLimitHeaders(
   144→  identifier: string,
   145→  maxRequests?: number,
   146→  windowMs?: number
   147→): Record<string, string> {
   148→  const limit = checkRateLimit(identifier, maxRequests, windowMs);
   149→  return {
   150→    'X-RateLimit-Limit': limit.totalLimit.toString(),
   151→    'X-RateLimit-Remaining': Math.max(0, limit.remaining).toString(),
   152→    'X-RateLimit-Reset': new Date(limit.resetTime).toISOString(),
   153→  };
   154→}
   155→
   156→/**
   157→ * Create a rate limit response with proper headers
   158→ */
   159→export function createRateLimitResponse(
   160→  identifier: string,
   161→  retryAfterSeconds?: number
   162→): Response {
   163→  const headers = new Headers({
   164→    'Content-Type': 'application/json',
   165→    'X-RateLimit-Limit': MAX_REQUESTS.toString(),
   166→    'X-RateLimit-Remaining': '0',
   167→  });
   168→
   169→  if (retryAfterSeconds) {
   170→    headers.set('Retry-After', retryAfterSeconds.toString());
   171→  }
   172→
   173→  return new Response(
   174→    JSON.stringify({
   175→      error: 'Rate limit exceeded. Please try again later.',
   176→      retryAfter: retryAfterSeconds,
   177→    }),
   178→    {
   179→      status: 429,
   180→      headers,
   181→    }
   182→  );
   183→}
   184→
   185→// Auto-start cleanup when module loads
   186→startRateLimitCleanup();
   187→
   188→// Export constants for use in route handlers
   189→export { MAX_REQUESTS, WINDOW_MS };
   190→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


     1→/**
     2→ * Sanitizes user input to prevent prompt injection attacks
     3→ * Strips control characters, limits length, and escapes template injection
     4→ */
     5→
     6→/**
     7→ * Strip control characters (0x00-0x1F, 0x7F)
     8→ */
     9→function stripControlChars(str: string): string {
    10→  return str.replace(/[\x00-\x1F\x7F]/g, '');
    11→}
    12→
    13→/**
    14→ * Escape template literals to prevent injection
    15→ */
    16→function escapeTemplateLiterals(str: string): string {
    17→  return str
    18→    .replace(/\\/g, '\\\\')  // Escape backslashes first
    19→    .replace(/\$/g, '\\$')   // Escape $ to prevent ${} injection
    20→    .replace(/`/g, '\\`');   // Escape backticks
    21→}
    22→
    23→/**
    24→ * Sanitize string input
    25→ */
    26→export function sanitizeInput(
    27→  input: string,
    28→  options: {
    29→    maxLength?: number;
    30→    stripControls?: boolean;
    31→    escapeTemplates?: boolean;
    32→  } = {}
    33→): string {
    34→  if (typeof input !== 'string') {
    35→    return '';
    36→  }
    37→
    38→  let sanitized = input;
    39→
    40→  // Strip control characters by default
    41→  if (options.stripControls !== false) {
    42→    sanitized = stripControlChars(sanitized);
    43→  }
    44→
    45→  // Escape template literals by default
    46→  if (options.escapeTemplates !== false) {
    47→    sanitized = escapeTemplateLiterals(sanitized);
    48→  }
    49→
    50→  // Trim whitespace
    51→  sanitized = sanitized.trim();
    52→
    53→  // Limit length if specified
    54→  if (options.maxLength && sanitized.length > options.maxLength) {
    55→    sanitized = sanitized.substring(0, options.maxLength);
    56→  }
    57→
    58→  return sanitized;
    59→}
    60→
    61→/**
    62→ * Sanitize multiple string inputs at once
    63→ */
    64→export function sanitizeInputs(
    65→  inputs: Record<string, string>,
    66→  fieldOptions: Record<string, { maxLength?: number }> = {}
    67→): Record<string, string> {
    68→  const sanitized: Record<string, string> = {};
    69→
    70→  for (const [key, value] of Object.entries(inputs)) {
    71→    const options = fieldOptions[key] || {};
    72→    sanitized[key] = sanitizeInput(value, options);
    73→  }
    74→
    75→  return sanitized;
    76→}

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


     1→import { NextResponse } from "next/server";
     2→
     3→export class APIError extends Error {
     4→  constructor(
     5→    message: string,
     6→    public statusCode: number = 500,
     7→    public code?: string
     8→  ) {
     9→    super(message);
    10→    this.name = "APIError";
    11→  }
    12→}
    13→
    14→export const ErrorCodes = {
    15→  // Authentication errors
    16→  UNAUTHORIZED: "UNAUTHORIZED",
    17→  FORBIDDEN: "FORBIDDEN",
    18→  TOKEN_EXPIRED: "TOKEN_EXPIRED",
    19→
    20→  // Validation errors
    21→  VALIDATION_ERROR: "VALIDATION_ERROR",
    22→  INVALID_INPUT: "INVALID_INPUT",
    23→  MISSING_REQUIRED_FIELD: "MISSING_REQUIRED_FIELD",
    24→
    25→  // Rate limiting
    26→  RATE_LIMIT_EXCEEDED: "RATE_LIMIT_EXCEEDED",
    27→
    28→  // Service errors
    29→  SERVICE_UNAVAILABLE: "SERVICE_UNAVAILABLE",
    30→  EXTERNAL_SERVICE_ERROR: "EXTERNAL_SERVICE_ERROR",
    31→
    32→  // Resource errors
    33→  NOT_FOUND: "NOT_FOUND",
    34→  CONFLICT: "CONFLICT",
    35→
    36→  // Generic error
    37→  INTERNAL_ERROR: "INTERNAL_ERROR",
    38→} as const;
    39→
    40→export type ErrorCode = typeof ErrorCodes[keyof typeof ErrorCodes];
    41→
    42→interface ErrorResponse {
    43→  error: string;
    44→  code?: ErrorCode;
    45→  details?: any;
    46→  timestamp: string;
    47→  path?: string;
    48→}
    49→
    50→/**
    51→ * Creates a standardized error response
    52→ */
    53→export function createErrorResponse(
    54→  message: string,
    55→  statusCode: number = 500,
    56→  code?: ErrorCode,
    57→  details?: any
    58→): NextResponse<ErrorResponse> {
    59→  const errorResponse: ErrorResponse = {
    60→    error: message,
    61→    code,
    62→    details: process.env.NODE_ENV === "development" ? details : undefined,
    63→    timestamp: new Date().toISOString(),
    64→  };
    65→
    66→  return NextResponse.json(errorResponse, { status: statusCode });
    67→}
    68→
    69→/**
    70→ * Common error response helpers
    71→ */
    72→export const ErrorResponses = {
    73→  unauthorized: (message = "Unauthorized") =>
    74→    createErrorResponse(message, 401, ErrorCodes.UNAUTHORIZED),
    75→
    76→  forbidden: (message = "Forbidden") =>
    77→    createErrorResponse(message, 403, ErrorCodes.FORBIDDEN),
    78→
    79→  notFound: (message = "Not found") =>
    80→    createErrorResponse(message, 404, ErrorCodes.NOT_FOUND),
    81→
    82→  validation: (message = "Validation error", details?: any) =>
    83→    createErrorResponse(message, 400, ErrorCodes.VALIDATION_ERROR, details),
    84→
    85→  rateLimit: (message = "Rate limit exceeded", retryAfter?: number) => {
    86→    const response = createErrorResponse(message, 429, ErrorCodes.RATE_LIMIT_EXCEEDED);
    87→    if (retryAfter) {
    88→      response.headers.set("Retry-After", String(retryAfter));
    89→    }
    90→    return response;
    91→  },
    92→
    93→  internal: (message = "Internal server error", details?: any) =>
    94→    createErrorResponse(message, 500, ErrorCodes.INTERNAL_ERROR, details),
    95→
    96→  serviceUnavailable: (message = "Service temporarily unavailable") =>
    97→    createErrorResponse(message, 503, ErrorCodes.SERVICE_UNAVAILABLE),
    98→} as const;
    99→
   100→/**
   101→ * Wraps an API route handler with error handling
   102→ */
   103→export function withErrorHandler<T extends Request = Request>(
   104→  handler: (request: T) => Promise<Response>
   105→) {
   106→  return async (request: T): Promise<Response> => {
   107→    try {
   108→      return await handler(request);
   109→    } catch (error) {
   110→      console.error("API route error:", error);
   111→
   112→      if (error instanceof APIError) {
   113→        return createErrorResponse(
   114→          error.message,
   115→          error.statusCode,
   116→          error.code as ErrorCode
   117→        );
   118→      }
   119→
   120→      if (error instanceof Error) {
   121→        return ErrorResponses.internal(
   122→          "An unexpected error occurred",
   123→          process.env.NODE_ENV === "development" ? error.stack : undefined
   124→        );
   125→      }
   126→
   127→      return ErrorResponses.internal("An unexpected error occurred");
   128→    }
   129→  };
   130→}

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


     1→import Stripe from 'stripe';
     2→
     3→// Initialize Stripe with secret key
     4→export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY || '', {
     5→  apiVersion: '2026-01-28.clover', // Use latest API version
     6→  typescript: true,
     7→});
     8→
     9→// Validate Stripe is configured
    10→export function validateStripeConfig(): void {
    11→  if (!process.env.STRIPE_SECRET_KEY) {
    12→    throw new Error('STRIPE_SECRET_KEY is not configured');
    13→  }
    14→  if (!process.env.STRIPE_WEBHOOK_SECRET) {
    15→    throw new Error('STRIPE_WEBHOOK_SECRET is not configured');
    16→  }
    17→}
    18→
    19→// Create a PaymentIntent for hiring a worker
    20→export async function createPaymentIntent({
    21→  amount,
    22→  matchId,
    23→  companyId,
    24→  companyName,
    25→  description,
    26→}: {
    27→  amount: number;
    28→  matchId: string;
    29→  companyId: string;
    30→  companyName: string;
    31→  description: string;
    32→}) {
    33→  try {
    34→    const paymentIntent = await stripe.paymentIntents.create({
    35→      amount: Math.round(amount * 100), // Convert to cents
    36→      currency: 'aud',
    37→      automatic_payment_methods: {
    38→        enabled: true,
    39→      },
    40→      metadata: {
    41→        match_id: matchId,
    42→        company_id: companyId,
    43→        company_name: companyName,
    44→        description,
    45→      },
    46→      description: `RateRight Hire Fee - ${description}`,
    47→    });
    48→
    49→    return {
    50→      clientSecret: paymentIntent.client_secret,
    51→      paymentIntentId: paymentIntent.id,
    52→    };
    53→  } catch (error) {
    54→    console.error('Error creating PaymentIntent:', error);
    55→    throw new Error('Failed to create payment intent');
    56→  }
    57→}
    58→
    59→// Retrieve a PaymentIntent
    60→export async function retrievePaymentIntent(paymentIntentId: string) {
    61→  try {
    62→    return await stripe.paymentIntents.retrieve(paymentIntentId);
    63→  } catch (error) {
    64→    console.error('Error retrieving PaymentIntent:', error);
    65→    throw new Error('Failed to retrieve payment intent');
    66→  }
    67→}
    68→
    69→// Construct webhook event with signature verification
    70→export function constructWebhookEvent(
    71→  payload: string | Buffer,
    72→  signature: string
    73→): Stripe.Event {
    74→  const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
    75→
    76→  if (!webhookSecret) {
    77→    throw new Error('STRIPE_WEBHOOK_SECRET is not configured');
    78→  }
    79→
    80→  try {
    81→    return stripe.webhooks.constructEvent(payload, signature, webhookSecret);
    82→  } catch (error) {
    83→    console.error('Webhook signature verification failed:', error);
    84→    throw new Error('Invalid webhook signature');
    85→  }
    86→}
    87→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


     1→/**
     2→ * ABN (Australian Business Number) validation and formatting utilities
     3→ *
     4→ * Based on the official ABN validation algorithm:
     5→ * https://abr.business.gov.au/Help/AbnFormat
     6→ *
     7→ * Algorithm:
     8→ *   1. Subtract 1 from the first (left-most) digit
     9→ *   2. Multiply each of the 11 digits by its positional weight:
    10→ *      [10, 1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
    11→ *   3. Sum all 11 products
    12→ *   4. If sum mod 89 === 0 → valid ABN
    13→ *
    14→ * Worked example — 51 824 753 556:
    15→ *   Step 1: 41 824 753 556
    16→ *   Step 2: 4×10 + 1×1 + 8×3 + 2×5 + 4×7 + 7×9 + 5×11 + 3×13 + 5×15 + 5×17 + 6×19
    17→ *   Step 3: = 534
    18→ *   Step 4: 534 % 89 = 0  ✓
    19→ */
    20→
    21→const ABN_WEIGHTS = [10, 1, 3, 5, 7, 9, 11, 13, 15, 17, 19] as const;
    22→
    23→/**
    24→ * Strip an ABN string down to just its 11 digits.
    25→ * Returns the cleaned string (may be empty / wrong length if input is garbage).
    26→ */
    27→export function cleanABN(abn: string): string {
    28→  return abn.replace(/\D/g, '');
    29→}
    30→
    31→/**
    32→ * Validate an ABN using the modulus-89 checksum algorithm.
    33→ *
    34→ * Accepts any string — spaces, dashes, dots are stripped first.
    35→ * Returns `true` only when the input is exactly 11 digits AND the checksum passes.
    36→ */
    37→export function validateABN(abn: string): boolean {
    38→  const digits = cleanABN(abn);
    39→
    40→  if (digits.length !== 11) return false;
    41→
    42→  // Convert to number array, subtract 1 from first digit
    43→  const d = digits.split('').map(Number);
    44→  d[0] -= 1;
    45→
    46→  // Weighted sum
    47→  let sum = 0;
    48→  for (let i = 0; i < 11; i++) {
    49→    sum += d[i] * ABN_WEIGHTS[i];
    50→  }
    51→
    52→  return sum % 89 === 0;
    53→}
    54→
    55→/** Convenience alias used by the API route. */
    56→export const isValidABN = validateABN;
    57→
    58→/**
    59→ * Format an 11-digit ABN into the standard display format: XX XXX XXX XXX
    60→ * Returns empty string if input doesn't contain exactly 11 digits.
    61→ */
    62→export function formatABN(abn: string): string {
    63→  const d = cleanABN(abn);
    64→  if (d.length !== 11) return '';
    65→  return `${d.slice(0, 2)} ${d.slice(2, 5)} ${d.slice(5, 8)} ${d.slice(8)}`;
    66→}
    67→
    68→/**
    69→ * Validate + format in one call.
    70→ */
    71→export function validateAndFormatABN(abn: string): {
    72→  valid: boolean;
    73→  cleaned: string;
    74→  formatted: string;
    75→} {
    76→  const cleaned = cleanABN(abn);
    77→  const valid = validateABN(cleaned);
    78→  return {
    79→    valid,
    80→    cleaned,
    81→    formatted: valid ? formatABN(cleaned) : '',
    82→  };
    83→}
    84→
    85→/* ── Test ABNs from ABR documentation ── */
    86→export const TEST_ABNS = {
    87→  /** Valid — passes checksum */
    88→  VALID: '51 824 753 556',
    89→  /** Invalid — fails checksum (last digit changed) */
    90→  INVALID: '51 824 753 557',
    91→  /** Suppressed ABN (details hidden on ABR) */
    92→  SUPPRESSED: '34 241 177 887',
    93→} as const;
    94→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


     1→/**
     2→ * Converts PostgreSQL point format to JSON object format
     3→ * PostgreSQL point can come as:
     4→ * - String: "(lat,lng)"
     5→ * - Object: {x: lat, y: lng}
     6→ * - Already in JSON format: {lat, lng}
     7→ */
     8→export function postgresPointToJson(point: any): { lat: number; lng: number } | null {
     9→  if (!point) return null;
    10→
    11→  // If it's already in the correct JSON format
    12→  if (typeof point === 'object' && 'lat' in point && 'lng' in point) {
    13→    return point;
    14→  }
    15→
    16→  // If it's a string like "(lat,lng)"
    17→  if (typeof point === 'string') {
    18→    const match = point.match(/^\s*\(\s*(-?\d+\.?\d*)\s*,\s*(-?\d+\.?\d*)\s*\)\s*$/);
    19→    if (match) {
    20→      return {
    21→        lat: parseFloat(match[1]),
    22→        lng: parseFloat(match[2])
    23→      };
    24→    }
    25→  }
    26→
    27→  // If it's an object with x,y properties (PostgreSQL point object)
    28→  if (typeof point === 'object' && 'x' in point && 'y' in point) {
    29→    return {
    30→      lat: parseFloat(point.x),
    31→      lng: parseFloat(point.y)
    32→    };
    33→  }
    34→
    35→  console.warn('Unexpected location format:', point);
    36→  return null;
    37→}
    38→
    39→/**
    40→ * Converts JSON object format to PostgreSQL point format
    41→ * Returns a string in the format "(lat,lng)"
    42→ */
    43→export function jsonToPostgresPoint(location: { lat: number; lng: number } | null): string | null {
    44→  if (!location) return null;
    45→  return `(${location.lat},${location.lng})`;
    46→}
    47→
    48→/**
    49→ * Type guard to check if a value is a valid location object
    50→ */
    51→export function isValidLocation(obj: any): obj is { lat: number; lng: number } {
    52→  return obj &&
    53→         typeof obj === 'object' &&
    54→         typeof obj.lat === 'number' &&
    55→         typeof obj.lng === 'number' &&
    56→         !isNaN(obj.lat) &&
    57→         !isNaN(obj.lng);
    58→}

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


Now let me search for TODO, FIXME, HACK, and XXX comments across the codebase, and then read some of the page files.


/home/ccuser/the-50-dollar-app/src/lib/abn-utils.ts:59: * Format an 11-digit ABN into the standard display format: XX XXX XXX XXX
/home/ccuser/the-50-dollar-app/src/app/api/abn-lookup/route.ts:8: * ABN Lookup API Route — GET /api/abn-lookup?abn=XXXXXXXXXXX
/home/ccuser/the-50-dollar-app/src/app/api/abn-lookup/route.ts:201:  // TODO: Implement ASIC Connect API integration


     1→import { createClient } from "@/lib/supabase/server";
     2→
     3→export async function getOrCreateConversation(user1Id: string, user2Id: string) {
     4→  const supabase = await createClient();
     5→
     6→  // Use the database function to get or create conversation
     7→  const { data: conversationId, error } = await supabase
     8→    .rpc('get_or_create_conversation', {
     9→      user1_id: user1Id,
    10→      user2_id: user2Id
    11→    });
    12→
    13→  if (error) {
    14→    throw new Error(`Failed to get or create conversation: ${error.message}`);
    15→  }
    16→
    17→  return conversationId as string;
    18→}

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


     1→/**
     2→ * Extract domain from a URL
     3→ */
     4→export function extractDomain(url: string): string {
     5→  try {
     6→    // Add protocol if missing
     7→    if (!url.match(/^https?:\/\//)) {
     8→      url = 'https://' + url;
     9→    }
    10→
    11→    const urlObj = new URL(url);
    12→    return urlObj.hostname.replace(/^www\./, '');
    13→  } catch {
    14→    // If URL parsing fails, try to extract domain manually
    15→    const match = url.match(/^(?:https?:\/\/)?(?:www\.)?([^\/]+)/);
    16→    return match ? match[1] : url;
    17→  }
    18→}
    19→
    20→/**
    21→ * Generate likely domain variations from a company name
    22→ */
    23→export function guessCompanyDomain(name: string): string[] {
    24→  if (!name) return [];
    25→
    26→  // Remove common company suffixes
    27→  const cleanName = name
    28→    .toLowerCase()
    29→    .replace(/\b(?:pty|ltd|limited|inc|incorporated|corp|corporation|co|company)\b/gi, '')
    30→    .replace(/[^a-z0-9\s]/g, ' ')
    31→    .trim();
    32→
    33→  // Extract words
    34→  const words = cleanName.split(/\s+/).filter(word => word.length > 1);
    35→
    36→  const domains: string[] = [];
    37→
    38→  // Common TLDs for Australian businesses
    39→  const tlds = ['.com.au', '.net.au', '.org.au', '.com', '.net', '.org'];
    40→
    41→  // Strategy 1: Full company name (without spaces)
    42→  const fullName = words.join('');
    43→  tlds.forEach(tld => {
    44→    domains.push(fullName + tld);
    45→  });
    46→
    47→  // Strategy 2: First word only (if more than one word)
    48→  if (words.length > 1) {
    49→    tlds.forEach(tld => {
    50→      domains.push(words[0] + tld);
    51→    });
    52→  }
    53→
    54→  // Strategy 3: First two words (if more than two words)
    55→  if (words.length > 2) {
    56→    const firstTwo = words.slice(0, 2).join('');
    57→    tlds.forEach(tld => {
    58→      domains.push(firstTwo + tld);
    59→    });
    60→  }
    61→
    62→  // Strategy 4: Common abbreviations
    63→  const abbreviations = words.map(word => word[0]).join('');
    64→  if (abbreviations.length >= 2 && abbreviations.length <= 5) {
    65→    tlds.forEach(tld => {
    66→      domains.push(abbreviations + tld);
    67→    });
    68→  }
    69→
    70→  // Strategy 5: Building/Construction specific variations
    71→  const industryWords = ['building', 'construction', 'construct', 'builders', 'group'];
    72→  const hasIndustryWord = words.some(word => industryWords.includes(word));
    73→
    74→  if (!hasIndustryWord) {
    75→    // Add 'builders' to the end
    76→    tlds.forEach(tld => {
    77→      domains.push(fullName + 'builders' + tld);
    78→      if (words.length > 1) {
    79→        domains.push(words[0] + 'builders' + tld);
    80→      }
    81→    });
    82→  }
    83→
    84→  // Remove duplicates and return
    85→  return [...new Set(domains)];
    86→}
    87→
    88→/**
    89→ * Try to find company website from ABR data
    90→ * Note: This is a placeholder - ABR API doesn't typically provide website URLs
    91→ * You may need to integrate with other services or databases
    92→ */
    93→export function extractWebsiteFromABR(abrData: any): string | null {
    94→  // ABR typically doesn't provide website information
    95→  // This is a placeholder for future integration with other data sources
    96→  return null;
    97→}

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


1→import { useState, useCallback, useEffect } from "react";
     2→
     3→interface UseErrorBoundaryReturn {
     4→  error: Error | null;
     5→  hasError: boolean;
     6→  resetError: () => void;
     7→  setError: (error: Error) => void;
     8→}
     9→
    10→/**
    11→ * Hook for managing error boundary state
    12→ * Provides a way to catch and handle errors in components
    13→ */
    14→export function useErrorBoundary(): UseErrorBoundaryReturn {
    15→  const [error, setError] = useState<Error | null>(null);
    16→
    17→  const resetError = useCallback(() => {
    18→    setError(null);
    19→  }, []);
    20→
    21→  const handleSetError = useCallback((newError: Error) => {
    22→    console.error("Error caught by boundary:", newError);
    23→    setError(newError);
    24→  }, []);
    25→
    26→  return {
    27→    error,
    28→    hasError: error !== null,
    29→    resetError,
    30→    setError: handleSetError,
    31→  };
    32→}
    33→
    34→interface UseAsyncErrorReturn<T> {
    35→  data: T | null;
    36→  error: Error | null;
    37→  isLoading: boolean;
    38→  execute: (...args: any[]) => Promise<T | null>;
    39→  reset: () => void;
    40→}
    41→
    42→/**
    43→ * Hook for handling async operations with error boundaries
    44→ * Automatically catches and manages errors from async functions
    45→ */
    46→export function useAsyncError<T = any>(
    47→  asyncFunction: (...args: any[]) => Promise<T>,
    48→  deps: any[] = []
    49→): UseAsyncErrorReturn<T> {
    50→  const [data, setData] = useState<T | null>(null);
    51→  const [error, setError] = useState<Error | null>(null);
    52→  const [isLoading, setIsLoading] = useState(false);
    53→
    54→  const execute = useCallback(async (...args: any[]): Promise<T | null> => {
    55→    try {
    56→      setIsLoading(true);
    57→      setError(null);
    58→      const result = await asyncFunction(...args);
    59→      setData(result);
    60→      return result;
    61→    } catch (err) {
    62→      const error = err instanceof Error ? err : new Error(String(err));
    63→      setError(error);
    64→      console.error("Async operation error:", error);
    65→      return null;
    66→    } finally {
    67→      setIsLoading(false);
    68→    }
    69→  }, deps);
    70→
    71→  const reset = useCallback(() => {
    72→    setData(null);
    73→    setError(null);
    74→    setIsLoading(false);
    75→  }, []);
    76→
    77→  return {
    78→    data,
    79→    error,
    80→    isLoading,
    81→    execute,
    82→    reset,
    83→  };
    84→}
    85→
    86→/**
    87→ * Error boundary component that can be used to wrap components
    88→ * This is a simple implementation for functional components
    89→ */
    90→export function withErrorBoundary
(
    91→  Component: React.ComponentType
,
    92→  fallback?: React.ComponentType<{ error: Error; reset: () => void }>
    93→) {
    94→  return function WrappedComponent(props: P) {
    95→    const [error, setError] = useState<Error | null>(null);
    96→
    97→    useEffect(() => {
    98→      const handleError = (event: ErrorEvent) => {
    99→        setError(event.error);
   100→        event.preventDefault();
   101→      };
   102→
   103→      window.addEventListener("error", handleError);
   104→      return () => window.removeEventListener("error", handleError);
   105→    }, []);
   106→
   107→    const reset = useCallback(() => {
   108→      setError(null);
   109→    }, []);
   110→
   111→    if (error) {
   112→      if (fallback) {
   113→        const FallbackComponent = fallback;
   114→        return <FallbackComponent error={error} reset={reset} />;
   115→      }
   116→
   117→      // Default fallback
   118→      return (
   119→

   120→
Something went wrong

   121→
{error.message}

   122→          <button
   123→            onClick={reset}
   124→            className="mt-4 px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90"
   125→          >
   126→            Try again
   127→          </button>
   128→

   129→      );
   130→    }
   131→
   132→    return <Component {...props} />;
   133→  };
   134→}

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


Now let me read some key page files to understand the routes better. Let me start with reading some authenticated pages and auth pages.