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>
64 lines
1.9 KiB
JavaScript
64 lines
1.9 KiB
JavaScript
import https from 'https';
|
|
|
|
const keywords = [
|
|
'zimmerpflanze',
|
|
'zimmerpflanzen',
|
|
'zimmerpflanzen pflege',
|
|
'pflanzen bestimmen',
|
|
'pflanzen app',
|
|
'pflanzendoktor',
|
|
'pflanzen gießen erinnerung',
|
|
'houseplant',
|
|
'plant care'
|
|
];
|
|
|
|
const storefronts = [
|
|
{ code: 'de', name: 'Deutschland' },
|
|
{ code: 'at', name: 'Österreich' },
|
|
{ code: 'ch', name: 'Schweiz' }
|
|
];
|
|
|
|
function fetchSearch(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' } }, (res) => {
|
|
let data = '';
|
|
res.on('data', chunk => data += chunk);
|
|
res.on('end', () => {
|
|
try {
|
|
resolve(JSON.parse(data).results || []);
|
|
} catch {
|
|
resolve([]);
|
|
}
|
|
});
|
|
}).on('error', () => resolve([]));
|
|
});
|
|
}
|
|
|
|
async function run() {
|
|
console.log("Analysiere 'Zimmerpflanze' und verwandte Keywords im App Store...\n");
|
|
|
|
for (const sf of storefronts) {
|
|
console.log(`========================================`);
|
|
console.log(`Storefront: ${sf.name} (${sf.code.toUpperCase()})`);
|
|
console.log(`========================================`);
|
|
|
|
for (const kw of keywords) {
|
|
const apps = await fetchSearch(kw, sf.code);
|
|
const top5 = apps.slice(0, 5);
|
|
const avgReviews = top5.length ? Math.round(top5.reduce((s, a) => s + (a.userRatingCount || 0), 0) / top5.length) : 0;
|
|
const top1 = top5[0] || {};
|
|
|
|
console.log(`\nKeyword: "${kw}"`);
|
|
console.log(`- Apps gefunden: ${apps.length}`);
|
|
console.log(`- Top 1: ${top1.trackName || 'Keine'} (${top1.userRatingCount || 0} Reviews, ★${top1.averageUserRating?.toFixed(1) || 0})`);
|
|
console.log(`- Ø Reviews Top 5: ${avgReviews.toLocaleString('de-DE')}`);
|
|
|
|
await new Promise(r => setTimeout(r, 150));
|
|
}
|
|
console.log("\n");
|
|
}
|
|
}
|
|
|
|
run();
|