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