This commit is contained in:
@@ -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
|
||||
|
||||
68
backend/src/routes/tools.ts
Normal file
68
backend/src/routes/tools.ts
Normal 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;
|
||||
Reference in New Issue
Block a user