4 Commits

6 changed files with 41 additions and 132 deletions

32
lib/fetchTranscript.ts Normal file
View File

@@ -0,0 +1,32 @@
/**
* Fetches transcript content from a URL with optional authentication
* @param url The URL to fetch the transcript from
* @param username Optional username for Basic Auth
* @param password Optional password for Basic Auth
* @returns The transcript content or null if fetching fails
*/
export async function fetchTranscriptContent(
url: string,
username?: string,
password?: string
): Promise<string | null> {
try {
const authHeader =
username && password
? "Basic " + Buffer.from(`${username}:${password}`).toString("base64")
: undefined;
const response = await fetch(url, {
headers: authHeader ? { Authorization: authHeader } : {},
});
if (!response.ok) {
process.stderr.write(`Error fetching transcript from ${url}: ${response.statusText}\n`);
return null;
}
return await response.text();
} catch (error) {
process.stderr.write(`Failed to fetch transcript from ${url}: ${error}\n`);
return null;
}
}

View File

@@ -2,6 +2,7 @@
import cron from "node-cron"; import cron from "node-cron";
import { prisma } from "./prisma"; import { prisma } from "./prisma";
import { fetchAndParseCsv } from "./csvFetcher"; import { fetchAndParseCsv } from "./csvFetcher";
import { fetchTranscriptContent } from "./fetchTranscript";
interface SessionCreateData { interface SessionCreateData {
id: string; id: string;
@@ -10,41 +11,6 @@ interface SessionCreateData {
[key: string]: unknown; [key: string]: unknown;
} }
/**
* Fetches transcript content from a URL with optional authentication
* @param url The URL to fetch the transcript from
* @param username Optional username for Basic Auth
* @param password Optional password for Basic Auth
* @returns The transcript content or null if fetching fails
*/
async function fetchTranscriptContent(
url: string,
username?: string,
password?: string
): Promise<string | null> {
try {
const authHeader =
username && password
? "Basic " + Buffer.from(`${username}:${password}`).toString("base64")
: undefined;
const response = await fetch(url, {
headers: authHeader ? { Authorization: authHeader } : {},
});
if (!response.ok) {
process.stderr.write(
`Error fetching transcript: ${response.statusText}\n`
);
return null;
}
return await response.text();
} catch (error) {
process.stderr.write(`Failed to fetch transcript: ${error}\n`);
return null;
}
}
export function startScheduler() { export function startScheduler() {
cron.schedule("*/15 * * * *", async () => { cron.schedule("*/15 * * * *", async () => {
const companies = await prisma.company.findMany(); const companies = await prisma.company.findMany();

View File

@@ -2,6 +2,7 @@
import { NextApiRequest, NextApiResponse } from "next"; import { NextApiRequest, NextApiResponse } from "next";
import { fetchAndParseCsv } from "../../../lib/csvFetcher"; import { fetchAndParseCsv } from "../../../lib/csvFetcher";
import { prisma } from "../../../lib/prisma"; import { prisma } from "../../../lib/prisma";
import { fetchTranscriptContent } from "../../../lib/fetchTranscript";
interface SessionCreateData { interface SessionCreateData {
id: string; id: string;
@@ -11,41 +12,6 @@ interface SessionCreateData {
[key: string]: unknown; [key: string]: unknown;
} }
/**
* Fetches transcript content from a URL with optional authentication
* @param url The URL to fetch the transcript from
* @param username Optional username for Basic Auth
* @param password Optional password for Basic Auth
* @returns The transcript content or null if fetching fails
*/
async function fetchTranscriptContent(
url: string,
username?: string,
password?: string
): Promise<string | null> {
try {
const authHeader =
username && password
? "Basic " + Buffer.from(`${username}:${password}`).toString("base64")
: undefined;
const response = await fetch(url, {
headers: authHeader ? { Authorization: authHeader } : {},
});
if (!response.ok) {
process.stderr.write(
`Error fetching transcript: ${response.statusText}\n`
);
return null;
}
return await response.text();
} catch (error) {
process.stderr.write(`Failed to fetch transcript: ${error}\n`);
return null;
}
}
export default async function handler( export default async function handler(
req: NextApiRequest, req: NextApiRequest,
res: NextApiResponse res: NextApiResponse

View File

@@ -3,13 +3,11 @@
import { PrismaClient } from '@prisma/client'; import { PrismaClient } from '@prisma/client';
import { PrismaD1 } from '@prisma/adapter-d1'; import { PrismaD1 } from '@prisma/adapter-d1';
import { formatError } from './utils/error';
export interface Env { export interface Env {
DB: D1Database; DB: D1Database;
NEXTAUTH_SECRET?: string; NEXTAUTH_SECRET?: string;
NEXTAUTH_URL?: string; NEXTAUTH_URL?: string;
WORKER_ENV?: string; // 'development' | 'production'
} }
export default { export default {
@@ -210,22 +208,21 @@ export default {
}); });
} catch (error) { } catch (error) {
console.error('Worker error:', error); // Log full error details, including stack trace console.error('Worker error:', error);
// Use the formatError utility to properly format the error response
const errorPayload = formatError(error, env);
return new Response( return new Response(
JSON.stringify(errorPayload), JSON.stringify({
error: 'Internal Server Error',
message: error instanceof Error ? error.message : 'Unknown error',
stack: error instanceof Error ? error.stack : undefined
}),
{ {
status: 500, status: 500,
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*' 'Access-Control-Allow-Origin': '*'
} },
} }
); );
} }
}, },
}; };

View File

@@ -1,16 +0,0 @@
export function formatError(error: unknown, env?: { WORKER_ENV?: string }): Record<string, unknown> {
const payload: Record<string, unknown> = {
error: 'Internal Server Error',
message: error instanceof Error ? error.message : 'Unknown error'
};
// Only include stack trace in development environment
// In Cloudflare Workers, check environment via env parameter
const isDevelopment = env?.WORKER_ENV !== 'production';
if (isDevelopment) {
payload.stack = error instanceof Error ? error.stack : undefined;
}
return payload;
}

View File

@@ -1,36 +0,0 @@
import { test } from 'node:test';
import assert from 'node:assert';
import { formatError } from '../src/utils/error';
const originalEnv = process.env.NODE_ENV;
test('includes stack when not in production', () => {
const err = new Error('boom');
const payload = formatError(err, { WORKER_ENV: 'development' });
assert.ok('stack' in payload);
});
test('omits stack in production', () => {
const err = new Error('boom');
const payload = formatError(err, { WORKER_ENV: 'production' });
assert.ok(!('stack' in payload));
});
test('includes message for all environments', () => {
const err = new Error('boom');
const devPayload = formatError(err, { WORKER_ENV: 'development' });
const prodPayload = formatError(err, { WORKER_ENV: 'production' });
assert.strictEqual(devPayload.message, 'boom');
assert.strictEqual(prodPayload.message, 'boom');
});
test('handles non-Error objects', () => {
const payload = formatError('string error', { WORKER_ENV: 'development' });
assert.strictEqual(payload.message, 'Unknown error');
assert.strictEqual(payload.error, 'Internal Server Error');
});
test.after(() => {
if (originalEnv === undefined) delete process.env.NODE_ENV; else process.env.NODE_ENV = originalEnv;
});