mirror of
https://github.com/docmost/docmost.git
synced 2025-11-13 04:32:41 +10:00
251 lines
6.5 KiB
TypeScript
251 lines
6.5 KiB
TypeScript
import { Injectable } from '@nestjs/common';
|
|
import { InjectKysely } from 'nestjs-kysely';
|
|
import { KyselyDB, KyselyTransaction } from '../../types/kysely.types';
|
|
import { dbOrTx } from '../../utils';
|
|
import {
|
|
InsertablePage,
|
|
Page,
|
|
UpdatablePage,
|
|
} from '@docmost/db/types/entity.types';
|
|
import { PaginationOptions } from '@docmost/db/pagination/pagination-options';
|
|
import { executeWithPagination } from '@docmost/db/pagination/pagination';
|
|
import { validate as isValidUUID } from 'uuid';
|
|
import { ExpressionBuilder, sql } from 'kysely';
|
|
import { DB } from '@docmost/db/types/db';
|
|
import { jsonArrayFrom, jsonObjectFrom } from 'kysely/helpers/postgres';
|
|
import { SpaceMemberRepo } from '@docmost/db/repos/space/space-member.repo';
|
|
|
|
@Injectable()
|
|
export class PageRepo {
|
|
constructor(
|
|
@InjectKysely() private readonly db: KyselyDB,
|
|
private spaceMemberRepo: SpaceMemberRepo,
|
|
) {}
|
|
|
|
private baseFields: Array<keyof Page> = [
|
|
'id',
|
|
'slugId',
|
|
'title',
|
|
'icon',
|
|
'coverPhoto',
|
|
'position',
|
|
'parentPageId',
|
|
'creatorId',
|
|
'lastUpdatedById',
|
|
'spaceId',
|
|
'workspaceId',
|
|
'isLocked',
|
|
'createdAt',
|
|
'updatedAt',
|
|
'deletedAt',
|
|
'contributorIds',
|
|
];
|
|
|
|
async findById(
|
|
pageId: string,
|
|
opts?: {
|
|
includeContent?: boolean;
|
|
includeYdoc?: boolean;
|
|
includeSpace?: boolean;
|
|
includeCreator?: boolean;
|
|
includeLastUpdatedBy?: boolean;
|
|
includeContributors?: boolean;
|
|
withLock?: boolean;
|
|
trx?: KyselyTransaction;
|
|
},
|
|
): Promise<Page> {
|
|
const db = dbOrTx(this.db, opts?.trx);
|
|
|
|
let query = db
|
|
.selectFrom('pages')
|
|
.select(this.baseFields)
|
|
.$if(opts?.includeContent, (qb) => qb.select('content'))
|
|
.$if(opts?.includeYdoc, (qb) => qb.select('ydoc'));
|
|
|
|
if (opts?.includeCreator) {
|
|
query = query.select((eb) => this.withCreator(eb));
|
|
}
|
|
|
|
if (opts?.includeLastUpdatedBy) {
|
|
query = query.select((eb) => this.withLastUpdatedBy(eb));
|
|
}
|
|
|
|
if (opts?.includeContributors) {
|
|
query = query.select((eb) => this.withContributors(eb));
|
|
}
|
|
|
|
if (opts?.includeSpace) {
|
|
query = query.select((eb) => this.withSpace(eb));
|
|
}
|
|
|
|
if (opts?.withLock && opts?.trx) {
|
|
query = query.forUpdate();
|
|
}
|
|
|
|
if (isValidUUID(pageId)) {
|
|
query = query.where('id', '=', pageId);
|
|
} else {
|
|
query = query.where('slugId', '=', pageId);
|
|
}
|
|
|
|
return query.executeTakeFirst();
|
|
}
|
|
|
|
async updatePage(
|
|
updatablePage: UpdatablePage,
|
|
pageId: string,
|
|
trx?: KyselyTransaction,
|
|
) {
|
|
return this.updatePages(updatablePage, [pageId], trx);
|
|
}
|
|
|
|
async updatePages(
|
|
updatePageData: UpdatablePage,
|
|
pageIds: string[],
|
|
trx?: KyselyTransaction,
|
|
) {
|
|
return dbOrTx(this.db, trx)
|
|
.updateTable('pages')
|
|
.set({ ...updatePageData, updatedAt: new Date() })
|
|
.where(
|
|
pageIds.some((pageId) => !isValidUUID(pageId)) ? 'slugId' : 'id',
|
|
'in',
|
|
pageIds,
|
|
)
|
|
.executeTakeFirst();
|
|
}
|
|
|
|
async insertPage(
|
|
insertablePage: InsertablePage,
|
|
trx?: KyselyTransaction,
|
|
): Promise<Page> {
|
|
const db = dbOrTx(this.db, trx);
|
|
return db
|
|
.insertInto('pages')
|
|
.values(insertablePage)
|
|
.returning(this.baseFields)
|
|
.executeTakeFirst();
|
|
}
|
|
|
|
async deletePage(pageId: string): Promise<void> {
|
|
let query = this.db.deleteFrom('pages');
|
|
|
|
if (isValidUUID(pageId)) {
|
|
query = query.where('id', '=', pageId);
|
|
} else {
|
|
query = query.where('slugId', '=', pageId);
|
|
}
|
|
|
|
await query.execute();
|
|
}
|
|
|
|
async getRecentPagesInSpace(spaceId: string, pagination: PaginationOptions) {
|
|
const query = this.db
|
|
.selectFrom('pages')
|
|
.select(this.baseFields)
|
|
.select((eb) => this.withSpace(eb))
|
|
.where('spaceId', '=', spaceId)
|
|
.orderBy('updatedAt', 'desc');
|
|
|
|
const result = executeWithPagination(query, {
|
|
page: pagination.page,
|
|
perPage: pagination.limit,
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
async getRecentPages(userId: string, pagination: PaginationOptions) {
|
|
const userSpaceIds = await this.spaceMemberRepo.getUserSpaceIds(userId);
|
|
|
|
const query = this.db
|
|
.selectFrom('pages')
|
|
.select(this.baseFields)
|
|
.select((eb) => this.withSpace(eb))
|
|
.where('spaceId', 'in', userSpaceIds)
|
|
.orderBy('updatedAt', 'desc');
|
|
|
|
const hasEmptyIds = userSpaceIds.length === 0;
|
|
const result = executeWithPagination(query, {
|
|
page: pagination.page,
|
|
perPage: pagination.limit,
|
|
hasEmptyIds,
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
withSpace(eb: ExpressionBuilder<DB, 'pages'>) {
|
|
return jsonObjectFrom(
|
|
eb
|
|
.selectFrom('spaces')
|
|
.select(['spaces.id', 'spaces.name', 'spaces.slug'])
|
|
.whereRef('spaces.id', '=', 'pages.spaceId'),
|
|
).as('space');
|
|
}
|
|
|
|
withCreator(eb: ExpressionBuilder<DB, 'pages'>) {
|
|
return jsonObjectFrom(
|
|
eb
|
|
.selectFrom('users')
|
|
.select(['users.id', 'users.name', 'users.avatarUrl'])
|
|
.whereRef('users.id', '=', 'pages.creatorId'),
|
|
).as('creator');
|
|
}
|
|
|
|
withLastUpdatedBy(eb: ExpressionBuilder<DB, 'pages'>) {
|
|
return jsonObjectFrom(
|
|
eb
|
|
.selectFrom('users')
|
|
.select(['users.id', 'users.name', 'users.avatarUrl'])
|
|
.whereRef('users.id', '=', 'pages.lastUpdatedById'),
|
|
).as('lastUpdatedBy');
|
|
}
|
|
|
|
withContributors(eb: ExpressionBuilder<DB, 'pages'>) {
|
|
return jsonArrayFrom(
|
|
eb
|
|
.selectFrom('users')
|
|
.select(['users.id', 'users.name', 'users.avatarUrl'])
|
|
.whereRef('users.id', '=', sql`ANY(${eb.ref('pages.contributorIds')})`),
|
|
).as('contributors');
|
|
}
|
|
|
|
async getPageAndDescendants(parentPageId: string) {
|
|
return this.db
|
|
.withRecursive('page_hierarchy', (db) =>
|
|
db
|
|
.selectFrom('pages')
|
|
.select([
|
|
'id',
|
|
'slugId',
|
|
'title',
|
|
'icon',
|
|
'content',
|
|
'parentPageId',
|
|
'spaceId',
|
|
'workspaceId',
|
|
])
|
|
.where('id', '=', parentPageId)
|
|
.unionAll((exp) =>
|
|
exp
|
|
.selectFrom('pages as p')
|
|
.select([
|
|
'p.id',
|
|
'p.slugId',
|
|
'p.title',
|
|
'p.icon',
|
|
'p.content',
|
|
'p.parentPageId',
|
|
'p.spaceId',
|
|
'p.workspaceId',
|
|
])
|
|
.innerJoin('page_hierarchy as ph', 'p.parentPageId', 'ph.id'),
|
|
),
|
|
)
|
|
.selectFrom('page_hierarchy')
|
|
.selectAll()
|
|
.execute();
|
|
}
|
|
}
|