mirror of
https://github.com/SamyRai/tercul-frontend.git
synced 2025-12-27 04:51:34 +00:00
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
507 lines
17 KiB
TypeScript
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;
|
|
}
|