This commit is contained in:
Timo Knuth
2026-01-20 15:54:16 +01:00
parent 9fa8045c26
commit 4733e1a1cc
10 changed files with 290 additions and 756 deletions

View File

@@ -42,12 +42,14 @@ app.get('/health', async (_req, res) => {
import testRoutes from './routes/test';
import waitlistRoutes from './routes/waitlist';
import { toolsRouter } from './routes/tools';
// Routes
app.use('/api/auth', authLimiter, authRoutes);
app.use('/api/monitors', authMiddleware, monitorRoutes);
app.use('/api/settings', authMiddleware, settingsRoutes);
app.use('/api/waitlist', waitlistRoutes); // Public route - no auth required
app.use('/api/tools', toolsRouter); // Public tools
app.use('/test', testRoutes);
// 404 handler

View File

@@ -0,0 +1,68 @@
import { Router } from 'express';
import axios from 'axios';
import * as cheerio from 'cheerio';
import { z } from 'zod';
const router = Router();
const previewSchema = z.object({
url: z.string().min(1)
});
router.post('/meta-preview', async (req, res) => {
try {
let { url } = previewSchema.parse(req.body);
// Add protocol if missing
if (!url.startsWith('http://') && !url.startsWith('https://')) {
url = `https://${url}`;
}
const response = await axios.get(url, {
headers: {
'User-Agent': 'Mozilla/5.0 (compatible; WebsiteMonitorBot/1.0; +https://websitemonitor.com)'
},
timeout: 5000,
validateStatus: (status) => status < 500 // Resolve even if 404/403 to avoid crashing flow immediately
});
const html = response.data;
const $ = cheerio.load(html);
const title = $('title').text() || $('meta[property="og:title"]').attr('content') || '';
const description = $('meta[name="description"]').attr('content') || $('meta[property="og:description"]').attr('content') || '';
// Attempt to find favicon
let favicon = '';
const linkIcon = $('link[rel="icon"], link[rel="shortcut icon"], link[rel="apple-touch-icon"]').attr('href');
if (linkIcon) {
if (linkIcon.startsWith('http')) {
favicon = linkIcon;
} else if (linkIcon.startsWith('//')) {
favicon = `https:${linkIcon}`;
} else {
const urlObj = new URL(url);
favicon = `${urlObj.protocol}//${urlObj.host}${linkIcon.startsWith('/') ? '' : '/'}${linkIcon}`;
}
} else {
const urlObj = new URL(url);
favicon = `${urlObj.protocol}//${urlObj.host}/favicon.ico`;
}
res.json({
title: title.trim(),
description: description.trim(),
favicon,
url: url
});
} catch (error) {
console.error('Meta preview error:', error);
if (error instanceof z.ZodError) {
return res.status(400).json({ error: 'Invalid URL provided' });
}
res.status(500).json({ error: 'Failed to fetch page metadata' });
}
});
export const toolsRouter = router;