Onboarding

This commit is contained in:
2026-05-08 13:00:30 +02:00
parent d37b49f1f6
commit 9386ae1be7
37 changed files with 5606 additions and 2275 deletions

View File

@@ -291,4 +291,55 @@ const signInWithApple = async (db, identityToken, profile = {}) => {
return { id, email: normalizedEmail, name, isNewUser: true };
};
module.exports = { ensureAuthSchema, signUp, login, signInWithApple, issueToken, verifyJwt, verifyAppleIdentityToken };
const runInTransaction = async (db, worker) => {
const client = typeof db.connect === 'function' ? await db.connect() : db;
const release = typeof client.release === 'function' ? () => client.release() : () => {};
await run(client, 'BEGIN');
try {
const result = await worker(client);
await run(client, 'COMMIT');
return result;
} catch (error) {
try {
await run(client, 'ROLLBACK');
} catch (rollbackError) {
console.error('Failed to rollback account deletion transaction.', rollbackError);
}
throw error;
} finally {
release();
}
};
const deleteAccount = async (db, userId) => {
if (!userId || typeof userId !== 'string') {
const err = new Error('Valid user id is required.');
err.code = 'BAD_REQUEST';
err.status = 400;
throw err;
}
return runInTransaction(db, async (tx) => {
await run(tx, 'DELETE FROM billing_accounts WHERE user_id = $1', [userId]);
await run(
tx,
`DELETE FROM billing_idempotency
WHERE id LIKE $1 OR id LIKE $2`,
[`endpoint:%:${userId}:%`, `charge:%:${userId}:%`],
);
const result = await run(tx, 'DELETE FROM auth_users WHERE id = $1', [userId]);
return { deleted: result.changes > 0 };
});
};
module.exports = {
deleteAccount,
ensureAuthSchema,
signUp,
login,
signInWithApple,
issueToken,
verifyJwt,
verifyAppleIdentityToken,
};

View File

@@ -142,9 +142,10 @@ const normalizeIdentifyResult = (raw, language) => {
};
const normalizeHealthAnalysis = (raw, language) => {
const scoreRaw = getNumber(raw.overallHealthScore);
const statusRaw = getString(raw.status);
const issuesRaw = raw.likelyIssues;
const scoreRaw = getNumber(raw.overallHealthScore);
const statusRaw = getString(raw.status);
const analysisSummary = getString(raw.analysisSummary);
const issuesRaw = raw.likelyIssues;
const actionsNowRaw = getStringArray(raw.actionsNow).slice(0, 8);
const plan7DaysRaw = getStringArray(raw.plan7Days).slice(0, 10);
@@ -180,9 +181,10 @@ const normalizeHealthAnalysis = (raw, language) => {
? 'La IA no pudo extraer senales de salud estables.'
: 'AI could not extract stable health signals.';
return {
overallHealthScore: Math.round(clamp(score, 0, 100)),
status,
likelyIssues: [
overallHealthScore: Math.round(clamp(score, 0, 100)),
status,
analysisSummary: analysisSummary || fallbackIssue,
likelyIssues: [
{
title: language === 'de'
? 'Analyse unsicher'
@@ -203,9 +205,10 @@ const normalizeHealthAnalysis = (raw, language) => {
}
return {
overallHealthScore: Math.round(clamp(score, 0, 100)),
status,
likelyIssues,
overallHealthScore: Math.round(clamp(score, 0, 100)),
status,
analysisSummary,
likelyIssues,
actionsNow: actionsNowRaw,
plan7Days: plan7DaysRaw,
};
@@ -260,12 +263,13 @@ const buildHealthPrompt = (language, plantContext) => {
'Inspect the following in detail: leaf color (yellowing, browning, bleaching, dark spots, necrosis), leaf texture (wilting, crispy edges, curling, drooping), stem condition (rot, soft spots, discoloration), soil surface (dry cracks, mold, pests, waterlogging signs), visible pests (spider mites, fungus gnats, scale insects, aphids, mealybugs), root health (if visible), pot size and drainage.',
'',
'Return strict JSON only in this exact shape:',
'{"overallHealthScore":72,"status":"watch","likelyIssues":[{"title":"...","confidence":0.64,"details":"..."}],"actionsNow":["..."],"plan7Days":["..."]}',
'{"overallHealthScore":72,"status":"watch","analysisSummary":"...","likelyIssues":[{"title":"...","confidence":0.64,"details":"..."}],"actionsNow":["..."],"plan7Days":["..."]}',
'',
'Rules:',
'- "overallHealthScore": integer 0100. 100=perfect health, 8099=minor cosmetic only, 6079=noticeable issues needing attention, 4059=significant stress, below 40=severe/critical.',
'- "status": exactly one of "healthy" (score>=80, no active threats), "watch" (score 5079, needs monitoring), "critical" (score<50, urgent action needed).',
'- "likelyIssues": 2 to 4 items, sorted by confidence descending. Each item:',
`- "analysisSummary": 6 to 9 precise sentences in ${getLanguageLabel(language)} describing visible condition, symptom pattern, likely root cause, urgency, confidence limits, and what the owner should monitor next.`,
'- "likelyIssues": 2 to 4 items, sorted by confidence descending. Each item:',
' - "title": concise issue name (e.g. "Overwatering / Root Rot Risk")',
' - "confidence": float 0.050.99 reflecting visual certainty',
' - "details": 24 sentence detailed explanation of what you observe visually, what causes it, and what happens if untreated. Be specific — mention leaf color, location, pattern.',