Read and review all library/utility files in /home/ccuser/the-50-dollar-app/src/lib/. Read every file in:
1. src/lib/supabase/ — all files
2. src/lib/ — all .ts files at the top level
3. Any other subdirectories under src/lib/

Check for:
- Supabase client configuration (server vs client usage)
- Database query security (RLS reliance, raw queries)
- Type safety issues
- Missing error handling in data access functions
- Stripe integration issues
- Rate limiting configuration
- Logger setup
- Any hardcoded secrets or URLs

Also check the Supabase migration files if they exist under /home/ccuser/the-50-dollar-app/supabase/

Return a detailed list of all issues found.


I'll start by exploring the directory structure to understand what files exist in the relevant locations.


No files found


Bash

List top-level lib directory


total 108
drwxrwxr-x 4 ccuser ccuser 4096 Feb 10 09:56 .
drwxrwxr-x 7 ccuser ccuser 4096 Feb  9 07:00 ..
-rw-r--r-- 1 ccuser ccuser 5830 Feb 10 09:32 abn-cache.ts
-rw-rw-r-- 1 ccuser ccuser 4160 Feb  9 04:59 abn-utils.ts
-rw-rw-r-- 1 ccuser ccuser 4659 Feb  9 07:01 api-errors.ts
-rw-rw-r-- 1 ccuser ccuser 3915 Feb  9 04:47 company-utils.ts
-rw-r--r-- 1 ccuser ccuser 2834 Feb  9 06:54 constants.ts
-rw-rw-r-- 1 ccuser ccuser 3868 Feb  9 05:01 csrf-client.ts
-rw-rw-r-- 1 ccuser ccuser 6709 Feb  9 05:01 csrf.ts
-rw-rw-r-- 1 ccuser ccuser  779 Feb 10 09:37 display-names.ts
-rw-rw-r-- 1 ccuser ccuser 3517 Feb  9 05:02 error-handling.tsx
-rw-rw-r-- 1 ccuser ccuser 2753 Feb 10 09:31 location-utils.ts
-rw-r--r-- 1 ccuser ccuser 2117 Feb  9 04:51 logger.ts
-rw-rw-r-- 1 ccuser ccuser 1058 Feb  9 04:44 messaging.ts
-rw-rw-r-- 1 ccuser ccuser 6756 Feb 10 09:56 rate-limit.ts
-rw-rw-r-- 1 ccuser ccuser 5156 Feb  9 19:18 sanitize.ts
drwxrwxr-x 2 ccuser ccuser 4096 Feb  9 07:01 stripe
drwxrwxr-x 2 ccuser ccuser 4096 Feb  9 04:05 supabase
-rw-rw-r-- 1 ccuser ccuser 3550 Feb  9 04:05 types.ts
-rw-rw-r-- 1 ccuser ccuser  887 Feb  9 04:44 utils.ts
-rw-r--r-- 1 ccuser ccuser 1125 Feb  9 04:54 voice-complete-test.ts


ls -la /home/ccuser/the-50-dollar-app/src/lib/supabase/ && echo "---" && ls -la /home/ccuser/the-50-dollar-app/src/lib/stripe/

List supabase and stripe subdirectories


total 20
drwxrwxr-x 2 ccuser ccuser 4096 Feb  9 04:05 .
drwxrwxr-x 4 ccuser ccuser 4096 Feb 10 09:56 ..
-rw-rw-r-- 1 ccuser ccuser 1120 Feb  9 04:53 client.ts
-rw-rw-r-- 1 ccuser ccuser 3022 Feb  9 04:56 middleware.ts
-rw-rw-r-- 1 ccuser ccuser 3472 Feb  9 04:54 server.ts
---
total 12
drwxrwxr-x 2 ccuser ccuser 4096 Feb  9 07:01 .
drwxrwxr-x 4 ccuser ccuser 4096 Feb 10 09:56 ..
-rw-rw-r-- 1 ccuser ccuser 2373 Feb  9 07:01 server.ts


Now let me read all the files. I'll read them in parallel batches.


     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→ * IMPORTANT: This function does NOT export a singleton instance.
    20→ * Always call createClient() to get a fresh client instance.
    21→ * This prevents stale auth credentials on shared devices.
    22→ *
    23→ * @returns Fresh Supabase client instance for browser use
    24→ *
    25→ * @example
    26→ * const supabase = createClient();
    27→ * const { data, error } = await supabase.from('users').select('*');
    28→ */
    29→export function createClient(): SupabaseClient {
    30→  return createBrowserClient(supabaseUrl, supabaseAnonKey)
    31→}

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

<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 { 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→ * @returns Promise resolving to Supabase client with server-side cookie handling
    11→ *
    12→ * @example
    13→ * const supabase = await createClient();
    14→ * const { data: { user } } = await supabase.auth.getUser();
    15→ */
    16→export async function createClient() {
    17→  const cookieStore = await cookies();
    18→
    19→  return createServerClient(
    20→    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    21→    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    22→    {
    23→      cookies: {
    24→        getAll() {
    25→          return cookieStore.getAll();
    26→        },
    27→        setAll(cookiesToSet) {
    28→          try {
    29→            cookiesToSet.forEach(({ name, value, options }) =>
    30→              cookieStore.set(name, value, options)
    31→            );
    32→          } catch {
    33→            // Cookies can only be set in Server Actions or Route Handlers
    34→          }
    35→        },
    36→      },
    37→    }
    38→  );
    39→}
    40→
    41→// Lazy-initialized admin client to avoid build-time errors (H11 fix)
    42→let _supabaseAdmin: SupabaseClient | null = null;
    43→
    44→/**
    45→ * Admin client with service key — bypasses RLS.
    46→ * Use ONLY for admin operations (email verification, etc.)
    47→ * NEVER use this to read user data in server components.
    48→ *
    49→ * Throws if SUPABASE_SERVICE_KEY is not configured.
    50→ */
    51→export function getSupabaseAdmin(): SupabaseClient {
    52→  if (_supabaseAdmin) {
    53→    return _supabaseAdmin;
    54→  }
    55→
    56→  const serviceKey = process.env.SUPABASE_SERVICE_KEY;
    57→  const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
    58→
    59→  if (!serviceKey) {
    60→    throw new Error(
    61→      'SUPABASE_SERVICE_KEY is not configured. ' +
    62→      'Please add it to your .env.local file. ' +
    63→      'See SUPABASE-KEY-ROTATION-GUIDE.md for details.'
    64→    );
    65→  }
    66→
    67→  if (!supabaseUrl) {
    68→    throw new Error(
    69→      'NEXT_PUBLIC_SUPABASE_URL is not configured. ' +
    70→      'Please add it to your .env.local file.'
    71→    );
    72→  }
    73→
    74→  _supabaseAdmin = createSupabaseClient(supabaseUrl, serviceKey, {
    75→    auth: {
    76→      autoRefreshToken: false,
    77→      persistSession: false,
    78→    },
    79→  });
    80→
    81→  return _supabaseAdmin;
    82→}
    83→
    84→/**
    85→ * Helper to verify email addresses (admin operation)
    86→ * Bypasses normal email verification process - use with caution
    87→ *
    88→ * @param userId - User ID to verify
    89→ * @returns Promise resolving to user data
    90→ * @throws Error if verification fails
    91→ *
    92→ * @example
    93→ * await verifyEmail('user-123');
    94→ * // User's email is now marked as verified
    95→ */
    96→export async function verifyEmail(userId: string) {
    97→  const admin = getSupabaseAdmin();
    98→  const { data, error } = await admin.auth.admin.updateUserById(
    99→    userId,
   100→    { email_confirm: true }
   101→  );
   102→
   103→  if (error) throw error;
   104→  return data;
   105→}
   106→
   107→/**
   108→ * Helper to check if user has verified their email (admin operation)
   109→ *
   110→ * @param userId - User ID to check
   111→ * @returns Promise resolving to true if email is verified
   112→ *
   113→ * @example
   114→ * const isVerified = await isEmailVerified('user-123');
   115→ * if (isVerified) {
   116→ *   // Allow access to verified-only features
   117→ * }
   118→ */
   119→export async function isEmailVerified(userId: string): Promise<boolean> {
   120→  const admin = getSupabaseAdmin();
   121→  const { data, error } = await admin.auth.admin.getUserById(userId);
   122→
   123→  if (error) return false;
   124→  return !!data.user?.email_confirmed_at;
   125→}
   126→

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

<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 { getSupabaseAdmin } from '@/lib/supabase/server';
     2→import { formatABN } from '@/lib/abn-utils';
     3→import { logger } from '@/lib/logger';
     4→
     5→/**
     6→ * ABN Cache Service
     7→ *
     8→ * Provides caching functionality for ABN lookups to reduce API calls to ABR.
     9→ * Cached entries expire based on ABN status:
    10→ * - Valid ABNs (Active): 30 days expiry
    11→ * - Invalid/Not found ABNs: 7 days expiry
    12→ */
    13→
    14→export interface CachedABNData {
    15→  abn: string;
    16→  companyName: string;
    17→  status: string;
    18→  statusDate?: string;
    19→  state?: string;
    20→  postcode?: string;
    21→  entityType?: string;
    22→  entityTypeCode?: string;
    23→  gstRegistered?: boolean;
    24→  acn?: string;
    25→  abrResponse?: any;
    26→}
    27→
    28→export interface ABNCacheEntry extends CachedABNData {
    29→  id: string;
    30→  cachedAt: string;
    31→  updatedAt: string;
    32→  expiresAt: string;
    33→  lookupCount: number;
    34→  lastLookupAt: string;
    35→}
    36→
    37→/**
    38→ * Get cached ABN data if available and not expired
    39→ */
    40→export async function getCachedABN(abn: string): Promise<ABNCacheEntry | null> {
    41→  try {
    42→    const admin = getSupabaseAdmin();
    43→    const formattedABN = formatABN(abn);
    44→
    45→    // Use the database function for cache lookup
    46→    const { data, error } = await admin
    47→      .rpc('get_cached_abn', { p_abn: formattedABN })
    48→      .single();
    49→
    50→    if (error) {
    51→      logger.error('[ABN Cache] Error retrieving cached ABN:', error);
    52→      return null;
    53→    }
    54→
    55→    if (!data) {
    56→      return null;
    57→    }
    58→
    59→    // Increment lookup count for cache hit statistics
    60→    await incrementABNLookup(formattedABN);
    61→
    62→    const row = data as Record<string, any>;
    63→    return {
    64→      id: row.id,
    65→      abn: row.abn,
    66→      companyName: row.company_name,
    67→      status: row.status,
    68→      statusDate: row.status_date,
    69→      state: row.state,
    70→      postcode: row.postcode,
    71→      entityType: row.entity_type,
    72→      entityTypeCode: row.entity_type_code,
    73→      gstRegistered: row.gst_registered,
    74→      acn: row.acn,
    75→      cachedAt: row.cached_at,
    76→      updatedAt: row.updated_at,
    77→      expiresAt: row.expires_at,
    78→      lookupCount: row.lookup_count,
    79→      lastLookupAt: row.last_lookup_at,
    80→      abrResponse: row.abr_response,
    81→    };
    82→  } catch (error) {
    83→    logger.error('[ABN Cache] Unexpected error in getCachedABN:', error);
    84→    return null;
    85→  }
    86→}
    87→
    88→/**
    89→ * Cache ABN data from ABR response
    90→ */
    91→export async function cacheABNData(data: CachedABNData): Promise<void> {
    92→  try {
    93→    const admin = getSupabaseAdmin();
    94→    const formattedABN = formatABN(data.abn);
    95→
    96→    // Calculate expiry based on status
    97→    const isActive = data.status.toLowerCase() === 'active';
    98→    const expiryDays = isActive ? 30 : 7;
    99→    const expiresAt = new Date(Date.now() + expiryDays * 24 * 60 * 60 * 1000).toISOString();
   100→
   101→    const cacheData = {
   102→      abn: formattedABN,
   103→      company_name: data.companyName,
   104→      status: data.status,
   105→      status_date: data.statusDate,
   106→      state: data.state,
   107→      postcode: data.postcode,
   108→      entity_type: data.entityType,
   109→      entity_type_code: data.entityTypeCode,
   110→      gst_registered: data.gstRegistered,
   111→      acn: data.acn,
   112→      abr_response: data.abrResponse,
   113→      expires_at: expiresAt,
   114→    };
   115→
   116→    // Use upsert to handle both insert and update cases
   117→    const { error } = await admin
   118→      .from('abn_cache')
   119→      .upsert(cacheData, {
   120→        onConflict: 'abn',
   121→        ignoreDuplicates: false,
   122→      });
   123→
   124→    if (error) {
   125→      logger.error('[ABN Cache] Error caching ABN data:', error);
   126→      throw error;
   127→    }
   128→
   129→    logger.info(`[ABN Cache] Successfully cached ABN: ${formattedABN}`);
   130→  } catch (error) {
   131→    logger.error('[ABN Cache] Unexpected error in cacheABNData:', error);
   132→    throw error;
   133→  }
   134→}
   135→
   136→/**
   137→ * Increment lookup count for cache hit statistics
   138→ */
   139→async function incrementABNLookup(abn: string): Promise<void> {
   140→  try {
   141→    const admin = getSupabaseAdmin();
   142→
   143→    await admin.rpc('increment_abn_lookup', { p_abn: abn });
   144→  } catch (error) {
   145→    // Don't throw error for stats update failure
   146→    logger.error('[ABN Cache] Error incrementing lookup count:', error);
   147→  }
   148→}
   149→
   150→/**
   151→ * Clean up expired cache entries
   152→ */
   153→export async function cleanupExpiredABNCache(): Promise<number> {
   154→  try {
   155→    const admin = getSupabaseAdmin();
   156→
   157→    const { data, error } = await admin
   158→      .rpc('cleanup_expired_abn_cache')
   159→      .single();
   160→
   161→    if (error) {
   162→      logger.error('[ABN Cache] Error cleaning up expired entries:', error);
   163→      return 0;
   164→    }
   165→
   166→    const deletedCount = (data as Record<string, any>)?.deleted_count || 0;
   167→    logger.info(`[ABN Cache] Cleaned up ${deletedCount} expired entries`);
   168→    return deletedCount;
   169→  } catch (error) {
   170→    logger.error('[ABN Cache] Unexpected error in cleanup:', error);
   171→    return 0;
   172→  }
   173→}
   174→
   175→/**
   176→ * Get cache statistics for monitoring
   177→ */
   178→export async function getABNCacheStats(): Promise<{
   179→  totalCached: number;
   180→  activeEntries: number;
   181→  expiredEntries: number;
   182→  avgLookups: number;
   183→  maxLookups: number;
   184→}> {
   185→  try {
   186→    const admin = getSupabaseAdmin();
   187→
   188→    const { data, error } = await admin
   189→      .rpc('get_abn_cache_stats')
   190→      .single();
   191→
   192→    if (error) {
   193→      logger.error('[ABN Cache] Error getting cache stats:', error);
   194→      return {
   195→        totalCached: 0,
   196→        activeEntries: 0,
   197→        expiredEntries: 0,
   198→        avgLookups: 0,
   199→        maxLookups: 0,
   200→      };
   201→    }
   202→
   203→    const stats = data as Record<string, any>;
   204→    return {
   205→      totalCached: stats?.total_cached || 0,
   206→      activeEntries: stats?.active_entries || 0,
   207→      expiredEntries: stats?.expired_entries || 0,
   208→      avgLookups: parseFloat(stats?.avg_lookups) || 0,
   209→      maxLookups: stats?.max_lookups || 0,
   210→    };
   211→  } catch (error) {
   212→    logger.error('[ABN Cache] Unexpected error getting stats:', error);
   213→    return {
   214→      totalCached: 0,
   215→      activeEntries: 0,
   216→      expiredEntries: 0,
   217→      avgLookups: 0,
   218→      maxLookups: 0,
   219→    };
   220→  }
   221→}

<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→ * Removes all non-digit characters (spaces, dashes, dots, etc.)
    26→ *
    27→ * @param abn - ABN string to clean (may contain formatting characters)
    28→ * @returns Cleaned 11-digit string (may be empty or wrong length if input is invalid)
    29→ *
    30→ * @example
    31→ * cleanABN('51 824 753 556')
    32→ * // Returns: '51824753556'
    33→ *
    34→ * @example
    35→ * cleanABN('51-824-753-556')
    36→ * // Returns: '51824753556'
    37→ */
    38→export function cleanABN(abn: string): string {
    39→  return abn.replace(/\D/g, '');
    40→}
    41→
    42→/**
    43→ * Validate an ABN using the modulus-89 checksum algorithm.
    44→ * Based on the official ABN validation algorithm from ABR
    45→ *
    46→ * Accepts any string — spaces, dashes, dots are stripped first.
    47→ * Returns `true` only when the input is exactly 11 digits AND the checksum passes.
    48→ *
    49→ * @param abn - ABN string to validate (can include formatting characters)
    50→ * @returns True if ABN is valid, false otherwise
    51→ *
    52→ * @example
    53→ * validateABN('51 824 753 556')
    54→ * // Returns: true (valid ABN)
    55→ *
    56→ * @example
    57→ * validateABN('51 824 753 557')
    58→ * // Returns: false (invalid checksum)
    59→ *
    60→ * @example
    61→ * validateABN('12345')
    62→ * // Returns: false (wrong length)
    63→ */
    64→export function validateABN(abn: string): boolean {
    65→  const digits = cleanABN(abn);
    66→
    67→  if (digits.length !== 11) return false;
    68→
    69→  // Convert to number array, subtract 1 from first digit
    70→  const d = digits.split('').map(Number);
    71→  d[0] -= 1;
    72→
    73→  // Weighted sum
    74→  let sum = 0;
    75→  for (let i = 0; i < 11; i++) {
    76→    sum += d[i] * ABN_WEIGHTS[i];
    77→  }
    78→
    79→  return sum % 89 === 0;
    80→}
    81→
    82→/**
    83→ * Convenience alias for validateABN used by the API route
    84→ *
    85→ * @see validateABN
    86→ */
    87→export const isValidABN = validateABN;
    88→
    89→/**
    90→ * Format an 11-digit ABN into the standard display format: XX XXX XXX XXX
    91→ *
    92→ * @param abn - ABN string to format (can include formatting characters)
    93→ * @returns Formatted ABN string or empty string if input doesn't contain exactly 11 digits
    94→ *
    95→ * @example
    96→ * formatABN('51824753556')
    97→ * // Returns: '51 824 753 556'
    98→ *
    99→ * @example
   100→ * formatABN('51-824-753-556')
   101→ * // Returns: '51 824 753 556'
   102→ *
   103→ * @example
   104→ * formatABN('12345')
   105→ * // Returns: '' (empty string)
   106→ */
   107→export function formatABN(abn: string): string {
   108→  const d = cleanABN(abn);
   109→  if (d.length !== 11) return '';
   110→  return `${d.slice(0, 2)} ${d.slice(2, 5)} ${d.slice(5, 8)} ${d.slice(8)}`;
   111→}
   112→
   113→/**
   114→ * Validate + format in one call.
   115→ *
   116→ * @param abn - ABN string to validate and format
   117→ * @returns Object containing validation result, cleaned digits, and formatted string
   118→ *
   119→ * @example
   120→ * validateAndFormatABN('51 824 753 556')
   121→ * // Returns: {
   122→ * //   valid: true,
   123→ * //   cleaned: '51824753556',
   124→ * //   formatted: '51 824 753 556'
   125→ * // }
   126→ *
   127→ * @example
   128→ * validateAndFormatABN('invalid')
   129→ * // Returns: {
   130→ * //   valid: false,
   131→ * //   cleaned: '',
   132→ * //   formatted: ''
   133→ * // }
   134→ */
   135→export function validateAndFormatABN(abn: string): {
   136→  valid: boolean;
   137→  cleaned: string;
   138→  formatted: string;
   139→} {
   140→  const cleaned = cleanABN(abn);
   141→  const valid = validateABN(cleaned);
   142→  return {
   143→    valid,
   144→    cleaned,
   145→    formatted: valid ? formatABN(cleaned) : '',
   146→  };
   147→}
   148→
   149→/* ── Test ABNs from ABR documentation ── */
   150→export const TEST_ABNS = {
   151→  /** Valid — passes checksum */
   152→  VALID: '51 824 753 556',
   153→  /** Invalid — fails checksum (last digit changed) */
   154→  INVALID: '51 824 753 557',
   155→  /** Suppressed ABN (details hidden on ABR) */
   156→  SUPPRESSED: '34 241 177 887',
   157→} as const;
   158→

<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 { logger } from "@/lib/logger";
     3→
     4→/**
     5→ * Custom error class for API errors with status codes and error codes
     6→ *
     7→ * @example
     8→ * throw new APIError('User not found', 404, ErrorCodes.NOT_FOUND);
     9→ *
    10→ * @example
    11→ * throw new APIError('Validation failed', 400, ErrorCodes.VALIDATION_ERROR);
    12→ */
    13→export class APIError extends Error {
    14→  constructor(
    15→    message: string,
    16→    public statusCode: number = 500,
    17→    public code?: string
    18→  ) {
    19→    super(message);
    20→    this.name = "APIError";
    21→  }
    22→}
    23→
    24→export const ErrorCodes = {
    25→  // Authentication errors
    26→  UNAUTHORIZED: "UNAUTHORIZED",
    27→  FORBIDDEN: "FORBIDDEN",
    28→  TOKEN_EXPIRED: "TOKEN_EXPIRED",
    29→
    30→  // Validation errors
    31→  VALIDATION_ERROR: "VALIDATION_ERROR",
    32→  INVALID_INPUT: "INVALID_INPUT",
    33→  MISSING_REQUIRED_FIELD: "MISSING_REQUIRED_FIELD",
    34→
    35→  // Rate limiting
    36→  RATE_LIMIT_EXCEEDED: "RATE_LIMIT_EXCEEDED",
    37→
    38→  // Service errors
    39→  SERVICE_UNAVAILABLE: "SERVICE_UNAVAILABLE",
    40→  EXTERNAL_SERVICE_ERROR: "EXTERNAL_SERVICE_ERROR",
    41→
    42→  // Resource errors
    43→  NOT_FOUND: "NOT_FOUND",
    44→  CONFLICT: "CONFLICT",
    45→
    46→  // Generic error
    47→  INTERNAL_ERROR: "INTERNAL_ERROR",
    48→} as const;
    49→
    50→export type ErrorCode = typeof ErrorCodes[keyof typeof ErrorCodes];
    51→
    52→interface ErrorResponse {
    53→  error: string;
    54→  code?: ErrorCode;
    55→  details?: any;
    56→  timestamp: string;
    57→  path?: string;
    58→}
    59→
    60→/**
    61→ * Creates a standardized error response
    62→ *
    63→ * @param message - Error message for the response
    64→ * @param statusCode - HTTP status code (default: 500)
    65→ * @param code - Error code from ErrorCodes enum (optional)
    66→ * @param details - Additional error details (only included in development)
    67→ * @returns NextResponse with standardized error format
    68→ *
    69→ * @example
    70→ * return createErrorResponse('User not found', 404, ErrorCodes.NOT_FOUND);
    71→ *
    72→ * @example
    73→ * return createErrorResponse('Validation failed', 400, ErrorCodes.VALIDATION_ERROR, {
    74→ *   fields: ['email', 'password']
    75→ * });
    76→ */
    77→export function createErrorResponse(
    78→  message: string,
    79→  statusCode: number = 500,
    80→  code?: ErrorCode,
    81→  details?: any
    82→): NextResponse<ErrorResponse> {
    83→  const errorResponse: ErrorResponse = {
    84→    error: message,
    85→    code,
    86→    details: process.env.NODE_ENV === "development" ? details : undefined,
    87→    timestamp: new Date().toISOString(),
    88→  };
    89→
    90→  return NextResponse.json(errorResponse, { status: statusCode });
    91→}
    92→
    93→/**
    94→ * Common error response helpers
    95→ */
    96→export const ErrorResponses = {
    97→  unauthorized: (message = "Unauthorized") =>
    98→    createErrorResponse(message, 401, ErrorCodes.UNAUTHORIZED),
    99→
   100→  forbidden: (message = "Forbidden") =>
   101→    createErrorResponse(message, 403, ErrorCodes.FORBIDDEN),
   102→
   103→  notFound: (message = "Not found") =>
   104→    createErrorResponse(message, 404, ErrorCodes.NOT_FOUND),
   105→
   106→  validation: (message = "Validation error", details?: any) =>
   107→    createErrorResponse(message, 400, ErrorCodes.VALIDATION_ERROR, details),
   108→
   109→  rateLimit: (message = "Rate limit exceeded", retryAfter?: number) => {
   110→    const response = createErrorResponse(message, 429, ErrorCodes.RATE_LIMIT_EXCEEDED);
   111→    if (retryAfter) {
   112→      response.headers.set("Retry-After", String(retryAfter));
   113→    }
   114→    return response;
   115→  },
   116→
   117→  internal: (message = "Internal server error", details?: any) =>
   118→    createErrorResponse(message, 500, ErrorCodes.INTERNAL_ERROR, details),
   119→
   120→  serviceUnavailable: (message = "Service temporarily unavailable") =>
   121→    createErrorResponse(message, 503, ErrorCodes.SERVICE_UNAVAILABLE),
   122→} as const;
   123→
   124→/**
   125→ * Wraps an API route handler with error handling
   126→ * Catches errors and returns standardized error responses
   127→ *
   128→ * @param handler - API route handler function to wrap
   129→ * @returns Wrapped handler with error handling
   130→ *
   131→ * @example
   132→ * export const GET = withErrorHandler(async (request: Request) => {
   133→ *   // Your API logic here
   134→ *   return NextResponse.json({ data: 'success' });
   135→ * });
   136→ */
   137→export function withErrorHandler<T extends Request = Request>(
   138→  handler: (request: T) => Promise<Response>
   139→) {
   140→  return async (request: T): Promise<Response> => {
   141→    try {
   142→      return await handler(request);
   143→    } catch (error) {
   144→      logger.error("API route error:", error);
   145→
   146→      if (error instanceof APIError) {
   147→        return createErrorResponse(
   148→          error.message,
   149→          error.statusCode,
   150→          error.code as ErrorCode
   151→        );
   152→      }
   153→
   154→      if (error instanceof Error) {
   155→        return ErrorResponses.internal(
   156→          "An unexpected error occurred",
   157→          process.env.NODE_ENV === "development" ? error.stack : undefined
   158→        );
   159→      }
   160→
   161→      return ErrorResponses.internal("An unexpected error occurred");
   162→    }
   163→  };
   164→}

<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→ * @param url - URL string to extract domain from (with or without protocol)
     5→ * @returns Clean domain name without protocol or www prefix
     6→ *
     7→ * @example
     8→ * extractDomain('https://www.example.com.au') // Returns 'example.com.au'
     9→ * extractDomain('www.example.com') // Returns 'example.com'
    10→ * extractDomain('example.com') // Returns 'example.com'
    11→ */
    12→export function extractDomain(url: string): string {
    13→  try {
    14→    // Add protocol if missing
    15→    if (!url.match(/^https?:\/\//)) {
    16→      url = 'https://' + url;
    17→    }
    18→
    19→    const urlObj = new URL(url);
    20→    return urlObj.hostname.replace(/^www\./, '');
    21→  } catch {
    22→    // If URL parsing fails, try to extract domain manually
    23→    const match = url.match(/^(?:https?:\/\/)?(?:www\.)?([^\/]+)/);
    24→    return match ? match[1] : url;
    25→  }
    26→}
    27→
    28→/**
    29→ * Generate likely domain variations from a company name
    30→ * Uses multiple strategies to create potential domain names for Australian businesses
    31→ *
    32→ * @param name - Company name to generate domains from
    33→ * @returns Array of potential domain names with various TLDs
    34→ *
    35→ * @example
    36→ * guessCompanyDomain('ABC Construction Pty Ltd')
    37→ * // Returns: ['abcconstruction.com.au', 'abcconstruction.net.au', 'abc.com.au', ...]
    38→ *
    39→ * @example
    40→ * guessCompanyDomain('Smith & Co Builders')
    41→ * // Returns: ['smithbuilders.com.au', 'smith.net.au', 'scb.com.au', ...]
    42→ */
    43→export function guessCompanyDomain(name: string): string[] {
    44→  if (!name) return [];
    45→
    46→  // Remove common company suffixes
    47→  const cleanName = name
    48→    .toLowerCase()
    49→    .replace(/\b(?:pty|ltd|limited|inc|incorporated|corp|corporation|co|company)\b/gi, '')
    50→    .replace(/[^a-z0-9\s]/g, ' ')
    51→    .trim();
    52→
    53→  // Extract words
    54→  const words = cleanName.split(/\s+/).filter(word => word.length > 1);
    55→
    56→  const domains: string[] = [];
    57→
    58→  // Common TLDs for Australian businesses
    59→  const tlds = ['.com.au', '.net.au', '.org.au', '.com', '.net', '.org'];
    60→
    61→  // Strategy 1: Full company name (without spaces)
    62→  const fullName = words.join('');
    63→  tlds.forEach(tld => {
    64→    domains.push(fullName + tld);
    65→  });
    66→
    67→  // Strategy 2: First word only (if more than one word)
    68→  if (words.length > 1) {
    69→    tlds.forEach(tld => {
    70→      domains.push(words[0] + tld);
    71→    });
    72→  }
    73→
    74→  // Strategy 3: First two words (if more than two words)
    75→  if (words.length > 2) {
    76→    const firstTwo = words.slice(0, 2).join('');
    77→    tlds.forEach(tld => {
    78→      domains.push(firstTwo + tld);
    79→    });
    80→  }
    81→
    82→  // Strategy 4: Common abbreviations
    83→  const abbreviations = words.map(word => word[0]).join('');
    84→  if (abbreviations.length >= 2 && abbreviations.length <= 5) {
    85→    tlds.forEach(tld => {
    86→      domains.push(abbreviations + tld);
    87→    });
    88→  }
    89→
    90→  // Strategy 5: Building/Construction specific variations
    91→  const industryWords = ['building', 'construction', 'construct', 'builders', 'group'];
    92→  const hasIndustryWord = words.some(word => industryWords.includes(word));
    93→
    94→  if (!hasIndustryWord) {
    95→    // Add 'builders' to the end
    96→    tlds.forEach(tld => {
    97→      domains.push(fullName + 'builders' + tld);
    98→      if (words.length > 1) {
    99→        domains.push(words[0] + 'builders' + tld);
   100→      }
   101→    });
   102→  }
   103→
   104→  // Remove duplicates and return
   105→  return [...new Set(domains)];
   106→}
   107→
   108→/**
   109→ * Try to find company website from ABR data
   110→ * Note: This is a placeholder - ABR API doesn't typically provide website URLs
   111→ * You may need to integrate with other services or databases
   112→ *
   113→ * @param abrData - ABR (Australian Business Register) data object
   114→ * @returns Website URL if found, null otherwise
   115→ *
   116→ * @example
   117→ * const website = extractWebsiteFromABR(abrData);
   118→ * // Currently always returns null - placeholder for future enhancement
   119→ */
   120→export function extractWebsiteFromABR(abrData: any): string | null {
   121→  // ABR typically doesn't provide website information
   122→  // This is a placeholder for future integration with other data sources
   123→  return null;
   124→}

<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→// Payment constants
     2→export const PAYMENT_AMOUNT = 50.0;
     3→
     4→// Rate limiting constants
     5→export const RATE_LIMIT_REQUESTS_PER_IP = 5;
     6→export const RATE_LIMIT_REQUESTS_PER_USER = 10;
     7→export const RATE_LIMIT_WINDOW_MINUTES = 15;
     8→export const RATE_LIMIT_WINDOW_MS = RATE_LIMIT_WINDOW_MINUTES * 60 * 1000;
     9→export const SEND_MESSAGE_RATE_LIMIT = 60; // 60 messages per 15 min window per IP
    10→export const VERIFY_RATE_LIMIT = 5; // 5 email verification attempts per window
    11→export const VERIFY_WINDOW_MS = RATE_LIMIT_WINDOW_MS;
    12→
    13→// AI rate limiting constants
    14→export const AI_RATE_LIMIT = 5;
    15→export const AI_WINDOW_MS = RATE_LIMIT_WINDOW_MS;
    16→
    17→// Input validation constants
    18→export const MAX_LENGTH_TRADE = 100;
    19→export const MAX_LENGTH_LOCATION = 200;
    20→export const MAX_LENGTH_AVAILABILITY = 50;
    21→export const MAX_LENGTH_TRANSCRIPT = 5000;
    22→export const MAX_LENGTH_VOICE_TRANSCRIPT = 2000;
    23→export const MAX_LENGTH_MESSAGE_CONTENT = 5000;
    24→export const MAX_LENGTH_EMAIL = 254;
    25→export const MAX_RATING_TEXT_LENGTH = 500;
    26→
    27→// Timeout constants (in milliseconds)
    28→export const TIMEOUT_ABN_LOOKUP = 10000;
    29→export const DELAY_RESET_PASSWORD_REDIRECT = 2000;
    30→
    31→// HTTP status codes
    32→export const HTTP_STATUS_INTERNAL_SERVER_ERROR = 500;
    33→export const HTTP_STATUS_OK = 200;
    34→
    35→// Australian postcode constants (for testing/validation)
    36→export const POSTCODE_SYDNEY = '2000';
    37→export const POSTCODE_MELBOURNE = '3000';
    38→
    39→// Percentage constants
    40→export const PERCENTAGE_WORKER_KEEP = 100; // Workers keep 100% of wages
    41→
    42→// Worker trades - used in worker profiles and AI validation
    43→export const TRADES = [
    44→  "Labourer",
    45→  "Steel Fixer",
    46→  "Formworker",
    47→  "Carpenter",
    48→  "Electrician",
    49→  "Plumber",
    50→  "Concreter",
    51→  "Bricklayer",
    52→  "Painter",
    53→  "Roofer",
    54→  "Scaffolder",
    55→  "Tiler",
    56→  "Plasterer",
    57→  "Welder",
    58→  "Crane Operator",
    59→  "Excavator Operator",
    60→] as const;
    61→
    62→export type Trade = typeof TRADES[number];
    63→
    64→// Contractor job categories - used in job posting
    65→export const TRADE_CATEGORIES = [
    66→  { id: "steel-fixer", label: "Steel Fixer" },
    67→  { id: "formworker", label: "Formworker" },
    68→  { id: "labourer", label: "Labourer" },
    69→  { id: "concreter", label: "Concreter" },
    70→  { id: "carpenter", label: "Carpenter" },
    71→  { id: "scaffolder", label: "Scaffolder" },
    72→  { id: "other", label: "Other" },
    73→] as const;
    74→
    75→export type TradeCategory = typeof TRADE_CATEGORIES[number];
    76→
    77→/**
    78→ * Convert job category ID to trade name
    79→ *
    80→ * @param categoryId - ID from TRADE_CATEGORIES
    81→ * @returns Trade name label or null if not found
    82→ *
    83→ * @example
    84→ * getTradeFromCategoryId('steel-fixer')
    85→ * // Returns: 'Steel Fixer'
    86→ *
    87→ * @example
    88→ * getTradeFromCategoryId('invalid-id')
    89→ * // Returns: null
    90→ */
    91→export function getTradeFromCategoryId(categoryId: string): string | null {
    92→  const category = TRADE_CATEGORIES.find(cat => cat.id === categoryId);
    93→  return category ? category.label : null;
    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→ * Client-side CSRF protection utilities
     3→ */
     4→
     5→import { logger } from './logger';
     6→
     7→const CSRF_HEADER_NAME = 'x-csrf-token';
     8→const CSRF_COOKIE_NAME = 'csrf-token';
     9→
    10→/**
    11→ * Get CSRF token from browser cookies
    12→ *
    13→ * @returns CSRF token value or null if not found
    14→ *
    15→ * @example
    16→ * const token = getCSRFCookie();
    17→ * if (token) {
    18→ *   // Use token in API requests
    19→ * }
    20→ */
    21→export function getCSRFCookie(): string | null {
    22→  if (typeof document === 'undefined') return null;
    23→
    24→  const cookies = document.cookie.split(';');
    25→  for (const cookie of cookies) {
    26→    const [name, value] = cookie.trim().split('=');
    27→    if (name === CSRF_COOKIE_NAME) {
    28→      return value;
    29→    }
    30→  }
    31→  return null;
    32→}
    33→
    34→/**
    35→ * Fetch a new CSRF token from the server
    36→ * This should be called after authentication to get a fresh token
    37→ *
    38→ * @returns Promise resolving to CSRF token or null if fetch failed
    39→ *
    40→ * @example
    41→ * const token = await fetchCSRFToken();
    42→ * if (token) {
    43→ *   // Store or use the new token
    44→ * }
    45→ */
    46→export async function fetchCSRFToken(): Promise<string | null> {
    47→  try {
    48→    const response = await fetch('/api/auth/csrf', {
    49→      method: 'GET',
    50→      credentials: 'include', // Include cookies
    51→    });
    52→
    53→    if (!response.ok) {
    54→      logger.error('Failed to fetch CSRF token:', response.status);
    55→      return null;
    56→    }
    57→
    58→    const data = await response.json();
    59→    return data.token || null;
    60→  } catch (error) {
    61→    logger.error('Error fetching CSRF token:', error);
    62→    return null;
    63→  }
    64→}
    65→
    66→/**
    67→ * Add CSRF token to fetch options
    68→ * Enhances existing request options with CSRF protection headers
    69→ *
    70→ * @param options - Existing fetch options to enhance
    71→ * @returns Enhanced fetch options with CSRF token header
    72→ *
    73→ * @example
    74→ * const response = await fetch('/api/data', withCSRFToken({
    75→ *   method: 'POST',
    76→ *   body: JSON.stringify(data)
    77→ * }));
    78→ */
    79→export function withCSRFToken(options: RequestInit = {}): RequestInit {
    80→  const token = getCSRFCookie();
    81→
    82→  if (!token) {
    83→    logger.warn('No CSRF token found in cookies');
    84→    return options;
    85→  }
    86→
    87→  return {
    88→    ...options,
    89→    headers: {
    90→      ...options.headers,
    91→      [CSRF_HEADER_NAME]: token,
    92→    },
    93→  };
    94→}
    95→
    96→/**
    97→ * Enhanced fetch with CSRF protection
    98→ * Automatically adds CSRF token to requests, falling back to regular fetch if no token
    99→ *
   100→ * @param input - URL or Request object
   101→ * @param options - Fetch options
   102→ * @returns Promise resolving to Response
   103→ *
   104→ * @example
   105→ * const response = await csrfFetch('/api/data', {
   106→ *   method: 'POST',
   107→ *   body: JSON.stringify(data)
   108→ * });
   109→ */
   110→export async function csrfFetch(input: RequestInfo | URL, options?: RequestInit): Promise<Response> {
   111→  const enhancedOptions = withCSRFToken(options);
   112→  return fetch(input, enhancedOptions);
   113→}
   114→
   115→/**
   116→ * Add CSRF token to FormData for multipart/form-data requests
   117→ * Appends the CSRF token as a form field
   118→ *
   119→ * @param formData - FormData object to enhance
   120→ * @returns The same FormData object with CSRF token added
   121→ *
   122→ * @example
   123→ * const formData = new FormData();
   124→ * formData.append('file', file);
   125→ * addCSRFTokenToFormData(formData);
   126→ * // formData now includes csrf_token field
   127→ */
   128→export function addCSRFTokenToFormData(formData: FormData): FormData {
   129→  const token = getCSRFCookie();
   130→
   131→  if (token) {
   132→    formData.append('csrf_token', token);
   133→  } else {
   134→    logger.warn('No CSRF token found in cookies');
   135→  }
   136→
   137→  return formData;
   138→}
   139→
   140→/**
   141→ * Initialize CSRF token after authentication
   142→ * Call this after successful login/signup to get a fresh CSRF token
   143→ *
   144→ * @returns Promise resolving to true if token was successfully fetched
   145→ *
   146→ * @example
   147→ * const success = await initializeCSRF();
   148→ * if (success) {
   149→ *   // CSRF token is now available for subsequent requests
   150→ * }
   151→ */
   152→export async function initializeCSRF(): Promise<boolean> {
   153→  const token = await fetchCSRFToken();
   154→  return token !== null;
   155→}

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

<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→// Construction-themed anonymous display names for workers pre-hire
     2→
     3→const ADJECTIVES = [
     4→  "Steady", "Reliable", "Skilled", "Solid", "Sharp",
     5→  "Swift", "Tough", "Bold", "Ready", "Strong",
     6→  "Keen", "Handy", "Quick", "Ace", "Top",
     7→  "Sure", "Pro", "Hard", "Level", "Square",
     8→];
     9→
    10→const NOUNS = [
    11→  "Hammer", "Rivet", "Bolt", "Brick", "Beam",
    12→  "Steel", "Crane", "Level", "Plumb", "Mason",
    13→  "Chisel", "Anvil", "Wrench", "Hoist", "Rebar",
    14→  "Timber", "Anchor", "Welder", "Grit", "Scaffold",
    15→];
    16→
    17→export function generateDisplayName(): string {
    18→  const adj = ADJECTIVES[Math.floor(Math.random() * ADJECTIVES.length)];
    19→  const noun = NOUNS[Math.floor(Math.random() * NOUNS.length)];
    20→  const num = Math.floor(Math.random() * 900) + 100; // 100-999
    21→  return `${adj} ${noun} ${num}`;
    22→}
    23→

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

   121→
Something went wrong

   122→
{error.message}

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

   130→      );
   131→    }
   132→
   133→    return <Component {...props} />;
   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→/**
     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→ * @param point - PostgreSQL point data in various formats
     9→ * @returns Location object with lat/lng properties or null if invalid
    10→ *
    11→ * @example
    12→ * postgresPointToJson('(-33.8688,151.2093)')
    13→ * // Returns: { lat: -33.8688, lng: 151.2093 }
    14→ *
    15→ * @example
    16→ * postgresPointToJson({ x: -33.8688, y: 151.2093 })
    17→ * // Returns: { lat: -33.8688, lng: 151.2093 }
    18→ *
    19→ * @example
    20→ * postgresPointToJson({ lat: -33.8688, lng: 151.2093 })
    21→ * // Returns: { lat: -33.8688, lng: 151.2093 }
    22→ */
    23→export function postgresPointToJson(point: any): { lat: number; lng: number } | null {
    24→  if (!point) return null;
    25→
    26→  // If it's already in the correct JSON format
    27→  if (typeof point === 'object' && 'lat' in point && 'lng' in point) {
    28→    return point;
    29→  }
    30→
    31→  // If it's a string like "(lat,lng)"
    32→  if (typeof point === 'string') {
    33→    const match = point.match(/^\s*\(\s*(-?\d+\.?\d*)\s*,\s*(-?\d+\.?\d*)\s*\)\s*$/);
    34→    if (match) {
    35→      return {
    36→        lat: parseFloat(match[1]),
    37→        lng: parseFloat(match[2])
    38→      };
    39→    }
    40→  }
    41→
    42→  // If it's an object with x,y properties (PostgreSQL point object)
    43→  if (typeof point === 'object' && 'x' in point && 'y' in point) {
    44→    return {
    45→      lat: parseFloat(point.x),
    46→      lng: parseFloat(point.y)
    47→    };
    48→  }
    49→
    50→  return null;
    51→}
    52→
    53→/**
    54→ * Converts JSON object format to PostgreSQL point format
    55→ * Returns a string in the format "(lat,lng)"
    56→ *
    57→ * @param location - Location object with lat/lng properties
    58→ * @returns PostgreSQL point format string or null if input is null
    59→ *
    60→ * @example
    61→ * jsonToPostgresPoint({ lat: -33.8688, lng: 151.2093 })
    62→ * // Returns: "(-33.8688,151.2093)"
    63→ *
    64→ * @example
    65→ * jsonToPostgresPoint(null)
    66→ * // Returns: null
    67→ */
    68→export function jsonToPostgresPoint(location: { lat: number; lng: number } | null): string | null {
    69→  if (!location) return null;
    70→  return `(${location.lat},${location.lng})`;
    71→}
    72→
    73→/**
    74→ * Type guard to check if a value is a valid location object
    75→ *
    76→ * @param obj - Value to check
    77→ * @returns True if obj is a valid location object with numeric lat/lng
    78→ *
    79→ * @example
    80→ * isValidLocation({ lat: -33.8688, lng: 151.2093 })
    81→ * // Returns: true
    82→ *
    83→ * @example
    84→ * isValidLocation({ lat: 'invalid', lng: 151.2093 })
    85→ * // Returns: false
    86→ *
    87→ * @example
    88→ * isValidLocation(null)
    89→ * // Returns: false
    90→ */
    91→export function isValidLocation(obj: any): obj is { lat: number; lng: number } {
    92→  return obj &&
    93→         typeof obj === 'object' &&
    94→         typeof obj.lat === 'number' &&
    95→         typeof obj.lng === 'number' &&
    96→         !isNaN(obj.lat) &&
    97→         !isNaN(obj.lng);
    98→}

<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 pino from 'pino';
     2→
     3→/**
     4→ * Logger configuration using pino for structured logging
     5→ * Only active in development environment, silent in production
     6→ */
     7→
     8→// Create logger configuration based on environment
     9→const isDevelopment = process.env.NODE_ENV === 'development';
    10→
    11→const pinoLogger = pino({
    12→  level: isDevelopment ? 'debug' : 'silent',
    13→  transport: isDevelopment ? {
    14→    target: 'pino-pretty',
    15→    options: {
    16→      colorize: true,
    17→      translateTime: 'SYS:standard',
    18→      ignore: 'pid,hostname',
    19→    },
    20→  } : undefined,
    21→  // In production, we could potentially log to a file or external service
    22→  // For now, we're making it completely silent in production
    23→});
    24→
    25→/**
    26→ * Logger utility with multiple log levels
    27→ * Only outputs in development environment to avoid production noise
    28→ *
    29→ * @example
    30→ * logger.info('User logged in', { userId: '123' });
    31→ * logger.error('Database connection failed', error);
    32→ * logger.debug('Processing request', { method: 'POST', path: '/api/users' });
    33→ */
    34→export const logger = {
    35→  /**
    36→   * Log debug information (development only)
    37→   * @param message - Log message
    38→   * @param args - Additional context data
    39→   */
    40→  debug: (message: string, ...args: any[]) => {
    41→    if (isDevelopment) {
    42→      pinoLogger.debug({ args }, message);
    43→    }
    44→  },
    45→  /**
    46→   * Log informational messages (development only)
    47→   * @param message - Log message
    48→   * @param args - Additional context data
    49→   */
    50→  info: (message: string, ...args: any[]) => {
    51→    if (isDevelopment) {
    52→      pinoLogger.info({ args }, message);
    53→    }
    54→  },
    55→  /**
    56→   * Log warning messages (development only)
    57→   * @param message - Log message
    58→   * @param args - Additional context data
    59→   */
    60→  warn: (message: string, ...args: any[]) => {
    61→    if (isDevelopment) {
    62→      pinoLogger.warn({ args }, message);
    63→    }
    64→  },
    65→  /**
    66→   * Log error messages (development only)
    67→   * @param message - Log message
    68→   * @param args - Additional context data (errors, stack traces, etc.)
    69→   */
    70→  error: (message: string, ...args: any[]) => {
    71→    if (isDevelopment) {
    72→      pinoLogger.error({ args }, message);
    73→    }
    74→  },
    75→};
    76→
    77→export default logger;

<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→
     3→/**
     4→ * Gets or creates a conversation between two users
     5→ * Uses the database RPC function 'get_or_create_conversation' to handle
     6→ * the logic of finding existing conversations or creating new ones
     7→ *
     8→ * @param user1Id - ID of the first user in the conversation
     9→ * @param user2Id - ID of the second user in the conversation
    10→ * @returns Promise resolving to the conversation ID
    11→ * @throws Error if the database operation fails
    12→ *
    13→ * @example
    14→ * const conversationId = await getOrCreateConversation('user-123', 'user-456');
    15→ */
    16→export async function getOrCreateConversation(user1Id: string, user2Id: string) {
    17→  const supabase = await createClient();
    18→
    19→  // Use the database function to get or create conversation
    20→  const { data: conversationId, error } = await supabase
    21→    .rpc('get_or_create_conversation', {
    22→      user1_id: user1Id,
    23→      user2_id: user2Id
    24→    });
    25→
    26→  if (error) {
    27→    throw new Error(`Failed to get or create conversation: ${error.message}`);
    28→  }
    29→
    30→  return conversationId as string;
    31→}

<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→ * Rate limiting for API endpoints
     3→ *
     4→ * Production: Uses Upstash Redis for distributed rate limiting across
     5→ * serverless invocations. Falls back to in-memory Map() if Redis is
     6→ * unavailable (local dev or connection failure).
     7→ *
     8→ * Env vars (pick one):
     9→ *   UPSTASH_REDIS_REST_URL + UPSTASH_REDIS_REST_TOKEN  (preferred)
    10→ *   LIMITER_STORAGE_URI  (legacy redis:// URL — auto-converted)
    11→ */
    12→
    13→import { Ratelimit } from "@upstash/ratelimit";
    14→import { Redis } from "@upstash/redis";
    15→
    16→// ---------------------------------------------------------------------------
    17→// Constants
    18→// ---------------------------------------------------------------------------
    19→const WINDOW_MS = 15 * 60 * 1000; // 15 minutes
    20→const MAX_REQUESTS = 100;
    21→
    22→// ---------------------------------------------------------------------------
    23→// In-memory fallback (local dev / Redis failure)
    24→// ---------------------------------------------------------------------------
    25→
    26→interface RateLimitEntry {
    27→  count: number;
    28→  resetTime: number;
    29→}
    30→
    31→const rateLimitStore = new Map<string, RateLimitEntry>();
    32→
    33→function memoryCheckRateLimit(
    34→  identifier: string,
    35→  maxRequests: number,
    36→  windowMs: number
    37→): { allowed: boolean; remaining: number; resetTime: number; totalLimit: number } {
    38→  const now = Date.now();
    39→  const entry = rateLimitStore.get(identifier);
    40→
    41→  if (!entry || now > entry.resetTime) {
    42→    rateLimitStore.set(identifier, { count: 1, resetTime: now + windowMs });
    43→    return { allowed: true, remaining: maxRequests - 1, resetTime: now + windowMs, totalLimit: maxRequests };
    44→  }
    45→
    46→  if (entry.count >= maxRequests) {
    47→    return { allowed: false, remaining: 0, resetTime: entry.resetTime, totalLimit: maxRequests };
    48→  }
    49→
    50→  entry.count++;
    51→  return { allowed: true, remaining: maxRequests - entry.count, resetTime: entry.resetTime, totalLimit: maxRequests };
    52→}
    53→
    54→// Cleanup interval to prevent memory leaks in long-running processes
    55→const CLEANUP_INTERVAL_MS = 5 * 60 * 1000;
    56→let cleanupInterval: NodeJS.Timeout | null = null;
    57→
    58→export function startRateLimitCleanup(): void {
    59→  if (cleanupInterval) return;
    60→  cleanupInterval = setInterval(() => {
    61→    const now = Date.now();
    62→    for (const [key, entry] of rateLimitStore.entries()) {
    63→      if (now > entry.resetTime) rateLimitStore.delete(key);
    64→    }
    65→  }, CLEANUP_INTERVAL_MS);
    66→  if (cleanupInterval.unref) cleanupInterval.unref();
    67→}
    68→
    69→export function stopRateLimitCleanup(): void {
    70→  if (cleanupInterval) {
    71→    clearInterval(cleanupInterval);
    72→    cleanupInterval = null;
    73→  }
    74→}
    75→
    76→// ---------------------------------------------------------------------------
    77→// Redis client (singleton, lazy-init)
    78→// ---------------------------------------------------------------------------
    79→
    80→let redis: Redis | null = null;
    81→let redisAvailable = true; // flipped to false on connection failure
    82→
    83→function getRedis(): Redis | null {
    84→  if (!redisAvailable) return null;
    85→  if (redis) return redis;
    86→
    87→  const restUrl = process.env.UPSTASH_REDIS_REST_URL;
    88→  const restToken = process.env.UPSTASH_REDIS_REST_TOKEN;
    89→
    90→  if (restUrl && restToken) {
    91→    redis = new Redis({ url: restUrl, token: restToken });
    92→    return redis;
    93→  }
    94→
    95→  // Parse legacy redis:// URI into Upstash REST format
    96→  // Expected: redis://default:<token>@<host>.upstash.io:6379
    97→  const legacyUri = process.env.LIMITER_STORAGE_URI;
    98→  if (legacyUri) {
    99→    try {
   100→      const parsed = new URL(legacyUri);
   101→      const host = parsed.hostname;
   102→      const token = decodeURIComponent(parsed.password);
   103→      if (host && token) {
   104→        redis = new Redis({ url: `https://${host}`, token });
   105→        return redis;
   106→      }
   107→    } catch {
   108→      // Parsing failed — fall through to memory
   109→    }
   110→  }
   111→
   112→  // No Redis config — use memory
   113→  redisAvailable = false;
   114→  return null;
   115→}
   116→
   117→// ---------------------------------------------------------------------------
   118→// Upstash Ratelimit instances (cached per unique window config)
   119→// ---------------------------------------------------------------------------
   120→
   121→const limiterCache = new Map<string, Ratelimit>();
   122→
   123→function getLimiter(maxRequests: number, windowMs: number): Ratelimit | null {
   124→  const client = getRedis();
   125→  if (!client) return null;
   126→
   127→  const key = `${maxRequests}:${windowMs}`;
   128→  let limiter = limiterCache.get(key);
   129→  if (limiter) return limiter;
   130→
   131→  const windowSec = Math.ceil(windowMs / 1000);
   132→  const windowStr = `${windowSec} s` as `${number} s`;
   133→
   134→  limiter = new Ratelimit({
   135→    redis: client,
   136→    limiter: Ratelimit.slidingWindow(maxRequests, windowStr),
   137→    prefix: "rr:rl",
   138→  });
   139→
   140→  limiterCache.set(key, limiter);
   141→  return limiter;
   142→}
   143→
   144→// ---------------------------------------------------------------------------
   145→// Public API (unchanged signatures)
   146→// ---------------------------------------------------------------------------
   147→
   148→export function getClientIP(request: Request): string {
   149→  const forwarded = request.headers.get("x-forwarded-for");
   150→  if (forwarded) return forwarded.split(",")[0].trim();
   151→  const realIP = request.headers.get("x-real-ip");
   152→  if (realIP) return realIP;
   153→  return "unknown";
   154→}
   155→
   156→export async function checkRateLimit(
   157→  identifier: string,
   158→  maxRequests?: number,
   159→  windowMs?: number
   160→): Promise<{
   161→  allowed: boolean;
   162→  remaining: number;
   163→  resetTime: number;
   164→  totalLimit: number;
   165→}> {
   166→  const limit = maxRequests ?? MAX_REQUESTS;
   167→  const window = windowMs ?? WINDOW_MS;
   168→
   169→  const limiter = getLimiter(limit, window);
   170→  if (limiter) {
   171→    try {
   172→      const result = await limiter.limit(identifier);
   173→      return {
   174→        allowed: result.success,
   175→        remaining: result.remaining,
   176→        resetTime: result.reset,
   177→        totalLimit: result.limit,
   178→      };
   179→    } catch {
   180→      // Redis failed — fall through to memory
   181→    }
   182→  }
   183→
   184→  return memoryCheckRateLimit(identifier, limit, window);
   185→}
   186→
   187→export function getRateLimitHeaders(result: {
   188→  allowed: boolean;
   189→  remaining: number;
   190→  resetTime: number;
   191→  totalLimit: number;
   192→}): Record<string, string> {
   193→  return {
   194→    "X-RateLimit-Limit": result.totalLimit.toString(),
   195→    "X-RateLimit-Remaining": Math.max(0, result.remaining).toString(),
   196→    "X-RateLimit-Reset": new Date(result.resetTime).toISOString(),
   197→  };
   198→}
   199→
   200→export function createRateLimitResponse(
   201→  _identifier: string,
   202→  retryAfterSeconds?: number
   203→): Response {
   204→  const headers = new Headers({
   205→    "Content-Type": "application/json",
   206→    "X-RateLimit-Limit": MAX_REQUESTS.toString(),
   207→    "X-RateLimit-Remaining": "0",
   208→  });
   209→
   210→  if (retryAfterSeconds) {
   211→    headers.set("Retry-After", retryAfterSeconds.toString());
   212→  }
   213→
   214→  return new Response(
   215→    JSON.stringify({
   216→      error: "Rate limit exceeded. Please try again later.",
   217→      retryAfter: retryAfterSeconds,
   218→    }),
   219→    { status: 429, headers }
   220→  );
   221→}
   222→
   223→// Auto-start memory cleanup
   224→startRateLimitCleanup();
   225→
   226→export { MAX_REQUESTS, WINDOW_MS };
   227→

<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→ * @param str - Input string to clean
    10→ * @returns String with control characters removed
    11→ *
    12→ * @example
    13→ * stripControlChars('Hello\x00World\x1F')
    14→ * // Returns: 'HelloWorld'
    15→ */
    16→function stripControlChars(str: string): string {
    17→  return str.replace(/[\x00-\x1F\x7F]/g, '');
    18→}
    19→
    20→/**
    21→ * Escape template literals to prevent injection
    22→ * Prevents template literal injection by escaping backticks, $, and backslashes
    23→ *
    24→ * @param str - Input string to escape
    25→ * @returns Escaped string safe for template literals
    26→ *
    27→ * @example
    28→ * escapeTemplateLiterals('Hello ${user.name}')
    29→ * // Returns: 'Hello \${user.name}'
    30→ *
    31→ * @example
    32→ * escapeTemplateLiterals('Backtick: `code`')
    33→ * // Returns: 'Backtick: \`code\`'
    34→ */
    35→function escapeTemplateLiterals(str: string): string {
    36→  return str
    37→    .replace(/\\/g, '\\\\')  // Escape backslashes first
    38→    .replace(/\$/g, '\\$')   // Escape $ to prevent ${} injection
    39→    .replace(/`/g, '\\`');   // Escape backticks
    40→}
    41→
    42→/**
    43→ * Sanitize string input to prevent security issues and normalize data
    44→ * Strips control characters, escapes template literals, trims whitespace, and enforces length limits
    45→ *
    46→ * @param input - String input to sanitize
    47→ * @param options - Sanitization options
    48→ * @param options.maxLength - Maximum allowed length (truncates if longer)
    49→ * @param options.stripControls - Whether to strip control characters (default: true)
    50→ * @param options.escapeTemplates - Whether to escape template literals (default: true)
    51→ * @returns Sanitized string
    52→ *
    53→ * @example
    54→ * sanitizeInput('Hello\x00World')
    55→ * // Returns: 'HelloWorld'
    56→ *
    57→ * @example
    58→ * sanitizeInput('  Hello ${injection}  ', { maxLength: 10 })
    59→ * // Returns: 'Hello \${injection}'
    60→ *
    61→ * @example
    62→ * sanitizeInput('user input', { stripControls: false, escapeTemplates: false })
    63→ * // Returns: 'user input'
    64→ */
    65→export function sanitizeInput(
    66→  input: string,
    67→  options: {
    68→    maxLength?: number;
    69→    stripControls?: boolean;
    70→    escapeTemplates?: boolean;
    71→  } = {}
    72→): string {
    73→  if (typeof input !== 'string') {
    74→    return '';
    75→  }
    76→
    77→  let sanitized = input;
    78→
    79→  // Strip control characters by default
    80→  if (options.stripControls !== false) {
    81→    sanitized = stripControlChars(sanitized);
    82→  }
    83→
    84→  // Escape template literals by default
    85→  if (options.escapeTemplates !== false) {
    86→    sanitized = escapeTemplateLiterals(sanitized);
    87→  }
    88→
    89→  // Trim whitespace
    90→  sanitized = sanitized.trim();
    91→
    92→  // Limit length if specified
    93→  if (options.maxLength && sanitized.length > options.maxLength) {
    94→    sanitized = sanitized.substring(0, options.maxLength);
    95→  }
    96→
    97→  return sanitized;
    98→}
    99→
   100→/**
   101→ * Strip phone numbers and email addresses from text to prevent
   102→ * contractors and workers from bypassing the platform.
   103→ *
   104→ * Handles Australian phone formats: 04xx xxx xxx, +614xxxxxxxx, (02) xxxx xxxx, etc.
   105→ * Handles email addresses: user@domain.com
   106→ *
   107→ * @param input - String to strip contact info from
   108→ * @returns String with phone numbers and emails replaced with [removed]
   109→ */
   110→export function stripContactInfo(input: string): string {
   111→  if (typeof input !== 'string') return '';
   112→
   113→  let result = input;
   114→
   115→  // Australian mobile: 04xx xxx xxx, 04xxxxxxxx, +614xxxxxxxx
   116→  result = result.replace(/(\+?61\s?|0)4\d{2}[\s.-]?\d{3}[\s.-]?\d{3}/g, '[removed]');
   117→
   118→  // Australian landline: (0x) xxxx xxxx, 0x xxxx xxxx
   119→  result = result.replace(/\(?0[2-9]\)?[\s.-]?\d{4}[\s.-]?\d{4}/g, '[removed]');
   120→
   121→  // General international numbers: +xx xxxxxxxxxx (10+ digits with optional separators)
   122→  result = result.replace(/\+\d{1,3}[\s.-]?\d[\d\s.-]{8,}/g, '[removed]');
   123→
   124→  // Catch remaining digit sequences that look like phone numbers (8-12 digits with separators)
   125→  result = result.replace(/\b\d{4}[\s.-]\d{3}[\s.-]\d{3}\b/g, '[removed]');
   126→
   127→  // Email addresses
   128→  result = result.replace(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g, '[removed]');
   129→
   130→  // Clean up multiple consecutive [removed] markers
   131→  result = result.replace(/(\[removed\]\s*){2,}/g, '[removed] ');
   132→
   133→  return result;
   134→}
   135→
   136→/**
   137→ * Sanitize multiple string inputs at once with field-specific options
   138→ * Useful for form data where different fields have different validation rules
   139→ *
   140→ * @param inputs - Object with string values to sanitize
   141→ * @param fieldOptions - Per-field sanitization options (keyed by field name)
   142→ * @returns Object with sanitized values
   143→ *
   144→ * @example
   145→ * sanitizeInputs(
   146→ *   { name: 'John Doe', bio: 'Hello\x00World', email: '  john@example.com  ' },
   147→ *   { bio: { maxLength: 500 }, email: { maxLength: 100 } }
   148→ * )
   149→ * // Returns: { name: 'John Doe', bio: 'HelloWorld', email: 'john@example.com' }
   150→ */
   151→export function sanitizeInputs(
   152→  inputs: Record<string, string>,
   153→  fieldOptions: Record<string, { maxLength?: number }> = {}
   154→): Record<string, string> {
   155→  const sanitized: Record<string, string> = {};
   156→
   157→  for (const [key, value] of Object.entries(inputs)) {
   158→    const options = fieldOptions[key] || {};
   159→    sanitized[key] = sanitizeInput(value, options);
   160→  }
   161→
   162→  return sanitized;
   163→}

<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→  last_seen_at: string | null;
    42→  ai_generated: boolean;
    43→}
    44→
    45→export interface Company {
    46→  id: string;
    47→  profile_id: string;
    48→  name: string;
    49→  abn: string | null;
    50→  address: string | null;
    51→  industry: string | null;
    52→  size: string | null;
    53→  website: string | null;
    54→  logo_url: string | null;
    55→  ai_research: Record<string, unknown> | null;
    56→  created_at: string;
    57→  updated_at: string;
    58→}
    59→
    60→export interface WorkerProfile {
    61→  id: string;
    62→  profile_id: string;
    63→  trades: string[];
    64→  experience_years: number | null;
    65→  certifications: string[];
    66→  white_card_number: string | null;
    67→  white_card_verified: boolean;
    68→  availability: Availability | null;
    69→  languages: string[];
    70→  bio: string | null;
    71→  voice_note_url: string | null;
    72→  ai_extracted: Record<string, unknown> | null;
    73→  created_at: string;
    74→  updated_at: string;
    75→}
    76→
    77→export interface Job {
    78→  id: string;
    79→  company_id: string;
    80→  title: string;
    81→  description: string | null;
    82→  trade: string;
    83→  /**
    84→   * Location as { lat, lng } object
    85→   * Note: Database stores this as PostgreSQL point type
    86→   * Use postgresPointToJson() when reading from DB
    87→   * Use jsonToPostgresPoint() when writing to DB
    88→   */
    89→  location: { lat: number; lng: number } | null;
    90→  suburb: string | null;
    91→  site_address: string | null;
    92→  rate_min: number | null;
    93→  rate_max: number | null;
    94→  rate_type: RateType;
    95→  start_date: string | null;
    96→  duration: string | null;
    97→  workers_needed: number;
    98→  certifications_required: string[];
    99→  status: JobStatus;
   100→  ai_generated: boolean;
   101→  created_at: string;
   102→  updated_at: string;
   103→}
   104→
   105→export interface Match {
   106→  id: string;
   107→  job_id: string;
   108→  worker_id: string;
   109→  status: MatchStatus;
   110→  match_score: number | null;
   111→  ai_summary: string | null;
   112→  contractor_action: ActionStatus;
   113→  worker_action: ActionStatus;
   114→  hired_at: string | null;
   115→  fee_charged: boolean;
   116→  created_at: string;
   117→  updated_at: string;
   118→}
   119→
   120→export interface Rating {
   121→  id: string;
   122→  match_id: string;
   123→  from_id: string;
   124→  to_id: string;
   125→  score: number;
   126→  comment: string | null;
   127→  created_at: string;
   128→}
   129→
   130→export interface Payment {
   131→  id: string;
   132→  match_id: string;
   133→  company_id: string;
   134→  amount: number;
   135→  status: PaymentStatus;
   136→  stripe_payment_id: string | null;
   137→  created_at: string;
   138→}
   139→
   140→export interface Notification {
   141→  id: string;
   142→  profile_id: string;
   143→  type: NotificationType;
   144→  title: string;
   145→  body: string | null;
   146→  data: Record<string, unknown> | null;
   147→  read: boolean;
   148→  created_at: string;
   149→}
   150→

<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→/**
     5→ * Combines class names using clsx and tailwind-merge for conditional styling
     6→ * Handles conditional classes, arrays, and objects with proper Tailwind CSS merging
     7→ *
     8→ * @param inputs - Class values to combine (strings, objects, arrays, etc.)
     9→ * @returns Combined and merged class name string
    10→ *
    11→ * @example
    12→ * // Basic usage
    13→ * cn('px-2 py-1', 'bg-red-500')
    14→ *
    15→ * @example
    16→ * // Conditional classes
    17→ * cn('base-class', isActive && 'active-class', hasError && 'error-class')
    18→ *
    19→ * @example
    20→ * // With objects
    21→ * cn('base-class', { 'text-red-500': isError, 'text-green-500': isSuccess })
    22→ *
    23→ * @example
    24→ * // Tailwind merge conflicts resolved automatically
    25→ * cn('px-2 py-1', 'px-4') // Results in 'px-4 py-1'
    26→ */
    27→export function cn(...inputs: ClassValue[]) {
    28→  return twMerge(clsx(inputs))
    29→}
    30→

<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→// Simple test to validate the voice-complete endpoint structure
     2→// This file can be used to manually test the endpoint
     3→
     4→export interface VoiceCompleteRequest {
     5→  audio?: File;
     6→  transcript?: string;
     7→}
     8→
     9→export interface VoiceCompleteResponse {
    10→  transcript: string;
    11→  parsed_data: {
    12→    trades: string[];
    13→    trade: string | null;
    14→    experience_years: number | null;
    15→    location: string | null;
    16→    availability: string | null;
    17→    additional_details: string | null;
    18→  };
    19→  generated_profile: {
    20→    bio: string;
    21→    skills: string[];
    22→    certifications: string[];
    23→  };
    24→  error?: string;
    25→  fallback?: boolean;
    26→  timing?: {
    27→    had_audio: boolean;
    28→    processing_complete: boolean;
    29→  };
    30→}
    31→
    32→// Example usage test
    33→export function testVoiceCompleteEndpoint() {
    34→  // Test with transcript only
    35→  const testTranscript = "I'm a chippie with 8 years experience in Parramatta, looking for full-time work";
    36→
    37→  // This would be called from the frontend
    38→  return fetch("/api/ai/voice-complete", {
    39→    method: "POST",
    40→    headers: { "Content-Type": "application/json" },
    41→    body: JSON.stringify({ transcript: testTranscript }),
    42→  });
    43→}

<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 check for Supabase migration files.


ls -la /home/ccuser/the-50-dollar-app/supabase/ 2>/dev/null || echo "No supabase directory found"

Check for supabase directory


total 68
drwxrwxr-x  4 ccuser ccuser  4096 Feb  8 11:18 .
drwxrwxr-x 12 ccuser ccuser  4096 Feb 10 10:06 ..
drwxrwxr-x  2 ccuser ccuser  4096 Feb  8 11:18 .temp
-rw-rw-r--  1 ccuser ccuser  5952 Feb  8 21:31 add-messaging.sql
-rw-rw-r--  1 ccuser ccuser  5973 Feb  8 11:18 fix-critical-rls-insert-policies.sql
-rw-rw-r--  1 ccuser ccuser  5951 Feb  8 11:18 fix-rls-policies-migration.sql
drwxrwxr-x  2 ccuser ccuser  4096 Feb 10 09:37 migrations
-rw-rw-r--  1 ccuser ccuser 20558 Feb  8 21:37 schema.sql
-rw-rw-r--  1 ccuser ccuser  2939 Feb  8 11:18 test-rls-policies.sql


find /home/ccuser/the-50-dollar-app -path "*/supabase/migrations*" -name "*.sql" 2>/dev/null | sort || echo "No migration files found"

Find all Supabase migration SQL files