mirror of
https://github.com/docmost/docmost.git
synced 2026-05-07 06:23:06 +08:00
@@ -41,9 +41,9 @@
|
||||
"@fastify/multipart": "^9.4.0",
|
||||
"@fastify/static": "^9.0.0",
|
||||
"@keyv/redis": "^5.1.6",
|
||||
"@langchain/core": "1.1.34",
|
||||
"@langchain/core": "1.1.39",
|
||||
"@langchain/textsplitters": "1.0.1",
|
||||
"@modelcontextprotocol/sdk": "^1.27.1",
|
||||
"@modelcontextprotocol/sdk": "^1.29.0",
|
||||
"@nest-lab/throttler-storage-redis": "^1.2.0",
|
||||
"@nestjs-labs/nestjs-ioredis": "^11.0.4",
|
||||
"@nestjs/bullmq": "^11.0.4",
|
||||
@@ -94,7 +94,7 @@
|
||||
"nestjs-cls": "^6.2.0",
|
||||
"nestjs-kysely": "^3.1.2",
|
||||
"nestjs-pino": "^4.6.1",
|
||||
"nodemailer": "^8.0.4",
|
||||
"nodemailer": "^8.0.5",
|
||||
"openid-client": "^6.8.2",
|
||||
"otpauth": "^9.5.0",
|
||||
"p-limit": "^7.3.0",
|
||||
@@ -116,8 +116,8 @@
|
||||
"tlds": "^1.261.0",
|
||||
"tmp-promise": "^3.0.3",
|
||||
"tseep": "^1.3.1",
|
||||
"typesense": "^3.0.3",
|
||||
"ws": "^8.19.0",
|
||||
"typesense": "^3.0.5",
|
||||
"ws": "^8.20.0",
|
||||
"yauzl": "^3.2.1",
|
||||
"zod": "^4.3.6"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import { IsIn, IsNotEmpty, IsString } from 'class-validator';
|
||||
|
||||
export class FavoriteIdsDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@IsIn(['page', 'space', 'template'])
|
||||
type: 'page' | 'space' | 'template';
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from '@nestjs/common';
|
||||
import { FavoriteService } from './services/favorite.service';
|
||||
import { AddFavoriteDto, RemoveFavoriteDto } from './dto/favorite.dto';
|
||||
import { FavoriteIdsDto } from './dto/favorite-ids.dto';
|
||||
import { ListFavoritesDto } from './dto/list-favorites.dto';
|
||||
import { PaginationOptions } from '@docmost/db/pagination/pagination-options';
|
||||
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
|
||||
@@ -70,6 +71,20 @@ export class FavoriteController {
|
||||
});
|
||||
}
|
||||
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post('ids')
|
||||
async getFavoriteIds(
|
||||
@Body() dto: FavoriteIdsDto,
|
||||
@AuthUser() user: User,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
) {
|
||||
return this.favoriteService.getFavoriteIds(
|
||||
user.id,
|
||||
workspace.id,
|
||||
dto.type as FavoriteType,
|
||||
);
|
||||
}
|
||||
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post()
|
||||
async getUserFavorites(
|
||||
|
||||
@@ -16,6 +16,40 @@ export class FavoriteService {
|
||||
private readonly spaceMemberRepo: SpaceMemberRepo,
|
||||
) {}
|
||||
|
||||
async getFavoriteIds(
|
||||
userId: string,
|
||||
workspaceId: string,
|
||||
type: FavoriteType,
|
||||
) {
|
||||
const result = await this.favoriteRepo.getFavoriteIds(
|
||||
userId,
|
||||
workspaceId,
|
||||
type,
|
||||
);
|
||||
|
||||
if (result.items.length === 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (type === FavoriteType.PAGE) {
|
||||
const accessibleIds =
|
||||
await this.pagePermissionRepo.filterAccessiblePageIds({
|
||||
pageIds: result.items,
|
||||
userId,
|
||||
});
|
||||
const accessibleSet = new Set(accessibleIds);
|
||||
result.items = result.items.filter((id) => accessibleSet.has(id));
|
||||
}
|
||||
|
||||
if (type === FavoriteType.SPACE) {
|
||||
const userSpaceIds = await this.spaceMemberRepo.getUserSpaceIds(userId);
|
||||
const spaceSet = new Set(userSpaceIds);
|
||||
result.items = result.items.filter((id) => spaceSet.has(id));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async addFavorite(
|
||||
userId: string,
|
||||
workspaceId: string,
|
||||
|
||||
@@ -48,6 +48,15 @@ export class SpaceWatcherController {
|
||||
return space;
|
||||
}
|
||||
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post('watched-ids')
|
||||
async getWatchedSpaceIds(
|
||||
@AuthUser() user: User,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
) {
|
||||
return this.watcherService.getWatchedSpaceIds(user.id, workspace.id);
|
||||
}
|
||||
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post('watch')
|
||||
async watchSpace(
|
||||
|
||||
@@ -6,10 +6,14 @@ import {
|
||||
import { PaginationOptions } from '@docmost/db/pagination/pagination-options';
|
||||
import { KyselyTransaction } from '@docmost/db/types/kysely.types';
|
||||
import { InsertableWatcher } from '@docmost/db/types/entity.types';
|
||||
import { SpaceMemberRepo } from '@docmost/db/repos/space/space-member.repo';
|
||||
|
||||
@Injectable()
|
||||
export class WatcherService {
|
||||
constructor(private readonly watcherRepo: WatcherRepo) {}
|
||||
constructor(
|
||||
private readonly watcherRepo: WatcherRepo,
|
||||
private readonly spaceMemberRepo: SpaceMemberRepo,
|
||||
) {}
|
||||
|
||||
async watchPage(
|
||||
userId: string,
|
||||
@@ -84,6 +88,24 @@ export class WatcherService {
|
||||
return this.watcherRepo.deleteSpaceWatch(userId, spaceId);
|
||||
}
|
||||
|
||||
async getWatchedSpaceIds(userId: string, workspaceId: string) {
|
||||
const result = await this.watcherRepo.getWatchedSpaceIds(userId, workspaceId);
|
||||
|
||||
const spaceIds = result.items.map((r) => r.spaceId);
|
||||
|
||||
if (spaceIds.length === 0) {
|
||||
return { items: spaceIds, meta: result.meta };
|
||||
}
|
||||
|
||||
const userSpaceIds = await this.spaceMemberRepo.getUserSpaceIds(userId);
|
||||
const spaceSet = new Set(userSpaceIds);
|
||||
|
||||
return {
|
||||
items: spaceIds.filter((id) => spaceSet.has(id)),
|
||||
meta: result.meta,
|
||||
};
|
||||
}
|
||||
|
||||
async isWatchingSpace(userId: string, spaceId: string): Promise<boolean> {
|
||||
return this.watcherRepo.isWatchingSpace(userId, spaceId);
|
||||
}
|
||||
|
||||
@@ -62,6 +62,39 @@ export class FavoriteRepo {
|
||||
.execute();
|
||||
}
|
||||
|
||||
async getFavoriteIds(
|
||||
userId: string,
|
||||
workspaceId: string,
|
||||
type: FavoriteType,
|
||||
): Promise<{ items: string[]; meta: any }> {
|
||||
const idColumn =
|
||||
type === FavoriteType.PAGE
|
||||
? 'pageId'
|
||||
: type === FavoriteType.SPACE
|
||||
? 'spaceId'
|
||||
: 'templateId';
|
||||
|
||||
const query = this.db
|
||||
.selectFrom('favorites')
|
||||
.select(['favorites.id', `favorites.${idColumn} as entityId`])
|
||||
.where('userId', '=', userId)
|
||||
.where('workspaceId', '=', workspaceId)
|
||||
.where('type', '=', type);
|
||||
|
||||
const result = await executeWithCursorPagination(query, {
|
||||
perPage: 250,
|
||||
fields: [{ expression: 'favorites.id', direction: 'desc' }],
|
||||
parseCursor: (cursor) => ({ id: cursor.id }),
|
||||
});
|
||||
|
||||
return {
|
||||
items: result.items
|
||||
.map((r) => (r as any).entityId as string)
|
||||
.filter(Boolean),
|
||||
meta: result.meta,
|
||||
};
|
||||
}
|
||||
|
||||
async findUserFavorites(
|
||||
userId: string,
|
||||
workspaceId: string,
|
||||
|
||||
@@ -207,6 +207,22 @@ export class WatcherRepo {
|
||||
.execute();
|
||||
}
|
||||
|
||||
async getWatchedSpaceIds(userId: string, workspaceId: string) {
|
||||
const query = this.db
|
||||
.selectFrom('watchers')
|
||||
.select(['watchers.id', 'watchers.spaceId'])
|
||||
.where('userId', '=', userId)
|
||||
.where('workspaceId', '=', workspaceId)
|
||||
.where('pageId', 'is', null)
|
||||
.where('type', '=', WatcherType.SPACE);
|
||||
|
||||
return executeWithCursorPagination(query, {
|
||||
perPage: 250,
|
||||
fields: [{ expression: 'watchers.id', direction: 'asc' }],
|
||||
parseCursor: (cursor) => ({ id: cursor.id }),
|
||||
});
|
||||
}
|
||||
|
||||
async isWatchingSpace(userId: string, spaceId: string): Promise<boolean> {
|
||||
const watcher = await this.db
|
||||
.selectFrom('watchers')
|
||||
|
||||
Reference in New Issue
Block a user