import type { Express, Request, Response } from "express"; import { createServer, type Server } from "http"; import { storage } from "./storage"; import { z } from "zod"; import { insertUserSchema, insertAuthorSchema, insertWorkSchema, insertTranslationSchema, insertTagSchema, insertBookmarkSchema, insertCommentSchema, insertLikeSchema, insertCollectionSchema, insertCollectionItemSchema, insertTimelineEventSchema, insertBlogPostSchema, insertReadingProgressSchema } from "@shared/schema"; export async function registerRoutes(app: Express): Promise { // Users routes app.get("/api/users/:id", async (req: Request, res: Response) => { const user = await storage.getUser(Number(req.params.id)); if (!user) { return res.status(404).json({ message: "User not found" }); } // Remove sensitive data const { password, ...safeUser } = user; res.json(safeUser); }); app.post("/api/users", async (req: Request, res: Response) => { try { const userData = insertUserSchema.parse(req.body); const user = await storage.createUser(userData); const { password, ...safeUser } = user; res.status(201).json(safeUser); } catch (error) { if (error instanceof z.ZodError) { return res.status(400).json({ message: error.errors }); } res.status(500).json({ message: "Failed to create user" }); } }); // Authors routes app.get("/api/authors", async (req: Request, res: Response) => { const limit = req.query.limit ? Number(req.query.limit) : undefined; const offset = req.query.offset ? Number(req.query.offset) : undefined; const authors = await storage.getAuthors(limit, offset); res.json(authors); }); app.get("/api/authors/:slug", async (req: Request, res: Response) => { const author = await storage.getAuthorBySlug(req.params.slug); if (!author) { return res.status(404).json({ message: "Author not found" }); } res.json(author); }); app.post("/api/authors", async (req: Request, res: Response) => { try { const authorData = insertAuthorSchema.parse(req.body); const author = await storage.createAuthor(authorData); res.status(201).json(author); } catch (error) { if (error instanceof z.ZodError) { return res.status(400).json({ message: error.errors }); } res.status(500).json({ message: "Failed to create author" }); } }); // Works routes app.get("/api/works", async (req: Request, res: Response) => { const limit = req.query.limit ? Number(req.query.limit) : undefined; const offset = req.query.offset ? Number(req.query.offset) : undefined; const works = await storage.getWorks(limit, offset); res.json(works); }); app.get("/api/works/:slug", async (req: Request, res: Response) => { const work = await storage.getWorkBySlug(req.params.slug); if (!work) { return res.status(404).json({ message: "Work not found" }); } // Get the author const author = await storage.getAuthorById(work.authorId); // Get the tags const tags = await storage.getWorkTags(work.id); res.json({ ...work, author, tags }); }); app.get("/api/authors/:slug/works", async (req: Request, res: Response) => { const author = await storage.getAuthorBySlug(req.params.slug); if (!author) { return res.status(404).json({ message: "Author not found" }); } const works = await storage.getWorksByAuthorId(author.id); // Get tags for each work const worksWithTags = await Promise.all(works.map(async (work) => { const tags = await storage.getWorkTags(work.id); return { ...work, tags }; })); res.json(worksWithTags); }); app.post("/api/works", async (req: Request, res: Response) => { try { const workData = insertWorkSchema.parse(req.body); const work = await storage.createWork(workData); res.status(201).json(work); } catch (error) { if (error instanceof z.ZodError) { return res.status(400).json({ message: error.errors }); } res.status(500).json({ message: "Failed to create work" }); } }); // Translations routes app.get("/api/works/:slug/translations", async (req: Request, res: Response) => { const work = await storage.getWorkBySlug(req.params.slug); if (!work) { return res.status(404).json({ message: "Work not found" }); } const translations = await storage.getTranslations(work.id); res.json(translations); }); app.get("/api/translations/:id", async (req: Request, res: Response) => { const translation = await storage.getTranslationById(Number(req.params.id)); if (!translation) { return res.status(404).json({ message: "Translation not found" }); } res.json(translation); }); app.post("/api/translations", async (req: Request, res: Response) => { try { const translationData = insertTranslationSchema.parse(req.body); const translation = await storage.createTranslation(translationData); res.status(201).json(translation); } catch (error) { if (error instanceof z.ZodError) { return res.status(400).json({ message: error.errors }); } res.status(500).json({ message: "Failed to create translation" }); } }); // Tags routes app.get("/api/tags", async (req: Request, res: Response) => { const tags = await storage.getTags(); res.json(tags); }); app.get("/api/tags/type/:type", async (req: Request, res: Response) => { const tags = await storage.getTagsByType(req.params.type); res.json(tags); }); app.post("/api/tags", async (req: Request, res: Response) => { try { const tagData = insertTagSchema.parse(req.body); const tag = await storage.createTag(tagData); res.status(201).json(tag); } catch (error) { if (error instanceof z.ZodError) { return res.status(400).json({ message: error.errors }); } res.status(500).json({ message: "Failed to create tag" }); } }); app.post("/api/works/:workId/tags/:tagId", async (req: Request, res: Response) => { const workId = Number(req.params.workId); const tagId = Number(req.params.tagId); const work = await storage.getWorkById(workId); if (!work) { return res.status(404).json({ message: "Work not found" }); } const tag = await storage.tags; if (!tag) { return res.status(404).json({ message: "Tag not found" }); } await storage.addTagToWork(workId, tagId); res.status(201).json({ workId, tagId }); }); // Bookmarks routes app.get("/api/users/:userId/bookmarks", async (req: Request, res: Response) => { const userId = Number(req.params.userId); const bookmarks = await storage.getBookmarksByUserId(userId); // Get work details for each bookmark const bookmarksWithWorks = await Promise.all(bookmarks.map(async (bookmark) => { const work = await storage.getWorkById(bookmark.workId); return { ...bookmark, work }; })); res.json(bookmarksWithWorks); }); app.post("/api/bookmarks", async (req: Request, res: Response) => { try { const bookmarkData = insertBookmarkSchema.parse(req.body); // Check if bookmark already exists const existing = await storage.getBookmarkByUserAndWork( bookmarkData.userId, bookmarkData.workId ); if (existing) { return res.status(400).json({ message: "Bookmark already exists" }); } const bookmark = await storage.createBookmark(bookmarkData); res.status(201).json(bookmark); } catch (error) { if (error instanceof z.ZodError) { return res.status(400).json({ message: error.errors }); } res.status(500).json({ message: "Failed to create bookmark" }); } }); app.delete("/api/bookmarks/:id", async (req: Request, res: Response) => { const id = Number(req.params.id); await storage.deleteBookmark(id); res.status(204).send(); }); // Comments routes app.get("/api/works/:slug/comments", async (req: Request, res: Response) => { const work = await storage.getWorkBySlug(req.params.slug); if (!work) { return res.status(404).json({ message: "Work not found" }); } const comments = await storage.getCommentsByWorkId(work.id); // Get user details for each comment const commentsWithUsers = await Promise.all(comments.map(async (comment) => { const user = await storage.getUser(comment.userId); if (!user) return comment; const { password, ...safeUser } = user; return { ...comment, user: safeUser }; })); res.json(commentsWithUsers); }); app.post("/api/comments", async (req: Request, res: Response) => { try { const commentData = insertCommentSchema.parse(req.body); const comment = await storage.createComment(commentData); // Get user details const user = await storage.getUser(comment.userId); if (user) { const { password, ...safeUser } = user; res.status(201).json({ ...comment, user: safeUser }); } else { res.status(201).json(comment); } } catch (error) { if (error instanceof z.ZodError) { return res.status(400).json({ message: error.errors }); } res.status(500).json({ message: "Failed to create comment" }); } }); // Likes routes app.post("/api/likes", async (req: Request, res: Response) => { try { const likeData = insertLikeSchema.parse(req.body); const like = await storage.createLike(likeData); res.status(201).json(like); } catch (error) { if (error instanceof z.ZodError) { return res.status(400).json({ message: error.errors }); } res.status(500).json({ message: "Failed to create like" }); } }); app.delete("/api/likes/:id", async (req: Request, res: Response) => { const id = Number(req.params.id); await storage.deleteLike(id); res.status(204).send(); }); app.get("/api/likes/:entityType/:entityId", async (req: Request, res: Response) => { const entityType = req.params.entityType; const entityId = Number(req.params.entityId); const likes = await storage.getLikesByEntity(entityType, entityId); res.json(likes); }); // Collections routes app.get("/api/collections", async (req: Request, res: Response) => { const limit = req.query.limit ? Number(req.query.limit) : undefined; const offset = req.query.offset ? Number(req.query.offset) : undefined; const collections = await storage.getCollections(limit, offset); res.json(collections); }); app.get("/api/collections/:slug", async (req: Request, res: Response) => { const collection = await storage.getCollectionBySlug(req.params.slug); if (!collection) { return res.status(404).json({ message: "Collection not found" }); } // Get collection items const items = await storage.getCollectionItems(collection.id); // Get work details for each item const itemsWithWorks = await Promise.all(items.map(async (item) => { const work = await storage.getWorkById(item.workId); return { ...item, work }; })); res.json({ ...collection, items: itemsWithWorks }); }); app.get("/api/users/:userId/collections", async (req: Request, res: Response) => { const userId = Number(req.params.userId); const collections = await storage.getCollectionsByUserId(userId); res.json(collections); }); app.post("/api/collections", async (req: Request, res: Response) => { try { const collectionData = insertCollectionSchema.parse(req.body); const collection = await storage.createCollection(collectionData); res.status(201).json(collection); } catch (error) { if (error instanceof z.ZodError) { return res.status(400).json({ message: error.errors }); } res.status(500).json({ message: "Failed to create collection" }); } }); app.post("/api/collections/:collectionId/works", async (req: Request, res: Response) => { try { const collectionId = Number(req.params.collectionId); const collection = await storage.getCollectionById(collectionId); if (!collection) { return res.status(404).json({ message: "Collection not found" }); } const itemData = { ...insertCollectionItemSchema.parse(req.body), collectionId }; const item = await storage.addWorkToCollection(itemData); res.status(201).json(item); } catch (error) { if (error instanceof z.ZodError) { return res.status(400).json({ message: error.errors }); } res.status(500).json({ message: "Failed to add work to collection" }); } }); // Timeline events routes app.get("/api/authors/:slug/timeline", async (req: Request, res: Response) => { const author = await storage.getAuthorBySlug(req.params.slug); if (!author) { return res.status(404).json({ message: "Author not found" }); } const events = await storage.getTimelineEventsByAuthorId(author.id); res.json(events); }); app.post("/api/timeline-events", async (req: Request, res: Response) => { try { const eventData = insertTimelineEventSchema.parse(req.body); const event = await storage.createTimelineEvent(eventData); res.status(201).json(event); } catch (error) { if (error instanceof z.ZodError) { return res.status(400).json({ message: error.errors }); } res.status(500).json({ message: "Failed to create timeline event" }); } }); // Blog posts routes app.get("/api/blog", async (req: Request, res: Response) => { const limit = req.query.limit ? Number(req.query.limit) : undefined; const offset = req.query.offset ? Number(req.query.offset) : undefined; try { // Get basic blog posts const posts = await storage.getBlogPosts(limit, offset); // Enhance each post with author details, comment count, and like count const enhancedPosts = await Promise.all(posts.map(async (post) => { const author = post.authorId ? await storage.getUser(post.authorId) : undefined; const authorInfo = author ? { id: author.id, displayName: author.displayName, avatar: author.avatar } : undefined; // Get comment count const comments = await storage.getCommentsByEntityId('blogPost', post.id); const commentCount = comments.length; // Get like count const likes = await storage.getLikesByEntity('blogPost', post.id); const likeCount = likes.length; // Get tags for the post const tags = await storage.getBlogPostTags(post.id); return { ...post, author: authorInfo, commentCount, likeCount, tags }; })); res.json(enhancedPosts); } catch (error) { console.error("Error fetching blog posts:", error); res.status(500).json({ message: "Failed to fetch blog posts" }); } }); app.get("/api/blog/:slug", async (req: Request, res: Response) => { try { const post = await storage.getBlogPostBySlug(req.params.slug); if (!post) { return res.status(404).json({ message: "Blog post not found" }); } // Get author details const author = post.authorId ? await storage.getUser(post.authorId) : undefined; // Remove password from author if it exists const authorInfo = author ? { ...author, password: undefined } : undefined; // Get comments for the post const comments = await storage.getCommentsByEntityId('blogPost', post.id); // Get enhanced comments with user info const enhancedComments = await Promise.all(comments.map(async (comment) => { const user = await storage.getUser(comment.userId); return { ...comment, user: user ? { id: user.id, displayName: user.displayName, avatar: user.avatar, role: user.role } : undefined }; })); // Get like count const likes = await storage.getLikesByEntity('blogPost', post.id); const likeCount = likes.length; // Get tags for the post const tags = await storage.getBlogPostTags(post.id); res.json({ ...post, author: authorInfo, comments: enhancedComments, commentCount: comments.length, likeCount, tags }); } catch (error) { console.error("Error fetching blog post:", error); res.status(500).json({ message: "Failed to fetch blog post" }); } }); app.post("/api/blog", async (req: Request, res: Response) => { try { const postData = insertBlogPostSchema.parse(req.body); const post = await storage.createBlogPost(postData); res.status(201).json(post); } catch (error) { if (error instanceof z.ZodError) { return res.status(400).json({ message: error.errors }); } res.status(500).json({ message: "Failed to create blog post" }); } }); // Reading progress routes app.get("/api/reading-progress/:userId/:workId", async (req: Request, res: Response) => { const userId = Number(req.params.userId); const workId = Number(req.params.workId); const translationId = req.query.translationId ? Number(req.query.translationId) : undefined; const progress = await storage.getReadingProgress(userId, workId, translationId); if (!progress) { return res.status(404).json({ message: "Reading progress not found" }); } res.json(progress); }); app.post("/api/reading-progress", async (req: Request, res: Response) => { try { const progressData = insertReadingProgressSchema.parse(req.body); const progress = await storage.updateReadingProgress(progressData); res.status(201).json(progress); } catch (error) { if (error instanceof z.ZodError) { return res.status(400).json({ message: error.errors }); } res.status(500).json({ message: "Failed to update reading progress" }); } }); // Search routes app.get("/api/search", async (req: Request, res: Response) => { const query = req.query.q as string; if (!query) { return res.status(400).json({ message: "Search query is required" }); } const workLimit = req.query.workLimit ? Number(req.query.workLimit) : 5; const authorLimit = req.query.authorLimit ? Number(req.query.authorLimit) : 3; const works = await storage.searchWorks(query, workLimit); const authors = await storage.searchAuthors(query, authorLimit); res.json({ works, authors }); }); // Filter routes app.get("/api/filter", async (req: Request, res: Response) => { const language = req.query.language as string | undefined; const type = req.query.type as string | undefined; const yearStart = req.query.yearStart ? Number(req.query.yearStart) : undefined; const yearEnd = req.query.yearEnd ? Number(req.query.yearEnd) : undefined; let tags: number[] = []; if (req.query.tags) { tags = (req.query.tags as string).split(',').map(Number); } const works = await storage.filterWorks({ language, type, yearStart, yearEnd, tags: tags.length > 0 ? tags : undefined }); res.json(works); }); const httpServer = createServer(app); return httpServer; }