84 lines
3.1 KiB
TypeScript
84 lines
3.1 KiB
TypeScript
|
|
/**
|
||
|
|
* QBO Sync API
|
||
|
|
* POST /api/qbo/sync — trigger a full or incremental sync
|
||
|
|
* GET /api/qbo/sync — get last sync status and record counts
|
||
|
|
*/
|
||
|
|
|
||
|
|
import { NextRequest, NextResponse } from 'next/server';
|
||
|
|
import { QboSyncService } from '@/lib/services/qbo-sync-service';
|
||
|
|
import { QboClient } from '@/lib/services/qbo-client';
|
||
|
|
import postgresClient from '@/lib/services/postgres-client';
|
||
|
|
|
||
|
|
export async function POST(request: NextRequest) {
|
||
|
|
try {
|
||
|
|
const body = await request.json().catch(() => ({}));
|
||
|
|
const syncType: 'full' | 'incremental' = body.syncType === 'incremental' ? 'incremental' : 'full';
|
||
|
|
const triggeredBy = body.triggeredBy || 'api';
|
||
|
|
|
||
|
|
const client = new QboClient();
|
||
|
|
const service = new QboSyncService(client);
|
||
|
|
|
||
|
|
if (service.isSyncInProgress()) {
|
||
|
|
return NextResponse.json({ error: 'QBO sync already in progress' }, { status: 409 });
|
||
|
|
}
|
||
|
|
|
||
|
|
// Run async — return immediately
|
||
|
|
service[syncType === 'full' ? 'fullSync' : 'incrementalSync'](triggeredBy).catch((err) => {
|
||
|
|
console.error('[QBO Sync API] Sync failed:', err);
|
||
|
|
});
|
||
|
|
|
||
|
|
return NextResponse.json({
|
||
|
|
message: `QBO ${syncType} sync started`,
|
||
|
|
triggeredBy,
|
||
|
|
});
|
||
|
|
} catch (err) {
|
||
|
|
const msg = err instanceof Error ? err.message : String(err);
|
||
|
|
return NextResponse.json({ error: msg }, { status: 500 });
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export async function GET() {
|
||
|
|
try {
|
||
|
|
const [invoices, payments, deposits, transactions, reports] = await Promise.all([
|
||
|
|
postgresClient.query(`SELECT COUNT(*) as count, MAX(synced_at) as last_sync FROM qbo_invoices`),
|
||
|
|
postgresClient.query(`SELECT COUNT(*) as count, MAX(synced_at) as last_sync FROM qbo_payments`),
|
||
|
|
postgresClient.query(`SELECT COUNT(*) as count, MAX(synced_at) as last_sync FROM qbo_deposits`),
|
||
|
|
postgresClient.query(`SELECT COUNT(*) as count, MAX(synced_at) as last_sync FROM qbo_transactions`),
|
||
|
|
postgresClient.query(`SELECT COUNT(*) as count, MAX(synced_at) as last_sync FROM qbo_reports`),
|
||
|
|
]);
|
||
|
|
|
||
|
|
// Check token status
|
||
|
|
let tokenStatus: 'valid' | 'expired' | 'missing' = 'missing';
|
||
|
|
try {
|
||
|
|
const client = new QboClient();
|
||
|
|
const token = await client.loadToken();
|
||
|
|
if (token) {
|
||
|
|
tokenStatus = new Date() < new Date(token.access_token_expires_at) ? 'valid' : 'expired';
|
||
|
|
}
|
||
|
|
} catch {
|
||
|
|
tokenStatus = 'missing';
|
||
|
|
}
|
||
|
|
|
||
|
|
return NextResponse.json({
|
||
|
|
tokenStatus,
|
||
|
|
counts: {
|
||
|
|
invoices: parseInt(invoices.rows[0]?.count || '0'),
|
||
|
|
payments: parseInt(payments.rows[0]?.count || '0'),
|
||
|
|
deposits: parseInt(deposits.rows[0]?.count || '0'),
|
||
|
|
transactions: parseInt(transactions.rows[0]?.count || '0'),
|
||
|
|
reports: parseInt(reports.rows[0]?.count || '0'),
|
||
|
|
},
|
||
|
|
lastSync: {
|
||
|
|
invoices: invoices.rows[0]?.last_sync ?? null,
|
||
|
|
payments: payments.rows[0]?.last_sync ?? null,
|
||
|
|
deposits: deposits.rows[0]?.last_sync ?? null,
|
||
|
|
transactions: transactions.rows[0]?.last_sync ?? null,
|
||
|
|
reports: reports.rows[0]?.last_sync ?? null,
|
||
|
|
},
|
||
|
|
});
|
||
|
|
} catch (err) {
|
||
|
|
const msg = err instanceof Error ? err.message : String(err);
|
||
|
|
return NextResponse.json({ error: msg }, { status: 500 });
|
||
|
|
}
|
||
|
|
}
|