Function bodies 234 total
POST function · typescript · L6-L77 (72 LOC)app/api/applications/fix-status/route.ts
export async function POST(request: NextRequest) {
try {
const supabase = createServerClient();
if (!supabase) {
return NextResponse.json({ error: 'Database not configured' }, { status: 503 });
}
const { company, role, status } = await request.json();
if (!company || !status) {
return NextResponse.json({ error: 'Company and status required' }, { status: 400 });
}
// Valid statuses
const validStatuses = ['saved', 'applied', 'interviewing', 'offer', 'closed'];
if (!validStatuses.includes(status)) {
return NextResponse.json({ error: 'Invalid status', validStatuses }, { status: 400 });
}
// Find the application
let query = supabase
.from('applications')
.select('id, company, role, status')
.ilike('company', `%${company}%`);
if (role) {
query = query.ilike('role', `%${role}%`);
}
const { data: apps } = await query;
if (!apps || apps.length === 0) {
return NextResponse.jsoGET function · typescript · L15-L105 (91 LOC)app/api/applications/[id]/emails/route.ts
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const supabase = createServerClient();
if (!supabase) {
return NextResponse.json(
{ error: 'Database not configured' },
{ status: 503 }
);
}
const { id: applicationId } = await params;
// First verify the application exists
const { data: application, error: appError } = await supabase
.from('applications')
.select('id')
.eq('id', applicationId)
.single();
if (appError) {
if (appError.code === 'PGRST116') {
return NextResponse.json(
{ error: 'Application not found' },
{ status: 404 }
);
}
console.error('Application fetch error:', appError);
return NextResponse.json(
{ error: 'Failed to fetch application' },
{ status: 500 }
);
}
if (!application) {
return NextResponse.json(
{ error: 'ApplicaGET function · typescript · L7-L38 (32 LOC)app/api/applications/[id]/route.ts
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const supabase = createServerClient();
if (!supabase) {
return NextResponse.json({ error: 'Database not configured' }, { status: 503 });
}
const { id } = await params;
const { data, error } = await supabase
.from('applications')
.select('*')
.eq('id', id)
.single();
if (error) {
if (error.code === 'PGRST116') {
return NextResponse.json({ error: 'Application not found' }, { status: 404 });
}
return NextResponse.json({ error: 'Failed to fetch application' }, { status: 500 });
}
return NextResponse.json({ application: dbToApplication(data) });
} catch (error) {
console.error('Error fetching application:', error);
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
}
}PATCH function · typescript · L41-L90 (50 LOC)app/api/applications/[id]/route.ts
export async function PATCH(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const supabase = createServerClient();
if (!supabase) {
return NextResponse.json({ error: 'Database not configured' }, { status: 503 });
}
const { id } = await params;
const body = await request.json();
// Build update object with snake_case keys
const updateData: Record<string, unknown> = {};
if (body.company !== undefined) updateData.company = body.company;
if (body.role !== undefined) updateData.role = body.role;
if (body.status !== undefined) updateData.status = body.status;
if (body.closeReason !== undefined) updateData.close_reason = body.closeReason;
if (body.notes !== undefined) updateData.notes = body.notes;
if (body.appliedDate !== undefined) updateData.applied_date = body.appliedDate;
if (body.jobUrl !== undefined) updateData.job_url = body.jobUrl;
if (body.location !== undefined) updateData.loDELETE function · typescript · L93-L120 (28 LOC)app/api/applications/[id]/route.ts
export async function DELETE(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const supabase = createServerClient();
if (!supabase) {
return NextResponse.json({ error: 'Database not configured' }, { status: 503 });
}
const { id } = await params;
const { error } = await supabase
.from('applications')
.delete()
.eq('id', id);
if (error) {
return NextResponse.json({ error: 'Failed to delete application' }, { status: 500 });
}
return NextResponse.json({ success: true });
} catch (error) {
console.error('Error deleting application:', error);
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
}
}GET function · typescript · L20-L75 (56 LOC)app/api/applications/[id]/tailored-cv/route.ts
export async function GET(
request: NextRequest,
context: RouteContext
): Promise<NextResponse> {
try {
const { id } = await context.params;
const supabase = createServerClient();
if (!supabase) {
return NextResponse.json(
{ error: 'Database not configured' },
{ status: 503 }
);
}
// Fetch application to get tailored CV info
const { data: application, error } = await supabase
.from('applications')
.select('tailored_cv_url, tailored_cv_filename, tailored_cv_generated_at')
.eq('id', id)
.single();
if (error) {
if (error.code === 'PGRST116') {
return NextResponse.json(
{ error: 'Application not found' },
{ status: 404 }
);
}
console.error('Database error:', error);
return NextResponse.json(
{ error: 'Failed to fetch application' },
{ status: 500 }
);
}
// Return null if no tailored CV exists
if (!applicationPOST function · typescript · L81-L271 (191 LOC)app/api/applications/[id]/tailored-cv/route.ts
export async function POST(
request: NextRequest,
context: RouteContext
): Promise<NextResponse> {
try {
const { id } = await context.params;
// Parse request body
let feedback: string | undefined;
try {
const body = await request.json();
feedback = body?.feedback;
} catch {
// Empty body is OK
}
const supabase = createServerClient();
if (!supabase) {
return NextResponse.json(
{ error: 'Database not configured' },
{ status: 503 }
);
}
// Step 1: Fetch the application
const { data: application, error: appError } = await supabase
.from('applications')
.select('id, company, role, job_url')
.eq('id', id)
.single();
if (appError) {
if (appError.code === 'PGRST116') {
return NextResponse.json(
{ error: 'Application not found' },
{ status: 404 }
);
}
console.error('Database error:', appError);
return NextResWant fix-PRs on findings? Install Repobility's GitHub App · github.com/apps/repobility-bot
GET function · typescript · L7-L46 (40 LOC)app/api/applications/route.ts
export async function GET() {
try {
const supabase = createServerClient();
if (!supabase) {
return NextResponse.json(
{ error: 'Database not configured', applications: [], stats: { total: 0, responseRate: 0 } },
{ status: 503 }
);
}
// For now, get all applications (auth will be added later)
// Order by created_at since saved jobs may not have applied_date
const { data, error } = await supabase
.from('applications')
.select('*')
.order('created_at', { ascending: false });
if (error) {
console.error('Database error:', error);
return NextResponse.json({ error: 'Failed to fetch applications' }, { status: 500 });
}
const applications = (data || []).map(dbToApplication);
// Calculate stats (exclude saved jobs - they're not applications yet)
const applied = applications.filter(a => a.status !== 'saved');
const total = applied.length;
const responded = applied.filter(a => a.staPOST function · typescript · L49-L83 (35 LOC)app/api/applications/route.ts
export async function POST(request: NextRequest) {
try {
const supabase = createServerClient();
if (!supabase) {
return NextResponse.json({ error: 'Database not configured' }, { status: 503 });
}
const body = await request.json();
const { data, error } = await supabase
.from('applications')
.insert(applicationToDb(body))
.select()
.single();
if (error) {
console.error('Database error:', error);
if (error.code === '23505') {
return NextResponse.json({ error: 'Application already exists' }, { status: 409 });
}
return NextResponse.json({ error: 'Failed to create application' }, { status: 500 });
}
// Auto-generate CV if job URL provided
if (body.jobUrl) {
triggerCVGeneration(data.id);
}
return NextResponse.json({ application: dbToApplication(data) }, { status: 201 });
} catch (error) {
console.error('Error creating application:', error);
return NextResponse.jsoGET function · typescript · L6-L54 (49 LOC)app/api/auth/gmail/callback/route.ts
export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const code = searchParams.get('code');
const error = searchParams.get('error');
if (error) {
console.error('OAuth error:', error);
return NextResponse.redirect(new URL('/?error=oauth_denied', request.url));
}
if (!code) {
return NextResponse.redirect(new URL('/?error=no_code', request.url));
}
try {
// Exchange code for tokens
const tokens = await getTokensFromCode(code);
if (!tokens.access_token || !tokens.refresh_token) {
throw new Error('Missing tokens in response');
}
// Store tokens in Supabase
const supabase = createServerClient();
if (supabase) {
// For now, store with a placeholder user_id (we'll add proper auth later)
const { error: dbError } = await supabase
.from('gmail_tokens')
.upsert({
user_id: '00000000-0000-0000-0000-000000000000', // Placeholder
access_tokeGET function · typescript · L5-L16 (12 LOC)app/api/auth/gmail/route.ts
export async function GET() {
try {
const authUrl = getAuthUrl();
return NextResponse.redirect(authUrl);
} catch (error) {
console.error('Gmail auth error:', error);
return NextResponse.json(
{ error: 'Gmail authentication not configured' },
{ status: 500 }
);
}
}GET function · typescript · L7-L57 (51 LOC)app/api/credits/route.ts
export async function GET(request: NextRequest) {
try {
const supabase = createServerClient();
if (!supabase) {
return NextResponse.json(
{ error: 'Database not configured' },
{ status: 503 }
);
}
// Get user from auth header or cookie
const authHeader = request.headers.get('authorization');
let userId: string | null = null;
if (authHeader?.startsWith('Bearer ')) {
const token = authHeader.substring(7);
const { data: { user } } = await supabase.auth.getUser(token);
userId = user?.id || null;
}
// For development/testing, allow a demo user
if (!userId && process.env.NODE_ENV === 'development') {
userId = 'demo-user-id';
}
if (!userId) {
return NextResponse.json(
{ error: 'Unauthorized. Please log in.' },
{ status: 401 }
);
}
// Check and reset free credits if eligible (monthly reset)
await checkAndResetFreeCredits(userId);
// Get credit POST function · typescript · L14-L86 (73 LOC)app/api/credits/use/route.ts
export async function POST(request: NextRequest) {
try {
const supabase = createServerClient();
if (!supabase) {
return NextResponse.json(
{ error: 'Database not configured' },
{ status: 503 }
);
}
// Get user from auth header
const authHeader = request.headers.get('authorization');
let userId: string | null = null;
if (authHeader?.startsWith('Bearer ')) {
const token = authHeader.substring(7);
const { data: { user } } = await supabase.auth.getUser(token);
userId = user?.id || null;
}
// For development/testing, allow a demo user
if (!userId && process.env.NODE_ENV === 'development') {
userId = 'demo-user-id';
}
if (!userId) {
return NextResponse.json(
{ error: 'Unauthorized. Please log in.' },
{ status: 401 }
);
}
// Check if user has credits available
const hasCredits = await canUseCredit(userId);
if (!hasCredits) {
return NextRPUT function · typescript · L6-L56 (51 LOC)app/api/cv/[id]/route.ts
export async function PUT(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const supabase = createServerClient();
if (!supabase) {
return NextResponse.json({ error: 'Database not configured' }, { status: 503 });
}
const { id } = await params;
const body: UpdateCVRequest = await request.json();
// Build update object
const updateData: Record<string, unknown> = {
updated_at: new Date().toISOString(),
};
if (body.rawText !== undefined) {
if (body.rawText.trim().length === 0) {
return NextResponse.json({ error: 'CV text cannot be empty' }, { status: 400 });
}
updateData.raw_text = body.rawText.trim();
}
if (body.fileName !== undefined) {
updateData.file_name = body.fileName;
}
const { data, error } = await supabase
.from('cvs')
.update(updateData)
.eq('id', id)
.select()
.single();
if (error) {
if (error.code ==DELETE function · typescript · L59-L87 (29 LOC)app/api/cv/[id]/route.ts
export async function DELETE(
request: NextRequest,
{ params }: { params: Promise<{ id: string }> }
) {
try {
const supabase = createServerClient();
if (!supabase) {
return NextResponse.json({ error: 'Database not configured' }, { status: 503 });
}
const { id } = await params;
const { error } = await supabase
.from('cvs')
.delete()
.eq('id', id);
if (error) {
console.error('Database error:', error);
return NextResponse.json({ error: 'Failed to delete CV' }, { status: 500 });
}
return NextResponse.json({ success: true });
} catch (error) {
console.error('Error deleting CV:', error);
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
}
}Citation: Repobility (2026). State of AI-Generated Code. https://repobility.com/research/
POST function · typescript · L19-L122 (104 LOC)app/api/cv/parse-pdf/route.ts
export async function POST(request: NextRequest): Promise<NextResponse<ParsePDFResponse>> {
try {
const formData = await request.formData();
const file = formData.get('file');
// Validate file exists
if (!file || !(file instanceof File)) {
return NextResponse.json(
{
success: false,
error: 'No file uploaded. Please provide a PDF file.',
},
{ status: 400 }
);
}
// Validate file type
const allowedTypes = ['application/pdf'];
if (!allowedTypes.includes(file.type)) {
return NextResponse.json(
{
success: false,
error: 'Invalid file type. Please upload a PDF file.',
},
{ status: 400 }
);
}
// Validate file size
if (file.size > MAX_FILE_SIZE) {
return NextResponse.json(
{
success: false,
error: 'File too large. Maximum size is 10MB.',
},
{ status: 400 }
);
}
// Convert fGET function · typescript · L6-L40 (35 LOC)app/api/cv/route.ts
export async function GET() {
try {
const supabase = createServerClient();
if (!supabase) {
return NextResponse.json(
{ error: 'Database not configured', cv: null },
{ status: 503 }
);
}
// For now, get the first CV (single user mode)
// TODO: Add proper user authentication
const { data, error } = await supabase
.from('cvs')
.select('*')
.order('created_at', { ascending: false })
.limit(1)
.single();
if (error) {
// No CV found is not an error - return null
if (error.code === 'PGRST116') {
return NextResponse.json({ cv: null });
}
console.error('Database error:', error);
return NextResponse.json({ error: 'Failed to fetch CV' }, { status: 500 });
}
return NextResponse.json({ cv: dbToCV(data) });
} catch (error) {
console.error('Error fetching CV:', error);
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
}
}POST function · typescript · L43-L83 (41 LOC)app/api/cv/route.ts
export async function POST(request: NextRequest) {
try {
const supabase = createServerClient();
if (!supabase) {
return NextResponse.json({ error: 'Database not configured' }, { status: 503 });
}
const body: CreateCVRequest = await request.json();
if (!body.rawText || body.rawText.trim().length === 0) {
return NextResponse.json({ error: 'CV text is required' }, { status: 400 });
}
// Create new CV
// Using a fixed UUID for single-user mode until auth is implemented
const cvData = cvToDb({
userId: '00000000-0000-0000-0000-000000000001', // TODO: Get from auth
rawText: body.rawText.trim(),
fileName: body.fileName || null,
pdfStoragePath: null,
fileSizeBytes: null,
});
const { data, error } = await supabase
.from('cvs')
.insert(cvData)
.select()
.single();
if (error) {
console.error('Database error:', error);
return NextResponse.json({ error: 'Failed to creDELETE function · typescript · L12-L106 (95 LOC)app/api/cvs/[id]/route.ts
export async function DELETE(
request: NextRequest,
context: RouteContext
): Promise<NextResponse> {
try {
const { id } = await context.params;
// Only allow deleting tailored CVs (id format: tailored-{applicationId})
if (!id.startsWith('tailored-')) {
return NextResponse.json(
{ error: 'Cannot delete master CV from this endpoint' },
{ status: 400 }
);
}
const applicationId = id.replace('tailored-', '');
const supabase = createServerClient();
if (!supabase) {
return NextResponse.json(
{ error: 'Database not configured' },
{ status: 503 }
);
}
// Get the application to find the storage path
const { data: application, error: fetchError } = await supabase
.from('applications')
.select('tailored_cv_url, tailored_cv_filename')
.eq('id', applicationId)
.single();
if (fetchError) {
if (fetchError.code === 'PGRST116') {
return NextResponse.json(
GET function · typescript · L9-L81 (73 LOC)app/api/cvs/route.ts
export async function GET(): Promise<NextResponse> {
try {
const supabase = createServerClient();
if (!supabase) {
return NextResponse.json(
{ error: 'Database not configured' },
{ status: 503 }
);
}
const cvs: CV[] = [];
// Fetch master CV from cvs table
const { data: masterCV, error: masterError } = await supabase
.from('cvs')
.select('id, raw_text, created_at, updated_at')
.order('created_at', { ascending: false })
.limit(1)
.single();
if (masterError && masterError.code !== 'PGRST116') {
console.error('Error fetching master CV:', masterError);
}
if (masterCV) {
cvs.push({
id: masterCV.id,
type: 'master',
filename: 'Master CV',
url: null,
createdAt: masterCV.created_at,
updatedAt: masterCV.updated_at || masterCV.created_at,
});
}
// Fetch tailored CVs from applications table
const { data: applications, erroGET function · typescript · L5-L19 (15 LOC)app/api/debug/cvs/route.ts
export async function GET(request: NextRequest) {
const supabase = createServerClient();
if (!supabase) {
return NextResponse.json({ error: 'No DB' }, { status: 503 });
}
const company = request.nextUrl.searchParams.get('company') || 'Stripe';
const { data, error } = await supabase
.from('applications')
.select('id, company, role, tailored_cv_url, tailored_cv_filename, tailored_cv_generated_at')
.ilike('company', `%${company}%`);
return NextResponse.json({ applications: data, error });
}GET function · typescript · L5-L52 (48 LOC)app/api/debug/emails/route.ts
export async function GET(request: NextRequest) {
try {
const supabase = createServerClient();
if (!supabase) {
return NextResponse.json({ error: 'Database not configured' }, { status: 503 });
}
const company = request.nextUrl.searchParams.get('company');
if (!company) {
return NextResponse.json({ error: 'Company required' }, { status: 400 });
}
// Get applications
const { data: apps } = await supabase
.from('applications')
.select('id, company, role, status')
.ilike('company', `%${company}%`);
if (!apps) {
return NextResponse.json({ error: 'No applications' }, { status: 404 });
}
// Get emails for each application
const result = [];
for (const app of apps) {
const { data: emails } = await supabase
.from('application_emails')
.select('id, subject, email_date, email_type')
.eq('application_id', app.id)
.order('email_date', { ascending: false });
reshtmlToText function · typescript · L16-L23 (8 LOC)app/api/emails/[messageId]/route.ts
function htmlToText(html: string): string {
return html
.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '')
.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
.replace(/<[^>]+>/g, ' ')
.replace(/\s+/g, ' ')
.trim();
}Open data scored by Repobility · https://repobility.com
GET function · typescript · L26-L172 (147 LOC)app/api/emails/[messageId]/route.ts
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ messageId: string }> }
) {
try {
const supabase = createServerClient();
if (!supabase) {
return NextResponse.json(
{ error: 'Database not configured' },
{ status: 503 }
);
}
const { messageId } = await params;
// Get stored Gmail tokens
const { data: tokenData, error: tokenError } = await supabase
.from('gmail_tokens')
.select('*')
.single();
if (tokenError) {
console.error('Token fetch error:', tokenError);
return NextResponse.json(
{ error: 'Gmail not connected', needsAuth: true },
{ status: 401 }
);
}
if (!tokenData) {
return NextResponse.json(
{ error: 'Gmail not connected', needsAuth: true },
{ status: 401 }
);
}
// Create Gmail client with stored tokens
const gmail = createGmailClient(tokenData.access_token, tokenData.refresh_token);
GET function · typescript · L6-L106 (101 LOC)app/api/extension/check-duplicate/route.ts
export async function GET(request: NextRequest) {
try {
const supabase = createServerClient();
if (!supabase) {
return NextResponse.json(
{ error: 'Database not configured' },
{ status: 503 }
);
}
const { searchParams } = new URL(request.url);
const url = searchParams.get('url');
const company = searchParams.get('company');
const role = searchParams.get('role');
// Must provide either url or both company and role
if (!url && (!company || !role)) {
return NextResponse.json(
{ error: 'Must provide either url or both company and role' },
{ status: 400 }
);
}
// Check by URL first (most reliable)
if (url) {
const { data: existingByUrl, error: urlError } = await supabase
.from('applications')
.select('id, company, role, job_url, status, created_at')
.eq('job_url', url)
.maybeSingle();
if (urlError) {
console.error('Database error POST function · typescript · L15-L159 (145 LOC)app/api/extension/save/route.ts
export async function POST(request: NextRequest) {
try {
const supabase = createServerClient();
if (!supabase) {
return NextResponse.json(
{ error: 'Database not configured' },
{ status: 503 }
);
}
const body: ExtensionSaveRequest = await request.json();
// Validate required fields
if (!body.company || typeof body.company !== 'string' || !body.company.trim()) {
return NextResponse.json(
{ error: 'Company is required' },
{ status: 400 }
);
}
if (!body.role || typeof body.role !== 'string' || !body.role.trim()) {
return NextResponse.json(
{ error: 'Role is required' },
{ status: 400 }
);
}
if (!body.jobUrl || typeof body.jobUrl !== 'string' || !body.jobUrl.trim()) {
return NextResponse.json(
{ error: 'Job URL is required' },
{ status: 400 }
);
}
// Validate URL format
try {
new URL(body.jobUrl);
} catch {
POST function · typescript · L4-L46 (43 LOC)app/api/extract-url/route.ts
export async function POST(request: NextRequest) {
try {
const { url } = await request.json();
if (!url) {
return NextResponse.json({ error: 'URL is required' }, { status: 400 });
}
// Validate URL
let parsedUrl: URL;
try {
parsedUrl = new URL(url);
} catch {
return NextResponse.json({ error: 'Invalid URL' }, { status: 400 });
}
// Fetch the page
const response = await fetch(url, {
headers: {
'User-Agent': 'Mozilla/5.0 (compatible; Canopy/1.0)',
'Accept': 'text/html',
},
signal: AbortSignal.timeout(10000), // 10s timeout
});
if (!response.ok) {
return NextResponse.json({
error: 'Failed to fetch URL',
company: extractCompanyFromUrl(parsedUrl.hostname, parsedUrl.pathname),
}, { status: 200 }); // Still return partial data
}
const html = await response.text();
// Extract metadata from HTML
const metadata = extractMetadata(html, parsedUrl.hextractMetadata function · typescript · L48-L87 (40 LOC)app/api/extract-url/route.ts
function extractMetadata(html: string, hostname: string, pathname: string) {
const result: {
company: string | null;
role: string | null;
location: string | null;
description: string | null;
} = {
company: null,
role: null,
location: null,
description: null,
};
// Extract OpenGraph tags
const ogTitle = extractMeta(html, 'og:title');
const ogDescription = extractMeta(html, 'og:description');
const ogSiteName = extractMeta(html, 'og:site_name');
// Extract standard meta tags
const title = extractTitle(html);
const description = extractMeta(html, 'description');
// Try to extract company name - prioritize URL-based extraction for job boards
const urlCompany = extractCompanyFromUrl(hostname, pathname);
// Don't use ogSiteName if it's a job board name
const jobBoards = ['lever', 'greenhouse', 'ashby', 'workable', 'smartrecruiters', 'hibob', 'workday'];
const isJobBoardSiteName = ogSiteName && jobBoards.some(jb => ogSiteName.toextractMeta function · typescript · L89-L105 (17 LOC)app/api/extract-url/route.ts
function extractMeta(html: string, name: string): string | null {
// Match both property="og:..." and name="..." patterns
const patterns = [
new RegExp(`<meta[^>]*property=["']${name}["'][^>]*content=["']([^"']+)["']`, 'i'),
new RegExp(`<meta[^>]*content=["']([^"']+)["'][^>]*property=["']${name}["']`, 'i'),
new RegExp(`<meta[^>]*name=["']${name}["'][^>]*content=["']([^"']+)["']`, 'i'),
new RegExp(`<meta[^>]*content=["']([^"']+)["'][^>]*name=["']${name}["']`, 'i'),
];
for (const pattern of patterns) {
const match = html.match(pattern);
if (match) {
return decodeHtmlEntities(match[1]);
}
}
return null;
}extractCompanyFromUrl function · typescript · L121-L147 (27 LOC)app/api/extract-url/route.ts
function extractCompanyFromUrl(hostname: string, pathname: string): string | null {
// FIRST: Check for subdomain pattern like "leboncoin.careers.hibob.com" or "company.jobs.lever.co"
// This must come first because hibob/etc use subdomain for company name
const subdomainMatch = hostname.match(/^([^.]+)\.(careers|jobs|apply|hire)\./);
if (subdomainMatch) {
const subdomain = subdomainMatch[1].toLowerCase();
// Make sure it's not a generic subdomain
if (!['www', 'app', 'api', 'jobs', 'careers', 'apply'].includes(subdomain)) {
const company = subdomain.replace(/-/g, ' ');
return company.split(' ').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
}
}
// SECOND: Check job boards where company is in the PATH (lever, greenhouse, etc)
for (const [domain, pattern] of Object.entries(JOB_BOARD_DOMAINS)) {
if (hostname.includes(domain)) {
const match = pathname.match(pattern);
if (match) {
const company = match[1].replacextractCompanyFromDomain function · typescript · L149-L162 (14 LOC)app/api/extract-url/route.ts
function extractCompanyFromDomain(hostname: string): string | null {
// Skip known job board domains
const jobBoards = ['lever', 'greenhouse', 'ashbyhq', 'workable', 'smartrecruiters', 'bamboohr', 'workday', 'icims', 'taleo', 'successfactors', 'myworkdayjobs', 'hibob', 'breezy', 'recruitee', 'jazz'];
// Remove common prefixes/suffixes
let domain = hostname
.replace(/^(www\.|careers\.|jobs\.|apply\.|hire\.|recruiting\.|boards\.)/, '')
.replace(/\.(com|org|net|io|co|hr).*$/, '');
if (!domain || jobBoards.includes(domain.toLowerCase())) return null;
// Capitalize first letter
return domain.charAt(0).toUpperCase() + domain.slice(1);
}Powered by Repobility — scan your code at https://repobility.com
extractCompanyFromTitle function · typescript · L164-L182 (19 LOC)app/api/extract-url/route.ts
function extractCompanyFromTitle(title: string | null): string | null {
if (!title) return null;
// Common patterns: "Role at Company", "Role - Company", "Company | Role"
const patterns = [
/ at ([A-Z][A-Za-z0-9\s&]+?)(?:\s*[-|]|$)/,
/ - ([A-Z][A-Za-z0-9\s&]+?)(?:\s*[-|]|$)/,
/^([A-Z][A-Za-z0-9\s&]+?) \| /,
];
for (const pattern of patterns) {
const match = title.match(pattern);
if (match) {
return match[1].trim();
}
}
return null;
}extractRoleFromTitle function · typescript · L184-L200 (17 LOC)app/api/extract-url/route.ts
function extractRoleFromTitle(title: string | null): string | null {
if (!title) return null;
// Remove company name and common suffixes
let role = title
.replace(/ at .+$/, '')
.replace(/ - .+$/, '')
.replace(/ \| .+$/, '')
.replace(/\s*\([^)]+\)\s*$/, '')
.replace(/\s*[-–—]\s*.*$/, '')
.trim();
// Don't return if it's too short or looks like a company name only
if (role.length < 3 || role.length > 100) return null;
return role;
}extractLocation function · typescript · L202-L221 (20 LOC)app/api/extract-url/route.ts
function extractLocation(text: string): string | null {
if (!text) return null;
// Common location patterns
const patterns = [
/\b(Remote|Hybrid|On-?site)\b/i,
/\b([A-Z][a-z]+(?:\s+[A-Z][a-z]+)*,\s*[A-Z]{2})\b/, // City, ST
/\b([A-Z][a-z]+(?:\s+[A-Z][a-z]+)*,\s*[A-Z][a-z]+)\b/, // City, Country
/\bLocation:\s*([^.|\n]+)/i,
];
for (const pattern of patterns) {
const match = text.match(pattern);
if (match) {
return match[1].trim();
}
}
return null;
}decodeHtmlEntities function · typescript · L223-L233 (11 LOC)app/api/extract-url/route.ts
function decodeHtmlEntities(text: string): string {
return text
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, "'")
.replace(/ /g, ' ')
.replace(/&#(\d+);/g, (_, num) => String.fromCharCode(parseInt(num, 10)))
.replace(/&#x([a-fA-F0-9]+);/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)));
}POST function · typescript · L17-L56 (40 LOC)app/api/onboarding/analyze-cv/route.ts
export async function POST(request: NextRequest): Promise<NextResponse<AnalyzeCVResponse>> {
try {
const body: AnalyzeCVRequest = await request.json();
// Validate input - cvText can be empty for "no CV" path
if (typeof body.cvText !== 'string') {
return NextResponse.json(
{
success: false,
error: 'cvText must be a string',
questions: [],
cvQuality: 'none' as const,
},
{ status: 400 }
);
}
// Analyze the CV
const result = await analyzeCV(body.cvText);
return NextResponse.json({
success: true,
...result,
});
} catch (error) {
console.error('Error analyzing CV:', error);
const errorMessage = error instanceof Error ? error.message : 'Failed to analyze CV';
return NextResponse.json(
{
success: false,
error: errorMessage,
questions: [],
cvQuality: 'none' as const,
},
{ status: 500 }
);
}
}POST function · typescript · L30-L155 (126 LOC)app/api/onboarding/complete/route.ts
export async function POST(request: NextRequest): Promise<NextResponse<CompleteOnboardingResponse>> {
try {
const supabase = createServerClient();
if (!supabase) {
return NextResponse.json(
{
success: false,
error: 'Database not configured',
},
{ status: 503 }
);
}
const body: CompleteOnboardingRequest = await request.json();
// Validate master CV
if (!body.masterCV || typeof body.masterCV !== 'string' || body.masterCV.trim().length < 50) {
return NextResponse.json(
{
success: false,
error: 'Master CV is required and must be at least 50 characters',
},
{ status: 400 }
);
}
// Save master CV to database
const cvData = cvToDb({
userId: DEFAULT_USER_ID,
rawText: body.masterCV.trim(),
fileName: 'Master CV (Onboarding)',
pdfStoragePath: null,
fileSizeBytes: null,
});
const { data: savedCV, error: cvErextractCompanyFromUrl function · typescript · L160-L202 (43 LOC)app/api/onboarding/complete/route.ts
function extractCompanyFromUrl(url: string): string | null {
if (!url) return null;
try {
const urlObj = new URL(url);
const hostname = urlObj.hostname.toLowerCase();
// Common job board patterns
const patterns: Record<string, RegExp> = {
lever: /jobs\.lever\.co\/([^/]+)/,
greenhouse: /boards\.greenhouse\.io\/([^/]+)/,
ashby: /jobs\.ashbyhq\.com\/([^/]+)/,
workday: /([^.]+)\.wd\d+\.myworkdayjobs\.com/,
linkedin: /linkedin\.com\/jobs\/view/,
};
// Check for job board patterns
for (const [, regex] of Object.entries(patterns)) {
const match = url.match(regex);
if (match && match[1]) {
return formatCompanyName(match[1]);
}
}
// LinkedIn doesn't have company in URL reliably
if (hostname.includes('linkedin.com')) {
return null;
}
// Fallback: use hostname without common prefixes
const cleanHostname = hostname
.replace('www.', '')
.replace('jobs.', '')
.formatCompanyName function · typescript · L207-L212 (6 LOC)app/api/onboarding/complete/route.ts
function formatCompanyName(slug: string): string {
return slug
.split('-')
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
}Want fix-PRs on findings? Install Repobility's GitHub App · github.com/apps/repobility-bot
POST function · typescript · L19-L83 (65 LOC)app/api/onboarding/enhance-cv/route.ts
export async function POST(request: NextRequest): Promise<NextResponse<EnhanceCVResponse>> {
try {
const body: EnhanceCVRequest = await request.json();
// Validate input
if (typeof body.cvText !== 'string') {
return NextResponse.json(
{
success: false,
error: 'cvText must be a string',
},
{ status: 400 }
);
}
if (!body.answers || typeof body.answers !== 'object') {
return NextResponse.json(
{
success: false,
error: 'answers must be an object',
},
{ status: 400 }
);
}
// Check if we have enough input to generate a CV
const hasCV = body.cvText.trim().length >= 50;
const hasAnswers = Object.values(body.answers).some(
(v) => typeof v === 'string' && v.trim().length > 0
);
if (!hasCV && !hasAnswers) {
return NextResponse.json(
{
success: false,
error: 'Please provide either a CV or answer somGET function · typescript · L8-L48 (41 LOC)app/api/research/chat/[applicationId]/route.ts
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ applicationId: string }> }
) {
try {
const { applicationId } = await params;
const supabase = createServerClient();
if (!supabase) {
return NextResponse.json(
{ error: 'Database not configured' },
{ status: 503 }
);
}
const { data, error } = await supabase
.from('research_chats')
.select('messages')
.eq('application_id', applicationId)
.single();
if (error && error.code !== 'PGRST116') {
console.error('Failed to fetch chat history:', error);
return NextResponse.json(
{ error: 'Failed to load chat history' },
{ status: 500 }
);
}
return NextResponse.json({
messages: data?.messages || [],
});
} catch (error) {
console.error('Chat history error:', error);
return NextResponse.json(
{ error: 'Failed to load chat history' },
{ status: 500 }
);
}
}getAnthropic function · typescript · L9-L16 (8 LOC)app/api/research/chat/route.ts
function getAnthropic(): Anthropic {
if (!anthropicClient) {
anthropicClient = new Anthropic({
apiKey: process.env.ANTHROPIC_API_KEY,
});
}
return anthropicClient;
}fetchJobPosting function · typescript · L43-L90 (48 LOC)app/api/research/chat/route.ts
async function fetchJobPosting(url: string): Promise<string | null> {
try {
const response = await fetch(url, {
headers: {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
},
});
if (!response.ok) return null;
const html = await response.text();
const contentParts: string[] = [];
// Extract meta description
const metaDesc = html.match(/<meta[^>]*name="description"[^>]*content="([^"]*)"[^>]*>/i);
if (metaDesc?.[1]) contentParts.push(`Description: ${metaDesc[1]}`);
// Extract JSON-LD
const jsonLdMatch = html.match(/<script[^>]*type="application\/ld\+json"[^>]*>([\s\S]*?)<\/script>/gi);
if (jsonLdMatch) {
for (const match of jsonLdMatch) {
const jsonContent = match.replace(/<script[^>]*>|<\/script>/gi, '');
try {
const data = JSON.parse(jsonContent);
if (data['@POST function · typescript · L96-L230 (135 LOC)app/api/research/chat/route.ts
export async function POST(request: NextRequest) {
try {
const body: ResearchChatRequest = await request.json();
const { applicationId, message, context } = body;
if (!applicationId || !message || !context?.company) {
return NextResponse.json(
{ error: 'Missing required fields' },
{ status: 400 }
);
}
const supabase = createServerClient();
if (!supabase) {
return NextResponse.json(
{ error: 'Database not configured' },
{ status: 503 }
);
}
// Load existing chat history
const { data: chatData } = await supabase
.from('research_chats')
.select('messages')
.eq('application_id', applicationId)
.single();
const existingMessages: ChatMessage[] = chatData?.messages || [];
// Fetch job posting content if URL provided and first message
let jobPostingContent: string | null = null;
if (context.jobUrl && existingMessages.length === 0) {
jobPostingContent getOpenAI function · typescript · L8-L15 (8 LOC)app/api/research/route.ts
function getOpenAI(): OpenAI {
if (!openaiClient) {
openaiClient = new OpenAI({
apiKey: process.env.OPENAI_API_KEY,
});
}
return openaiClient;
}fetchJobPosting function · typescript · L24-L99 (76 LOC)app/api/research/route.ts
async function fetchJobPosting(url: string): Promise<string | null> {
try {
const response = await fetch(url, {
headers: {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
},
});
if (!response.ok) {
console.warn(`Failed to fetch job posting: ${response.status}`);
return null;
}
const html = await response.text();
// Extract meaningful content from various sources
const contentParts: string[] = [];
// 1. Extract from meta description
const metaDesc = html.match(/<meta[^>]*name="description"[^>]*content="([^"]*)"[^>]*>/i);
if (metaDesc?.[1]) {
contentParts.push(`Description: ${metaDesc[1]}`);
}
// 2. Extract from og:description
const ogDesc = html.match(/<meta[^>]*property="og:description"[^>]*content="([^"]*)"[^>]*>/i);
fetchEmailContext function · typescript · L104-L131 (28 LOC)app/api/research/route.ts
async function fetchEmailContext(applicationId: string, supabase: ReturnType<typeof createServerClient>): Promise<string | null> {
if (!supabase) return null;
try {
// Get the application to find the source email
const { data: app } = await supabase
.from('applications')
.select('source_email_id')
.eq('id', applicationId)
.single();
if (!app?.source_email_id) return null;
// Get the email content
const { data: email } = await supabase
.from('emails')
.select('subject, body, snippet')
.eq('gmail_message_id', app.source_email_id)
.single();
if (!email) return null;
return `Email Subject: ${email.subject}\nEmail Content: ${email.body || email.snippet || ''}`;
} catch (error) {
console.warn('Failed to fetch email context:', error);
return null;
}
}Citation: Repobility (2026). State of AI-Generated Code. https://repobility.com/research/
generateResearch function · typescript · L167-L309 (143 LOC)app/api/research/route.ts
async function generateResearch(
company: string,
role: string,
jobUrl?: string,
context?: ResearchContext
): Promise<ResearchResponse> {
// Build context section with real data
let contextSection = '';
if (context?.jobPostingContent) {
contextSection += `\n\n## ACTUAL JOB POSTING CONTENT (USE THIS AS PRIMARY SOURCE):\n${context.jobPostingContent}\n`;
}
if (context?.emailContent) {
contextSection += `\n\n## EMAIL FROM COMPANY:\n${context.emailContent}\n`;
}
if (jobUrl) {
contextSection += `\n\nJob posting URL: ${jobUrl}`;
}
const prompt = `You are an expert career coach and company researcher. Research the following company and role to help someone prepare for a job interview.
Company: ${company}
Role: ${role}
${contextSection}
IMPORTANT: If job posting content is provided above, extract REAL information from it. Do NOT make up information - use what's actually in the job posting. For any fields where you don't have real data, use null or leaPOST function · typescript · L315-L438 (124 LOC)app/api/research/route.ts
export async function POST(request: NextRequest) {
try {
// Validate API key is configured
if (!process.env.OPENAI_API_KEY) {
return NextResponse.json(
{ error: 'OpenAI API key not configured' },
{ status: 503 }
);
}
// Parse request body
const body: ResearchRequest = await request.json();
// Validate required fields
if (!body.company || typeof body.company !== 'string') {
return NextResponse.json(
{ error: 'Company name is required' },
{ status: 400 }
);
}
if (!body.role || typeof body.role !== 'string') {
return NextResponse.json(
{ error: 'Role is required' },
{ status: 400 }
);
}
const company = body.company.trim();
const role = body.role.trim();
const jobUrl = body.jobUrl?.trim();
const applicationId = body.applicationId;
// Check cache in Supabase
const supabase = createServerClient();
if (supabase) {
try {
cPOST function · typescript · L17-L147 (131 LOC)app/api/stripe/create-checkout/route.ts
export async function POST(request: NextRequest) {
// Check if Stripe is configured
if (!isStripeConfigured() || !stripe) {
return NextResponse.json(
{ error: 'Stripe is not configured' },
{ status: 503 }
);
}
try {
// Parse request body
const body: CheckoutRequest = await request.json();
const { priceId, successUrl, cancelUrl } = body;
// Validate priceId
if (!priceId) {
return NextResponse.json(
{ error: 'priceId is required' },
{ status: 400 }
);
}
// Validate that priceId is one of our valid bundle prices
if (!isValidPriceId(priceId)) {
return NextResponse.json(
{ error: 'Invalid priceId' },
{ status: 400 }
);
}
// Get the Supabase client
const supabase = createServerClient();
if (!supabase) {
return NextResponse.json(
{ error: 'Database not configured' },
{ status: 503 }
);
}
// Get user from auth header or coopage 1 / 5next ›