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:
113
scripts/fetch_live_store_data.mjs
Normal file
113
scripts/fetch_live_store_data.mjs
Normal file
@@ -0,0 +1,113 @@
|
||||
import https from 'https';
|
||||
import fs from 'fs';
|
||||
|
||||
const EN_KEYWORDS = [
|
||||
'receipt scanner to excel',
|
||||
'receipt to excel',
|
||||
'receipt scanner',
|
||||
'expense tracker',
|
||||
'tax receipt organizer',
|
||||
'receipt keeper',
|
||||
'invoice scanner',
|
||||
'bookkeeping scanner',
|
||||
'ocr receipt scanner',
|
||||
'extract receipt to csv'
|
||||
];
|
||||
|
||||
const DE_KEYWORDS = [
|
||||
'beleg scanner',
|
||||
'belege digitalisieren',
|
||||
'kassenbon scanner',
|
||||
'quittung scanner',
|
||||
'spesen app',
|
||||
'rechnung scanner',
|
||||
'receipt to excel',
|
||||
'datev scanner',
|
||||
'belegmanager',
|
||||
'ausgaben tracker'
|
||||
];
|
||||
|
||||
const STOREFRONTS = [
|
||||
{ id: 'US', country: 'us', name: 'USA', lang: 'en' },
|
||||
{ id: 'GB', country: 'gb', name: 'UK', lang: 'en' },
|
||||
{ id: 'CA', country: 'ca', name: 'Kanada', lang: 'en' },
|
||||
{ id: 'AU', country: 'au', name: 'Australien', lang: 'en' },
|
||||
{ id: 'DE', country: 'de', name: 'Deutschland', lang: 'de' },
|
||||
{ id: 'AT', country: 'at', name: 'Österreich', lang: 'de' },
|
||||
{ id: 'CH', country: 'ch', name: 'Schweiz', lang: 'de' }
|
||||
];
|
||||
|
||||
function fetchStoreData(term, country) {
|
||||
return new Promise((resolve) => {
|
||||
const url = `https://itunes.apple.com/search?term=${encodeURIComponent(term)}&country=${country}&entity=software&limit=10`;
|
||||
https.get(url, { headers: { 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)' } }, (res) => {
|
||||
let data = '';
|
||||
res.on('data', chunk => data += chunk);
|
||||
res.on('end', () => {
|
||||
try {
|
||||
const json = JSON.parse(data);
|
||||
resolve(json.results || []);
|
||||
} catch {
|
||||
resolve([]);
|
||||
}
|
||||
});
|
||||
}).on('error', () => resolve([]));
|
||||
});
|
||||
}
|
||||
|
||||
function calculateDifficulty(apps, term) {
|
||||
if (!apps || apps.length === 0) return { score: 10, tier: 'Sehr gering' };
|
||||
|
||||
const top5 = apps.slice(0, 5);
|
||||
const totalReviews = top5.reduce((sum, a) => sum + (a.userRatingCount || 0), 0);
|
||||
const avgReviews = totalReviews / top5.length;
|
||||
|
||||
const titleMatches = top5.filter(a => (a.trackName || '').toLowerCase().includes(term.toLowerCase())).length;
|
||||
|
||||
// Logarithmic scale for reviews
|
||||
let reviewScore = 0;
|
||||
if (avgReviews > 500000) reviewScore = 95;
|
||||
else if (avgReviews > 100000) reviewScore = 85;
|
||||
else if (avgReviews > 20000) reviewScore = 70;
|
||||
else if (avgReviews > 5000) reviewScore = 55;
|
||||
else if (avgReviews > 1000) reviewScore = 40;
|
||||
else if (avgReviews > 100) reviewScore = 25;
|
||||
else reviewScore = 15;
|
||||
|
||||
const matchScore = titleMatches * 5;
|
||||
const finalScore = Math.min(99, Math.max(10, Math.round((reviewScore * 0.8) + (matchScore * 0.2))));
|
||||
|
||||
return {
|
||||
score: finalScore,
|
||||
avgReviews: Math.round(avgReviews),
|
||||
top1: top5[0] ? `${top5[0].trackName} (${top5[0].userRatingCount || 0} rev, ★${top5[0].averageUserRating?.toFixed(1) || '0'})` : 'Keine',
|
||||
resultCount: apps.length
|
||||
};
|
||||
}
|
||||
|
||||
async function run() {
|
||||
console.log("Starte Live-Abfrage der Apple Storefront API...");
|
||||
const report = {};
|
||||
|
||||
for (const sf of STOREFRONTS) {
|
||||
console.log(`\nPrüfe Storefront: ${sf.name} (${sf.id})...`);
|
||||
report[sf.id] = { name: sf.name, data: [] };
|
||||
const keywords = sf.lang === 'de' ? DE_KEYWORDS : EN_KEYWORDS;
|
||||
|
||||
for (const kw of keywords) {
|
||||
const apps = await fetchStoreData(kw, sf.country);
|
||||
const diff = calculateDifficulty(apps, kw);
|
||||
|
||||
report[sf.id].data.push({
|
||||
keyword: kw,
|
||||
...diff
|
||||
});
|
||||
await new Promise(r => setTimeout(r, 200));
|
||||
}
|
||||
}
|
||||
|
||||
fs.writeFileSync('scripts/live_api_results.json', JSON.stringify(report, null, 2));
|
||||
console.log("\nLive-Ergebnisse erfolgreich in scripts/live_api_results.json gespeichert!");
|
||||
}
|
||||
|
||||
run();
|
||||
Reference in New Issue
Block a user