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; getBlogPostTags(postId: 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; getCommentsByEntityId(entityType: string, entityId: 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 blogPostTagsRelations: 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.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, entityType: "blogPost", entityId: blogPost.id, parentId: null }); } // 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); } async getBlogPostTags(postId: number): Promise { 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 { if (!this.blogPostTagsRelations.has(postId)) { this.blogPostTagsRelations.set(postId, new Set()); } this.blogPostTagsRelations.get(postId)!.add(tagId); } // 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 getCommentsByEntityId(entityType: string, entityId: number): Promise { // If we're using the primary entity fields (workId or translationId) if (entityType === 'work') { return this.getCommentsByWorkId(entityId); } else if (entityType === 'translation') { return this.getCommentsByTranslationId(entityId); } else { // For all other entity types, use the generic entityType and entityId fields return Array.from(this.comments.values()).filter( comment => comment.entityType === entityType && comment.entityId === entityId ); } } 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();