Add full application: receipt scanning, auth, billing, and account deletion

Brings the working codebase (Next.js app, auth system, Stripe billing,
Docker/deploy config, tests, docs) into version control on top of the
placeholder initial commit, and adds account self-deletion (Danger Zone
in Settings, password + typed-email confirmation, cascading DB cleanup,
Stripe cancellation) per GDPR right-to-erasure.

Excludes local build caches, node_modules, and internal agent scratch
files; .gitignore hardened to keep those out going forward.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Timo
2026-08-19 20:59:04 +02:00
parent 650a74da97
commit 84b9987c49
415 changed files with 96619 additions and 0 deletions

View File

@@ -0,0 +1,83 @@
const EN_KEYWORDS = [
'receipt scanner',
'receipt to excel',
'receipt scanner to excel',
'expense tracker',
'receipt keeper',
'invoice scanner',
'tax receipts',
'ocr receipt scanner',
'smart receipts',
'mileage and receipts',
'receipt organizer',
'bookkeeping scanner',
'scan receipts for taxes',
'extract receipt to csv',
'business expense tracker'
];
const DE_KEYWORDS = [
'beleg scanner',
'belege digitalisieren',
'rechnung scanner',
'kassenbon scanner',
'quittung scanner',
'spesen app',
'buchhaltung scanner',
'datev scanner',
'receipt to excel',
'haushaltsbuch',
'ausgaben tracker',
'steuer belege',
'belegmanager'
];
const COUNTRIES = {
US: 'us',
GB: 'gb',
CA: 'ca',
AU: 'au',
DE: 'de'
};
async function fetchSearch(term, country) {
try {
const url = `https://itunes.apple.com/search?term=${encodeURIComponent(term)}&country=${country}&entity=software&limit=5`;
const res = await fetch(url, { signal: AbortSignal.timeout(3000) });
if (!res.ok) return [];
const data = await res.json();
return data.results || [];
} catch {
return [];
}
}
async function run() {
const summary = {};
for (const [cName, cCode] of Object.entries(COUNTRIES)) {
summary[cName] = {};
const kws = cName === 'DE' ? DE_KEYWORDS : EN_KEYWORDS;
// run in chunks of 5
for (let i = 0; i < kws.length; i += 5) {
const chunk = kws.slice(i, i + 5);
await Promise.all(chunk.map(async (kw) => {
const apps = await fetchSearch(kw, cCode);
const top5Reviews = apps.reduce((sum, a) => sum + (a.userRatingCount || 0), 0);
const avgReviews = Math.round(top5Reviews / (apps.length || 1));
const top1 = apps[0] || {};
summary[cName][kw] = {
count: apps.length,
top1: top1.trackName ? `${top1.trackName.slice(0, 30)} (${top1.userRatingCount || 0} rev)` : 'None',
avgReviews
};
}));
}
}
console.log(JSON.stringify(summary, null, 2));
}
run();