Add initial project skeleton

This commit is contained in:
Dexter Barnes
2025-08-17 20:55:38 +01:00
commit edf68e8ddc
9 changed files with 1317 additions and 0 deletions

82
server.js Normal file
View File

@@ -0,0 +1,82 @@
import 'dotenv/config';
import express from 'express';
import cors from 'cors';
import path from 'path';
import { fileURLToPath } from 'url';
import fetch from 'node-fetch';
const app = express();
app.use(cors());
app.use(express.json());
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Serve static frontend
app.use(express.static(path.join(__dirname, 'public')));
// (Mock data routes removed)
// eBay completed (sold) listings search
app.get('/api/ebay/search', async (req, res) => {
try {
const { player, team, manufacturer, page = 1 } = req.query;
const appId = process.env.EBAY_APP_ID;
if (!appId) {
return res.status(500).json({ error: 'Server missing EBAY_APP_ID environment variable' });
}
const name = String(player || '').trim();
if (!name) return res.status(400).json({ error: 'Missing required "player" query param' });
const keywords = [name, team, manufacturer].filter(Boolean).join(' ');
const params = new URLSearchParams({
'OPERATION-NAME': 'findCompletedItems',
'SERVICE-VERSION': '1.13.0',
'SECURITY-APPNAME': appId,
'RESPONSE-DATA-FORMAT': 'JSON',
keywords,
'paginationInput.entriesPerPage': '24',
'paginationInput.pageNumber': String(page),
'sortOrder': 'EndTimeSoonest',
'outputSelector': 'PictureURLLarge'
});
// item filters
params.append('itemFilter(0).name', 'SoldItemsOnly');
params.append('itemFilter(0).value', 'true');
params.append('GLOBAL-ID', 'EBAY-US');
const url = `https://svcs.ebay.com/services/search/FindingService/v1?${params.toString()}`;
const resp = await fetch(url);
if (!resp.ok) {
const text = await resp.text();
return res.status(502).json({ error: 'eBay API error', detail: text });
}
const data = await resp.json();
const root = (data && data.findCompletedItemsResponse && data.findCompletedItemsResponse[0]) || {};
const items = ((root.searchResult && root.searchResult[0] && root.searchResult[0].item) || []).map((it) => {
const title = it.title?.[0] || '';
const url = it.viewItemURL?.[0] || '';
const image = (it.pictureURLLarge?.[0] || it.galleryURL?.[0] || '');
const price = it.sellingStatus?.[0]?.currentPrice?.[0]?.__value__ || null;
const currency = it.sellingStatus?.[0]?.currentPrice?.[0]?.['@currencyId'] || null;
const ended = it.listingInfo?.[0]?.endTime?.[0] || null;
return { title, url, image, price, currency, ended };
});
res.json({ results: items });
} catch (err) {
console.error('eBay search error', err);
res.status(500).json({ error: 'Internal server error' });
}
});
// Fallback to index.html for root
app.get('/', (_req, res) => {
res.sendFile(path.join(__dirname, 'public', 'index.html'));
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Baseball Cards Search running on http://localhost:${PORT}`);
});