mirror of
https://github.com/docmost/docmost.git
synced 2026-08-22 14:02:12 +10:00
Merge branch 'main' into feat/integrations
This commit is contained in:
@@ -51,19 +51,19 @@
|
||||
"@nestjs-labs/nestjs-ioredis": "11.0.4",
|
||||
"@nestjs/bullmq": "11.0.4",
|
||||
"@nestjs/cache-manager": "3.1.3",
|
||||
"@nestjs/common": "11.1.27",
|
||||
"@nestjs/common": "11.1.28",
|
||||
"@nestjs/config": "4.0.4",
|
||||
"@nestjs/core": "11.1.27",
|
||||
"@nestjs/event-emitter": "3.1.0",
|
||||
"@nestjs/jwt": "11.0.2",
|
||||
"@nestjs/mapped-types": "2.1.1",
|
||||
"@nestjs/passport": "11.0.5",
|
||||
"@nestjs/platform-fastify": "11.1.27",
|
||||
"@nestjs/platform-socket.io": "11.1.27",
|
||||
"@nestjs/platform-fastify": "11.1.28",
|
||||
"@nestjs/platform-socket.io": "11.1.28",
|
||||
"@nestjs/schedule": "6.1.3",
|
||||
"@nestjs/terminus": "11.1.1",
|
||||
"@nestjs/throttler": "6.5.0",
|
||||
"@nestjs/websockets": "11.1.27",
|
||||
"@nestjs/websockets": "11.1.28",
|
||||
"@node-saml/passport-saml": "5.1.0",
|
||||
"@socket.io/redis-adapter": "8.3.0",
|
||||
"ai": "6.0.134",
|
||||
@@ -90,8 +90,8 @@
|
||||
"ldapts": "8.1.7",
|
||||
"mammoth": "1.12.0",
|
||||
"mime-types": "3.0.2",
|
||||
"msgpackr": "^1.11.9",
|
||||
"nanoid": "5.1.7",
|
||||
"msgpackr": "1.11.9",
|
||||
"nanoid": "5.1.16",
|
||||
"nestjs-cls": "6.2.0",
|
||||
"nestjs-kysely": "3.1.2",
|
||||
"nestjs-pino": "4.6.1",
|
||||
@@ -102,7 +102,7 @@
|
||||
"passport-google-oauth20": "2.0.0",
|
||||
"passport-jwt": "4.0.1",
|
||||
"pg-tsquery": "8.4.2",
|
||||
"pgvector": "^0.2.1",
|
||||
"pgvector": "0.2.1",
|
||||
"pino-http": "11.0.0",
|
||||
"pino-pretty": "13.1.3",
|
||||
"postgres": "3.4.8",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { StarterKit } from '@tiptap/starter-kit';
|
||||
import { Document } from '@tiptap/extension-document';
|
||||
import { TextAlign } from '@tiptap/extension-text-align';
|
||||
import { Superscript } from '@tiptap/extension-superscript';
|
||||
import SubScript from '@tiptap/extension-subscript';
|
||||
@@ -45,10 +46,18 @@ import {
|
||||
TransclusionSource,
|
||||
TransclusionReference,
|
||||
BaseEmbed,
|
||||
Footnotes,
|
||||
Footnote,
|
||||
FootnoteReference,
|
||||
IntegrationLink,
|
||||
IntegrationMention,
|
||||
} from '@docmost/editor-ext';
|
||||
import { generateText, getSchema, JSONContent } from '@tiptap/core';
|
||||
import {
|
||||
extensions as coreExtensions,
|
||||
generateText,
|
||||
getSchema,
|
||||
JSONContent,
|
||||
} from '@tiptap/core';
|
||||
import { generateHTML, generateJSON } from '../common/helpers/prosemirror/html';
|
||||
// @tiptap/html library works best for generating prosemirror json state but not HTML
|
||||
// see: https://github.com/ueberdosis/tiptap/issues/5352
|
||||
@@ -59,12 +68,17 @@ import * as Y from 'yjs';
|
||||
import { Logger } from '@nestjs/common';
|
||||
|
||||
export const tiptapExtensions = [
|
||||
coreExtensions.TextDirection.configure({ direction: 'auto' }),
|
||||
StarterKit.configure({
|
||||
document: false,
|
||||
codeBlock: false,
|
||||
link: false,
|
||||
trailingNode: false,
|
||||
heading: false,
|
||||
}),
|
||||
Document.extend({
|
||||
content: 'block+ footnotes?',
|
||||
}),
|
||||
Heading,
|
||||
UniqueID.configure({
|
||||
types: ['heading', 'paragraph', 'transclusionSource'],
|
||||
@@ -113,6 +127,9 @@ export const tiptapExtensions = [
|
||||
TransclusionSource,
|
||||
TransclusionReference,
|
||||
BaseEmbed,
|
||||
Footnotes,
|
||||
Footnote,
|
||||
FootnoteReference,
|
||||
IntegrationLink,
|
||||
IntegrationMention
|
||||
] as any;
|
||||
|
||||
@@ -53,8 +53,14 @@ import { EnvironmentService } from '../../integrations/environment/environment.s
|
||||
import { TokenService } from '../auth/services/token.service';
|
||||
import { JwtAttachmentPayload, JwtType } from '../auth/dto/jwt-payload';
|
||||
import * as path from 'path';
|
||||
import { AttachmentInfoDto, RemoveIconDto } from './dto/attachment.dto';
|
||||
import {
|
||||
AttachmentInfoDto,
|
||||
PageIdDto,
|
||||
RemoveIconDto,
|
||||
} from './dto/attachment.dto';
|
||||
import { PaginationOptions } from '@docmost/db/pagination/pagination-options';
|
||||
import { PageAccessService } from '../page/page-access/page-access.service';
|
||||
import { DomainService } from '../../integrations/environment/domain.service';
|
||||
import { AuditEvent, AuditResource } from '../../common/events/audit-events';
|
||||
import {
|
||||
AUDIT_SERVICE,
|
||||
@@ -75,6 +81,7 @@ export class AttachmentController {
|
||||
private readonly environmentService: EnvironmentService,
|
||||
private readonly tokenService: TokenService,
|
||||
private readonly pageAccessService: PageAccessService,
|
||||
private readonly domainService: DomainService,
|
||||
@Inject(AUDIT_SERVICE) private readonly auditService: IAuditService,
|
||||
) {}
|
||||
|
||||
@@ -151,7 +158,10 @@ export class AttachmentController {
|
||||
},
|
||||
});
|
||||
|
||||
return res.send(fileResponse);
|
||||
return res.send({
|
||||
...fileResponse,
|
||||
url: this.buildFileUrl(workspace, fileResponse),
|
||||
});
|
||||
} catch (err: any) {
|
||||
if (err?.statusCode === 413) {
|
||||
const errMessage = `File too large. Exceeds the ${this.environmentService.getFileUploadSizeLimit()} limit`;
|
||||
@@ -411,7 +421,37 @@ export class AttachmentController {
|
||||
|
||||
await this.pageAccessService.validateCanView(page, user);
|
||||
|
||||
return attachment;
|
||||
return { ...attachment, url: this.buildFileUrl(workspace, attachment) };
|
||||
}
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post('pages/attachments')
|
||||
async getPageAttachments(
|
||||
@Body() dto: PageIdDto,
|
||||
@Body() pagination: PaginationOptions,
|
||||
@AuthUser() user: User,
|
||||
@AuthWorkspace() workspace: Workspace,
|
||||
) {
|
||||
const page = await this.pageRepo.findById(dto.pageId);
|
||||
if (!page || page.workspaceId !== workspace.id) {
|
||||
throw new NotFoundException('Page not found');
|
||||
}
|
||||
|
||||
await this.pageAccessService.validateCanView(page, user);
|
||||
|
||||
const result = await this.attachmentRepo.findPageAttachments(
|
||||
page.id,
|
||||
pagination,
|
||||
);
|
||||
|
||||
return {
|
||||
...result,
|
||||
items: result.items.map((attachment) => ({
|
||||
...attachment,
|
||||
url: this.buildFileUrl(workspace, attachment),
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
@UseGuards(JwtAuthGuard)
|
||||
@@ -465,6 +505,10 @@ export class AttachmentController {
|
||||
}
|
||||
}
|
||||
|
||||
private buildFileUrl(workspace: Workspace, attachment: Attachment): string {
|
||||
return `${this.domainService.getUrl(workspace.hostname)}/api/files/${attachment.id}/${encodeURIComponent(attachment.fileName)}`;
|
||||
}
|
||||
|
||||
private async sendFileResponse(
|
||||
req: FastifyRequest,
|
||||
res: FastifyReply,
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
import { IsEnum, IsIn, IsNotEmpty, IsOptional, IsUUID } from 'class-validator';
|
||||
import {
|
||||
IsEnum,
|
||||
IsIn,
|
||||
IsNotEmpty,
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsUUID,
|
||||
} from 'class-validator';
|
||||
import { AttachmentType } from '../attachment.constants';
|
||||
|
||||
export class AttachmentInfoDto {
|
||||
@@ -7,6 +14,12 @@ export class AttachmentInfoDto {
|
||||
attachmentId: string;
|
||||
}
|
||||
|
||||
export class PageIdDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
pageId: string;
|
||||
}
|
||||
|
||||
export class RemoveIconDto {
|
||||
@IsEnum(AttachmentType)
|
||||
@IsIn([
|
||||
|
||||
@@ -810,6 +810,10 @@ export class PageService {
|
||||
throw new BadRequestException('Invalid move position');
|
||||
}
|
||||
|
||||
if (dto.parentPageId && dto.parentPageId === dto.pageId) {
|
||||
throw new BadRequestException('A page cannot be its own parent');
|
||||
}
|
||||
|
||||
let parentPageId = null;
|
||||
if (movedPage.parentPageId === dto.parentPageId) {
|
||||
parentPageId = undefined;
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectKysely } from 'nestjs-kysely';
|
||||
import { ExpressionBuilder, sql } from 'kysely';
|
||||
import { jsonObjectFrom } from 'kysely/helpers/postgres';
|
||||
import { DB } from '@docmost/db/types/db';
|
||||
import { KyselyDB, KyselyTransaction } from '@docmost/db/types/kysely.types';
|
||||
import { dbOrTx } from '@docmost/db/utils';
|
||||
import {
|
||||
@@ -8,6 +11,8 @@ import {
|
||||
UpdatableAttachment,
|
||||
} from '@docmost/db/types/entity.types';
|
||||
import { AttachmentType } from '../../../core/attachment/attachment.constants';
|
||||
import { PaginationOptions } from '@docmost/db/pagination/pagination-options';
|
||||
import { executeWithCursorPagination } from '@docmost/db/pagination/cursor-pagination';
|
||||
|
||||
@Injectable()
|
||||
export class AttachmentRepo {
|
||||
@@ -89,6 +94,41 @@ export class AttachmentRepo {
|
||||
.execute();
|
||||
}
|
||||
|
||||
async findPageAttachments(pageId: string, pagination: PaginationOptions) {
|
||||
let query = this.db
|
||||
.selectFrom('attachments')
|
||||
.select(this.baseFields)
|
||||
.select((eb) => this.withCreator(eb))
|
||||
.where('pageId', '=', pageId)
|
||||
.where('type', '=', AttachmentType.File)
|
||||
.where('deletedAt', 'is', null);
|
||||
|
||||
if (pagination.query) {
|
||||
query = query.where(
|
||||
sql`f_unaccent(file_name)`,
|
||||
'ilike',
|
||||
sql`f_unaccent(${'%' + pagination.query + '%'})`,
|
||||
);
|
||||
}
|
||||
|
||||
return executeWithCursorPagination(query, {
|
||||
perPage: pagination.limit,
|
||||
cursor: pagination.cursor,
|
||||
beforeCursor: pagination.beforeCursor,
|
||||
fields: [{ expression: 'id', direction: 'desc' }],
|
||||
parseCursor: (cursor) => ({ id: cursor.id }),
|
||||
});
|
||||
}
|
||||
|
||||
withCreator(eb: ExpressionBuilder<DB, 'attachments'>) {
|
||||
return jsonObjectFrom(
|
||||
eb
|
||||
.selectFrom('users')
|
||||
.select(['users.id', 'users.name', 'users.avatarUrl'])
|
||||
.whereRef('users.id', '=', 'attachments.creatorId'),
|
||||
).as('creator');
|
||||
}
|
||||
|
||||
async findByIds(
|
||||
ids: string[],
|
||||
opts?: {
|
||||
|
||||
+1
-1
Submodule apps/server/src/ee updated: 592ef10a71...5ec445b7b2
Reference in New Issue
Block a user