mirror of
https://github.com/kjanat/livedash-node.git
synced 2026-04-19 10:42:08 +02:00
Compare commits
4 Commits
codex/modi
...
69ebaee3a1
| Author | SHA1 | Date | |
|---|---|---|---|
| 69ebaee3a1 | |||
| c2f225096d | |||
| c5352e1235 | |||
| ce3188d8ee |
32
lib/fetchTranscript.ts
Normal file
32
lib/fetchTranscript.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
17
src/index.ts
17
src/index.ts
@@ -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': '*'
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
});
|
|
||||||
Reference in New Issue
Block a user