···
// Get subscription from database
720
-
const subscription = db.query<
724
-
current_period_start: number | null;
725
-
current_period_end: number | null;
726
-
cancel_at_period_end: number;
727
-
canceled_at: number | null;
731
-
"SELECT id, status, current_period_start, current_period_end, cancel_at_period_end, canceled_at FROM subscriptions WHERE user_id = ? ORDER BY created_at DESC LIMIT 1",
720
+
const subscription = db
725
+
current_period_start: number | null;
726
+
current_period_end: number | null;
727
+
cancel_at_period_end: number;
728
+
canceled_at: number | null;
732
+
"SELECT id, status, current_period_start, current_period_end, cancel_at_period_end, canceled_at FROM subscriptions WHERE user_id = ? ORDER BY created_at DESC LIMIT 1",
return Response.json({ subscription: null });
···
const { polar } = await import("./lib/polar");
// Get subscription to find customer ID
763
-
const subscription = db.query<
765
-
customer_id: string;
769
-
"SELECT customer_id FROM subscriptions WHERE user_id = ? ORDER BY created_at DESC LIMIT 1",
765
+
const subscription = db
768
+
customer_id: string;
772
+
"SELECT customer_id FROM subscriptions WHERE user_id = ? ORDER BY created_at DESC LIMIT 1",
if (!subscription || !subscription.customer_id) {
···
const webhookSecret = process.env.POLAR_WEBHOOK_SECRET;
console.error("[Webhook] POLAR_WEBHOOK_SECRET not configured");
807
-
return Response.json({ error: "Webhook secret not configured" }, { status: 500 });
811
+
return Response.json(
812
+
{ error: "Webhook secret not configured" },
const event = validateEvent(rawBody, headers, webhookSecret);
···
event.data.currentPeriodStart
844
-
? Math.floor(new Date(event.data.currentPeriodStart).getTime() / 1000)
852
+
new Date(event.data.currentPeriodStart).getTime() /
event.data.currentPeriodEnd
847
-
? Math.floor(new Date(event.data.currentPeriodEnd).getTime() / 1000)
858
+
new Date(event.data.currentPeriodEnd).getTime() / 1000,
event.data.cancelAtPeriodEnd ? 1 : 0,
851
-
? Math.floor(new Date(event.data.canceledAt).getTime() / 1000)
864
+
new Date(event.data.canceledAt).getTime() / 1000,
856
-
console.log(`[Webhook] Updated subscription ${id} for user ${userId}`);
871
+
`[Webhook] Updated subscription ${id} for user ${userId}`,
···
// Event-driven SSE stream with reconnection support
const stream = new ReadableStream({
async start(controller) {
894
-
const encoder = new TextEncoder();
895
-
let isClosed = false;
896
-
let lastEventId = Math.floor(Date.now() / 1000);
910
+
const encoder = new TextEncoder();
911
+
let isClosed = false;
912
+
let lastEventId = Math.floor(Date.now() / 1000);
898
-
const sendEvent = (data: Partial<TranscriptionUpdate>) => {
899
-
if (isClosed) return;
901
-
// Send event ID for reconnection support
902
-
lastEventId = Math.floor(Date.now() / 1000);
903
-
controller.enqueue(
905
-
`id: ${lastEventId}\nevent: update\ndata: ${JSON.stringify(data)}\n\n`,
909
-
// Controller already closed (client disconnected)
914
+
const sendEvent = (data: Partial<TranscriptionUpdate>) => {
915
+
if (isClosed) return;
917
+
// Send event ID for reconnection support
918
+
lastEventId = Math.floor(Date.now() / 1000);
919
+
controller.enqueue(
921
+
`id: ${lastEventId}\nevent: update\ndata: ${JSON.stringify(data)}\n\n`,
925
+
// Controller already closed (client disconnected)
914
-
const sendHeartbeat = () => {
915
-
if (isClosed) return;
917
-
controller.enqueue(encoder.encode(": heartbeat\n\n"));
930
+
const sendHeartbeat = () => {
931
+
if (isClosed) return;
933
+
controller.enqueue(encoder.encode(": heartbeat\n\n"));
938
+
// Send initial state from DB and file
946
+
>("SELECT status, progress FROM transcriptions WHERE id = ?")
947
+
.get(transcriptionId);
950
+
status: current.status as TranscriptionUpdate["status"],
951
+
progress: current.progress,
954
+
// If already complete, close immediately
956
+
current?.status === "completed" ||
957
+
current?.status === "failed"
960
+
controller.close();
922
-
// Send initial state from DB and file
930
-
>("SELECT status, progress FROM transcriptions WHERE id = ?")
931
-
.get(transcriptionId);
934
-
status: current.status as TranscriptionUpdate["status"],
935
-
progress: current.progress,
938
-
// If already complete, close immediately
940
-
current?.status === "completed" ||
941
-
current?.status === "failed"
944
-
controller.close();
947
-
// Send heartbeats every 2.5 seconds to keep connection alive
948
-
const heartbeatInterval = setInterval(sendHeartbeat, 2500);
963
+
// Send heartbeats every 2.5 seconds to keep connection alive
964
+
const heartbeatInterval = setInterval(sendHeartbeat, 2500);
950
-
// Subscribe to EventEmitter for live updates
951
-
const updateHandler = (data: TranscriptionUpdate) => {
952
-
if (isClosed) return;
966
+
// Subscribe to EventEmitter for live updates
967
+
const updateHandler = (data: TranscriptionUpdate) => {
968
+
if (isClosed) return;
954
-
// Only send changed fields to save bandwidth
955
-
const payload: Partial<TranscriptionUpdate> = {
956
-
status: data.status,
957
-
progress: data.progress,
970
+
// Only send changed fields to save bandwidth
971
+
const payload: Partial<TranscriptionUpdate> = {
972
+
status: data.status,
973
+
progress: data.progress,
960
-
if (data.transcript !== undefined) {
961
-
payload.transcript = data.transcript;
963
-
if (data.error_message !== undefined) {
964
-
payload.error_message = data.error_message;
976
+
if (data.transcript !== undefined) {
977
+
payload.transcript = data.transcript;
979
+
if (data.error_message !== undefined) {
980
+
payload.error_message = data.error_message;
967
-
sendEvent(payload);
983
+
sendEvent(payload);
969
-
// Close stream when done
970
-
if (data.status === "completed" || data.status === "failed") {
985
+
// Close stream when done
986
+
if (data.status === "completed" || data.status === "failed") {
988
+
clearInterval(heartbeatInterval);
989
+
transcriptionEvents.off(transcriptionId, updateHandler);
990
+
controller.close();
993
+
transcriptionEvents.on(transcriptionId, updateHandler);
994
+
// Cleanup on client disconnect
clearInterval(heartbeatInterval);
transcriptionEvents.off(transcriptionId, updateHandler);
974
-
controller.close();
977
-
transcriptionEvents.on(transcriptionId, updateHandler);
978
-
// Cleanup on client disconnect
981
-
clearInterval(heartbeatInterval);
982
-
transcriptionEvents.off(transcriptionId, updateHandler);
986
-
return new Response(stream, {
1002
+
return new Response(stream, {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
···
const { polar } = await import("./lib/polar");
await polar.subscriptions.revoke({ id: subscriptionId });
1461
-
`[Admin] Revoked subscription ${subscriptionId} for user ${userId}`,
message: "Subscription revoked successfully",
···
: "Failed to revoke subscription",
1496
+
return handleError(error);
1499
+
PUT: async (req) => {
1501
+
requireAdmin(req);
1502
+
const userId = Number.parseInt(req.params.id, 10);
1503
+
if (Number.isNaN(userId)) {
1504
+
return Response.json({ error: "Invalid user ID" }, { status: 400 });
1508
+
const { polar } = await import("./lib/polar");
1512
+
.query<{ email: string }, [number]>(
1513
+
"SELECT email FROM users WHERE id = ?",
1518
+
return Response.json(
1519
+
{ error: "User not found" },
1524
+
console.log(`[Admin] Looking for Polar customer with email: ${user.email}`);
1526
+
// Search for customer by email
1527
+
const customers = await polar.customers.list({
1528
+
organizationId: process.env.POLAR_ORGANIZATION_ID,
1529
+
query: user.email,
1533
+
`[Admin] Found ${customers.result.items?.length || 0} customer(s) matching email`,
1536
+
if (!customers.result.items || customers.result.items.length === 0) {
1537
+
return Response.json(
1538
+
{ error: "No Polar customer found with this email" },
1543
+
const customer = customers.result.items[0];
1544
+
console.log(`[Admin] Customer ID: ${customer.id}`);
1546
+
// Get all subscriptions for this customer
1547
+
const subscriptions = await polar.subscriptions.list({
1548
+
customerId: customer.id,
1552
+
`[Admin] Found ${subscriptions.result.items?.length || 0} subscription(s) for customer`,
1555
+
if (!subscriptions.result.items || subscriptions.result.items.length === 0) {
1556
+
return Response.json(
1557
+
{ error: "No subscriptions found for this customer" },
1562
+
// Update each subscription in the database
1563
+
for (const subscription of subscriptions.result.items) {
1565
+
`INSERT INTO subscriptions (id, user_id, customer_id, status, current_period_start, current_period_end, cancel_at_period_end, canceled_at, updated_at)
1566
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
1567
+
ON CONFLICT(id) DO UPDATE SET
1568
+
user_id = excluded.user_id,
1569
+
status = excluded.status,
1570
+
current_period_start = excluded.current_period_start,
1571
+
current_period_end = excluded.current_period_end,
1572
+
cancel_at_period_end = excluded.cancel_at_period_end,
1573
+
canceled_at = excluded.canceled_at,
1574
+
updated_at = excluded.updated_at`,
1578
+
subscription.customerId,
1579
+
subscription.status,
1580
+
subscription.currentPeriodStart
1582
+
new Date(subscription.currentPeriodStart).getTime() /
1586
+
subscription.currentPeriodEnd
1588
+
new Date(subscription.currentPeriodEnd).getTime() /
1592
+
subscription.cancelAtPeriodEnd ? 1 : 0,
1593
+
subscription.canceledAt
1595
+
new Date(subscription.canceledAt).getTime() / 1000,
1598
+
Math.floor(Date.now() / 1000),
1604
+
`[Admin] Synced ${subscriptions.result.items.length} subscription(s) for user ${userId} (${user.email})`,
1606
+
return Response.json({
1608
+
message: "Subscription synced successfully",
1612
+
`[Admin] Failed to sync subscription for user ${userId}:`,
1615
+
return Response.json(
1618
+
error instanceof Error
1620
+
: "Failed to sync subscription",