folx-tv/server/routes.ts
sebastjanartic 4a7639b15d Add new pages and improve blog content display functionality
Implement new routes for articles, categories, and individual articles. Update the UI to display articles with improved content rendering, including safe HTML and media embeds. Refactor storage to use a database and add image upload capabilities.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 413891e8-d784-4bea-b9f5-91a5a68316b4
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: b96b221e-0ed6-418f-80df-e4670bf5ba4b
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/f209e72a-0939-48fa-84fc-57854de71967/413891e8-d784-4bea-b9f5-91a5a68316b4/cftwqyT
Replit-Helium-Checkpoint-Created: true
2026-02-28 16:38:38 +00:00

107 lines
3.4 KiB
TypeScript

import type { Express } from "express";
import { createServer, type Server } from "http";
import { storage } from "./storage";
import { insertArticleSchema } from "@shared/schema";
import { seedDatabase } from "./seed";
import multer from "multer";
import path from "path";
import fs from "fs";
const uploadDir = path.join(process.cwd(), "client/public/uploads");
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir, { recursive: true });
}
const upload = multer({
storage: multer.diskStorage({
destination: (_req, _file, cb) => cb(null, uploadDir),
filename: (_req, file, cb) => {
const uniqueSuffix = Date.now() + "-" + Math.round(Math.random() * 1e9);
const ext = path.extname(file.originalname);
cb(null, uniqueSuffix + ext);
},
}),
limits: { fileSize: 10 * 1024 * 1024 },
fileFilter: (_req, file, cb) => {
const allowed = /jpeg|jpg|png|gif|webp/;
const ext = allowed.test(path.extname(file.originalname).toLowerCase());
const mime = allowed.test(file.mimetype);
cb(null, ext && mime);
},
});
export async function registerRoutes(
httpServer: Server,
app: Express
): Promise<Server> {
await seedDatabase();
app.get("/api/articles", async (_req, res) => {
const articles = await storage.getArticles();
res.json(articles);
});
app.get("/api/articles/featured", async (_req, res) => {
const articles = await storage.getFeaturedArticles();
res.json(articles);
});
app.get("/api/articles/popular", async (req, res) => {
const limit = parseInt(req.query.limit as string) || 5;
const articles = await storage.getPopularArticles(limit);
res.json(articles);
});
app.get("/api/articles/category/:category", async (req, res) => {
const articles = await storage.getArticlesByCategory(req.params.category);
res.json(articles);
});
app.get("/api/articles/:slug", async (req, res) => {
const article = await storage.getArticleBySlug(req.params.slug);
if (!article) {
return res.status(404).json({ message: "Article not found" });
}
await storage.incrementViews(article.id);
res.json({ ...article, views: article.views + 1 });
});
app.post("/api/articles", async (req, res) => {
const parsed = insertArticleSchema.safeParse(req.body);
if (!parsed.success) {
return res.status(400).json({ message: "Invalid article data", errors: parsed.error.flatten() });
}
const article = await storage.createArticle(parsed.data);
res.status(201).json(article);
});
app.patch("/api/articles/:id", async (req, res) => {
const id = parseInt(req.params.id);
const partial = insertArticleSchema.partial().safeParse(req.body);
if (!partial.success) {
return res.status(400).json({ message: "Invalid article data", errors: partial.error.flatten() });
}
const article = await storage.updateArticle(id, partial.data);
if (!article) {
return res.status(404).json({ message: "Article not found" });
}
res.json(article);
});
app.delete("/api/articles/:id", async (req, res) => {
const id = parseInt(req.params.id);
await storage.deleteArticle(id);
res.status(204).send();
});
app.post("/api/upload", upload.single("image"), (req, res) => {
if (!req.file) {
return res.status(400).json({ message: "No file uploaded" });
}
const url = `/uploads/${req.file.filename}`;
res.json({ url });
});
return httpServer;
}