Read and report the FULL contents of every API route in /home/ccuser/rateright-v2/src/app/api/. Search for all route.ts files under the api directory. Also check for any server actions (files with "use server"). Report complete file contents for each file found.


I'll search for all API routes and server actions in the api directory. Let me start by finding all route.ts files and checking for server actions.


Bash

find /home/ccuser/rateright-v2/src/app/api -type f -name "*.ts" -o -name "*.js"


Now let me search for any files with "use server" directive to find server actions:


No files found


Now let me read all four route.ts files to get their complete contents:


     1→/**
     2→ * Email verification API endpoint
     3→ * Placeholder for email verification
     4→ */
     5→
     6→import { NextRequest, NextResponse } from 'next/server'
     7→
     8→export async function POST(request: NextRequest) {
     9→  try {
    10→    const { userId, token } = await request.json()
    11→
    12→    if (!userId || !token) {
    13→      return NextResponse.json({ error: 'Missing required fields' }, { status: 400 })
    14→    }
    15→
    16→    // In production, implement proper token validation
    17→    // For now, return success
    18→
    19→    return NextResponse.json({
    20→      success: true,
    21→      message: 'Email verification endpoint ready'
    22→    })
    23→
    24→  } catch (error) {
    25→    console.error('Email verification error:', error)
    26→    return NextResponse.json(
    27→      { error: 'Internal server error' },
    28→      { status: 500 }
    29→    )
    30→  }
    31→}
    32→

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


     1→import { NextRequest, NextResponse } from "next/server";
     2→import OpenAI from "openai";
     3→
     4→const openai = new OpenAI({
     5→  apiKey: process.env.OPENAI_API_KEY || "",
     6→});
     7→
     8→export async function POST(request: NextRequest) {
     9→  try {
    10→    const { trade, experience, location, availability } = await request.json();
    11→
    12→    if (!trade || experience === undefined || !location || !availability) {
    13→      return NextResponse.json(
    14→        { error: "Missing required fields" },
    15→        { status: 400 }
    16→      );
    17→    }
    18→
    19→    const prompt = `You are an expert construction industry recruiter. Based on the following information about a construction worker, generate a professional profile.
    20→
    21→Worker Information:
    22→- Trade: ${trade}
    23→- Years of Experience: ${experience} years
    24→- Location: ${location}
    25→- Availability: ${availability}
    26→
    27→Generate a JSON response with:
    28→1. bio: A professional 2-3 sentence bio (50-80 words) highlighting their expertise, experience, and work style
    29→2. skills: An array of 6-10 relevant technical skills for their trade (specific tools, techniques, materials)
    30→3. certifications: An array of 3-5 common/recommended certifications for their trade (actual Australian construction certifications)
    31→
    32→Make it sound professional but approachable. Focus on practical skills and experience.
    33→
    34→Return ONLY valid JSON in this exact format:
    35→{
    36→  "bio": "string",
    37→  "skills": ["skill1", "skill2", ...],
    38→  "certifications": ["cert1", "cert2", ...]
    39→}`;
    40→
    41→    const completion = await openai.chat.completions.create({
    42→      model: "gpt-4o-mini",
    43→      messages: [
    44→        {
    45→          role: "system",
    46→          content: "You are a construction industry expert. Always respond with valid JSON only.",
    47→        },
    48→        {
    49→          role: "user",
    50→          content: prompt,
    51→        },
    52→      ],
    53→      temperature: 0.7,
    54→      response_format: { type: "json_object" },
    55→    });
    56→
    57→    const content = completion.choices[0]?.message?.content;
    58→    if (!content) {
    59→      throw new Error("No response from AI");
    60→    }
    61→
    62→    const generated = JSON.parse(content);
    63→
    64→    return NextResponse.json({
    65→      bio: generated.bio || "",
    66→      skills: generated.skills || [],
    67→      certifications: generated.certifications || [],
    68→    });
    69→  } catch (error) {
    70→    console.error("AI generation error:", error);
    71→    return NextResponse.json(
    72→      { error: "Failed to generate profile" },
    73→      { status: 500 }
    74→    );
    75→  }
    76→}
    77→

<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→
     3→// Cache to store logo URLs (in production, use Redis or database)
     4→const logoCache = new Map<string, { logoUrl: string | null; source: string; timestamp: number }>();
     5→const CACHE_DURATION = 24 * 60 * 60 * 1000; // 24 hours
     6→
     7→interface LogoResponse {
     8→  logoUrl: string | null;
     9→  source: 'clearbit' | 'google' | 'none';
    10→}
    11→
    12→async function checkLogoExists(url: string): Promise<boolean> {
    13→  try {
    14→    const response = await fetch(url, {
    15→      method: 'HEAD',
    16→      headers: {
    17→        'User-Agent': 'RateRight/1.0',
    18→      },
    19→    });
    20→    return response.ok;
    21→  } catch {
    22→    return false;
    23→  }
    24→}
    25→
    26→export async function GET(request: NextRequest) {
    27→  try {
    28→    const searchParams = request.nextUrl.searchParams;
    29→    const domain = searchParams.get('domain');
    30→
    31→    if (!domain) {
    32→      return NextResponse.json<LogoResponse>(
    33→        { logoUrl: null, source: 'none' },
    34→        { status: 400 }
    35→      );
    36→    }
    37→
    38→    // Clean the domain (remove protocol if present)
    39→    const cleanDomain = domain.replace(/^https?:\/\//, '').replace(/\/.*$/, '');
    40→
    41→    // Check cache first
    42→    const cacheKey = cleanDomain;
    43→    const cached = logoCache.get(cacheKey);
    44→    if (cached && Date.now() - cached.timestamp < CACHE_DURATION) {
    45→      return NextResponse.json<LogoResponse>({
    46→        logoUrl: cached.logoUrl,
    47→        source: cached.source as LogoResponse['source'],
    48→      });
    49→    }
    50→
    51→    // Try Clearbit Logo API first
    52→    const clearbitUrl = `https://logo.clearbit.com/${cleanDomain}`;
    53→    const clearbitExists = await checkLogoExists(clearbitUrl);
    54→
    55→    if (clearbitExists) {
    56→      logoCache.set(cacheKey, {
    57→        logoUrl: clearbitUrl,
    58→        source: 'clearbit',
    59→        timestamp: Date.now(),
    60→      });
    61→
    62→      return NextResponse.json<LogoResponse>({
    63→        logoUrl: clearbitUrl,
    64→        source: 'clearbit',
    65→      });
    66→    }
    67→
    68→    // Try Google Favicon as fallback
    69→    const googleUrl = `https://www.google.com/s2/favicons?domain=${cleanDomain}&sz=128`;
    70→    const googleExists = await checkLogoExists(googleUrl);
    71→
    72→    if (googleExists) {
    73→      logoCache.set(cacheKey, {
    74→        logoUrl: googleUrl,
    75→        source: 'google',
    76→        timestamp: Date.now(),
    77→      });
    78→
    79→      return NextResponse.json<LogoResponse>({
    80→        logoUrl: googleUrl,
    81→        source: 'google',
    82→      });
    83→    }
    84→
    85→    // No logo found
    86→    logoCache.set(cacheKey, {
    87→      logoUrl: null,
    88→      source: 'none',
    89→      timestamp: Date.now(),
    90→    });
    91→
    92→    return NextResponse.json<LogoResponse>({
    93→      logoUrl: null,
    94→      source: 'none',
    95→    });
    96→
    97→  } catch (error) {
    98→    console.error('Logo fetch error:', error);
    99→
   100→    return NextResponse.json<LogoResponse>(
   101→      { logoUrl: null, source: 'none' },
   102→      { status: 500 }
   103→    );
   104→  }
   105→}
   106→
   107→// Add OPTIONS handler for CORS
   108→export async function OPTIONS() {
   109→  return new NextResponse(null, {
   110→    status: 200,
   111→    headers: {
   112→      'Access-Control-Allow-Origin': '*',
   113→      'Access-Control-Allow-Methods': 'GET, OPTIONS',
   114→      'Access-Control-Allow-Headers': 'Content-Type',
   115→    },
   116→  });
   117→}

<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 { formatABN } from "@/lib/abn-utils";
     2→import { NextRequest, NextResponse } from 'next/server';
     3→import { isValidABN, cleanABN } from '@/lib/abn-utils';
     4→
     5→/**
     6→ * ABN Lookup API Route — GET /api/abn-lookup?abn=XXXXXXXXXXX
     7→ *
     8→ * Validates the ABN locally (checksum), then calls the ABR JSON endpoint
     9→ * to fetch entity details.
    10→ *
    11→ * ABR JSON docs: https://abr.business.gov.au/json/
    12→ * Registration:  https://abr.business.gov.au/Tools/WebServices
    13→ *
    14→ * Required env var:
    15→ *   ABR_GUID — the authentication GUID issued after registration
    16→ *
    17→ * Optional env var:
    18→ *   ABR_DEV_MODE — set to 'true' to enable development mode with mock data
    19→ */
    20→
    21→// ── ABR configuration ──────────────────────────────────────────────
    22→const ABR_JSON_URL = 'https://abr.business.gov.au/json/AbnDetails.aspx';
    23→const ABR_GUID = process.env.ABR_GUID ?? '';
    24→const ABR_DEV_MODE = process.env.ABR_DEV_MODE === 'true' || process.env.NODE_ENV === 'development';
    25→
    26→// ── Types ──────────────────────────────────────────────────────────
    27→
    28→/** Shape returned by the ABR JSON endpoint (JSONP-unwrapped). */
    29→interface ABRJsonResponse {
    30→  Abn: string;
    31→  AbnStatus: string;
    32→  AbnStatusEffectiveFrom: string;
    33→  Acn: string;
    34→  AddressDate: string | null;
    35→  AddressPostcode: string;
    36→  AddressState: string;
    37→  BusinessName: Array<string | { Value: string; EffectiveFrom: string }>;
    38→  EntityName: string;
    39→  EntityTypeCode: string;
    40→  EntityTypeName: string;
    41→  Gst: string | null;
    42→  Message: string;
    43→}
    44→
    45→/** Director verification result */
    46→interface DirectorVerification {
    47→  status: 'pending' | 'verified' | 'mismatch' | 'not_available';
    48→  message: string;
    49→  requiresManualReview?: boolean;
    50→}
    51→
    52→/** Company data from ABR */
    53→interface CompanyData {
    54→  name: string;
    55→  abn: string;
    56→  status: string;
    57→  statusDate: string;
    58→  state: string;
    59→  postcode: string;
    60→  type: string;
    61→  typeCode: string;
    62→  gstRegistered: boolean;
    63→  acn?: string;
    64→  directorVerification?: DirectorVerification;
    65→}
    66→
    67→/** Our normalised response. */
    68→interface ABNLookupResult {
    69→  success: boolean;
    70→  data?: CompanyData;
    71→  error?: string;
    72→  devMode?: boolean;
    73→}
    74→
    75→// ── Development Mode Mock Data ─────────────────────────────────────
    76→
    77→/**
    78→ * Mock company data for development when ABR_GUID is not configured.
    79→ * Uses obviously fake ABNs to prevent confusion with real data.
    80→ */
    81→const MOCK_COMPANIES: Record<string, CompanyData> = {
    82→  // Test ABN for development - passes checksum but is obviously fake
    83→  '51824753556': {
    84→    name: 'TEST COMPANY PTY LTD (Dev Mode)',
    85→    abn: '51 824 753 556',
    86→    status: 'Active',
    87→    statusDate: '2020-01-01',
    88→    state: 'NSW',
    89→    postcode: '2000',
    90→    type: 'Australian Private Company',
    91→    typeCode: 'PRV',
    92→    gstRegistered: true,
    93→    directorVerification: {
    94→      status: 'not_available',
    95→      message: 'Director verification unavailable in development mode',
    96→    },
    97→  },
    98→  // Another test ABN
    99→  '34241177887': {
   100→    name: 'EXAMPLE BUILDERS PTY LTD (Dev Mode)',
   101→    abn: '34 241 177 887',
   102→    status: 'Active',
   103→    statusDate: '2019-06-15',
   104→    state: 'VIC',
   105→    postcode: '3000',
   106→    type: 'Australian Private Company',
   107→    typeCode: 'PRV',
   108→    gstRegistered: true,
   109→    directorVerification: {
   110→      status: 'not_available',
   111→      message: 'Director verification unavailable in development mode',
   112→    },
   113→  },
   114→};
   115→
   116→/**
   117→ * Generate mock data for any valid ABN in development mode.
   118→ */
   119→function generateMockData(abn: string): CompanyData {
   120→  const formatted = formatABN(abn);
   121→  return {
   122→    name: `MOCK COMPANY ${abn.slice(-4)} PTY LTD (Dev Mode)`,
   123→    abn: formatted,
   124→    status: 'Active',
   125→    statusDate: new Date().toISOString().split('T')[0],
   126→    state: 'NSW',
   127→    postcode: '2000',
   128→    type: 'Australian Private Company',
   129→    typeCode: 'PRV',
   130→    gstRegistered: Math.random() > 0.3,
   131→    directorVerification: {
   132→      status: 'not_available',
   133→      message: 'Director verification unavailable in development mode. Register at https://abr.business.gov.au/Tools/WebServices',
   134→    },
   135→  };
   136→}
   137→
   138→// ── Helpers ────────────────────────────────────────────────────────
   139→
   140→function buildABRUrl(abn: string): string {
   141→  const params = new URLSearchParams({
   142→    abn,
   143→    guid: ABR_GUID,
   144→    callback: 'cbr',
   145→  });
   146→  return `${ABR_JSON_URL}?${params.toString()}`;
   147→}
   148→
   149→function stripJSONP(jsonp: string): ABRJsonResponse {
   150→  const match = jsonp.match(/^cbr\((.*)\)$/);
   151→  if (!match) throw new Error('Unexpected ABR response format');
   152→  return JSON.parse(match[1]);
   153→}
   154→
   155→function normaliseABRResponse(abr: ABRJsonResponse): CompanyData {
   156→  const name = abr.EntityName ||
   157→    (Array.isArray(abr.BusinessName) && abr.BusinessName[0]) ||
   158→    'Unknown';
   159→
   160→  return {
   161→    name: typeof name === 'string' ? name : (name as any).Value || 'Unknown',
   162→    abn: formatABN(abr.Abn),
   163→    status: abr.AbnStatus,
   164→    statusDate: abr.AbnStatusEffectiveFrom,
   165→    state: abr.AddressState,
   166→    postcode: abr.AddressPostcode,
   167→    type: abr.EntityTypeName,
   168→    typeCode: abr.EntityTypeCode,
   169→    gstRegistered: abr.Gst !== null,
   170→    acn: abr.Acn || undefined,
   171→    // Director verification will be done separately via ASIC
   172→    directorVerification: {
   173→      status: 'pending',
   174→      message: 'Director verification pending. ASIC lookup required.',
   175→      requiresManualReview: true,
   176→    },
   177→  };
   178→}
   179→
   180→/**
   181→ * ASIC Director Verification (Stub)
   182→ *
   183→ * ASIC Connect API requires paid subscription.
   184→ * For MVP, we flag all signups for manual review.
   185→ *
   186→ * Future implementation:
   187→ * - Call ASIC Connect API to get director list
   188→ * - Cross-reference user email/name with director records
   189→ * - Auto-verify if match found
   190→ * - Flag for manual review if no match
   191→ *
   192→ * @see https://connectonline.asic.gov.au/
   193→ */
   194→async function verifyDirector(
   195→  abn: string,
   196→  acn?: string,
   197→  userEmail?: string
   198→): Promise<DirectorVerification> {
   199→  // TODO: Implement ASIC Connect API integration
   200→  // For now, flag all for manual review
   201→
   202→  console.log(`[ASIC] Director verification requested for ABN ${abn}, ACN ${acn || 'N/A'}`);
   203→
   204→  return {
   205→    status: 'pending',
   206→    message: 'Director verification requires manual review. ASIC integration pending.',
   207→    requiresManualReview: true,
   208→  };
   209→}
   210→
   211→// ── Route Handler ──────────────────────────────────────────────────
   212→
   213→export async function GET(request: NextRequest) {
   214→  try {
   215→    const searchParams = request.nextUrl.searchParams;
   216→    const rawABN = searchParams.get('abn');
   217→    const userEmail = searchParams.get('email'); // Optional: for director verification
   218→
   219→    // --- 1. Validate input --------------------------------------------------
   220→    if (!rawABN) {
   221→      return NextResponse.json({
   222→        success: false,
   223→        error: 'Missing query parameter: abn'
   224→      }, { status: 400 });
   225→    }
   226→
   227→    const abn = cleanABN(rawABN);
   228→    if (!isValidABN(abn)) {
   229→      return NextResponse.json({
   230→        success: false,
   231→        error: 'Invalid ABN format. Please check the number and try again.'
   232→      }, { status: 400 });
   233→    }
   234→
   235→    // --- 2. Check if ABR_GUID is configured --------------------------------
   236→    const isGuidConfigured = ABR_GUID &&
   237→      ABR_GUID !== 'REGISTER-TOMORROW-AT-ABR-BUSINESS-GOV-AU' &&
   238→      ABR_GUID !== 'temporary-abr-guid-for-build';
   239→
   240→    if (!isGuidConfigured) {
   241→      // Development mode: return mock data
   242→      if (ABR_DEV_MODE) {
   243→        console.log(`[ABN Lookup] Dev mode - returning mock data for ${abn}`);
   244→        const mockData = MOCK_COMPANIES[abn] || generateMockData(abn);
   245→        return NextResponse.json({
   246→          success: true,
   247→          data: mockData,
   248→          devMode: true,
   249→        });
   250→      }
   251→
   252→      // Production without GUID: return error with validation info
   253→      return NextResponse.json({
   254→        success: false,
   255→        error: 'ABR service not configured',
   256→        devMode: false,
   257→      }, { status: 503 });
   258→    }
   259→
   260→    // --- 3. Call ABR web service -------------------------------------------
   261→    const url = buildABRUrl(abn);
   262→    const abrResponse = await fetch(url, {
   263→      method: 'GET',
   264→      headers: { 'User-Agent': 'RateRight/1.0' },
   265→      signal: AbortSignal.timeout(10000),
   266→    });
   267→
   268→    if (!abrResponse.ok) {
   269→      const text = await abrResponse.text();
   270→      console.error(`[ABN Lookup] ABR error ${abrResponse.status}:`, text);
   271→      return NextResponse.json({
   272→        success: false,
   273→        error: `ABR service error: ${abrResponse.status}`,
   274→      }, { status: 502 });
   275→    }
   276→
   277→    const jsonpText = await abrResponse.text();
   278→    const abrData = stripJSONP(jsonpText);
   279→
   280→    // --- 4. Handle ABR business logic ---------------------------------------
   281→    if (abrData.Message) {
   282→      return NextResponse.json({
   283→        success: false,
   284→        error: abrData.Message
   285→      }, { status: 404 });
   286→    }
   287→
   288→    const result = normaliseABRResponse(abrData);
   289→
   290→    // --- 5. Director verification (async, non-blocking) --------------------
   291→    if (result.acn || userEmail) {
   292→      result.directorVerification = await verifyDirector(
   293→        abn,
   294→        result.acn,
   295→        userEmail || undefined
   296→      );
   297→    }
   298→
   299→    return NextResponse.json({
   300→      success: true,
   301→      data: result,
   302→    });
   303→
   304→  } catch (err: any) {
   305→    console.error('[ABN Lookup] Uncaught error:', err);
   306→    return NextResponse.json({
   307→      success: false,
   308→      error: 'Internal server error',
   309→    }, { status: 500 });
   310→  }
   311→}
   312→

<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>


Perfect! I have found and read all API routes in the /home/ccuser/rateright-v2/src/app/api/ directory. Let me verify there are no other files I might have missed:


find /home/ccuser/rateright-v2/src/app/api -type f \( -name "*.ts" -o -name "*.tsx" -o -name "*.js" -o -name "*.jsx" \)


## Complete API Routes Report

I have found **4 API route files** in `/home/ccuser/rateright-v2/src/app/api/`. There are **NO server actions** (files with "use server" directive) in the codebase. Below is the full contents of each route:

---

### 1. `/home/ccuser/rateright-v2/src/app/api/auth/verify-email/route.ts`

**Purpose:** Email verification endpoint (placeholder for email verification)

```typescript
/**
 * Email verification API endpoint
 * Placeholder for email verification
 */

import { NextRequest, NextResponse } from 'next/server'

export async function POST(request: NextRequest) {
  try {
    const { userId, token } = await request.json()

    if (!userId || !token) {
      return NextResponse.json({ error: 'Missing required fields' }, { status: 400 })
    }

    // In production, implement proper token validation
    // For now, return success

    return NextResponse.json({
      success: true,
      message: 'Email verification endpoint ready'
    })

  } catch (error) {
    console.error('Email verification error:', error)
    return NextResponse.json(
      { error: 'Internal server error' },
      { status: 500 }
    )
  }
}
```

**Notes:** Currently a placeholder - requires production implementation of token validation.

---

### 2. `/home/ccuser/rateright-v2/src/app/api/ai/generate-profile/route.ts`

**Purpose:** AI-powered worker profile generation using OpenAI GPT-4o-mini

**Method:** POST

**Input Parameters:**
- `trade` (required): Construction trade type
- `experience` (required): Years of experience
- `location` (required): Worker location
- `availability` (required): Availability status

**Output:** JSON with:
- `bio`: Professional 2-3 sentence bio (50-80 words)
- `skills`: Array of 6-10 technical skills
- `certifications`: Array of 3-5 Australian construction certifications

```typescript
import { NextRequest, NextResponse } from "next/server";
import OpenAI from "openai";

const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY || "",
});

export async function POST(request: NextRequest) {
  try {
    const { trade, experience, location, availability } = await request.json();

    if (!trade || experience === undefined || !location || !availability) {
      return NextResponse.json(
        { error: "Missing required fields" },
        { status: 400 }
      );
    }

    const prompt = `You are an expert construction industry recruiter. Based on the following information about a construction worker, generate a professional profile.

Worker Information:
- Trade: ${trade}
- Years of Experience: ${experience} years
- Location: ${location}
- Availability: ${availability}

Generate a JSON response with:
1. bio: A professional 2-3 sentence bio (50-80 words) highlighting their expertise, experience, and work style
2. skills: An array of 6-10 relevant technical skills for their trade (specific tools, techniques, materials)
3. certifications: An array of 3-5 common/recommended certifications for their trade (actual Australian construction certifications)

Make it sound professional but approachable. Focus on practical skills and experience.

Return ONLY valid JSON in this exact format:
{
  "bio": "string",
  "skills": ["skill1", "skill2", ...],
  "certifications": ["cert1", "cert2", ...]
}`;

    const completion = await openai.chat.completions.create({
      model: "gpt-4o-mini",
      messages: [
        {
          role: "system",
          content: "You are a construction industry expert. Always respond with valid JSON only.",
        },
        {
          role: "user",
          content: prompt,
        },
      ],
      temperature: 0.7,
      response_format: { type: "json_object" },
    });

    const content = completion.choices[0]?.message?.content;
    if (!content) {
      throw new Error("No response from AI");
    }

    const generated = JSON.parse(content);

    return NextResponse.json({
      bio: generated.bio || "",
      skills: generated.skills || [],
      certifications: generated.certifications || [],
    });
  } catch (error) {
    console.error("AI generation error:", error);
    return NextResponse.json(
      { error: "Failed to generate profile" },
      { status: 500 }
    );
  }
}
```

**Notes:** Uses OpenAI's gpt-4o-mini model with JSON response format. Requires `OPENAI_API_KEY` environment variable.

---

### 3. `/home/ccuser/rateright-v2/src/app/api/company-logo/route.ts`

**Purpose:** Fetch company logos from Clearbit or Google Favicon APIs with caching

**Method:** GET, OPTIONS (CORS)

**Query Parameter:** `domain` (required)

**Features:**
- In-memory cache with 24-hour TTL
- Tries Clearbit first, falls back to Google Favicon
- CORS-enabled

```typescript
import { NextRequest, NextResponse } from 'next/server';

// Cache to store logo URLs (in production, use Redis or database)
const logoCache = new Map<string, { logoUrl: string | null; source: string; timestamp: number }>();
const CACHE_DURATION = 24 * 60 * 60 * 1000; // 24 hours

interface LogoResponse {
  logoUrl: string | null;
  source: 'clearbit' | 'google' | 'none';
}

async function checkLogoExists(url: string): Promise<boolean> {
  try {
    const response = await fetch(url, {
      method: 'HEAD',
      headers: {
        'User-Agent': 'RateRight/1.0',
      },
    });
    return response.ok;
  } catch {
    return false;
  }
}

export async function GET(request: NextRequest) {
  try {
    const searchParams = request.nextUrl.searchParams;
    const domain = searchParams.get('domain');

    if (!domain) {
      return NextResponse.json<LogoResponse>(
        { logoUrl: null, source: 'none' },
        { status: 400 }
      );
    }

    // Clean the domain (remove protocol if present)
    const cleanDomain = domain.replace(/^https?:\/\//, '').replace(/\/.*$/, '');

    // Check cache first
    const cacheKey = cleanDomain;
    const cached = logoCache.get(cacheKey);
    if (cached && Date.now() - cached.timestamp < CACHE_DURATION) {
      return NextResponse.json<LogoResponse>({
        logoUrl: cached.logoUrl,
        source: cached.source as LogoResponse['source'],
      });
    }

    // Try Clearbit Logo API first
    const clearbitUrl = `https://logo.clearbit.com/${cleanDomain}`;
    const clearbitExists = await checkLogoExists(clearbitUrl);

    if (clearbitExists) {
      logoCache.set(cacheKey, {
        logoUrl: clearbitUrl,
        source: 'clearbit',
        timestamp: Date.now(),
      });

      return NextResponse.json<LogoResponse>({
        logoUrl: clearbitUrl,
        source: 'clearbit',
      });
    }

    // Try Google Favicon as fallback
    const googleUrl = `https://www.google.com/s2/favicons?domain=${cleanDomain}&sz=128`;
    const googleExists = await checkLogoExists(googleUrl);

    if (googleExists) {
      logoCache.set(cacheKey, {
        logoUrl: googleUrl,
        source: 'google',
        timestamp: Date.now(),
      });

      return NextResponse.json<LogoResponse>({
        logoUrl: googleUrl,
        source: 'google',
      });
    }

    // No logo found
    logoCache.set(cacheKey, {
      logoUrl: null,
      source: 'none',
      timestamp: Date.now(),
    });

    return NextResponse.json<LogoResponse>({
      logoUrl: null,
      source: 'none',
    });

  } catch (error) {
    console.error('Logo fetch error:', error);

    return NextResponse.json<LogoResponse>(
      { logoUrl: null, source: 'none' },
      { status: 500 }
    );
  }
}

// Add OPTIONS handler for CORS
export async function OPTIONS() {
  return new NextResponse(null, {
    status: 200,
    headers: {
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Methods': 'GET, OPTIONS',
      'Access-Control-Allow-Headers': 'Content-Type',
    },
  });
}
```

**Notes:** Uses in-memory cache - should use Redis or database in production. Returns logo URL from either Clearbit or Google, with fallback to `null`.

---

### 4. `/home/ccuser/rateright-v2/src/app/api/abn-lookup/route.ts`

**Purpose:** Australian Business Number (ABN) lookup with validation and director verification

**Method:** GET

**Query Parameters:**
- `abn` (required): Australian Business Number
- `email` (optional): User email for director verification

**Features:**
- Local ABN checksum validation
- ABR JSON endpoint integration
- Development mode with mock data
- Director verification stub (pending ASIC integration)
- Comprehensive error handling

```typescript
import { formatABN } from "@/lib/abn-utils";
import { NextRequest, NextResponse } from 'next/server';
import { isValidABN, cleanABN } from '@/lib/abn-utils';

/**
 * ABN Lookup API Route — GET /api/abn-lookup?abn=XXXXXXXXXXX
 *
 * Validates the ABN locally (checksum), then calls the ABR JSON endpoint
 * to fetch entity details.
 *
 * ABR JSON docs: https://abr.business.gov.au/json/
 * Registration:  https://abr.business.gov.au/Tools/WebServices
 *
 * Required env var:
 *   ABR_GUID — the authentication GUID issued after registration
 *
 * Optional env var:
 *   ABR_DEV_MODE — set to 'true' to enable development mode with mock data
 */

// ── ABR configuration ──────────────────────────────────────────────
const ABR_JSON_URL = 'https://abr.business.gov.au/json/AbnDetails.aspx';
const ABR_GUID = process.env.ABR_GUID ?? '';
const ABR_DEV_MODE = process.env.ABR_DEV_MODE === 'true' || process.env.NODE_ENV === 'development';

// ── Types ──────────────────────────────────────────────────────────

/** Shape returned by the ABR JSON endpoint (JSONP-unwrapped). */
interface ABRJsonResponse {
  Abn: string;
  AbnStatus: string;
  AbnStatusEffectiveFrom: string;
  Acn: string;
  AddressDate: string | null;
  AddressPostcode: string;
  AddressState: string;
  BusinessName: Array<string | { Value: string; EffectiveFrom: string }>;
  EntityName: string;
  EntityTypeCode: string;
  EntityTypeName: string;
  Gst: string | null;
  Message: string;
}

/** Director verification result */
interface DirectorVerification {
  status: 'pending' | 'verified' | 'mismatch' | 'not_available';
  message: string;
  requiresManualReview?: boolean;
}

/** Company data from ABR */
interface CompanyData {
  name: string;
  abn: string;
  status: string;
  statusDate: string;
  state: string;
  postcode: string;
  type: string;
  typeCode: string;
  gstRegistered: boolean;
  acn?: string;
  directorVerification?: DirectorVerification;
}

/** Our normalised response. */
interface ABNLookupResult {
  success: boolean;
  data?: CompanyData;
  error?: string;
  devMode?: boolean;
}

// ── Development Mode Mock Data ─────────────────────────────────────

/**
 * Mock company data for development when ABR_GUID is not configured.
 * Uses obviously fake ABNs to prevent confusion with real data.
 */
const MOCK_COMPANIES: Record<string, CompanyData> = {
  // Test ABN for development - passes checksum but is obviously fake
  '51824753556': {
    name: 'TEST COMPANY PTY LTD (Dev Mode)',
    abn: '51 824 753 556',
    status: 'Active',
    statusDate: '2020-01-01',
    state: 'NSW',
    postcode: '2000',
    type: 'Australian Private Company',
    typeCode: 'PRV',
    gstRegistered: true,
    directorVerification: {
      status: 'not_available',
      message: 'Director verification unavailable in development mode',
    },
  },
  // Another test ABN
  '34241177887': {
    name: 'EXAMPLE BUILDERS PTY LTD (Dev Mode)',
    abn: '34 241 177 887',
    status: 'Active',
    statusDate: '2019-06-15',
    state: 'VIC',
    postcode: '3000',
    type: 'Australian Private Company',
    typeCode: 'PRV',
    gstRegistered: true,
    directorVerification: {
      status: 'not_available',
      message: 'Director verification unavailable in development mode',
    },
  },
};

/**
 * Generate mock data for any valid ABN in development mode.
 */
function generateMockData(abn: string): CompanyData {
  const formatted = formatABN(abn);
  return {
    name: `MOCK COMPANY ${abn.slice(-4)} PTY LTD (Dev Mode)`,
    abn: formatted,
    status: 'Active',
    statusDate: new Date().toISOString().split('T')[0],
    state: 'NSW',
    postcode: '2000',
    type: 'Australian Private Company',
    typeCode: 'PRV',
    gstRegistered: Math.random() > 0.3,
    directorVerification: {
      status: 'not_available',
      message: 'Director verification unavailable in development mode. Register at https://abr.business.gov.au/Tools/WebServices',
    },
  };
}

// ── Helpers ────────────────────────────────────────────────────────

function buildABRUrl(abn: string): string {
  const params = new URLSearchParams({
    abn,
    guid: ABR_GUID,
    callback: 'cbr',
  });
  return `${ABR_JSON_URL}?${params.toString()}`;
}

function stripJSONP(jsonp: string): ABRJsonResponse {
  const match = jsonp.match(/^cbr\((.*)\)$/);
  if (!match) throw new Error('Unexpected ABR response format');
  return JSON.parse(match[1]);
}

function normaliseABRResponse(abr: ABRJsonResponse): CompanyData {
  const name = abr.EntityName ||
    (Array.isArray(abr.BusinessName) && abr.BusinessName[0]) ||
    'Unknown';

  return {
    name: typeof name === 'string' ? name : (name as any).Value || 'Unknown',
    abn: formatABN(abr.Abn),
    status: abr.AbnStatus,
    statusDate: abr.AbnStatusEffectiveFrom,
    state: abr.AddressState,
    postcode: abr.AddressPostcode,
    type: abr.EntityTypeName,
    typeCode: abr.EntityTypeCode,
    gstRegistered: abr.Gst !== null,
    acn: abr.Acn || undefined,
    // Director verification will be done separately via ASIC
    directorVerification: {
      status: 'pending',
      message: 'Director verification pending. ASIC lookup required.',
      requiresManualReview: true,
    },
  };
}

/**
 * ASIC Director Verification (Stub)
 *
 * ASIC Connect API requires paid subscription.
 * For MVP, we flag all signups for manual review.
 *
 * Future implementation:
 * - Call ASIC Connect API to get director list
 * - Cross-reference user email/name with director records
 * - Auto-verify if match found
 * - Flag for manual review if no match
 *
 * @see https://connectonline.asic.gov.au/
 */
async function verifyDirector(
  abn: string,
  acn?: string,
  userEmail?: string
): Promise<DirectorVerification> {
  // TODO: Implement ASIC Connect API integration
  // For now, flag all for manual review

  console.log(`[ASIC] Director verification requested for ABN ${abn}, ACN ${acn || 'N/A'}`);

  return {
    status: 'pending',
    message: 'Director verification requires manual review. ASIC integration pending.',
    requiresManualReview: true,
  };
}

// ── Route Handler ──────────────────────────────────────────────────

export async function GET(request: NextRequest) {
  try {
    const searchParams = request.nextUrl.searchParams;
    const rawABN = searchParams.get('abn');
    const userEmail = searchParams.get('email'); // Optional: for director verification

    // --- 1. Validate input --------------------------------------------------
    if (!rawABN) {
      return NextResponse.json({
        success: false,
        error: 'Missing query parameter: abn'
      }, { status: 400 });
    }

    const abn = cleanABN(rawABN);
    if (!isValidABN(abn)) {
      return NextResponse.json({
        success: false,
        error: 'Invalid ABN format. Please check the number and try again.'
      }, { status: 400 });
    }

    // --- 2. Check if ABR_GUID is configured --------------------------------
    const isGuidConfigured = ABR_GUID &&
      ABR_GUID !== 'REGISTER-TOMORROW-AT-ABR-BUSINESS-GOV-AU' &&
      ABR_GUID !== 'temporary-abr-guid-for-build';

    if (!isGuidConfigured) {
      // Development mode: return mock data
      if (ABR_DEV_MODE) {
        console.log(`[ABN Lookup] Dev mode - returning mock data for ${abn}`);
        const mockData = MOCK_COMPANIES[abn] || generateMockData(abn);
        return NextResponse.json({
          success: true,
          data: mockData,
          devMode: true,
        });
      }

      // Production without GUID: return error with validation info
      return NextResponse.json({
        success: false,
        error: 'ABR service not configured',
        devMode: false,
      }, { status: 503 });
    }

    // --- 3. Call ABR web service -------------------------------------------
    const url = buildABRUrl(abn);
    const abrResponse = await fetch(url, {
      method: 'GET',
      headers: { 'User-Agent': 'RateRight/1.0' },
      signal: AbortSignal.timeout(10000),
    });

    if (!abrResponse.ok) {
      const text = await abrResponse.text();
      console.error(`[ABN Lookup] ABR error ${abrResponse.status}:`, text);
      return NextResponse.json({
        success: false,
        error: `ABR service error: ${abrResponse.status}`,
      }, { status: 502 });
    }

    const jsonpText = await abrResponse.text();
    const abrData = stripJSONP(jsonpText);

    // --- 4. Handle ABR business logic ---------------------------------------
    if (abrData.Message) {
      return NextResponse.json({
        success: false,
        error: abrData.Message
      }, { status: 404 });
    }

    const result = normaliseABRResponse(abrData);

    // --- 5. Director verification (async, non-blocking) --------------------
    if (result.acn || userEmail) {
      result.directorVerification = await verifyDirector(
        abn,
        result.acn,
        userEmail || undefined
      );
    }

    return NextResponse.json({
      success: true,
      data: result,
    });

  } catch (err: any) {
    console.error('[ABN Lookup] Uncaught error:', err);
    return NextResponse.json({
      success: false,
      error: 'Internal server error',
    }, { status: 500 });
  }
}
```

**Environment Variables Required:**
- `ABR_GUID`: GUID from ABR Business registration
- `ABR_DEV_MODE` (optional): Enable development mode with mock data
- `NODE_ENV`: Set to 'development' for dev mode

**Notes:**
- Uses ABR JSON endpoint for real lookups
- Director verification is stubbed pending ASIC Connect API integration
- Supports development mode with mock data
- All directors verification flagged for manual review in current implementation

---

## Summary

**Total API Routes:** 4
**Server Actions:** 0
**Environment Variables Used:**
- `OPENAI_API_KEY` (generate-profile)
- `ABR_GUID` (abn-lookup)
- `ABR_DEV_MODE` (abn-lookup, optional)
- `NODE_ENV` (abn-lookup, implicit)

**Key Features:**
1. Email verification (placeholder)
2. AI profile generation for workers using OpenAI
3. Company logo fetching with caching
4. Australian ABN lookup with validation and director verification stubs