This commit is contained in:
2026-07-08 22:07:59 +02:00
parent 0b2305a3ad
commit 9be9b2987b
10 changed files with 349 additions and 268 deletions

View File

@@ -204,11 +204,11 @@ const resolveIdempotencyKey = (request) => {
return '';
};
const ensureNotGuest = (userId, requiredCredits) => {
// Guests may use the limited pre-auth demo identification scan. Other
// endpoints must not consume credits from the shared 'guest' account.
if (isGuest(userId)) {
const error = new Error('Sign in to use scan credits.');
const ensureNotGuest = (userId, requiredCredits) => {
// Guests may use the limited pre-auth demo identification scan. Other
// endpoints must not consume credits from the shared 'guest' account.
if (isGuest(userId)) {
const error = new Error('Sign in to use scan credits.');
error.code = 'INSUFFICIENT_CREDITS';
error.status = 402;
error.metadata = { required: requiredCredits, available: 0 };
@@ -726,20 +726,20 @@ app.post('/v1/scan', async (request, response) => {
let modelUsed = null;
let modelFallbackCount = 0;
const [accountSnapshot, catalogEntries] = await Promise.all([
getAccountSnapshot(db, userId),
getCachedCatalogEntries(db),
]);
if (isGuest(userId)) {
modelPath.push('guest-demo-no-credit');
} else {
creditsCharged += await consumeCreditsWithIdempotency(
db,
userId,
chargeKey('scan-primary', userId, idempotencyKey),
SCAN_PRIMARY_COST,
);
}
const [accountSnapshot, catalogEntries] = await Promise.all([
getAccountSnapshot(db, userId),
getCachedCatalogEntries(db),
]);
if (isGuest(userId)) {
modelPath.push('guest-demo-no-credit');
} else {
creditsCharged += await consumeCreditsWithIdempotency(
db,
userId,
chargeKey('scan-primary', userId, idempotencyKey),
SCAN_PRIMARY_COST,
);
}
// Free tier gets the same model quality; quantity (3 credits/month) is the differentiator.
const scanPlan = 'pro';
@@ -767,14 +767,14 @@ app.post('/v1/scan', async (request, response) => {
usedOpenAi = true;
modelUsed = openAiPrimary.modelUsed || modelUsed;
modelPath.push('openai-primary');
if (grounded.grounded) modelPath.push('catalog-grounded-primary');
} else {
if (isGuest(userId)) {
const error = new Error('AI demo scan failed. Please try again with a clearer plant photo.');
error.code = 'PROVIDER_ERROR';
error.status = 502;
throw error;
}
if (grounded.grounded) modelPath.push('catalog-grounded-primary');
} else {
if (isGuest(userId)) {
const error = new Error('AI demo scan failed. Please try again with a clearer plant photo.');
error.code = 'PROVIDER_ERROR';
error.status = 502;
throw error;
}
console.warn(`OpenAI primary identification returned null for user ${userId} — using catalog fallback.`, {
attemptedModels: openAiPrimary?.attemptedModels,
plant: result?.name,
@@ -782,14 +782,14 @@ app.post('/v1/scan', async (request, response) => {
modelPath.push('openai-primary-failed');
modelPath.push('catalog-primary-fallback');
}
} else {
if (isGuest(userId)) {
const error = new Error('AI demo scan is unavailable. Please configure OpenAI before enabling guest demo scans.');
error.code = 'PROVIDER_ERROR';
error.status = 502;
throw error;
}
console.log(`OpenAI not configured, using catalog fallback for user ${userId}`);
} else {
if (isGuest(userId)) {
const error = new Error('AI demo scan is unavailable. Please configure OpenAI before enabling guest demo scans.');
error.code = 'PROVIDER_ERROR';
error.status = 502;
throw error;
}
console.log(`OpenAI not configured, using catalog fallback for user ${userId}`);
modelPath.push('openai-not-configured');
modelPath.push('catalog-primary-fallback');
}
@@ -1219,7 +1219,7 @@ app.get('/api/tiktok/connect', (request, response) => {
const authUrl = new URL('https://www.tiktok.com/v2/auth/authorize/');
authUrl.searchParams.set('client_key', clientKey);
authUrl.searchParams.set('scope', 'user.info.basic,video.publish');
authUrl.searchParams.set('scope', 'user.info.basic,video.upload,video.publish');
authUrl.searchParams.set('response_type', 'code');
authUrl.searchParams.set('redirect_uri', redirectUri);
authUrl.searchParams.set('state', oauthState);