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>
71 lines
1.9 KiB
JavaScript
71 lines
1.9 KiB
JavaScript
import https from 'https';
|
|
|
|
const 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 COUNTRIES = [
|
|
{ code: 'us', name: 'USA' },
|
|
{ code: 'gb', name: 'UK' },
|
|
{ code: 'ca', name: 'Canada' },
|
|
{ code: 'au', name: 'Australia' }
|
|
];
|
|
|
|
function fetchSearch(term, country) {
|
|
return new Promise((resolve) => {
|
|
const url = `https://itunes.apple.com/search?term=${encodeURIComponent(term)}&country=${country}&entity=software&limit=5`;
|
|
const req = 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 {
|
|
resolve(JSON.parse(data));
|
|
} catch {
|
|
resolve({ results: [] });
|
|
}
|
|
});
|
|
});
|
|
req.on('error', () => resolve({ results: [] }));
|
|
req.setTimeout(4000, () => {
|
|
req.destroy();
|
|
resolve({ results: [] });
|
|
});
|
|
});
|
|
}
|
|
|
|
async function run() {
|
|
const table = [];
|
|
|
|
for (const kw of KEYWORDS) {
|
|
const row = { keyword: kw, countries: {} };
|
|
for (const c of COUNTRIES) {
|
|
const data = await fetchSearch(kw, c.code);
|
|
const apps = data.results || [];
|
|
const top1 = apps[0] || {};
|
|
const avgReviews = apps.length ? Math.round(apps.reduce((s, a) => s + (a.userRatingCount || 0), 0) / apps.length) : 0;
|
|
|
|
row.countries[c.name] = {
|
|
top1Name: top1.trackName ? top1.trackName.slice(0, 25) : 'None',
|
|
top1Reviews: top1.userRatingCount || 0,
|
|
avgReviews,
|
|
resultCount: apps.length
|
|
};
|
|
await new Promise(r => setTimeout(r, 150));
|
|
}
|
|
table.push(row);
|
|
}
|
|
|
|
console.log(JSON.stringify(table, null, 2));
|
|
}
|
|
|
|
run();
|