tercul-frontend/server/routes.ts
mukimovd 024e5d0ef5 Introduce the core functionality and basic structure of the platform
Sets up the project with initial files, components, routes, and UI elements.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: cbacfb18-842a-4116-a907-18c0105ad8ec
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/39b5c689-6e8a-4d5a-9792-69cc81a56534/affc56b0-365e-4ece-9cba-9e70bbbf0893.jpg
2025-05-01 03:05:33 +00:00

507 lines
17 KiB
TypeScript

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<Server> {
// 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;
const posts = await storage.getBlogPosts(limit, offset);
res.json(posts);
});
app.get("/api/blog/:slug", async (req: Request, res: Response) => {
const post = await storage.getBlogPostBySlug(req.params.slug);
if (!post) {
return res.status(404).json({ message: "Blog post not found" });
}
res.json(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;
}