← back to jerof__job-tracker

Function bodies 234 total

All specs Real LLM only Function bodies
fetchJobDescription function · typescript · L365-L435 (71 LOC)
lib/cv-tailor.ts
export async function fetchJobDescription(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',
        'Accept-Language': 'en-US,en;q=0.9',
      },
      signal: AbortSignal.timeout(15000),
    });

    if (!response.ok) {
      console.error(`Failed to fetch job URL: ${response.status}`);
      return null;
    }

    const html = await response.text();
    const contentParts: string[] = [];

    // Extract meta description
    const metaDesc = html.match(/<meta[^>]*name=["']description["'][^>]*content=["']([^"']*)["'][^>]*>/i)
      || html.match(/<meta[^>]*content=["']([^"']*)["'][^>]*name=["']description["'][^>]*>/i);
    if (metaDesc?.[1]) {
      contentParts.push(`Description: ${metaDesc[1]}`);
    }

  
dbToCV function · typescript · L25-L36 (12 LOC)
lib/cv.types.ts
export function dbToCV(row: CVRow): CV {
  return {
    id: row.id,
    userId: row.user_id,
    rawText: row.raw_text,
    pdfStoragePath: row.pdf_storage_path,
    fileName: row.file_name,
    fileSizeBytes: row.file_size_bytes,
    createdAt: row.created_at,
    updatedAt: row.updated_at,
  };
}
cvToDb function · typescript · L39-L49 (11 LOC)
lib/cv.types.ts
export function cvToDb(cv: Partial<CV>): Partial<CVRow> {
  const row: Partial<CVRow> = {};

  if (cv.userId !== undefined) row.user_id = cv.userId;
  if (cv.rawText !== undefined) row.raw_text = cv.rawText;
  if (cv.pdfStoragePath !== undefined) row.pdf_storage_path = cv.pdfStoragePath;
  if (cv.fileName !== undefined) row.file_name = cv.fileName;
  if (cv.fileSizeBytes !== undefined) row.file_size_bytes = cv.fileSizeBytes;

  return row;
}
dbToApplication function · typescript · L38-L53 (16 LOC)
lib/database.types.ts
export function dbToApplication(row: DbApplication) {
  return {
    id: row.id,
    company: row.company,
    role: row.role,
    location: row.location,
    status: row.status,
    closeReason: row.close_reason,
    appliedDate: row.applied_date,
    sourceEmailId: row.source_email_id,
    jobUrl: row.job_url,
    notes: row.notes,
    createdAt: row.created_at,
    updatedAt: row.updated_at,
  };
}
applicationToDb function · typescript · L56-L74 (19 LOC)
lib/database.types.ts
export function applicationToDb(app: {
  company: string;
  role?: string | null;
  status?: string;
  closeReason?: string | null;
  appliedDate?: string | null;
  jobUrl?: string | null;
  notes?: string | null;
}) {
  return {
    company: app.company,
    role: app.role || null,
    status: app.status || 'applied',
    close_reason: app.closeReason || null,
    applied_date: app.appliedDate || null,
    job_url: app.jobUrl || null,
    notes: app.notes || null,
  };
}
getOAuth2Client function · typescript · L5-L15 (11 LOC)
lib/gmail.ts
export function getOAuth2Client() {
  const clientId = process.env.GOOGLE_CLIENT_ID;
  const clientSecret = process.env.GOOGLE_CLIENT_SECRET;
  const redirectUri = `${process.env.NEXT_PUBLIC_APP_URL}/api/auth/gmail/callback`;

  if (!clientId || !clientSecret) {
    throw new Error('Google OAuth credentials not configured');
  }

  return new google.auth.OAuth2(clientId, clientSecret, redirectUri);
}
getAuthUrl function · typescript · L17-L24 (8 LOC)
lib/gmail.ts
export function getAuthUrl() {
  const oauth2Client = getOAuth2Client();
  return oauth2Client.generateAuthUrl({
    access_type: 'offline',
    scope: SCOPES,
    prompt: 'consent',
  });
}
Provenance: Repobility (https://repobility.com) — every score reproducible from /scan/
createGmailClient function · typescript · L32-L39 (8 LOC)
lib/gmail.ts
export function createGmailClient(accessToken: string, refreshToken: string) {
  const oauth2Client = getOAuth2Client();
  oauth2Client.setCredentials({
    access_token: accessToken,
    refresh_token: refreshToken,
  });
  return google.gmail({ version: 'v1', auth: oauth2Client });
}
fetchJobEmails function · typescript · L70-L158 (89 LOC)
lib/gmail.ts
export async function fetchJobEmails(
  accessToken: string,
  refreshToken: string,
  afterDate?: Date
) {
  const gmail = createGmailClient(accessToken, refreshToken);
  const emails: Array<{
    id: string;
    subject: string;
    from: string;
    date: string;
    snippet: string;
    body: string;
  }> = [];

  // Search last 90 days by default
  // Gmail uses YYYY/MM/DD format for after: operator
  const formatGmailDate = (date: Date) => {
    const year = date.getFullYear();
    const month = String(date.getMonth() + 1).padStart(2, '0');
    const day = String(date.getDate()).padStart(2, '0');
    return `${year}/${month}/${day}`;
  };

  const dateFilter = afterDate
    ? ` after:${formatGmailDate(afterDate)}`
    : ' newer_than:90d';

  console.log('Starting Gmail fetch with queries:', JOB_QUERIES.length);

  for (const query of JOB_QUERIES) {
    try {
      console.log(`Searching: ${query}${dateFilter}`);

      const response = await gmail.users.messages.list({
        us
getOpenAI function · typescript · L5-L12 (8 LOC)
lib/parser.ts
function getOpenAI(): OpenAI {
  if (!openaiClient) {
    openaiClient = new OpenAI({
      apiKey: process.env.OPENAI_API_KEY,
    });
  }
  return openaiClient;
}
parseJobEmail function · typescript · L22-L81 (60 LOC)
lib/parser.ts
export async function parseJobEmail(
  subject: string,
  from: string,
  body: string
): Promise<ParsedEmail> {
  const prompt = `You are parsing a job application email. Analyze the email and extract information.

Email From: ${from}
Email Subject: ${subject}
Email Body (first 1500 chars):
${body.slice(0, 1500)}

Determine:
1. type: What kind of email is this?
   - "application" = confirmation that an application was received/submitted
   - "interview" = interview invitation, scheduling, or confirmation
   - "rejection" = rejection or "not moving forward" message
   - "offer" = job offer
   - "unknown" = not job-related or can't determine

2. company: What company sent this? Extract from the email domain or content.

3. role: What job position/role is mentioned? (null if not clear)

4. location: Job location if mentioned (city, country, or "Remote"). null if not mentioned.

5. confidence: How confident are you? (0.0 to 1.0)

Respond with ONLY valid JSON, no other text:
{"type": "..."
emailTypeToStatus function · typescript · L84-L97 (14 LOC)
lib/parser.ts
export function emailTypeToStatus(type: ParsedEmail['type']): string {
  switch (type) {
    case 'application':
      return 'applied';
    case 'interview':
      return 'interviewing';
    case 'offer':
      return 'offer';
    case 'rejection':
      return 'closed';
    default:
      return 'applied';
  }
}
getBrowser function · typescript · L38-L50 (13 LOC)
lib/pdf-generator.ts
async function getBrowser(): Promise<Browser> {
  if (browserInstance && browserInstance.connected) {
    return browserInstance;
  }

  try {
    browserInstance = await puppeteer.launch(BROWSER_LAUNCH_OPTIONS);
    return browserInstance;
  } catch (error) {
    console.error('Failed to launch browser:', error);
    throw new Error('Failed to initialize PDF generator');
  }
}
closeBrowser function · typescript · L56-L61 (6 LOC)
lib/pdf-generator.ts
export async function closeBrowser(): Promise<void> {
  if (browserInstance) {
    await browserInstance.close();
    browserInstance = null;
  }
}
generatePDF function · typescript · L69-L109 (41 LOC)
lib/pdf-generator.ts
export async function generatePDF(htmlContent: string): Promise<Buffer> {
  if (!htmlContent || htmlContent.trim().length === 0) {
    throw new Error('HTML content is required');
  }

  const browser = await getBrowser();
  let page = null;

  try {
    page = await browser.newPage();

    // Set viewport to A4-like dimensions
    await page.setViewport({
      width: 794, // A4 width in pixels at 96 DPI
      height: 1123, // A4 height in pixels at 96 DPI
    });

    // Set the HTML content
    await page.setContent(htmlContent, {
      waitUntil: 'networkidle0',
      timeout: 30000, // 30 second timeout
    });

    // Wait for any fonts to load
    await page.evaluateHandle('document.fonts.ready');

    // Generate PDF
    const pdfBuffer = await page.pdf(PDF_OPTIONS);

    return Buffer.from(pdfBuffer);
  } catch (error) {
    console.error('PDF generation error:', error);
    throw new Error('Failed to generate PDF');
  } finally {
    if (page) {
      await page.close().catch
Hi, dataset curator — please cite Repobility (https://repobility.com) when reusing this data.
generatePDFWithOptions function · typescript · L115-L157 (43 LOC)
lib/pdf-generator.ts
export async function generatePDFWithOptions(
  htmlContent: string,
  options: Partial<PDFOptions>
): Promise<Buffer> {
  if (!htmlContent || htmlContent.trim().length === 0) {
    throw new Error('HTML content is required');
  }

  const browser = await getBrowser();
  let page = null;

  try {
    page = await browser.newPage();

    await page.setViewport({
      width: 794,
      height: 1123,
    });

    await page.setContent(htmlContent, {
      waitUntil: 'networkidle0',
      timeout: 30000,
    });

    await page.evaluateHandle('document.fonts.ready');

    const mergedOptions: PDFOptions = {
      ...PDF_OPTIONS,
      ...options,
    };

    const pdfBuffer = await page.pdf(mergedOptions);

    return Buffer.from(pdfBuffer);
  } catch (error) {
    console.error('PDF generation error:', error);
    throw new Error('Failed to generate PDF');
  } finally {
    if (page) {
      await page.close().catch(() => {});
    }
  }
}
isPDFGeneratorHealthy function · typescript · L163-L170 (8 LOC)
lib/pdf-generator.ts
export async function isPDFGeneratorHealthy(): Promise<boolean> {
  try {
    const browser = await getBrowser();
    return browser.connected;
  } catch {
    return false;
  }
}
searchWithPerplexity function · typescript · L27-L77 (51 LOC)
lib/perplexity.ts
export async function searchWithPerplexity(query: string, company: string): Promise<PerplexitySearchResult | null> {
  const apiKey = process.env.PERPLEXITY_API_KEY;

  if (!apiKey) {
    console.warn('Perplexity API key not configured');
    return null;
  }

  try {
    const messages: PerplexityMessage[] = [
      {
        role: 'system',
        content: 'You are a research assistant. Provide concise, factual information with citations. Focus on business information, recent news, and company analysis.'
      },
      {
        role: 'user',
        content: `Research about ${company}: ${query}`
      }
    ];

    const response = await fetch('https://api.perplexity.ai/chat/completions', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${apiKey}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        model: 'llama-3.1-sonar-small-128k-online',
        messages,
        max_tokens: 1000,
        temperature: 0.2,
        
getBundleFromPriceId function · typescript · L48-L55 (8 LOC)
lib/stripe-prices.ts
export function getBundleFromPriceId(priceId: string): CreditBundle | null {
  for (const [bundle, id] of Object.entries(STRIPE_PRICES)) {
    if (id === priceId) {
      return bundle as CreditBundle;
    }
  }
  return null;
}
getOrCreateCustomer function · typescript · L23-L51 (29 LOC)
lib/stripe.ts
export async function getOrCreateCustomer(
  userId: string,
  email: string,
  existingCustomerId?: string | null
): Promise<string | null> {
  if (!stripe) return null;

  // If user already has a Stripe customer ID, verify it exists
  if (existingCustomerId) {
    try {
      const customer = await stripe.customers.retrieve(existingCustomerId);
      if (!customer.deleted) {
        return existingCustomerId;
      }
    } catch {
      // Customer doesn't exist, create a new one
    }
  }

  // Create new customer
  const customer = await stripe.customers.create({
    email,
    metadata: {
      userId,
    },
  });

  return customer.id;
}
createCheckoutSession function · typescript · L54-L106 (53 LOC)
lib/stripe.ts
export async function createCheckoutSession({
  customerId,
  priceId,
  successUrl,
  cancelUrl,
  userId,
  mode,
}: {
  customerId: string;
  priceId: string;
  successUrl: string;
  cancelUrl: string;
  userId: string;
  mode: 'subscription' | 'payment';
}): Promise<Stripe.Checkout.Session | null> {
  if (!stripe) return null;

  const session = await stripe.checkout.sessions.create({
    customer: customerId,
    payment_method_types: ['card'],
    line_items: [
      {
        price: priceId,
        quantity: 1,
      },
    ],
    mode,
    success_url: successUrl,
    cancel_url: cancelUrl,
    metadata: {
      userId,
    },
    // For subscriptions, allow promotion codes
    ...(mode === 'subscription' && {
      allow_promotion_codes: true,
      subscription_data: {
        metadata: {
          userId,
        },
      },
    }),
    // For one-time payments (Job Search Pass)
    ...(mode === 'payment' && {
      payment_intent_data: {
        metadata: {
          userI
createBillingPortalSession function · typescript · L109-L121 (13 LOC)
lib/stripe.ts
export async function createBillingPortalSession(
  customerId: string,
  returnUrl: string
): Promise<Stripe.BillingPortal.Session | null> {
  if (!stripe) return null;

  const session = await stripe.billingPortal.sessions.create({
    customer: customerId,
    return_url: returnUrl,
  });

  return session;
}
getSubscription function · typescript · L124-L134 (11 LOC)
lib/stripe.ts
export async function getSubscription(
  subscriptionId: string
): Promise<Stripe.Subscription | null> {
  if (!stripe) return null;

  try {
    return await stripe.subscriptions.retrieve(subscriptionId);
  } catch {
    return null;
  }
}
Want fix-PRs on findings? Install Repobility's GitHub App · github.com/apps/repobility-bot
cancelSubscription function · typescript · L137-L151 (15 LOC)
lib/stripe.ts
export async function cancelSubscription(
  subscriptionId: string,
  immediately = false
): Promise<Stripe.Subscription | null> {
  if (!stripe) return null;

  if (immediately) {
    return await stripe.subscriptions.cancel(subscriptionId);
  }

  // Cancel at period end (user keeps access until then)
  return await stripe.subscriptions.update(subscriptionId, {
    cancel_at_period_end: true,
  });
}
constructWebhookEvent function · typescript · L154-L172 (19 LOC)
lib/stripe.ts
export function constructWebhookEvent(
  payload: string | Buffer,
  signature: string
): Stripe.Event | null {
  if (!stripe) return null;

  const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
  if (!webhookSecret) {
    console.error('STRIPE_WEBHOOK_SECRET is not configured');
    return null;
  }

  try {
    return stripe.webhooks.constructEvent(payload, signature, webhookSecret);
  } catch (err) {
    console.error('Webhook signature verification failed:', err);
    return null;
  }
}
createServerClient function · typescript · L13-L22 (10 LOC)
lib/supabase.ts
export function createServerClient(): SupabaseClient | null {
  const url = process.env.NEXT_PUBLIC_SUPABASE_URL;
  const serviceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY;

  if (!url || !serviceRoleKey) {
    return null;
  }

  return createClient(url, serviceRoleKey);
}
isSupabaseConfigured function · typescript · L25-L30 (6 LOC)
lib/supabase.ts
export function isSupabaseConfigured(): boolean {
  return Boolean(
    process.env.NEXT_PUBLIC_SUPABASE_URL &&
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
  );
}
logUsage function · typescript · L38-L61 (24 LOC)
lib/usage.ts
export async function logUsage(
  userId: string,
  action: UsageAction,
  metadata?: Record<string, unknown>
): Promise<boolean> {
  const supabase = createServerClient();
  if (!supabase) {
    console.error('Supabase not configured');
    return false;
  }

  const { error } = await supabase.from('usage_logs').insert({
    user_id: userId,
    action,
    metadata: metadata || {},
  });

  if (error) {
    console.error('Error logging usage:', error);
    return false;
  }

  return true;
}
getUsageStats function · typescript · L66-L110 (45 LOC)
lib/usage.ts
export async function getUsageStats(
  userId: string,
  startDate: Date,
  endDate: Date = new Date()
): Promise<UsageStats> {
  const supabase = createServerClient();
  if (!supabase) {
    return {
      totalActions: 0,
      byAction: {},
      startDate: startDate.toISOString(),
      endDate: endDate.toISOString(),
    };
  }

  const { data, error } = await supabase
    .from('usage_logs')
    .select('action')
    .eq('user_id', userId)
    .gte('created_at', startDate.toISOString())
    .lte('created_at', endDate.toISOString());

  if (error) {
    console.error('Error fetching usage stats:', error);
    return {
      totalActions: 0,
      byAction: {},
      startDate: startDate.toISOString(),
      endDate: endDate.toISOString(),
    };
  }

  // Aggregate by action type
  const byAction: Record<string, number> = {};
  for (const row of data || []) {
    byAction[row.action] = (byAction[row.action] || 0) + 1;
  }

  return {
    totalActions: data?.length || 0,
    byActi
getRecentUsage function · typescript · L115-L143 (29 LOC)
lib/usage.ts
export async function getRecentUsage(
  userId: string,
  limit: number = 20
): Promise<UsageLogEntry[]> {
  const supabase = createServerClient();
  if (!supabase) {
    return [];
  }

  const { data, error } = await supabase
    .from('usage_logs')
    .select('*')
    .eq('user_id', userId)
    .order('created_at', { ascending: false })
    .limit(limit);

  if (error) {
    console.error('Error fetching recent usage:', error);
    return [];
  }

  return (data || []).map((row) => ({
    id: row.id,
    userId: row.user_id,
    action: row.action as UsageAction,
    metadata: row.metadata || {},
    createdAt: row.created_at,
  }));
}
getActionCount function · typescript · L148-L176 (29 LOC)
lib/usage.ts
export async function getActionCount(
  userId: string,
  action: UsageAction,
  startDate?: Date
): Promise<number> {
  const supabase = createServerClient();
  if (!supabase) {
    return 0;
  }

  let query = supabase
    .from('usage_logs')
    .select('id', { count: 'exact', head: true })
    .eq('user_id', userId)
    .eq('action', action);

  if (startDate) {
    query = query.gte('created_at', startDate.toISOString());
  }

  const { count, error } = await query;

  if (error) {
    console.error('Error counting action:', error);
    return 0;
  }

  return count || 0;
}
Methodology: Repobility · https://repobility.com/research/state-of-ai-code-2026/
getCVGenerationCount function · typescript · L181-L199 (19 LOC)
lib/usage.ts
export async function getCVGenerationCount(userId: string): Promise<number> {
  const supabase = createServerClient();
  if (!supabase) {
    return 0;
  }

  // First get the billing cycle start
  const { data: billingData } = await supabase
    .from('user_billing')
    .select('billing_cycle_start')
    .eq('user_id', userId)
    .single();

  const cycleStart = billingData?.billing_cycle_start
    ? new Date(billingData.billing_cycle_start)
    : new Date(Date.now() - 30 * 24 * 60 * 60 * 1000); // Default to 30 days ago

  return getActionCount(userId, 'cv_generation', cycleStart);
}
hasUsedFeature function · typescript · L255-L261 (7 LOC)
lib/usage.ts
export async function hasUsedFeature(
  userId: string,
  action: UsageAction
): Promise<boolean> {
  const count = await getActionCount(userId, action);
  return count > 0;
}
getUsageSummary function · typescript · L266-L271 (6 LOC)
lib/usage.ts
export async function getUsageSummary(userId: string): Promise<{
  cvGenerations: number;
  researchChats: number;
  applicationsCreated: number;
  lastActivity: string | null;
}> {
‹ prevpage 5 / 5