Files
sapiens-web3/server/routes.ts
kimjaehyeon0101 3829c9bc87 Enable management of media outlet articles including creation, editing, and deletion
Adds article management functionality to media outlets, including API endpoints for creating, updating, and deleting articles, and integrates these features into the client-side UI with dialogs and mutations.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 9a264234-c5d7-4dcc-adf3-a954b149b30d
Replit-Commit-Checkpoint-Type: intermediate_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/3df548ff-50ae-432f-9be4-25d34eccc983/9a264234-c5d7-4dcc-adf3-a954b149b30d/QTw0kIA
2025-10-14 10:01:14 +00:00

588 lines
20 KiB
TypeScript

import type { Express } from "express";
import { createServer, type Server } from "http";
import { storage } from "./storage";
import { setupAuth, isAuthenticated } from "./simpleAuth";
import { insertArticleSchema, insertMediaOutletRequestSchema, insertBidSchema, insertCommentSchema, insertPredictionBetSchema, insertCommunityPostSchema, insertCommunityReplySchema } from "@shared/schema";
export async function registerRoutes(app: Express): Promise<Server> {
// Auth middleware
await setupAuth(app);
// Auth routes
app.get('/api/auth/user', isAuthenticated, async (req: any, res) => {
try {
const user = req.user;
res.json(user);
} catch (error) {
console.error("Error fetching user:", error);
res.status(500).json({ message: "Failed to fetch user" });
}
});
// Media outlet routes
app.get('/api/media-outlets', async (req, res) => {
try {
const category = req.query.category as string;
const outlets = await storage.getMediaOutlets(category);
res.json(outlets);
} catch (error) {
console.error("Error fetching media outlets:", error);
res.status(500).json({ message: "Failed to fetch media outlets" });
}
});
app.get('/api/media-outlets/:slug', async (req, res) => {
try {
const outlet = await storage.getMediaOutletBySlug(req.params.slug);
if (!outlet) {
return res.status(404).json({ message: "Media outlet not found" });
}
res.json(outlet);
} catch (error) {
console.error("Error fetching media outlet:", error);
res.status(500).json({ message: "Failed to fetch media outlet" });
}
});
app.patch('/api/media-outlets/:slug', isAuthenticated, async (req: any, res) => {
try {
const user = req.user;
if (!user || (user.role !== 'admin' && user.role !== 'superadmin')) {
return res.status(403).json({ message: "Insufficient permissions" });
}
const { description } = req.body;
const outlet = await storage.getMediaOutletBySlug(req.params.slug);
if (!outlet) {
return res.status(404).json({ message: "Media outlet not found" });
}
await storage.updateMediaOutlet(outlet.id, { description });
const updated = await storage.getMediaOutletBySlug(req.params.slug);
res.json(updated);
} catch (error) {
console.error("Error updating media outlet:", error);
res.status(500).json({ message: "Failed to update media outlet" });
}
});
// Article routes
app.get('/api/articles', async (req, res) => {
try {
const articles = await storage.getArticles();
res.json(articles);
} catch (error) {
console.error("Error fetching articles:", error);
res.status(500).json({ message: "Failed to fetch articles" });
}
});
app.get('/api/media-outlets/:slug/articles', async (req, res) => {
try {
const outlet = await storage.getMediaOutletBySlug(req.params.slug);
if (!outlet) {
return res.status(404).json({ message: "Media outlet not found" });
}
const articles = await storage.getArticlesByOutlet(outlet.id);
res.json(articles);
} catch (error) {
console.error("Error fetching articles by slug:", error);
res.status(500).json({ message: "Failed to fetch articles" });
}
});
app.get('/api/articles/:slug', async (req, res) => {
try {
const article = await storage.getArticleBySlug(req.params.slug);
if (!article) {
return res.status(404).json({ message: "Article not found" });
}
res.json(article);
} catch (error) {
console.error("Error fetching article:", error);
res.status(500).json({ message: "Failed to fetch article" });
}
});
app.get('/api/articles/:slug/markets', async (req, res) => {
try {
const article = await storage.getArticleBySlug(req.params.slug);
if (!article) {
return res.status(404).json({ message: "Article not found" });
}
const markets = await storage.getPredictionMarkets(article.id);
res.json(markets);
} catch (error) {
console.error("Error fetching prediction markets for article:", error);
res.status(500).json({ message: "Failed to fetch prediction markets" });
}
});
app.get('/api/articles/featured', async (req, res) => {
try {
const limit = parseInt(req.query.limit as string) || 10;
const articles = await storage.getFeaturedArticles(limit);
res.json(articles);
} catch (error) {
console.error("Error fetching featured articles:", error);
res.status(500).json({ message: "Failed to fetch featured articles" });
}
});
app.post('/api/articles', isAuthenticated, async (req: any, res) => {
try {
const user = req.user;
if (!user || (user.role !== 'admin' && user.role !== 'superadmin')) {
return res.status(403).json({ message: "Insufficient permissions" });
}
const articleData = insertArticleSchema.parse({
...req.body,
authorId: user.id
});
const article = await storage.createArticle(articleData);
res.status(201).json(article);
} catch (error) {
console.error("Error creating article:", error);
res.status(500).json({ message: "Failed to create article" });
}
});
app.patch('/api/articles/:id', isAuthenticated, async (req: any, res) => {
try {
const user = req.user;
if (!user || (user.role !== 'admin' && user.role !== 'superadmin')) {
return res.status(403).json({ message: "Insufficient permissions" });
}
const article = await storage.getArticleById(req.params.id);
if (!article) {
return res.status(404).json({ message: "Article not found" });
}
const articleData = insertArticleSchema.partial().parse(req.body);
const updated = await storage.updateArticle(req.params.id, articleData);
res.json(updated);
} catch (error) {
console.error("Error updating article:", error);
res.status(500).json({ message: "Failed to update article" });
}
});
app.delete('/api/articles/:id', isAuthenticated, async (req: any, res) => {
try {
const user = req.user;
if (!user || (user.role !== 'admin' && user.role !== 'superadmin')) {
return res.status(403).json({ message: "Insufficient permissions" });
}
const article = await storage.getArticleById(req.params.id);
if (!article) {
return res.status(404).json({ message: "Article not found" });
}
await storage.deleteArticle(req.params.id);
res.status(204).send();
} catch (error) {
console.error("Error deleting article:", error);
res.status(500).json({ message: "Failed to delete article" });
}
});
// Prediction market routes
app.get('/api/prediction-markets', async (req, res) => {
try {
const articleId = req.query.articleId as string;
const markets = await storage.getPredictionMarkets(articleId);
res.json(markets);
} catch (error) {
console.error("Error fetching prediction markets:", error);
res.status(500).json({ message: "Failed to fetch prediction markets" });
}
});
// Auction routes
app.get('/api/auctions', async (req, res) => {
try {
const auctions = await storage.getActiveAuctions();
res.json(auctions);
} catch (error) {
console.error("Error fetching auctions:", error);
res.status(500).json({ message: "Failed to fetch auctions" });
}
});
app.get('/api/media-outlets/:slug/auction', async (req, res) => {
try {
const outlet = await storage.getMediaOutletBySlug(req.params.slug);
if (!outlet) {
return res.status(404).json({ message: "Media outlet not found" });
}
const auction = await storage.getAuctionByMediaOutlet(outlet.id);
res.json(auction || null);
} catch (error) {
console.error("Error fetching auction:", error);
res.status(500).json({ message: "Failed to fetch auction" });
}
});
app.get('/api/auctions/:id/bids', async (req, res) => {
try {
const bids = await storage.getBidsByAuctionId(req.params.id);
res.json(bids);
} catch (error) {
console.error("Error fetching bids:", error);
res.status(500).json({ message: "Failed to fetch bids" });
}
});
app.post('/api/auctions/:id/bid', isAuthenticated, async (req: any, res) => {
try {
const userId = req.user.claims?.sub || req.user.id;
const bidData = insertBidSchema.parse({
...req.body,
auctionId: req.params.id,
bidderId: userId
});
const bid = await storage.placeBid(bidData);
res.status(201).json(bid);
} catch (error) {
console.error("Error placing bid:", error);
res.status(500).json({ message: "Failed to place bid" });
}
});
app.post('/api/media-outlets/:slug/auction/bids', isAuthenticated, async (req: any, res) => {
try {
const outlet = await storage.getMediaOutletBySlug(req.params.slug);
if (!outlet) {
return res.status(404).json({ message: "Media outlet not found" });
}
const auction = await storage.getAuctionByMediaOutlet(outlet.id);
if (!auction) {
return res.status(404).json({ message: "No active auction found for this media outlet" });
}
const userId = req.user.claims?.sub || req.user.id;
const bidData = insertBidSchema.parse({
...req.body,
auctionId: auction.id,
bidderId: userId
});
const bid = await storage.placeBid(bidData);
res.status(201).json(bid);
} catch (error) {
console.error("Error placing bid:", error);
res.status(500).json({ message: "Failed to place bid" });
}
});
// Prediction market betting endpoints
app.post('/api/prediction-markets/:marketId/bets', isAuthenticated, async (req: any, res) => {
try {
const userId = req.user.claims?.sub || req.user.id;
const { side, amount } = req.body;
// Validate request
if (!side || !amount || !["yes", "no"].includes(side)) {
return res.status(400).json({ message: "Invalid bet data" });
}
if (parseFloat(amount) <= 0) {
return res.status(400).json({ message: "Bet amount must be positive" });
}
const betData = insertPredictionBetSchema.parse({
marketId: req.params.marketId,
userId,
side,
amount: amount.toString()
});
const bet = await storage.createPredictionBet(betData);
res.status(201).json(bet);
} catch (error) {
console.error("Error placing prediction bet:", error);
if (error instanceof Error && error.message === "Prediction market not found") {
return res.status(404).json({ message: "Prediction market not found" });
}
res.status(500).json({ message: "Failed to place bet" });
}
});
// Media outlet request routes
app.get('/api/media-outlet-requests', isAuthenticated, async (req: any, res) => {
try {
const userId = req.user.claims?.sub || req.user.id;
const user = await storage.getUser(userId);
if (!user || user.role !== 'superadmin') {
return res.status(403).json({ message: "Insufficient permissions" });
}
const status = req.query.status as string;
const requests = await storage.getMediaOutletRequests(status);
res.json(requests);
} catch (error) {
console.error("Error fetching requests:", error);
res.status(500).json({ message: "Failed to fetch requests" });
}
});
app.post('/api/media-outlet-requests', isAuthenticated, async (req: any, res) => {
try {
const userId = req.user.claims?.sub || req.user.id;
const requestData = insertMediaOutletRequestSchema.parse({
...req.body,
requesterId: userId
});
const request = await storage.createMediaOutletRequest(requestData);
res.status(201).json(request);
} catch (error) {
console.error("Error creating request:", error);
res.status(500).json({ message: "Failed to create request" });
}
});
app.patch('/api/media-outlet-requests/:id', isAuthenticated, async (req: any, res) => {
try {
const userId = req.user.claims?.sub || req.user.id;
const user = await storage.getUser(userId);
if (!user || user.role !== 'superadmin') {
return res.status(403).json({ message: "Insufficient permissions" });
}
const { status } = req.body;
const request = await storage.updateMediaOutletRequestStatus(req.params.id, status, userId);
res.json(request);
} catch (error) {
console.error("Error updating request:", error);
res.status(500).json({ message: "Failed to update request" });
}
});
// Comment routes
app.get('/api/articles/:articleId/comments', async (req, res) => {
try {
const comments = await storage.getCommentsByArticle(req.params.articleId);
res.json(comments);
} catch (error) {
console.error("Error fetching comments:", error);
res.status(500).json({ message: "Failed to fetch comments" });
}
});
app.post('/api/articles/:articleId/comments', isAuthenticated, async (req: any, res) => {
try {
const userId = req.user.claims?.sub || req.user.id;
const commentData = insertCommentSchema.parse({
...req.body,
articleId: req.params.articleId,
authorId: userId
});
const comment = await storage.createComment(commentData);
res.status(201).json(comment);
} catch (error) {
console.error("Error creating comment:", error);
res.status(500).json({ message: "Failed to create comment" });
}
});
// Analytics routes
app.get('/api/analytics', isAuthenticated, async (req: any, res) => {
try {
const userId = req.user.claims?.sub || req.user.id;
const user = await storage.getUser(userId);
if (!user || (user.role !== 'admin' && user.role !== 'superadmin')) {
return res.status(403).json({ message: "Insufficient permissions" });
}
const analytics = await storage.getAnalytics();
res.json(analytics);
} catch (error) {
console.error("Error fetching analytics:", error);
res.status(500).json({ message: "Failed to fetch analytics" });
}
});
// Search routes
app.get('/api/search', async (req, res) => {
try {
const { q } = req.query;
if (!q || typeof q !== 'string' || q.trim().length === 0) {
return res.json({ outlets: [], articles: [] });
}
const results = await storage.search(q.trim());
res.json(results);
} catch (error) {
console.error("Error performing search:", error);
res.status(500).json({ message: "Failed to perform search" });
}
});
// Community routes
app.get('/api/media-outlets/:slug/community', async (req, res) => {
try {
const outlet = await storage.getMediaOutletBySlug(req.params.slug);
if (!outlet) {
return res.status(404).json({ message: "Media outlet not found" });
}
const sort = req.query.sort as string || 'latest';
const posts = await storage.getCommunityPostsByOutlet(outlet.id, sort);
res.json(posts);
} catch (error) {
console.error("Error fetching community posts:", error);
res.status(500).json({ message: "Failed to fetch community posts" });
}
});
app.get('/api/community/posts/:id', async (req, res) => {
try {
const post = await storage.getCommunityPostById(req.params.id);
if (!post) {
return res.status(404).json({ message: "Post not found" });
}
// Increment view count
await storage.incrementPostViews(req.params.id);
res.json(post);
} catch (error) {
console.error("Error fetching community post:", error);
res.status(500).json({ message: "Failed to fetch community post" });
}
});
app.post('/api/media-outlets/:slug/community', isAuthenticated, async (req: any, res) => {
try {
const outlet = await storage.getMediaOutletBySlug(req.params.slug);
if (!outlet) {
return res.status(404).json({ message: "Media outlet not found" });
}
const userId = req.user.claims?.sub || req.user.id;
const postData = insertCommunityPostSchema.parse({
...req.body,
mediaOutletId: outlet.id,
authorId: userId
});
const post = await storage.createCommunityPost(postData);
res.status(201).json(post);
} catch (error) {
console.error("Error creating community post:", error);
res.status(500).json({ message: "Failed to create community post" });
}
});
app.patch('/api/community/posts/:id', isAuthenticated, async (req: any, res) => {
try {
const userId = req.user.claims?.sub || req.user.id;
const post = await storage.getCommunityPostById(req.params.id);
if (!post) {
return res.status(404).json({ message: "Post not found" });
}
if (post.authorId !== userId) {
return res.status(403).json({ message: "Not authorized to edit this post" });
}
const updated = await storage.updateCommunityPost(req.params.id, req.body);
res.json(updated);
} catch (error) {
console.error("Error updating community post:", error);
res.status(500).json({ message: "Failed to update community post" });
}
});
app.delete('/api/community/posts/:id', isAuthenticated, async (req: any, res) => {
try {
const userId = req.user.claims?.sub || req.user.id;
const user = await storage.getUser(userId);
const post = await storage.getCommunityPostById(req.params.id);
if (!post) {
return res.status(404).json({ message: "Post not found" });
}
if (post.authorId !== userId && user?.role !== 'admin' && user?.role !== 'superadmin') {
return res.status(403).json({ message: "Not authorized to delete this post" });
}
await storage.deleteCommunityPost(req.params.id);
res.status(204).send();
} catch (error) {
console.error("Error deleting community post:", error);
res.status(500).json({ message: "Failed to delete community post" });
}
});
app.post('/api/community/posts/:id/like', isAuthenticated, async (req: any, res) => {
try {
const post = await storage.getCommunityPostById(req.params.id);
if (!post) {
return res.status(404).json({ message: "Post not found" });
}
await storage.incrementPostLikes(req.params.id);
const updated = await storage.getCommunityPostById(req.params.id);
res.json(updated);
} catch (error) {
console.error("Error liking post:", error);
res.status(500).json({ message: "Failed to like post" });
}
});
app.get('/api/community/posts/:id/replies', async (req, res) => {
try {
const replies = await storage.getRepliesByPost(req.params.id);
res.json(replies);
} catch (error) {
console.error("Error fetching replies:", error);
res.status(500).json({ message: "Failed to fetch replies" });
}
});
app.post('/api/community/posts/:id/replies', isAuthenticated, async (req: any, res) => {
try {
const post = await storage.getCommunityPostById(req.params.id);
if (!post) {
return res.status(404).json({ message: "Post not found" });
}
const userId = req.user.claims?.sub || req.user.id;
const replyData = insertCommunityReplySchema.parse({
...req.body,
postId: req.params.id,
authorId: userId
});
const reply = await storage.createCommunityReply(replyData);
res.status(201).json(reply);
} catch (error) {
console.error("Error creating reply:", error);
res.status(500).json({ message: "Failed to create reply" });
}
});
const httpServer = createServer(app);
return httpServer;
}