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
107 lines
3.4 KiB
TypeScript
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;
|
|
}
|