const express = require('express'); const crypto = require('crypto'); const { Pool } = require('pg'); const app = express(); app.use(express.json()); const pool = new Pool({ connectionString: process.env.DATABASE_URL, }); async function initDB() { const client = await pool.connect(); try { await client.query(` CREATE TABLE IF NOT EXISTS links ( hash TEXT PRIMARY KEY, url TEXT NOT NULL, hits INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMP NOT NULL DEFAULT NOW() ) `); console.log('Database table ready'); } finally { client.release(); } } function makeHash(url) { return crypto.createHash('sha256').update(url).digest('base64url').slice(0, 7); } // POST /api/links {"url": "https://example.com"} -> {"hash": "...", "short_url": "..."} app.post('/api/links', async (req, res) => { const { url } = req.body; let parsed; try { parsed = new URL(url); } catch { return res.status(400).json({ error: '"url" must be a valid URL' }); } if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') { return res.status(400).json({ error: 'only http/https URLs allowed' }); } const hash = makeHash(parsed.href); try { await pool.query( 'INSERT INTO links (hash, url) VALUES ($1, $2) ON CONFLICT (hash) DO NOTHING', [hash, parsed.href] ); res.status(201).json({ hash, short_url: `${req.protocol}://${req.get('host')}/${hash}`, }); } catch (err) { console.error(err); res.status(500).json({ error: 'Failed to create link' }); } }); // GET /api/links -> recent links with hit counts app.get('/api/links', async (req, res) => { try { const { rows } = await pool.query( 'SELECT hash, url, hits, created_at FROM links ORDER BY created_at DESC LIMIT 50' ); res.json(rows); } catch (err) { console.error(err); res.status(500).json({ error: 'Failed to list links' }); } }); // GET /:hash -> 302 redirect app.get('/:hash([A-Za-z0-9_-]{7})', async (req, res) => { try { const { rows } = await pool.query( 'UPDATE links SET hits = hits + 1 WHERE hash = $1 RETURNING url', [req.params.hash] ); if (rows.length === 0) return res.status(404).json({ error: 'Link not found' }); res.redirect(302, rows[0].url); } catch (err) { console.error(err); res.status(500).json({ error: 'Failed to resolve link' }); } }); app.get('/', (req, res) => { res.json({ name: 'url-shortener', usage: { shorten: 'POST /api/links {"url": "https://example.com"}', list: 'GET /api/links', follow: 'GET /', }, }); }); initDB().then(() => { const PORT = process.env.PORT || 3000; app.listen(PORT, () => console.log(`Server running on port ${PORT}`)); });