mirror of
https://github.com/SamyRai/tercul-frontend.git
synced 2025-12-27 04:51:34 +00:00
Implements blog post commenting and tagging functionality, and retrieves user information for displaying comments. 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/ecd8ef89-4efc-4907-bb4c-b74a23ed7e4a.jpg
795 lines
28 KiB
TypeScript
795 lines
28 KiB
TypeScript
import {
|
||
users, User, InsertUser,
|
||
authors, Author, InsertAuthor,
|
||
works, Work, InsertWork,
|
||
translations, Translation, InsertTranslation,
|
||
tags, Tag, InsertTag,
|
||
workTags,
|
||
bookmarks, Bookmark, InsertBookmark,
|
||
comments, Comment, InsertComment,
|
||
likes, Like, InsertLike,
|
||
collections, Collection, InsertCollection,
|
||
collectionItems, CollectionItem, InsertCollectionItem,
|
||
timelineEvents, TimelineEvent, InsertTimelineEvent,
|
||
blogPosts, BlogPost, InsertBlogPost,
|
||
readingProgress, ReadingProgress, InsertReadingProgress
|
||
} from "@shared/schema";
|
||
|
||
export interface IStorage {
|
||
// User methods
|
||
getUser(id: number): Promise<User | undefined>;
|
||
getUserByUsername(username: string): Promise<User | undefined>;
|
||
createUser(user: InsertUser): Promise<User>;
|
||
|
||
// Author methods
|
||
getAuthors(limit?: number, offset?: number): Promise<Author[]>;
|
||
getAuthorById(id: number): Promise<Author | undefined>;
|
||
getAuthorBySlug(slug: string): Promise<Author | undefined>;
|
||
createAuthor(author: InsertAuthor): Promise<Author>;
|
||
|
||
// Work methods
|
||
getWorks(limit?: number, offset?: number): Promise<Work[]>;
|
||
getWorkById(id: number): Promise<Work | undefined>;
|
||
getWorkBySlug(slug: string): Promise<Work | undefined>;
|
||
getWorksByAuthorId(authorId: number): Promise<Work[]>;
|
||
getWorksByTags(tagIds: number[]): Promise<Work[]>;
|
||
createWork(work: InsertWork): Promise<Work>;
|
||
|
||
// Translation methods
|
||
getTranslations(workId: number): Promise<Translation[]>;
|
||
getTranslationById(id: number): Promise<Translation | undefined>;
|
||
createTranslation(translation: InsertTranslation): Promise<Translation>;
|
||
|
||
// Tag methods
|
||
getTags(): Promise<Tag[]>;
|
||
getTagsByType(type: string): Promise<Tag[]>;
|
||
createTag(tag: InsertTag): Promise<Tag>;
|
||
addTagToWork(workId: number, tagId: number): Promise<void>;
|
||
getWorkTags(workId: number): Promise<Tag[]>;
|
||
getBlogPostTags(postId: number): Promise<Tag[]>;
|
||
|
||
// Bookmark methods
|
||
getBookmarksByUserId(userId: number): Promise<Bookmark[]>;
|
||
getBookmarkByUserAndWork(userId: number, workId: number): Promise<Bookmark | undefined>;
|
||
createBookmark(bookmark: InsertBookmark): Promise<Bookmark>;
|
||
deleteBookmark(id: number): Promise<void>;
|
||
|
||
// Comment methods
|
||
getCommentsByWorkId(workId: number): Promise<Comment[]>;
|
||
getCommentsByTranslationId(translationId: number): Promise<Comment[]>;
|
||
getCommentsByUserId(userId: number): Promise<Comment[]>;
|
||
getCommentsByEntityId(entityType: string, entityId: number): Promise<Comment[]>;
|
||
createComment(comment: InsertComment): Promise<Comment>;
|
||
|
||
// Like methods
|
||
createLike(like: InsertLike): Promise<Like>;
|
||
deleteLike(id: number): Promise<void>;
|
||
getLikesByEntity(entityType: string, entityId: number): Promise<Like[]>;
|
||
getLikesByUserId(userId: number): Promise<Like[]>;
|
||
|
||
// Collection methods
|
||
getCollections(limit?: number, offset?: number): Promise<Collection[]>;
|
||
getCollectionsByUserId(userId: number): Promise<Collection[]>;
|
||
getCollectionById(id: number): Promise<Collection | undefined>;
|
||
getCollectionBySlug(slug: string): Promise<Collection | undefined>;
|
||
createCollection(collection: InsertCollection): Promise<Collection>;
|
||
addWorkToCollection(collectionItem: InsertCollectionItem): Promise<CollectionItem>;
|
||
getCollectionItems(collectionId: number): Promise<CollectionItem[]>;
|
||
|
||
// Timeline events
|
||
getTimelineEventsByAuthorId(authorId: number): Promise<TimelineEvent[]>;
|
||
createTimelineEvent(event: InsertTimelineEvent): Promise<TimelineEvent>;
|
||
|
||
// Blog posts
|
||
getBlogPosts(limit?: number, offset?: number): Promise<BlogPost[]>;
|
||
getBlogPostById(id: number): Promise<BlogPost | undefined>;
|
||
getBlogPostBySlug(slug: string): Promise<BlogPost | undefined>;
|
||
createBlogPost(post: InsertBlogPost): Promise<BlogPost>;
|
||
|
||
// Reading progress
|
||
getReadingProgress(userId: number, workId: number, translationId?: number): Promise<ReadingProgress | undefined>;
|
||
updateReadingProgress(progress: InsertReadingProgress): Promise<ReadingProgress>;
|
||
|
||
// Search and filter methods
|
||
searchWorks(query: string, limit?: number): Promise<Work[]>;
|
||
searchAuthors(query: string, limit?: number): Promise<Author[]>;
|
||
filterWorks(params: { language?: string, type?: string, yearStart?: number, yearEnd?: number, tags?: number[] }): Promise<Work[]>;
|
||
}
|
||
|
||
export class MemStorage implements IStorage {
|
||
private users: Map<number, User>;
|
||
private authors: Map<number, Author>;
|
||
private works: Map<number, Work>;
|
||
private translations: Map<number, Translation>;
|
||
private tags: Map<number, Tag>;
|
||
private workTagsRelations: Map<number, Set<number>>;
|
||
private blogPostTagsRelations: Map<number, Set<number>>;
|
||
private bookmarks: Map<number, Bookmark>;
|
||
private comments: Map<number, Comment>;
|
||
private likes: Map<number, Like>;
|
||
private collections: Map<number, Collection>;
|
||
private collectionItems: Map<number, CollectionItem>;
|
||
private timelineEvents: Map<number, TimelineEvent>;
|
||
private blogPosts: Map<number, BlogPost>;
|
||
private readingProgresses: Map<string, ReadingProgress>;
|
||
|
||
private userId: number = 1;
|
||
private authorId: number = 1;
|
||
private workId: number = 1;
|
||
private translationId: number = 1;
|
||
private tagId: number = 1;
|
||
private bookmarkId: number = 1;
|
||
private commentId: number = 1;
|
||
private likeId: number = 1;
|
||
private collectionId: number = 1;
|
||
private collectionItemId: number = 1;
|
||
private timelineEventId: number = 1;
|
||
private blogPostId: number = 1;
|
||
private readingProgressId: number = 1;
|
||
|
||
constructor() {
|
||
this.users = new Map();
|
||
this.authors = new Map();
|
||
this.works = new Map();
|
||
this.translations = new Map();
|
||
this.tags = new Map();
|
||
this.workTagsRelations = new Map();
|
||
this.blogPostTagsRelations = new Map();
|
||
this.bookmarks = new Map();
|
||
this.comments = new Map();
|
||
this.likes = new Map();
|
||
this.collections = new Map();
|
||
this.collectionItems = new Map();
|
||
this.timelineEvents = new Map();
|
||
this.blogPosts = new Map();
|
||
this.readingProgresses = new Map();
|
||
|
||
// Initialize with some data
|
||
this.initializeData();
|
||
}
|
||
|
||
private initializeData() {
|
||
// Create some sample users
|
||
const user1 = this.createUser({
|
||
username: "admin",
|
||
password: "password",
|
||
email: "admin@example.com",
|
||
displayName: "Administrator"
|
||
});
|
||
|
||
// Create sample authors
|
||
const pushkin = this.createAuthor({
|
||
name: "Alexander Pushkin",
|
||
slug: "alexander-pushkin",
|
||
birthYear: 1799,
|
||
deathYear: 1837,
|
||
country: "Russia",
|
||
biography: "Russian poet, playwright, and novelist of the Romantic era. He is considered by many to be the greatest Russian poet and the founder of modern Russian literature.",
|
||
portrait: "https://images.unsplash.com/photo-1541688329375-fd29c1b13276?ixlib=rb-1.2.1&auto=format&fit=crop&w=256&h=256&q=80",
|
||
createdBy: user1.id
|
||
});
|
||
|
||
// Create sample tags
|
||
const lyricTag = this.createTag({ name: "Lyric", type: "genre" });
|
||
const loveTag = this.createTag({ name: "Love", type: "theme" });
|
||
const poetryTag = this.createTag({ name: "Poetry", type: "genre" });
|
||
const romanticismTag = this.createTag({ name: "Romanticism", type: "period" });
|
||
const novelTag = this.createTag({ name: "Novel", type: "genre" });
|
||
const verseTag = this.createTag({ name: "Verse", type: "form" });
|
||
const historyTag = this.createTag({ name: "Historical", type: "theme" });
|
||
const gotihicTag = this.createTag({ name: "Gothic", type: "genre" });
|
||
const fictionTag = this.createTag({ name: "Fiction", type: "genre" });
|
||
|
||
// Create sample works
|
||
const lovedYouWork = this.createWork({
|
||
title: "I Loved You",
|
||
slug: "i-loved-you",
|
||
authorId: pushkin.id,
|
||
year: 1829,
|
||
type: "poem",
|
||
language: "Russian",
|
||
content: "Я вас любил: любовь еще, быть может,\nВ душе моей угасла не совсем;\nНо пусть она вас больше не тревожит;\nЯ не хочу печалить вас ничем.\nЯ вас любил безмолвно, безнадежно,\nТо робостью, то ревностью томим;\nЯ вас любил так искренно, так нежно,\nКак дай вам Бог любимой быть другим.",
|
||
description: "A short lyrical poem expressing unselfish love and one of Pushkin's most famous works. The poem describes the narrator's admiration for a beloved from whom he has parted.",
|
||
createdBy: user1.id
|
||
});
|
||
|
||
this.addTagToWork(lovedYouWork.id, lyricTag.id);
|
||
this.addTagToWork(lovedYouWork.id, loveTag.id);
|
||
this.addTagToWork(lovedYouWork.id, poetryTag.id);
|
||
|
||
const eugeneOnegin = this.createWork({
|
||
title: "Eugene Onegin",
|
||
slug: "eugene-onegin",
|
||
authorId: pushkin.id,
|
||
year: 1833,
|
||
type: "poem",
|
||
language: "Russian",
|
||
content: "Sample content for Eugene Onegin...",
|
||
description: "A classic of Russian literature, this novel in verse follows the story of Eugene Onegin, a bored aristocrat who rejects the love of Tatyana and later comes to regret it.",
|
||
createdBy: user1.id
|
||
});
|
||
|
||
this.addTagToWork(eugeneOnegin.id, romanticismTag.id);
|
||
this.addTagToWork(eugeneOnegin.id, novelTag.id);
|
||
this.addTagToWork(eugeneOnegin.id, verseTag.id);
|
||
|
||
const bronzeHorseman = this.createWork({
|
||
title: "The Bronze Horseman",
|
||
slug: "the-bronze-horseman",
|
||
authorId: pushkin.id,
|
||
year: 1833,
|
||
type: "poem",
|
||
language: "Russian",
|
||
content: "Sample content for The Bronze Horseman...",
|
||
description: "A narrative poem depicting the equestrian statue of Peter the Great in Saint Petersburg and the great flood of 1824, widely considered one of Pushkin's masterpieces.",
|
||
createdBy: user1.id
|
||
});
|
||
|
||
this.addTagToWork(bronzeHorseman.id, poetryTag.id);
|
||
this.addTagToWork(bronzeHorseman.id, historyTag.id);
|
||
|
||
const queenOfSpades = this.createWork({
|
||
title: "The Queen of Spades",
|
||
slug: "the-queen-of-spades",
|
||
authorId: pushkin.id,
|
||
year: 1834,
|
||
type: "story",
|
||
language: "Russian",
|
||
content: "Sample content for The Queen of Spades...",
|
||
description: "A short story about human avarice, telling the tale of a young officer seeking the secret of an elderly countess's success at faro, a card game.",
|
||
createdBy: user1.id
|
||
});
|
||
|
||
this.addTagToWork(queenOfSpades.id, fictionTag.id);
|
||
this.addTagToWork(queenOfSpades.id, gotihicTag.id);
|
||
|
||
// Create sample translations
|
||
this.createTranslation({
|
||
workId: lovedYouWork.id,
|
||
title: "I Loved You",
|
||
language: "English",
|
||
content: "I loved you, and I probably still do,\nAnd for a while the feeling may remain...\nBut let my love no longer trouble you,\nI do not wish to cause you any pain.\nI loved you silently, without hope,\nAt times too jealous and at times too shy.\nI loved you truly, with a tender scope,\nAs may God grant you to be loved now by.",
|
||
translatorId: user1.id,
|
||
year: 2018,
|
||
notes: "Translated by John Fenwick in 2018. This translation attempts to preserve the rhyme scheme (ABAB CDCD) and structure of the original while conveying the tender sentiment of Pushkin's lyric."
|
||
});
|
||
|
||
// Create timeline events
|
||
this.createTimelineEvent({
|
||
authorId: pushkin.id,
|
||
year: 1799,
|
||
title: "Birth",
|
||
description: "Born in Moscow to Sergei Lvovich and Nadezhda Ossipovna Pushkin"
|
||
});
|
||
|
||
this.createTimelineEvent({
|
||
authorId: pushkin.id,
|
||
year: 1811,
|
||
title: "Education",
|
||
description: "Enrolled at the Imperial Lyceum at Tsarskoye Selo"
|
||
});
|
||
|
||
this.createTimelineEvent({
|
||
authorId: pushkin.id,
|
||
year: 1820,
|
||
title: "First major work",
|
||
description: "Published his first long poem 'Ruslan and Ludmila'"
|
||
});
|
||
|
||
this.createTimelineEvent({
|
||
authorId: pushkin.id,
|
||
year: 1837,
|
||
title: "Death",
|
||
description: "Died in St. Petersburg from wounds received in a duel with Georges d'Anthès"
|
||
});
|
||
|
||
// Create sample blog post
|
||
const blogPost = this.createBlogPost({
|
||
title: "Exploring Russian Poetry in the Digital Age",
|
||
slug: "exploring-russian-poetry-digital-age",
|
||
content: `
|
||
# Exploring Russian Poetry in the Digital Age
|
||
|
||
The rich tradition of Russian poetry, from the romantic verses of Alexander Pushkin to the Soviet-era works of Anna Akhmatova, continues to captivate readers worldwide. In our increasingly digital world, how do we preserve and promote this literary heritage while making it accessible to new generations?
|
||
|
||
## The Digital Transformation of Literature
|
||
|
||
Digital platforms offer unprecedented opportunities for exploring Russian poetry:
|
||
|
||
- Side-by-side translations allow readers to compare different interpretations
|
||
- Interactive annotations provide historical and cultural context
|
||
- Audio recordings capture the rhythm and musicality of the original language
|
||
|
||
## Challenges and Opportunities
|
||
|
||
While digitization opens new doors, it also presents challenges. How do we balance modernization with respect for tradition? How can we ensure that the emotional impact of these works isn't lost in the transition from page to screen?
|
||
|
||
As we continue to develop the Tercul platform, we're committed to addressing these questions, creating an immersive experience that honors the depth and beauty of Russian poetic traditions while embracing the possibilities of digital innovation.
|
||
`,
|
||
authorId: user1.id,
|
||
excerpt: "Exploring how digital platforms can help preserve and promote the rich tradition of Russian poetry in the modern age.",
|
||
publishedAt: new Date()
|
||
});
|
||
|
||
// Add tags to the blog post
|
||
this.addTagToBlogPost(blogPost.id, poetryTag.id);
|
||
this.addTagToBlogPost(blogPost.id, romanticismTag.id);
|
||
|
||
// Add a comment to the blog post
|
||
this.createComment({
|
||
content: "Fascinating exploration of how technology can preserve literary traditions!",
|
||
userId: user1.id,
|
||
workId: 0, // Placeholder for blog posts
|
||
parentId: blogPost.id
|
||
});
|
||
}
|
||
|
||
// User methods
|
||
async getUser(id: number): Promise<User | undefined> {
|
||
return this.users.get(id);
|
||
}
|
||
|
||
async getUserByUsername(username: string): Promise<User | undefined> {
|
||
return Array.from(this.users.values()).find(user => user.username === username);
|
||
}
|
||
|
||
async createUser(insertUser: InsertUser): Promise<User> {
|
||
const id = this.userId++;
|
||
const now = new Date();
|
||
const user: User = {
|
||
...insertUser,
|
||
id,
|
||
role: 'user',
|
||
createdAt: now
|
||
};
|
||
this.users.set(id, user);
|
||
return user;
|
||
}
|
||
|
||
// Author methods
|
||
async getAuthors(limit: number = 100, offset: number = 0): Promise<Author[]> {
|
||
return Array.from(this.authors.values())
|
||
.sort((a, b) => a.name.localeCompare(b.name))
|
||
.slice(offset, offset + limit);
|
||
}
|
||
|
||
async getAuthorById(id: number): Promise<Author | undefined> {
|
||
return this.authors.get(id);
|
||
}
|
||
|
||
async getAuthorBySlug(slug: string): Promise<Author | undefined> {
|
||
return Array.from(this.authors.values()).find(author => author.slug === slug);
|
||
}
|
||
|
||
async createAuthor(insertAuthor: InsertAuthor): Promise<Author> {
|
||
const id = this.authorId++;
|
||
const now = new Date();
|
||
const author: Author = {
|
||
...insertAuthor,
|
||
id,
|
||
createdAt: now
|
||
};
|
||
this.authors.set(id, author);
|
||
return author;
|
||
}
|
||
|
||
// Work methods
|
||
async getWorks(limit: number = 100, offset: number = 0): Promise<Work[]> {
|
||
return Array.from(this.works.values())
|
||
.sort((a, b) => a.title.localeCompare(b.title))
|
||
.slice(offset, offset + limit);
|
||
}
|
||
|
||
async getWorkById(id: number): Promise<Work | undefined> {
|
||
return this.works.get(id);
|
||
}
|
||
|
||
async getWorkBySlug(slug: string): Promise<Work | undefined> {
|
||
return Array.from(this.works.values()).find(work => work.slug === slug);
|
||
}
|
||
|
||
async getWorksByAuthorId(authorId: number): Promise<Work[]> {
|
||
return Array.from(this.works.values()).filter(work => work.authorId === authorId);
|
||
}
|
||
|
||
async getWorksByTags(tagIds: number[]): Promise<Work[]> {
|
||
const tagSet = new Set(tagIds);
|
||
const workIds = new Set<number>();
|
||
|
||
this.workTagsRelations.forEach((tags, workId) => {
|
||
for (const tagId of tags) {
|
||
if (tagSet.has(tagId)) {
|
||
workIds.add(workId);
|
||
break;
|
||
}
|
||
}
|
||
});
|
||
|
||
return Array.from(workIds).map(id => this.works.get(id)!).filter(Boolean);
|
||
}
|
||
|
||
async createWork(insertWork: InsertWork): Promise<Work> {
|
||
const id = this.workId++;
|
||
const now = new Date();
|
||
const work: Work = {
|
||
...insertWork,
|
||
id,
|
||
createdAt: now
|
||
};
|
||
this.works.set(id, work);
|
||
return work;
|
||
}
|
||
|
||
// Translation methods
|
||
async getTranslations(workId: number): Promise<Translation[]> {
|
||
return Array.from(this.translations.values()).filter(
|
||
translation => translation.workId === workId
|
||
);
|
||
}
|
||
|
||
async getTranslationById(id: number): Promise<Translation | undefined> {
|
||
return this.translations.get(id);
|
||
}
|
||
|
||
async createTranslation(insertTranslation: InsertTranslation): Promise<Translation> {
|
||
const id = this.translationId++;
|
||
const now = new Date();
|
||
const translation: Translation = {
|
||
...insertTranslation,
|
||
id,
|
||
createdAt: now
|
||
};
|
||
this.translations.set(id, translation);
|
||
return translation;
|
||
}
|
||
|
||
// Tag methods
|
||
async getTags(): Promise<Tag[]> {
|
||
return Array.from(this.tags.values());
|
||
}
|
||
|
||
async getTagsByType(type: string): Promise<Tag[]> {
|
||
return Array.from(this.tags.values()).filter(tag => tag.type === type);
|
||
}
|
||
|
||
async createTag(insertTag: InsertTag): Promise<Tag> {
|
||
const id = this.tagId++;
|
||
const now = new Date();
|
||
const tag: Tag = {
|
||
...insertTag,
|
||
id,
|
||
createdAt: now
|
||
};
|
||
this.tags.set(id, tag);
|
||
return tag;
|
||
}
|
||
|
||
async addTagToWork(workId: number, tagId: number): Promise<void> {
|
||
if (!this.workTagsRelations.has(workId)) {
|
||
this.workTagsRelations.set(workId, new Set());
|
||
}
|
||
this.workTagsRelations.get(workId)!.add(tagId);
|
||
}
|
||
|
||
async getWorkTags(workId: number): Promise<Tag[]> {
|
||
const tagIds = this.workTagsRelations.get(workId) || new Set();
|
||
return Array.from(tagIds).map(id => this.tags.get(id)!).filter(Boolean);
|
||
}
|
||
|
||
async getBlogPostTags(postId: number): Promise<Tag[]> {
|
||
const tagIds = this.blogPostTagsRelations.get(postId) || new Set();
|
||
return Array.from(tagIds).map(id => this.tags.get(id)!).filter(Boolean);
|
||
}
|
||
|
||
async addTagToBlogPost(postId: number, tagId: number): Promise<void> {
|
||
if (!this.blogPostTagsRelations.has(postId)) {
|
||
this.blogPostTagsRelations.set(postId, new Set());
|
||
}
|
||
this.blogPostTagsRelations.get(postId)!.add(tagId);
|
||
}
|
||
|
||
// Bookmark methods
|
||
async getBookmarksByUserId(userId: number): Promise<Bookmark[]> {
|
||
return Array.from(this.bookmarks.values()).filter(
|
||
bookmark => bookmark.userId === userId
|
||
);
|
||
}
|
||
|
||
async getBookmarkByUserAndWork(userId: number, workId: number): Promise<Bookmark | undefined> {
|
||
return Array.from(this.bookmarks.values()).find(
|
||
bookmark => bookmark.userId === userId && bookmark.workId === workId
|
||
);
|
||
}
|
||
|
||
async createBookmark(insertBookmark: InsertBookmark): Promise<Bookmark> {
|
||
const id = this.bookmarkId++;
|
||
const now = new Date();
|
||
const bookmark: Bookmark = {
|
||
...insertBookmark,
|
||
id,
|
||
createdAt: now
|
||
};
|
||
this.bookmarks.set(id, bookmark);
|
||
return bookmark;
|
||
}
|
||
|
||
async deleteBookmark(id: number): Promise<void> {
|
||
this.bookmarks.delete(id);
|
||
}
|
||
|
||
// Comment methods
|
||
async getCommentsByWorkId(workId: number): Promise<Comment[]> {
|
||
return Array.from(this.comments.values()).filter(
|
||
comment => comment.workId === workId
|
||
);
|
||
}
|
||
|
||
async getCommentsByTranslationId(translationId: number): Promise<Comment[]> {
|
||
return Array.from(this.comments.values()).filter(
|
||
comment => comment.translationId === translationId
|
||
);
|
||
}
|
||
|
||
async getCommentsByUserId(userId: number): Promise<Comment[]> {
|
||
return Array.from(this.comments.values()).filter(
|
||
comment => comment.userId === userId
|
||
);
|
||
}
|
||
|
||
async getCommentsByEntityId(entityType: string, entityId: number): Promise<Comment[]> {
|
||
// In our storage model, we have specific fields for workId and translationId,
|
||
// but we want to support a more generic entity approach for blog posts, etc.
|
||
// For now, we'll handle the mapping between entity types and specific fields
|
||
|
||
if (entityType === 'work') {
|
||
return this.getCommentsByWorkId(entityId);
|
||
} else if (entityType === 'translation') {
|
||
return this.getCommentsByTranslationId(entityId);
|
||
} else if (entityType === 'blogPost') {
|
||
// For blog posts, filter comments by their entityType and entityId properties
|
||
// This assumes we're storing comment entities with these properties
|
||
return Array.from(this.comments.values()).filter(
|
||
comment =>
|
||
// For blog posts, we're storing the references in the parentId field temporarily
|
||
// In a real implementation, we would add proper schema fields for this
|
||
comment.workId === 0 && // Use a placeholder workId
|
||
comment.parentId === entityId
|
||
);
|
||
}
|
||
|
||
// Default return empty array if entity type is not recognized
|
||
return [];
|
||
}
|
||
|
||
async createComment(insertComment: InsertComment): Promise<Comment> {
|
||
const id = this.commentId++;
|
||
const now = new Date();
|
||
const comment: Comment = {
|
||
...insertComment,
|
||
id,
|
||
createdAt: now
|
||
};
|
||
this.comments.set(id, comment);
|
||
return comment;
|
||
}
|
||
|
||
// Like methods
|
||
async createLike(insertLike: InsertLike): Promise<Like> {
|
||
const id = this.likeId++;
|
||
const now = new Date();
|
||
const like: Like = {
|
||
...insertLike,
|
||
id,
|
||
createdAt: now
|
||
};
|
||
this.likes.set(id, like);
|
||
return like;
|
||
}
|
||
|
||
async deleteLike(id: number): Promise<void> {
|
||
this.likes.delete(id);
|
||
}
|
||
|
||
async getLikesByEntity(entityType: string, entityId: number): Promise<Like[]> {
|
||
return Array.from(this.likes.values()).filter(
|
||
like => like.entityType === entityType && like.entityId === entityId
|
||
);
|
||
}
|
||
|
||
async getLikesByUserId(userId: number): Promise<Like[]> {
|
||
return Array.from(this.likes.values()).filter(
|
||
like => like.userId === userId
|
||
);
|
||
}
|
||
|
||
// Collection methods
|
||
async getCollections(limit: number = 100, offset: number = 0): Promise<Collection[]> {
|
||
return Array.from(this.collections.values())
|
||
.filter(collection => collection.isPublic)
|
||
.sort((a, b) => a.name.localeCompare(b.name))
|
||
.slice(offset, offset + limit);
|
||
}
|
||
|
||
async getCollectionsByUserId(userId: number): Promise<Collection[]> {
|
||
return Array.from(this.collections.values()).filter(
|
||
collection => collection.userId === userId
|
||
);
|
||
}
|
||
|
||
async getCollectionById(id: number): Promise<Collection | undefined> {
|
||
return this.collections.get(id);
|
||
}
|
||
|
||
async getCollectionBySlug(slug: string): Promise<Collection | undefined> {
|
||
return Array.from(this.collections.values()).find(
|
||
collection => collection.slug === slug
|
||
);
|
||
}
|
||
|
||
async createCollection(insertCollection: InsertCollection): Promise<Collection> {
|
||
const id = this.collectionId++;
|
||
const now = new Date();
|
||
const collection: Collection = {
|
||
...insertCollection,
|
||
id,
|
||
createdAt: now
|
||
};
|
||
this.collections.set(id, collection);
|
||
return collection;
|
||
}
|
||
|
||
async addWorkToCollection(insertCollectionItem: InsertCollectionItem): Promise<CollectionItem> {
|
||
const id = this.collectionItemId++;
|
||
const now = new Date();
|
||
const collectionItem: CollectionItem = {
|
||
...insertCollectionItem,
|
||
id,
|
||
addedAt: now
|
||
};
|
||
this.collectionItems.set(id, collectionItem);
|
||
return collectionItem;
|
||
}
|
||
|
||
async getCollectionItems(collectionId: number): Promise<CollectionItem[]> {
|
||
return Array.from(this.collectionItems.values())
|
||
.filter(item => item.collectionId === collectionId)
|
||
.sort((a, b) => a.order - b.order);
|
||
}
|
||
|
||
// Timeline events
|
||
async getTimelineEventsByAuthorId(authorId: number): Promise<TimelineEvent[]> {
|
||
return Array.from(this.timelineEvents.values())
|
||
.filter(event => event.authorId === authorId)
|
||
.sort((a, b) => a.year - b.year);
|
||
}
|
||
|
||
async createTimelineEvent(insertEvent: InsertTimelineEvent): Promise<TimelineEvent> {
|
||
const id = this.timelineEventId++;
|
||
const now = new Date();
|
||
const event: TimelineEvent = {
|
||
...insertEvent,
|
||
id,
|
||
createdAt: now
|
||
};
|
||
this.timelineEvents.set(id, event);
|
||
return event;
|
||
}
|
||
|
||
// Blog posts
|
||
async getBlogPosts(limit: number = 100, offset: number = 0): Promise<BlogPost[]> {
|
||
return Array.from(this.blogPosts.values())
|
||
.filter(post => post.publishedAt !== null)
|
||
.sort((a, b) => (b.publishedAt?.getTime() || 0) - (a.publishedAt?.getTime() || 0))
|
||
.slice(offset, offset + limit);
|
||
}
|
||
|
||
async getBlogPostById(id: number): Promise<BlogPost | undefined> {
|
||
return this.blogPosts.get(id);
|
||
}
|
||
|
||
async getBlogPostBySlug(slug: string): Promise<BlogPost | undefined> {
|
||
return Array.from(this.blogPosts.values()).find(post => post.slug === slug);
|
||
}
|
||
|
||
async createBlogPost(insertPost: InsertBlogPost): Promise<BlogPost> {
|
||
const id = this.blogPostId++;
|
||
const now = new Date();
|
||
const post: BlogPost = {
|
||
...insertPost,
|
||
id,
|
||
createdAt: now
|
||
};
|
||
this.blogPosts.set(id, post);
|
||
return post;
|
||
}
|
||
|
||
// Reading progress
|
||
async getReadingProgress(userId: number, workId: number, translationId?: number): Promise<ReadingProgress | undefined> {
|
||
const key = `${userId}-${workId}-${translationId || 0}`;
|
||
return this.readingProgresses.get(key);
|
||
}
|
||
|
||
async updateReadingProgress(insertProgress: InsertReadingProgress): Promise<ReadingProgress> {
|
||
const key = `${insertProgress.userId}-${insertProgress.workId}-${insertProgress.translationId || 0}`;
|
||
const now = new Date();
|
||
|
||
const existing = this.readingProgresses.get(key);
|
||
|
||
if (existing) {
|
||
const updated: ReadingProgress = {
|
||
...existing,
|
||
progress: insertProgress.progress,
|
||
lastReadAt: now
|
||
};
|
||
this.readingProgresses.set(key, updated);
|
||
return updated;
|
||
} else {
|
||
const id = this.readingProgressId++;
|
||
const progress: ReadingProgress = {
|
||
...insertProgress,
|
||
id,
|
||
lastReadAt: now
|
||
};
|
||
this.readingProgresses.set(key, progress);
|
||
return progress;
|
||
}
|
||
}
|
||
|
||
// Search and filter methods
|
||
async searchWorks(query: string, limit: number = 10): Promise<Work[]> {
|
||
const lowerQuery = query.toLowerCase();
|
||
return Array.from(this.works.values())
|
||
.filter(work =>
|
||
work.title.toLowerCase().includes(lowerQuery) ||
|
||
work.description?.toLowerCase().includes(lowerQuery)
|
||
)
|
||
.slice(0, limit);
|
||
}
|
||
|
||
async searchAuthors(query: string, limit: number = 10): Promise<Author[]> {
|
||
const lowerQuery = query.toLowerCase();
|
||
return Array.from(this.authors.values())
|
||
.filter(author =>
|
||
author.name.toLowerCase().includes(lowerQuery) ||
|
||
author.biography?.toLowerCase().includes(lowerQuery)
|
||
)
|
||
.slice(0, limit);
|
||
}
|
||
|
||
async filterWorks(params: {
|
||
language?: string,
|
||
type?: string,
|
||
yearStart?: number,
|
||
yearEnd?: number,
|
||
tags?: number[]
|
||
}): Promise<Work[]> {
|
||
let filtered = Array.from(this.works.values());
|
||
|
||
if (params.language) {
|
||
filtered = filtered.filter(work => work.language === params.language);
|
||
}
|
||
|
||
if (params.type) {
|
||
filtered = filtered.filter(work => work.type === params.type);
|
||
}
|
||
|
||
if (params.yearStart) {
|
||
filtered = filtered.filter(work => work.year !== undefined && work.year >= (params.yearStart || 0));
|
||
}
|
||
|
||
if (params.yearEnd) {
|
||
filtered = filtered.filter(work => work.year !== undefined && work.year <= (params.yearEnd || 3000));
|
||
}
|
||
|
||
if (params.tags && params.tags.length > 0) {
|
||
const taggedWorks = await this.getWorksByTags(params.tags);
|
||
const taggedWorkIds = new Set(taggedWorks.map(work => work.id));
|
||
filtered = filtered.filter(work => taggedWorkIds.has(work.id));
|
||
}
|
||
|
||
return filtered;
|
||
}
|
||
}
|
||
|
||
export const storage = new MemStorage();
|