mirror of
https://github.com/SamyRai/tercul-frontend.git
synced 2025-12-27 02:31:34 +00:00
Serve demo content from a separate JSON file for easier data management
Implements JsonStorage class to load and manage sample data from sampleData.json, replacing MemStorage. 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/3575e342-a373-4351-a35e-5c684b14fd0f.jpg
This commit is contained in:
parent
5431f751fa
commit
9d24f47e68
819
server/jsonStorage.ts
Normal file
819
server/jsonStorage.ts
Normal file
@ -0,0 +1,819 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { IStorage } from './storage';
|
||||
import {
|
||||
User, Author, Work, Translation, Tag, Bookmark, Comment,
|
||||
Like, Collection, CollectionItem, TimelineEvent, BlogPost,
|
||||
ReadingProgress, InsertUser, InsertAuthor, InsertWork,
|
||||
InsertTranslation, InsertTag, InsertBookmark, InsertComment,
|
||||
InsertLike, InsertCollection, InsertCollectionItem, InsertTimelineEvent,
|
||||
InsertBlogPost, InsertReadingProgress
|
||||
} from '../shared/schema';
|
||||
|
||||
export class JsonStorage implements IStorage {
|
||||
private data: {
|
||||
users: Map<number, User>;
|
||||
authors: Map<number, Author>;
|
||||
works: Map<number, Work>;
|
||||
translations: Map<number, Translation>;
|
||||
tags: Map<number, Tag>;
|
||||
workTagsRelations: Map<number, Set<number>>;
|
||||
blogPostTagsRelations: Map<number, Set<number>>;
|
||||
bookmarks: Map<number, Bookmark>;
|
||||
comments: Map<number, Comment>;
|
||||
likes: Map<number, Like>;
|
||||
collections: Map<number, Collection>;
|
||||
collectionItems: Map<number, CollectionItem>;
|
||||
timelineEvents: Map<number, TimelineEvent>;
|
||||
blogPosts: Map<number, BlogPost>;
|
||||
readingProgresses: Map<string, ReadingProgress>;
|
||||
};
|
||||
|
||||
private counters: {
|
||||
userId: number;
|
||||
authorId: number;
|
||||
workId: number;
|
||||
translationId: number;
|
||||
tagId: number;
|
||||
bookmarkId: number;
|
||||
commentId: number;
|
||||
likeId: number;
|
||||
collectionId: number;
|
||||
collectionItemId: number;
|
||||
timelineEventId: number;
|
||||
blogPostId: number;
|
||||
readingProgressId: number;
|
||||
};
|
||||
|
||||
private dataFilePath: string;
|
||||
|
||||
constructor(dataFilePath: string = path.join(process.cwd(), 'data', 'sampleData.json')) {
|
||||
this.dataFilePath = dataFilePath;
|
||||
this.data = {
|
||||
users: new Map<number, User>(),
|
||||
authors: new Map<number, Author>(),
|
||||
works: new Map<number, Work>(),
|
||||
translations: new Map<number, Translation>(),
|
||||
tags: new Map<number, Tag>(),
|
||||
workTagsRelations: new Map<number, Set<number>>(),
|
||||
blogPostTagsRelations: new Map<number, Set<number>>(),
|
||||
bookmarks: new Map<number, Bookmark>(),
|
||||
comments: new Map<number, Comment>(),
|
||||
likes: new Map<number, Like>(),
|
||||
collections: new Map<number, Collection>(),
|
||||
collectionItems: new Map<number, CollectionItem>(),
|
||||
timelineEvents: new Map<number, TimelineEvent>(),
|
||||
blogPosts: new Map<number, BlogPost>(),
|
||||
readingProgresses: new Map<string, ReadingProgress>(),
|
||||
};
|
||||
|
||||
this.counters = {
|
||||
userId: 1,
|
||||
authorId: 1,
|
||||
workId: 1,
|
||||
translationId: 1,
|
||||
tagId: 1,
|
||||
bookmarkId: 1,
|
||||
commentId: 1,
|
||||
likeId: 1,
|
||||
collectionId: 1,
|
||||
collectionItemId: 1,
|
||||
timelineEventId: 1,
|
||||
blogPostId: 1,
|
||||
readingProgressId: 1,
|
||||
};
|
||||
|
||||
this.loadDataFromFile();
|
||||
}
|
||||
|
||||
private loadDataFromFile() {
|
||||
try {
|
||||
console.log(`Loading data from ${this.dataFilePath}`);
|
||||
if (!fs.existsSync(this.dataFilePath)) {
|
||||
console.error(`Data file not found: ${this.dataFilePath}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const jsonData = JSON.parse(fs.readFileSync(this.dataFilePath, 'utf8'));
|
||||
|
||||
// Load users
|
||||
if (jsonData.users) {
|
||||
jsonData.users.forEach((user: User) => {
|
||||
this.data.users.set(user.id, {
|
||||
...user,
|
||||
createdAt: new Date(user.createdAt)
|
||||
});
|
||||
this.counters.userId = Math.max(this.counters.userId, user.id + 1);
|
||||
});
|
||||
}
|
||||
|
||||
// Load authors
|
||||
if (jsonData.authors) {
|
||||
jsonData.authors.forEach((author: Author) => {
|
||||
this.data.authors.set(author.id, {
|
||||
...author,
|
||||
createdAt: new Date(author.createdAt)
|
||||
});
|
||||
this.counters.authorId = Math.max(this.counters.authorId, author.id + 1);
|
||||
});
|
||||
}
|
||||
|
||||
// Load works
|
||||
if (jsonData.works) {
|
||||
jsonData.works.forEach((work: Work) => {
|
||||
this.data.works.set(work.id, {
|
||||
...work,
|
||||
createdAt: new Date(work.createdAt)
|
||||
});
|
||||
this.counters.workId = Math.max(this.counters.workId, work.id + 1);
|
||||
});
|
||||
}
|
||||
|
||||
// Load translations
|
||||
if (jsonData.translations) {
|
||||
jsonData.translations.forEach((translation: Translation) => {
|
||||
this.data.translations.set(translation.id, {
|
||||
...translation,
|
||||
createdAt: new Date(translation.createdAt)
|
||||
});
|
||||
this.counters.translationId = Math.max(this.counters.translationId, translation.id + 1);
|
||||
});
|
||||
}
|
||||
|
||||
// Load tags
|
||||
if (jsonData.tags) {
|
||||
jsonData.tags.forEach((tag: Tag) => {
|
||||
this.data.tags.set(tag.id, {
|
||||
...tag,
|
||||
createdAt: new Date(tag.createdAt)
|
||||
});
|
||||
this.counters.tagId = Math.max(this.counters.tagId, tag.id + 1);
|
||||
});
|
||||
}
|
||||
|
||||
// Load work-tag relations
|
||||
if (jsonData.workTagsRelations) {
|
||||
for (const [workId, tagIds] of Object.entries(jsonData.workTagsRelations)) {
|
||||
this.data.workTagsRelations.set(
|
||||
parseInt(workId),
|
||||
new Set((tagIds as number[]).map(id => id))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Load blog post-tag relations
|
||||
if (jsonData.blogPostTagsRelations) {
|
||||
for (const [postId, tagIds] of Object.entries(jsonData.blogPostTagsRelations)) {
|
||||
this.data.blogPostTagsRelations.set(
|
||||
parseInt(postId),
|
||||
new Set((tagIds as number[]).map(id => id))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Load bookmarks
|
||||
if (jsonData.bookmarks) {
|
||||
jsonData.bookmarks.forEach((bookmark: Bookmark) => {
|
||||
this.data.bookmarks.set(bookmark.id, {
|
||||
...bookmark,
|
||||
createdAt: new Date(bookmark.createdAt)
|
||||
});
|
||||
this.counters.bookmarkId = Math.max(this.counters.bookmarkId, bookmark.id + 1);
|
||||
});
|
||||
}
|
||||
|
||||
// Load comments
|
||||
if (jsonData.comments) {
|
||||
jsonData.comments.forEach((comment: Comment) => {
|
||||
this.data.comments.set(comment.id, {
|
||||
...comment,
|
||||
createdAt: new Date(comment.createdAt)
|
||||
});
|
||||
this.counters.commentId = Math.max(this.counters.commentId, comment.id + 1);
|
||||
});
|
||||
}
|
||||
|
||||
// Load likes
|
||||
if (jsonData.likes) {
|
||||
jsonData.likes.forEach((like: Like) => {
|
||||
this.data.likes.set(like.id, {
|
||||
...like,
|
||||
createdAt: new Date(like.createdAt)
|
||||
});
|
||||
this.counters.likeId = Math.max(this.counters.likeId, like.id + 1);
|
||||
});
|
||||
}
|
||||
|
||||
// Load collections
|
||||
if (jsonData.collections) {
|
||||
jsonData.collections.forEach((collection: Collection) => {
|
||||
this.data.collections.set(collection.id, {
|
||||
...collection,
|
||||
createdAt: new Date(collection.createdAt)
|
||||
});
|
||||
this.counters.collectionId = Math.max(this.counters.collectionId, collection.id + 1);
|
||||
});
|
||||
}
|
||||
|
||||
// Load collection items
|
||||
if (jsonData.collectionItems) {
|
||||
jsonData.collectionItems.forEach((item: CollectionItem) => {
|
||||
this.data.collectionItems.set(item.id, {
|
||||
...item,
|
||||
createdAt: new Date(item.createdAt)
|
||||
});
|
||||
this.counters.collectionItemId = Math.max(this.counters.collectionItemId, item.id + 1);
|
||||
});
|
||||
}
|
||||
|
||||
// Load timeline events
|
||||
if (jsonData.timelineEvents) {
|
||||
jsonData.timelineEvents.forEach((event: TimelineEvent) => {
|
||||
this.data.timelineEvents.set(event.id, {
|
||||
...event,
|
||||
createdAt: new Date(event.createdAt)
|
||||
});
|
||||
this.counters.timelineEventId = Math.max(this.counters.timelineEventId, event.id + 1);
|
||||
});
|
||||
}
|
||||
|
||||
// Load blog posts
|
||||
if (jsonData.blogPosts) {
|
||||
jsonData.blogPosts.forEach((post: BlogPost) => {
|
||||
this.data.blogPosts.set(post.id, {
|
||||
...post,
|
||||
createdAt: new Date(post.createdAt),
|
||||
publishedAt: post.publishedAt ? new Date(post.publishedAt) : null
|
||||
});
|
||||
this.counters.blogPostId = Math.max(this.counters.blogPostId, post.id + 1);
|
||||
});
|
||||
}
|
||||
|
||||
// Load reading progresses
|
||||
if (jsonData.readingProgresses) {
|
||||
jsonData.readingProgresses.forEach((progress: ReadingProgress) => {
|
||||
const key = `${progress.userId}-${progress.workId}-${progress.translationId || 0}`;
|
||||
this.data.readingProgresses.set(key, {
|
||||
...progress,
|
||||
lastReadAt: new Date(progress.lastReadAt)
|
||||
});
|
||||
this.counters.readingProgressId = Math.max(this.counters.readingProgressId, progress.id + 1);
|
||||
});
|
||||
}
|
||||
|
||||
console.log('Data loaded successfully');
|
||||
console.log(`Users: ${this.data.users.size}`);
|
||||
console.log(`Authors: ${this.data.authors.size}`);
|
||||
console.log(`Works: ${this.data.works.size}`);
|
||||
console.log(`Translations: ${this.data.translations.size}`);
|
||||
} catch (error) {
|
||||
console.error('Error loading data from file:', error);
|
||||
}
|
||||
}
|
||||
|
||||
private saveDataToFile() {
|
||||
try {
|
||||
const jsonData = {
|
||||
users: Array.from(this.data.users.values()),
|
||||
authors: Array.from(this.data.authors.values()),
|
||||
works: Array.from(this.data.works.values()),
|
||||
translations: Array.from(this.data.translations.values()),
|
||||
tags: Array.from(this.data.tags.values()),
|
||||
workTagsRelations: Object.fromEntries(
|
||||
Array.from(this.data.workTagsRelations.entries()).map(
|
||||
([workId, tagIds]) => [workId, Array.from(tagIds)]
|
||||
)
|
||||
),
|
||||
blogPostTagsRelations: Object.fromEntries(
|
||||
Array.from(this.data.blogPostTagsRelations.entries()).map(
|
||||
([postId, tagIds]) => [postId, Array.from(tagIds)]
|
||||
)
|
||||
),
|
||||
bookmarks: Array.from(this.data.bookmarks.values()),
|
||||
comments: Array.from(this.data.comments.values()),
|
||||
likes: Array.from(this.data.likes.values()),
|
||||
collections: Array.from(this.data.collections.values()),
|
||||
collectionItems: Array.from(this.data.collectionItems.values()),
|
||||
timelineEvents: Array.from(this.data.timelineEvents.values()),
|
||||
blogPosts: Array.from(this.data.blogPosts.values()),
|
||||
readingProgresses: Array.from(this.data.readingProgresses.values())
|
||||
};
|
||||
|
||||
fs.writeFileSync(this.dataFilePath, JSON.stringify(jsonData, null, 2), 'utf8');
|
||||
console.log(`Data saved to ${this.dataFilePath}`);
|
||||
} catch (error) {
|
||||
console.error('Error saving data to file:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// User methods
|
||||
async getUser(id: number): Promise<User | undefined> {
|
||||
return this.data.users.get(id);
|
||||
}
|
||||
|
||||
async getUserByUsername(username: string): Promise<User | undefined> {
|
||||
return Array.from(this.data.users.values()).find(user => user.username === username);
|
||||
}
|
||||
|
||||
async createUser(insertUser: InsertUser): Promise<User> {
|
||||
const id = this.counters.userId++;
|
||||
const now = new Date();
|
||||
const user: User = {
|
||||
...insertUser,
|
||||
id,
|
||||
role: 'user',
|
||||
createdAt: now
|
||||
};
|
||||
this.data.users.set(id, user);
|
||||
this.saveDataToFile();
|
||||
return user;
|
||||
}
|
||||
|
||||
// Author methods
|
||||
async getAuthors(limit: number = 100, offset: number = 0): Promise<Author[]> {
|
||||
return Array.from(this.data.authors.values())
|
||||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
.slice(offset, offset + limit);
|
||||
}
|
||||
|
||||
async getAuthorById(id: number): Promise<Author | undefined> {
|
||||
return this.data.authors.get(id);
|
||||
}
|
||||
|
||||
async getAuthorBySlug(slug: string): Promise<Author | undefined> {
|
||||
return Array.from(this.data.authors.values()).find(author => author.slug === slug);
|
||||
}
|
||||
|
||||
async createAuthor(insertAuthor: InsertAuthor): Promise<Author> {
|
||||
const id = this.counters.authorId++;
|
||||
const now = new Date();
|
||||
const author: Author = {
|
||||
...insertAuthor,
|
||||
id,
|
||||
createdAt: now
|
||||
};
|
||||
this.data.authors.set(id, author);
|
||||
this.saveDataToFile();
|
||||
return author;
|
||||
}
|
||||
|
||||
// Work methods
|
||||
async getWorks(limit: number = 100, offset: number = 0): Promise<Work[]> {
|
||||
return Array.from(this.data.works.values())
|
||||
.sort((a, b) => a.title.localeCompare(b.title))
|
||||
.slice(offset, offset + limit);
|
||||
}
|
||||
|
||||
async getWorkById(id: number): Promise<Work | undefined> {
|
||||
return this.data.works.get(id);
|
||||
}
|
||||
|
||||
async getWorkBySlug(slug: string): Promise<Work | undefined> {
|
||||
return Array.from(this.data.works.values()).find(work => work.slug === slug);
|
||||
}
|
||||
|
||||
async getWorksByAuthorId(authorId: number): Promise<Work[]> {
|
||||
return Array.from(this.data.works.values()).filter(work => work.authorId === authorId);
|
||||
}
|
||||
|
||||
async getWorksByTags(tagIds: number[]): Promise<Work[]> {
|
||||
const tagSet = new Set(tagIds);
|
||||
const workIds = new Set<number>();
|
||||
|
||||
this.data.workTagsRelations.forEach((tags, workId) => {
|
||||
for (const tagId of tags) {
|
||||
if (tagSet.has(tagId)) {
|
||||
workIds.add(workId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return Array.from(workIds).map(id => this.data.works.get(id)!).filter(Boolean);
|
||||
}
|
||||
|
||||
async createWork(insertWork: InsertWork): Promise<Work> {
|
||||
const id = this.counters.workId++;
|
||||
const now = new Date();
|
||||
const work: Work = {
|
||||
...insertWork,
|
||||
id,
|
||||
createdAt: now
|
||||
};
|
||||
this.data.works.set(id, work);
|
||||
this.saveDataToFile();
|
||||
return work;
|
||||
}
|
||||
|
||||
// Translation methods
|
||||
async getTranslations(workId: number): Promise<Translation[]> {
|
||||
return Array.from(this.data.translations.values()).filter(
|
||||
translation => translation.workId === workId
|
||||
);
|
||||
}
|
||||
|
||||
async getTranslationById(id: number): Promise<Translation | undefined> {
|
||||
return this.data.translations.get(id);
|
||||
}
|
||||
|
||||
async createTranslation(insertTranslation: InsertTranslation): Promise<Translation> {
|
||||
const id = this.counters.translationId++;
|
||||
const now = new Date();
|
||||
const translation: Translation = {
|
||||
...insertTranslation,
|
||||
id,
|
||||
createdAt: now
|
||||
};
|
||||
this.data.translations.set(id, translation);
|
||||
this.saveDataToFile();
|
||||
return translation;
|
||||
}
|
||||
|
||||
// Tag methods
|
||||
async getTags(): Promise<Tag[]> {
|
||||
return Array.from(this.data.tags.values());
|
||||
}
|
||||
|
||||
async getTagsByType(type: string): Promise<Tag[]> {
|
||||
return Array.from(this.data.tags.values()).filter(tag => tag.type === type);
|
||||
}
|
||||
|
||||
async createTag(insertTag: InsertTag): Promise<Tag> {
|
||||
const id = this.counters.tagId++;
|
||||
const now = new Date();
|
||||
const tag: Tag = {
|
||||
...insertTag,
|
||||
id,
|
||||
createdAt: now
|
||||
};
|
||||
this.data.tags.set(id, tag);
|
||||
this.saveDataToFile();
|
||||
return tag;
|
||||
}
|
||||
|
||||
async addTagToWork(workId: number, tagId: number): Promise<void> {
|
||||
if (!this.data.workTagsRelations.has(workId)) {
|
||||
this.data.workTagsRelations.set(workId, new Set());
|
||||
}
|
||||
this.data.workTagsRelations.get(workId)!.add(tagId);
|
||||
this.saveDataToFile();
|
||||
}
|
||||
|
||||
async getWorkTags(workId: number): Promise<Tag[]> {
|
||||
const tagIds = this.data.workTagsRelations.get(workId) || new Set<number>();
|
||||
return Array.from(tagIds).map(id => this.data.tags.get(id)!).filter(Boolean);
|
||||
}
|
||||
|
||||
async getBlogPostTags(postId: number): Promise<Tag[]> {
|
||||
const tagIds = this.data.blogPostTagsRelations.get(postId) || new Set<number>();
|
||||
return Array.from(tagIds).map(id => this.data.tags.get(id)!).filter(Boolean);
|
||||
}
|
||||
|
||||
async addTagToBlogPost(postId: number, tagId: number): Promise<void> {
|
||||
console.log(`Adding tag ${tagId} to blog post ${postId}`);
|
||||
if (!this.data.blogPostTagsRelations.has(postId)) {
|
||||
console.log(`Creating new tag set for blog post ${postId}`);
|
||||
this.data.blogPostTagsRelations.set(postId, new Set());
|
||||
}
|
||||
this.data.blogPostTagsRelations.get(postId)!.add(tagId);
|
||||
console.log(`After adding tag, blog post ${postId} has tags:`,
|
||||
Array.from(this.data.blogPostTagsRelations.get(postId) || new Set()));
|
||||
this.saveDataToFile();
|
||||
}
|
||||
|
||||
// Bookmark methods
|
||||
async getBookmarksByUserId(userId: number): Promise<Bookmark[]> {
|
||||
return Array.from(this.data.bookmarks.values()).filter(bookmark => bookmark.userId === userId);
|
||||
}
|
||||
|
||||
async getBookmarkByUserAndWork(userId: number, workId: number): Promise<Bookmark | undefined> {
|
||||
return Array.from(this.data.bookmarks.values()).find(
|
||||
bookmark => bookmark.userId === userId && bookmark.workId === workId
|
||||
);
|
||||
}
|
||||
|
||||
async createBookmark(insertBookmark: InsertBookmark): Promise<Bookmark> {
|
||||
const id = this.counters.bookmarkId++;
|
||||
const now = new Date();
|
||||
const bookmark: Bookmark = {
|
||||
...insertBookmark,
|
||||
id,
|
||||
createdAt: now
|
||||
};
|
||||
this.data.bookmarks.set(id, bookmark);
|
||||
this.saveDataToFile();
|
||||
return bookmark;
|
||||
}
|
||||
|
||||
async deleteBookmark(id: number): Promise<void> {
|
||||
this.data.bookmarks.delete(id);
|
||||
this.saveDataToFile();
|
||||
}
|
||||
|
||||
// Comment methods
|
||||
async getCommentsByWorkId(workId: number): Promise<Comment[]> {
|
||||
return Array.from(this.data.comments.values()).filter(
|
||||
comment => comment.workId === workId
|
||||
);
|
||||
}
|
||||
|
||||
async getCommentsByTranslationId(translationId: number): Promise<Comment[]> {
|
||||
return Array.from(this.data.comments.values()).filter(
|
||||
comment => comment.translationId === translationId
|
||||
);
|
||||
}
|
||||
|
||||
async getCommentsByUserId(userId: number): Promise<Comment[]> {
|
||||
return Array.from(this.data.comments.values()).filter(
|
||||
comment => comment.userId === userId
|
||||
);
|
||||
}
|
||||
|
||||
async getCommentsByEntityId(entityType: string, entityId: number): Promise<Comment[]> {
|
||||
return Array.from(this.data.comments.values()).filter(
|
||||
comment => comment.entityType === entityType && comment.entityId === entityId
|
||||
);
|
||||
}
|
||||
|
||||
async createComment(insertComment: InsertComment): Promise<Comment> {
|
||||
const id = this.counters.commentId++;
|
||||
const now = new Date();
|
||||
const comment: Comment = {
|
||||
...insertComment,
|
||||
id,
|
||||
createdAt: now
|
||||
};
|
||||
this.data.comments.set(id, comment);
|
||||
this.saveDataToFile();
|
||||
return comment;
|
||||
}
|
||||
|
||||
// Like methods
|
||||
async createLike(insertLike: InsertLike): Promise<Like> {
|
||||
const id = this.counters.likeId++;
|
||||
const now = new Date();
|
||||
const like: Like = {
|
||||
...insertLike,
|
||||
id,
|
||||
createdAt: now
|
||||
};
|
||||
this.data.likes.set(id, like);
|
||||
this.saveDataToFile();
|
||||
return like;
|
||||
}
|
||||
|
||||
async deleteLike(id: number): Promise<void> {
|
||||
this.data.likes.delete(id);
|
||||
this.saveDataToFile();
|
||||
}
|
||||
|
||||
async getLikesByEntity(entityType: string, entityId: number): Promise<Like[]> {
|
||||
return Array.from(this.data.likes.values()).filter(
|
||||
like => like.entityType === entityType && like.entityId === entityId
|
||||
);
|
||||
}
|
||||
|
||||
async getLikesByUserId(userId: number): Promise<Like[]> {
|
||||
return Array.from(this.data.likes.values()).filter(
|
||||
like => like.userId === userId
|
||||
);
|
||||
}
|
||||
|
||||
// Collection methods
|
||||
async getCollections(limit: number = 100, offset: number = 0): Promise<Collection[]> {
|
||||
return Array.from(this.data.collections.values())
|
||||
.sort((a, b) => a.title.localeCompare(b.title))
|
||||
.slice(offset, offset + limit);
|
||||
}
|
||||
|
||||
async getCollectionsByUserId(userId: number): Promise<Collection[]> {
|
||||
return Array.from(this.data.collections.values()).filter(
|
||||
collection => collection.userId === userId
|
||||
);
|
||||
}
|
||||
|
||||
async getCollectionById(id: number): Promise<Collection | undefined> {
|
||||
return this.data.collections.get(id);
|
||||
}
|
||||
|
||||
async getCollectionBySlug(slug: string): Promise<Collection | undefined> {
|
||||
return Array.from(this.data.collections.values()).find(
|
||||
collection => collection.slug === slug
|
||||
);
|
||||
}
|
||||
|
||||
async createCollection(insertCollection: InsertCollection): Promise<Collection> {
|
||||
const id = this.counters.collectionId++;
|
||||
const now = new Date();
|
||||
const collection: Collection = {
|
||||
...insertCollection,
|
||||
id,
|
||||
createdAt: now
|
||||
};
|
||||
this.data.collections.set(id, collection);
|
||||
this.saveDataToFile();
|
||||
return collection;
|
||||
}
|
||||
|
||||
async addWorkToCollection(insertCollectionItem: InsertCollectionItem): Promise<CollectionItem> {
|
||||
const id = this.counters.collectionItemId++;
|
||||
const now = new Date();
|
||||
const collectionItem: CollectionItem = {
|
||||
...insertCollectionItem,
|
||||
id,
|
||||
createdAt: now
|
||||
};
|
||||
this.data.collectionItems.set(id, collectionItem);
|
||||
this.saveDataToFile();
|
||||
return collectionItem;
|
||||
}
|
||||
|
||||
async getCollectionItems(collectionId: number): Promise<CollectionItem[]> {
|
||||
return Array.from(this.data.collectionItems.values()).filter(
|
||||
item => item.collectionId === collectionId
|
||||
);
|
||||
}
|
||||
|
||||
// Timeline events
|
||||
async getTimelineEventsByAuthorId(authorId: number): Promise<TimelineEvent[]> {
|
||||
return Array.from(this.data.timelineEvents.values())
|
||||
.filter(event => event.authorId === authorId)
|
||||
.sort((a, b) => a.year - b.year);
|
||||
}
|
||||
|
||||
async createTimelineEvent(insertEvent: InsertTimelineEvent): Promise<TimelineEvent> {
|
||||
const id = this.counters.timelineEventId++;
|
||||
const now = new Date();
|
||||
const event: TimelineEvent = {
|
||||
...insertEvent,
|
||||
id,
|
||||
createdAt: now
|
||||
};
|
||||
this.data.timelineEvents.set(id, event);
|
||||
this.saveDataToFile();
|
||||
return event;
|
||||
}
|
||||
|
||||
// Blog posts
|
||||
async getBlogPosts(limit: number = 100, offset: number = 0): Promise<BlogPost[]> {
|
||||
return Array.from(this.data.blogPosts.values())
|
||||
.sort((a, b) => {
|
||||
const dateA = a.publishedAt || a.createdAt;
|
||||
const dateB = b.publishedAt || b.createdAt;
|
||||
return dateB.getTime() - dateA.getTime(); // newest first
|
||||
})
|
||||
.slice(offset, offset + limit);
|
||||
}
|
||||
|
||||
async getBlogPostById(id: number): Promise<BlogPost | undefined> {
|
||||
return this.data.blogPosts.get(id);
|
||||
}
|
||||
|
||||
async getBlogPostBySlug(slug: string): Promise<BlogPost | undefined> {
|
||||
return Array.from(this.data.blogPosts.values()).find(
|
||||
post => post.slug === slug
|
||||
);
|
||||
}
|
||||
|
||||
async createBlogPost(insertPost: InsertBlogPost): Promise<BlogPost> {
|
||||
const id = this.counters.blogPostId++;
|
||||
const now = new Date();
|
||||
const post: BlogPost = {
|
||||
...insertPost,
|
||||
id,
|
||||
createdAt: now
|
||||
};
|
||||
this.data.blogPosts.set(id, post);
|
||||
console.log('Created blog post with ID:', id);
|
||||
this.saveDataToFile();
|
||||
return post;
|
||||
}
|
||||
|
||||
// Reading progress
|
||||
async getReadingProgress(userId: number, workId: number, translationId?: number): Promise<ReadingProgress | undefined> {
|
||||
const key = `${userId}-${workId}-${translationId || 0}`;
|
||||
return this.data.readingProgresses.get(key);
|
||||
}
|
||||
|
||||
async updateReadingProgress(insertProgress: InsertReadingProgress): Promise<ReadingProgress> {
|
||||
const key = `${insertProgress.userId}-${insertProgress.workId}-${insertProgress.translationId || 0}`;
|
||||
|
||||
if (this.data.readingProgresses.has(key)) {
|
||||
const existing = this.data.readingProgresses.get(key)!;
|
||||
const updated: ReadingProgress = {
|
||||
...existing,
|
||||
progress: insertProgress.progress ?? existing.progress,
|
||||
lastReadAt: new Date()
|
||||
};
|
||||
this.data.readingProgresses.set(key, updated);
|
||||
this.saveDataToFile();
|
||||
return updated;
|
||||
} else {
|
||||
const id = this.counters.readingProgressId++;
|
||||
const progress: ReadingProgress = {
|
||||
id,
|
||||
workId: insertProgress.workId,
|
||||
progress: insertProgress.progress ?? 0,
|
||||
userId: insertProgress.userId,
|
||||
translationId: insertProgress.translationId ?? null,
|
||||
lastReadAt: new Date()
|
||||
};
|
||||
this.data.readingProgresses.set(key, progress);
|
||||
this.saveDataToFile();
|
||||
return progress;
|
||||
}
|
||||
}
|
||||
|
||||
// Search and filter methods
|
||||
async searchWorks(query: string, limit: number = 10): Promise<Work[]> {
|
||||
const searchTerms = query.toLowerCase().split(/\s+/);
|
||||
const works = Array.from(this.data.works.values());
|
||||
|
||||
const scoredWorks = works.map(work => {
|
||||
let score = 0;
|
||||
const title = work.title.toLowerCase();
|
||||
const description = work.description?.toLowerCase() || '';
|
||||
|
||||
searchTerms.forEach(term => {
|
||||
if (title.includes(term)) score += 10;
|
||||
if (description.includes(term)) score += 5;
|
||||
});
|
||||
|
||||
return { work, score };
|
||||
});
|
||||
|
||||
return scoredWorks
|
||||
.filter(item => item.score > 0)
|
||||
.sort((a, b) => b.score - a.score)
|
||||
.slice(0, limit)
|
||||
.map(item => item.work);
|
||||
}
|
||||
|
||||
async searchAuthors(query: string, limit: number = 10): Promise<Author[]> {
|
||||
const searchTerms = query.toLowerCase().split(/\s+/);
|
||||
const authors = Array.from(this.data.authors.values());
|
||||
|
||||
const scoredAuthors = authors.map(author => {
|
||||
let score = 0;
|
||||
const name = author.name.toLowerCase();
|
||||
const biography = author.biography?.toLowerCase() || '';
|
||||
const country = author.country?.toLowerCase() || '';
|
||||
|
||||
searchTerms.forEach(term => {
|
||||
if (name.includes(term)) score += 10;
|
||||
if (country.includes(term)) score += 7;
|
||||
if (biography.includes(term)) score += 3;
|
||||
});
|
||||
|
||||
return { author, score };
|
||||
});
|
||||
|
||||
return scoredAuthors
|
||||
.filter(item => item.score > 0)
|
||||
.sort((a, b) => b.score - a.score)
|
||||
.slice(0, limit)
|
||||
.map(item => item.author);
|
||||
}
|
||||
|
||||
async filterWorks(params: {
|
||||
language?: string,
|
||||
type?: string,
|
||||
yearStart?: number,
|
||||
yearEnd?: number,
|
||||
tags?: number[]
|
||||
}): Promise<Work[]> {
|
||||
let filteredWorks = Array.from(this.data.works.values());
|
||||
|
||||
if (params.language) {
|
||||
filteredWorks = filteredWorks.filter(work => work.language === params.language);
|
||||
}
|
||||
|
||||
if (params.type) {
|
||||
filteredWorks = filteredWorks.filter(work => work.type === params.type);
|
||||
}
|
||||
|
||||
if (params.yearStart) {
|
||||
filteredWorks = filteredWorks.filter(work => work.year! >= params.yearStart!);
|
||||
}
|
||||
|
||||
if (params.yearEnd) {
|
||||
filteredWorks = filteredWorks.filter(work => work.year! <= params.yearEnd!);
|
||||
}
|
||||
|
||||
if (params.tags && params.tags.length > 0) {
|
||||
const taggedWorkIds = new Set<number>();
|
||||
this.data.workTagsRelations.forEach((tagIds, workId) => {
|
||||
for (const tagId of params.tags!) {
|
||||
if (tagIds.has(tagId)) {
|
||||
taggedWorkIds.add(workId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
filteredWorks = filteredWorks.filter(work => taggedWorkIds.has(work.id));
|
||||
}
|
||||
|
||||
return filteredWorks;
|
||||
}
|
||||
}
|
||||
@ -1186,4 +1186,6 @@ As we continue to develop the Tercul platform, we're committed to addressing the
|
||||
}
|
||||
}
|
||||
|
||||
export const storage = new MemStorage();
|
||||
// Now using JsonStorage instead of MemStorage
|
||||
import { JsonStorage } from './jsonStorage';
|
||||
export const storage = new JsonStorage();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user