/* Simple URL shortener with preview page - Node.js + Express - SQLite for storage Features: - POST /shorten -> { url, title?, image_url?, auto_redirect_seconds? } returns a short URL using BASE_DOMAIN config - GET /:code -> shows a preview page with image + "Open link" button If auto_redirect_seconds is set, page will redirect after that many seconds - GET /admin -> simple listing of links (for testing) Run: 1) npm init -y 2) npm install express sqlite3 body-parser valid-url 3) node simple-url-shortener-with-preview.js Environment variables: - PORT (default 3000) - BASE_DOMAIN (default http://localhost:3000) <-- set this to your "সিম অফ চ্যাট" domain Example curl to shorten: curl -X POST -H "Content-Type: application/json" -d '{"url":"https://example.com","title":"Example","image_url":"https://picsum.photos/1200/600","auto_redirect_seconds":5}' http://localhost:3000/shorten */ const express = require('express'); const bodyParser = require('body-parser'); const sqlite3 = require('sqlite3').verbose(); const { isWebUri } = require('valid-url'); const path = require('path'); const PORT = process.env.PORT || 3000; const BASE_DOMAIN = (process.env.BASE_DOMAIN || `http://localhost:${PORT}`).replace(/\/$/, ""); // --- Setup DB --- const db = new sqlite3.Database(':memory:'); db.serialize(() => { db.run(`CREATE TABLE IF NOT EXISTS links ( id INTEGER PRIMARY KEY AUTOINCREMENT, code TEXT UNIQUE, target_url TEXT, title TEXT, image_url TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, auto_redirect_seconds INTEGER )`); }); // --- Helpers --- const alphabet = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; function encodeBase62(num) { if (num === 0) return '0'; let s = ''; while (num > 0) { s = alphabet[num % 62] + s; num = Math.floor(num / 62); } return s; } function generateCodeFromId(id) { return encodeBase62(id); } // sanitize URL: ensure has protocol function normalizeUrl(u) { if (!u) return null; u = u.trim(); if (!/^https?:\/\//i.test(u)) u = 'http://' + u; return u; } // --- App --- const app = express(); app.use(bodyParser.json()); app.use(express.urlencoded({ extended: true })); app.use('/static', express.static(path.join(__dirname, 'static'))); // Shorten endpoint app.post('/shorten', (req, res) => { try { let { url, title, image_url, auto_redirect_seconds } = req.body; if (!url) return res.status(400).json({ error: 'url is required' }); url = normalizeUrl(url); if (!isWebUri(url)) return res.status(400).json({ error: 'invalid url' }); if (image_url) { image_url = normalizeUrl(image_url); if (!isWebUri(image_url)) image_url = null; } auto_redirect_seconds = parseInt(auto_redirect_seconds) || null; // Insert row first to get numeric id const stmt = db.prepare('INSERT INTO links (target_url, title, image_url, auto_redirect_seconds) VALUES (?, ?, ?, ?)'); stmt.run(url, title || null, image_url || null, auto_redirect_seconds, function(err) { if (err) return res.status(500).json({ error: 'db insert error', details: err.message }); const id = this.lastID; const code = generateCodeFromId(id); db.run('UPDATE links SET code = ? WHERE id = ?', [code, id], function(err2) { if (err2) return res.status(500).json({ error: 'db update error', details: err2.message }); const short = `${BASE_DOMAIN}/${code}`; return res.json({ short, code, target: url, title: title || null, image_url: image_url || null, auto_redirect_seconds }); }); }); stmt.finalize(); } catch (e) { return res.status(500).json({ error: e.message }); } }); // Redirect / preview handler app.get('/:code', (req, res) => { const code = req.params.code; db.get('SELECT * FROM links WHERE code = ?', [code], (err, row) => { if (err) return res.status(500).send('DB error'); if (!row) return res.status(404).send('

Link not found

'); // If there's no image_url, use a scenic placeholder const image = row.image_url || `https://picsum.photos/1200/600?random=${row.id}`; const title = row.title || 'Preview'; const target = row.target_url; const autoSec = row.auto_redirect_seconds; // render simple HTML preview page res.send(` ${escapeHtml(title)}

${escapeHtml(title)}

Short link: ${BASE_DOMAIN}/${escapeHtml(code)}

এই লিংকটি টেস্ট করার জন্য নিচের বোতনে ক্লিক করুন।

Open Link

${autoSec ? `

এই পেজটি ${autoSec} সেকেন্ড পর স্বয়ংক্রিয়ভাবে রিডাইরেক্ট হবে...

` : ''}
`); }); }); // Simple admin list for testing app.get('/admin', (req, res) => { db.all('SELECT * FROM links ORDER BY id DESC LIMIT 200', [], (err, rows) => { if (err) return res.status(500).send('DB error'); let html = `Links

Links

`; for (const r of rows) { html += ``; } html += '
idcodetargetimagecreated
${r.id}${r.code}${escapeHtml(r.target_url)}${r.image_url ? `img` : '-'}${r.created_at}
'; res.send(html); }); }); // health app.get('/health', (req, res) => res.json({ ok: true })); app.listen(PORT, () => console.log(`URL shortener running at ${BASE_DOMAIN} (port ${PORT})`)); // --- small util --- function escapeHtml(s){ if (!s) return ''; return String(s) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); }

Comments

Popular posts from this blog