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; getUserByUsername(username: string): Promise; createUser(user: InsertUser): Promise; // Author methods getAuthors(limit?: number, offset?: number): Promise; getAuthorById(id: number): Promise; getAuthorBySlug(slug: string): Promise; createAuthor(author: InsertAuthor): Promise; // Work methods getWorks(limit?: number, offset?: number): Promise; getWorkById(id: number): Promise; getWorkBySlug(slug: string): Promise; getWorksByAuthorId(authorId: number): Promise; getWorksByTags(tagIds: number[]): Promise; createWork(work: InsertWork): Promise; // Translation methods getTranslations(workId: number): Promise; getTranslationById(id: number): Promise; createTranslation(translation: InsertTranslation): Promise; // Tag methods getTags(): Promise; getTagsByType(type: string): Promise; createTag(tag: InsertTag): Promise; addTagToWork(workId: number, tagId: number): Promise; getWorkTags(workId: number): Promise; // Bookmark methods getBookmarksByUserId(userId: number): Promise; getBookmarkByUserAndWork(userId: number, workId: number): Promise; createBookmark(bookmark: InsertBookmark): Promise; deleteBookmark(id: number): Promise; // Comment methods getCommentsByWorkId(workId: number): Promise; getCommentsByTranslationId(translationId: number): Promise; getCommentsByUserId(userId: number): Promise; createComment(comment: InsertComment): Promise; // Like methods createLike(like: InsertLike): Promise; deleteLike(id: number): Promise; getLikesByEntity(entityType: string, entityId: number): Promise; getLikesByUserId(userId: number): Promise; // Collection methods getCollections(limit?: number, offset?: number): Promise; getCollectionsByUserId(userId: number): Promise; getCollectionById(id: number): Promise; getCollectionBySlug(slug: string): Promise; createCollection(collection: InsertCollection): Promise; addWorkToCollection(collectionItem: InsertCollectionItem): Promise; getCollectionItems(collectionId: number): Promise; // Timeline events getTimelineEventsByAuthorId(authorId: number): Promise; createTimelineEvent(event: InsertTimelineEvent): Promise; // Blog posts getBlogPosts(limit?: number, offset?: number): Promise; getBlogPostById(id: number): Promise; getBlogPostBySlug(slug: string): Promise; createBlogPost(post: InsertBlogPost): Promise; // Reading progress getReadingProgress(userId: number, workId: number, translationId?: number): Promise; updateReadingProgress(progress: InsertReadingProgress): Promise; // Search and filter methods searchWorks(query: string, limit?: number): Promise; searchAuthors(query: string, limit?: number): Promise; filterWorks(params: { language?: string, type?: string, yearStart?: number, yearEnd?: number, tags?: number[] }): Promise; } export class MemStorage implements IStorage { private users: Map; private authors: Map; private works: Map; private translations: Map; private tags: Map; private workTagsRelations: Map>; private bookmarks: Map; private comments: Map; private likes: Map; private collections: Map; private collectionItems: Map; private timelineEvents: Map; private blogPosts: Map; private readingProgresses: Map; 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.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" }); } // User methods async getUser(id: number): Promise { return this.users.get(id); } async getUserByUsername(username: string): Promise { return Array.from(this.users.values()).find(user => user.username === username); } async createUser(insertUser: InsertUser): Promise { 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 { return Array.from(this.authors.values()) .sort((a, b) => a.name.localeCompare(b.name)) .slice(offset, offset + limit); } async getAuthorById(id: number): Promise { return this.authors.get(id); } async getAuthorBySlug(slug: string): Promise { return Array.from(this.authors.values()).find(author => author.slug === slug); } async createAuthor(insertAuthor: InsertAuthor): Promise { 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 { return Array.from(this.works.values()) .sort((a, b) => a.title.localeCompare(b.title)) .slice(offset, offset + limit); } async getWorkById(id: number): Promise { return this.works.get(id); } async getWorkBySlug(slug: string): Promise { return Array.from(this.works.values()).find(work => work.slug === slug); } async getWorksByAuthorId(authorId: number): Promise { return Array.from(this.works.values()).filter(work => work.authorId === authorId); } async getWorksByTags(tagIds: number[]): Promise { const tagSet = new Set(tagIds); const workIds = new Set(); 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 { 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 { return Array.from(this.translations.values()).filter( translation => translation.workId === workId ); } async getTranslationById(id: number): Promise { return this.translations.get(id); } async createTranslation(insertTranslation: InsertTranslation): Promise { 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 { return Array.from(this.tags.values()); } async getTagsByType(type: string): Promise { return Array.from(this.tags.values()).filter(tag => tag.type === type); } async createTag(insertTag: InsertTag): Promise { 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 { if (!this.workTagsRelations.has(workId)) { this.workTagsRelations.set(workId, new Set()); } this.workTagsRelations.get(workId)!.add(tagId); } async getWorkTags(workId: number): Promise { const tagIds = this.workTagsRelations.get(workId) || new Set(); return Array.from(tagIds).map(id => this.tags.get(id)!).filter(Boolean); } // Bookmark methods async getBookmarksByUserId(userId: number): Promise { return Array.from(this.bookmarks.values()).filter( bookmark => bookmark.userId === userId ); } async getBookmarkByUserAndWork(userId: number, workId: number): Promise { return Array.from(this.bookmarks.values()).find( bookmark => bookmark.userId === userId && bookmark.workId === workId ); } async createBookmark(insertBookmark: InsertBookmark): Promise { 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 { this.bookmarks.delete(id); } // Comment methods async getCommentsByWorkId(workId: number): Promise { return Array.from(this.comments.values()).filter( comment => comment.workId === workId ); } async getCommentsByTranslationId(translationId: number): Promise { return Array.from(this.comments.values()).filter( comment => comment.translationId === translationId ); } async getCommentsByUserId(userId: number): Promise { return Array.from(this.comments.values()).filter( comment => comment.userId === userId ); } async createComment(insertComment: InsertComment): Promise { 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 { 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 { this.likes.delete(id); } async getLikesByEntity(entityType: string, entityId: number): Promise { return Array.from(this.likes.values()).filter( like => like.entityType === entityType && like.entityId === entityId ); } async getLikesByUserId(userId: number): Promise { return Array.from(this.likes.values()).filter( like => like.userId === userId ); } // Collection methods async getCollections(limit: number = 100, offset: number = 0): Promise { 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 { return Array.from(this.collections.values()).filter( collection => collection.userId === userId ); } async getCollectionById(id: number): Promise { return this.collections.get(id); } async getCollectionBySlug(slug: string): Promise { return Array.from(this.collections.values()).find( collection => collection.slug === slug ); } async createCollection(insertCollection: InsertCollection): Promise { 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 { 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 { 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 { return Array.from(this.timelineEvents.values()) .filter(event => event.authorId === authorId) .sort((a, b) => a.year - b.year); } async createTimelineEvent(insertEvent: InsertTimelineEvent): Promise { 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 { 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 { return this.blogPosts.get(id); } async getBlogPostBySlug(slug: string): Promise { return Array.from(this.blogPosts.values()).find(post => post.slug === slug); } async createBlogPost(insertPost: InsertBlogPost): Promise { 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 { const key = `${userId}-${workId}-${translationId || 0}`; return this.readingProgresses.get(key); } async updateReadingProgress(insertProgress: InsertReadingProgress): Promise { 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 { 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 { 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 { 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();