Bild Carousel V2

This commit is contained in:
2026-07-09 17:02:33 +02:00
parent 2a14c84ad3
commit cc2522f7a4
5 changed files with 154 additions and 22 deletions

View File

@@ -3,6 +3,17 @@ import { db } from '@/lib/db';
export const TIKTOK_OAUTH_STATE_COOKIE_NAME = 'tiktok_oauth_state';
export const TIKTOK_ACCOUNT_KEY = 'hermes-agent';
export class TiktokApiError extends Error {
status: number;
body?: unknown;
constructor(message: string, status = 502, body?: unknown) {
super(message);
this.status = status;
this.body = body;
}
}
// Refresh when the access token expires within this window, so Hermes never
// receives a token that dies mid-upload.
const REFRESH_BUFFER_MS = 5 * 60 * 1000;
@@ -58,16 +69,12 @@ export async function getValidTiktokTokens() {
});
}
const REFRESH_BUFFER_MS = 5 * 60 * 1000;
export async function getLiveTiktokAccessToken() {
const integration = await db.tiktokIntegration.findUnique({
where: { accountKey: TIKTOK_ACCOUNT_KEY },
});
if (!integration) {
const error = new Error('No TikTok account connected.');
error.status = 404;
throw error;
throw new TiktokApiError('No TikTok account connected.', 404);
}
if (integration.accessTokenExpiresAt.getTime() - Date.now() > REFRESH_BUFFER_MS) {
@@ -77,12 +84,14 @@ export async function getLiveTiktokAccessToken() {
const clientKey = process.env.TIKTOK_CLIENT_KEY;
const clientSecret = process.env.TIKTOK_CLIENT_SECRET;
if (!clientKey || !clientSecret) {
const error = new Error('TikTok client credentials are not configured.');
error.status = 500;
throw error;
throw new TiktokApiError('TikTok client credentials are not configured.', 500);
}
return getValidTiktokTokens();
const refreshed = await getValidTiktokTokens();
if (!refreshed) {
throw new TiktokApiError('No TikTok account connected.', 404);
}
return refreshed;
}
export async function tiktokApi(url: string, options: RequestInit = {}) {
@@ -109,10 +118,7 @@ export async function tiktokApi(url: string, options: RequestInit = {}) {
if (!res.ok || (data?.error as Record<string, string> | undefined)?.code !== 'ok') {
const message = (data?.error as Record<string, string> | undefined)?.message || (typeof data?.raw === 'string' ? data.raw : '') || `TikTok API error: ${res.status}`;
const error = new Error(message);
error.status = res.status;
error.body = data;
throw error;
throw new TiktokApiError(message, res.status, data);
}
return data;
@@ -125,14 +131,12 @@ export async function uploadBinaryToTiktok(uploadUrl: string, buffer: Buffer, mi
'Content-Type': mimeType,
'Content-Length': String(buffer.length),
},
body: buffer,
body: new Uint8Array(buffer),
});
const text = await res.text();
if (!res.ok && res.status !== 201) {
const error = new Error(`TikTok binary upload failed: ${res.status} ${text}`);
error.status = res.status;
throw error;
throw new TiktokApiError(`TikTok binary upload failed: ${res.status} ${text}`, res.status);
}
return { status: res.status, body: text };