diff --git a/.dockerignore b/.dockerignore index 6528bce7..2ee17c14 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,6 +1,9 @@ node_modules +docker +custom-images .git +.env dist -/data -.env* +apps/*/dist +packages/*/dist .nx diff --git a/.gitignore b/.gitignore index 862c9e2c..c7ff6bd2 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,4 @@ lerna-debug.log* .nx .nx/installation .nx/cache +*.rdb \ No newline at end of file diff --git a/apps/server/src/core/page/services/page.service.ts b/apps/server/src/core/page/services/page.service.ts index bb6acaad..14f04850 100644 --- a/apps/server/src/core/page/services/page.service.ts +++ b/apps/server/src/core/page/services/page.service.ts @@ -1104,7 +1104,8 @@ export class PageService { nextDifficulty = w[4] - w[5] * (quality - 3); nextState = quality === 1 ? 1 : 2; // If Again, move to Learning, else Review } else { - const retrievability = Math.pow(0.9, elapsedDays / stability); + // Avoid division by zero if stability is 0 + const retrievability = stability > 0 ? Math.pow(0.9, elapsedDays / stability) : 0; if (quality === 1) { // Failed @@ -1123,8 +1124,9 @@ export class PageService { } } - // Constraints - nextStability = Math.min(Math.max(nextStability, 0.1), 36500); + // Constraints & NaN guard + nextStability = Math.min(Math.max(nextStability || 0.1, 0.1), 36500); + nextDifficulty = Math.min(Math.max(nextDifficulty || 5, 1), 10); // FSRS interval formula: I = S * ln(target_recall) / ln(0.9) // For default target_recall = 0.9, I = S @@ -1147,54 +1149,63 @@ export class PageService { pageId, ); - return this.pageRepo.findById(pageId); - } - - async getPagesToReview(userId: string, pagination: PaginationOptions, spaceId?: string, onlyDue = true) { - let query = this.db - .selectFrom('pages') - .innerJoin('spaces', 'spaces.id', 'pages.spaceId') - .select([ - 'pages.id', - 'pages.slugId', - 'pages.title', - 'pages.icon', - 'pages.spaceId', - 'pages.nextReviewDate', - 'pages.easeFactor', - 'pages.repetitionCount', - 'pages.reviewInterval', - 'pages.createdAt', - 'pages.updatedAt', - ]) - .select( - sql<{ name: string; slug: string }>`json_build_object('name', spaces.name, 'slug', spaces.slug)`.as('space'), - ) - .where('pages.nextReviewDate', 'is not', null) - .where('pages.deletedAt', 'is', null) - .orderBy('pages.nextReviewDate', 'asc'); - - if (onlyDue) { - query = query.where('pages.nextReviewDate', '<=', new Date()); - } - - if (spaceId) { - query = query.where('pages.spaceId', '=', spaceId); - } else { - query = query.where('pages.spaceId', 'in', this.spaceMemberRepo.getUserSpaceIdsQuery(userId)); - } - - return executeWithCursorPagination(query, { - perPage: pagination.limit, - cursor: pagination.cursor, - fields: [ - { expression: 'pages.nextReviewDate', direction: 'asc' }, - { expression: 'pages.id', direction: 'asc' }, - ], - parseCursor: (cursor: any) => ({ - nextReviewDate: new Date(cursor.nextReviewDate), - id: cursor.id, - }), + console.log('FSRS CALCULATION:', { + pageId, + quality, + nextStability, + nextDifficulty, + nextReviewDate: nextReviewDate.toISOString(), + reviewInterval: Math.round(exactReviewInterval) }); + +return this.pageRepo.findById(pageId); } + + async getPagesToReview(userId: string, pagination: PaginationOptions, spaceId ?: string, onlyDue = true) { + let query = this.db + .selectFrom('pages') + .innerJoin('spaces', 'spaces.id', 'pages.spaceId') + .select([ + 'pages.id', + 'pages.slugId', + 'pages.title', + 'pages.icon', + 'pages.spaceId', + 'pages.nextReviewDate', + 'pages.easeFactor', + 'pages.repetitionCount', + 'pages.reviewInterval', + 'pages.createdAt', + 'pages.updatedAt', + ]) + .select( + sql<{ name: string; slug: string }>`json_build_object('name', spaces.name, 'slug', spaces.slug)`.as('space'), + ) + .where('pages.nextReviewDate', 'is not', null) + .where('pages.deletedAt', 'is', null) + .orderBy('pages.nextReviewDate', 'asc'); + + if (onlyDue) { + query = query.where('pages.nextReviewDate', '<=', new Date()); + } + + if (spaceId) { + query = query.where('pages.spaceId', '=', spaceId); + } else { + query = query.where('pages.spaceId', 'in', this.spaceMemberRepo.getUserSpaceIdsQuery(userId)); + } + + return executeWithCursorPagination(query, { + perPage: pagination.limit, + cursor: pagination.cursor, + fields: [ + { expression: 'pages.nextReviewDate', direction: 'asc' }, + { expression: 'pages.id', direction: 'asc' }, + ], + parseCursor: (cursor: any) => ({ + nextReviewDate: new Date(cursor.nextReviewDate), + id: cursor.id, + }), + }); +} } diff --git a/custom-images/db/Dockerfile b/custom-images/db/Dockerfile new file mode 100644 index 00000000..043a0053 --- /dev/null +++ b/custom-images/db/Dockerfile @@ -0,0 +1 @@ +FROM postgres:18 diff --git a/custom-images/redis/Dockerfile b/custom-images/redis/Dockerfile new file mode 100644 index 00000000..6c0f59f0 --- /dev/null +++ b/custom-images/redis/Dockerfile @@ -0,0 +1 @@ +FROM redis:8 diff --git a/docker-compose.yml b/docker-compose.yml index 70e653af..c8c9f5dd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,40 +1,40 @@ services: docmost: - image: ghcr.io/victorphan03/docmost:v1.0.0 + build: . + image: ghcr.io/victorphan03/docmost:v1.0.2 depends_on: - db - redis environment: - # THAY ĐỔI: Địa chỉ IP hoặc Domain của server mới - APP_URL: 'http://your-server-ip:3000' - # THAY ĐỔI: Một chuỗi ký tự ngẫu nhiên dài để bảo mật - APP_SECRET: '32_KY_TU_NGAU_NHIEN_BAT_KY' + APP_URL: 'http://localhost:3000' + APP_SECRET: '1a635aac7d7d6afb4c84291baa94ed43a7749d390a739f9e541e210d3dd33399' DATABASE_URL: 'postgresql://docmost:STRONG_DB_PASSWORD@db:5432/docmost' REDIS_URL: 'redis://redis:6379' ports: - "3000:3000" restart: unless-stopped volumes: - - docmost_data:/app/data/storage + - ./data/docmost:/app/data/storage db: - image: postgres:16-alpine + build: ./custom-images/db + image: ghcr.io/victorphan03/db:v1.0.2 environment: POSTGRES_DB: docmost POSTGRES_USER: docmost POSTGRES_PASSWORD: STRONG_DB_PASSWORD restart: unless-stopped + ports: + - "5432:5432" volumes: - - postgres_data:/var/lib/postgresql/data + - ./data/db_data:/var/lib/postgresql redis: - image: redis:7-alpine - command: ["redis-server", "--appendonly", "yes"] + build: ./custom-images/redis + image: ghcr.io/victorphan03/redis:v1.0.2 + command: ["redis-server", "--appendonly", "yes", "--maxmemory-policy", "noeviction"] restart: unless-stopped + ports: + - "6379:6379" volumes: - - redis_data:/data - -volumes: - docmost_data: - postgres_data: - redis_data: + - ./data/redis_data:/data \ No newline at end of file