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:
133
scripts/test_ads_platform_api.mjs
Normal file
133
scripts/test_ads_platform_api.mjs
Normal file
@@ -0,0 +1,133 @@
|
||||
import crypto from 'crypto';
|
||||
import https from 'https';
|
||||
import fs from 'fs';
|
||||
|
||||
const envContent = fs.readFileSync('.env.local', 'utf8');
|
||||
const env = {};
|
||||
envContent.split('\n').forEach(line => {
|
||||
const match = line.match(/^([^=]+)=(.*)$/);
|
||||
if (match) {
|
||||
let val = match[2].trim();
|
||||
if (val.startsWith('"') && val.endsWith('"')) val = val.slice(1, -1);
|
||||
env[match[1].trim()] = val;
|
||||
}
|
||||
});
|
||||
|
||||
const CLIENT_ID = env.APPLE_SEARCH_ADS_CLIENT_ID;
|
||||
const TEAM_ID = env.APPLE_SEARCH_ADS_TEAM_ID;
|
||||
const KEY_ID = env.APPLE_SEARCH_ADS_KEY_ID;
|
||||
const PRIVATE_KEY = env.APPLE_SEARCH_ADS_PRIVATE_KEY;
|
||||
const ORG_ID = 22309440;
|
||||
|
||||
function generateClientSecret() {
|
||||
const header = { alg: "ES256", kid: KEY_ID, typ: "JWT" };
|
||||
const now = Math.floor(Date.now() / 1000);
|
||||
const payload = { sub: CLIENT_ID, aud: "https://appleid.apple.com", iat: now, exp: now + 86400 * 180, iss: TEAM_ID };
|
||||
|
||||
const base64Header = Buffer.from(JSON.stringify(header)).toString("base64url");
|
||||
const base64Payload = Buffer.from(JSON.stringify(payload)).toString("base64url");
|
||||
const signingInput = `${base64Header}.${base64Payload}`;
|
||||
|
||||
const signer = crypto.createSign("SHA256");
|
||||
signer.update(signingInput);
|
||||
const signature = signer.sign({ key: PRIVATE_KEY.replace(/\\n/g, '\n'), dsaEncoding: 'ieee-p1363' }, "base64url");
|
||||
return `${signingInput}.${signature}`;
|
||||
}
|
||||
|
||||
async function getAccessToken(clientSecret) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const postData = new URLSearchParams({
|
||||
grant_type: 'client_credentials',
|
||||
client_id: CLIENT_ID,
|
||||
client_secret: clientSecret,
|
||||
scope: 'searchadsorg'
|
||||
}).toString();
|
||||
|
||||
const req = https.request({
|
||||
hostname: 'appleid.apple.com',
|
||||
port: 443,
|
||||
path: '/auth/oauth2/token',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Content-Length': Buffer.byteLength(postData),
|
||||
'User-Agent': 'Mozilla/5.0'
|
||||
}
|
||||
}, (res) => {
|
||||
let data = '';
|
||||
res.on('data', chunk => data += chunk);
|
||||
res.on('end', () => resolve(JSON.parse(data)));
|
||||
});
|
||||
req.on('error', reject);
|
||||
req.write(postData);
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
function reqAdsApi(path, token, method = 'POST', body = null) {
|
||||
return new Promise((resolve) => {
|
||||
const headers = {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'X-AP-Context': `orgId=${ORG_ID}`,
|
||||
'Content-Type': 'application/json',
|
||||
'User-Agent': 'Mozilla/5.0'
|
||||
};
|
||||
|
||||
const req = https.request({
|
||||
hostname: 'api.ads.apple.com',
|
||||
port: 443,
|
||||
path,
|
||||
method,
|
||||
headers
|
||||
}, (res) => {
|
||||
let data = '';
|
||||
res.on('data', chunk => data += chunk);
|
||||
res.on('end', () => {
|
||||
try { resolve(JSON.parse(data)); }
|
||||
catch { resolve({ raw: data, status: res.statusCode }); }
|
||||
});
|
||||
});
|
||||
req.on('error', (e) => resolve({ error: e.message }));
|
||||
if (body) req.write(JSON.stringify(body));
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
async function run() {
|
||||
const secret = generateClientSecret();
|
||||
const token = (await getAccessToken(secret)).access_token;
|
||||
|
||||
console.log("Token obtained. Testing api.ads.apple.com endpoints...");
|
||||
|
||||
const endpoints = [
|
||||
{
|
||||
p: '/v1/connect/apple-ads/insights/search-term-popularity',
|
||||
body: {
|
||||
countryOrRegionCode: 'US',
|
||||
searchTerms: ['receipt scanner', 'receipt to excel', 'expense tracker', 'invoice scanner']
|
||||
}
|
||||
},
|
||||
{
|
||||
p: '/v1/connect/apple-ads/suggestions/keywords',
|
||||
body: {
|
||||
countryOrRegionCode: 'US',
|
||||
searchTerm: 'receipt scanner'
|
||||
}
|
||||
},
|
||||
{
|
||||
p: '/v1/connect/apple-ads/suggestions/phrases',
|
||||
body: {
|
||||
countryOrRegionCode: 'US',
|
||||
searchTerm: 'receipt to excel'
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
for (const ep of endpoints) {
|
||||
const res = await reqAdsApi(ep.p, token, 'POST', ep.body);
|
||||
console.log(`\nEndpoint ${ep.p}:`);
|
||||
console.log(JSON.stringify(res, null, 2));
|
||||
}
|
||||
}
|
||||
|
||||
run();
|
||||
Reference in New Issue
Block a user