mirror of
https://github.com/docmost/docmost.git
synced 2026-05-06 22:03:06 +08:00
fix: tighten export
This commit is contained in:
@@ -39,6 +39,8 @@ import {
|
|||||||
} from '../../common/helpers/prosemirror/utils';
|
} from '../../common/helpers/prosemirror/utils';
|
||||||
import { htmlToMarkdown } from '@docmost/editor-ext';
|
import { htmlToMarkdown } from '@docmost/editor-ext';
|
||||||
|
|
||||||
|
type AllowedAttachment = { id: string; fileName: string; filePath: string };
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ExportService {
|
export class ExportService {
|
||||||
private readonly logger = new Logger(ExportService.name);
|
private readonly logger = new Logger(ExportService.name);
|
||||||
@@ -272,6 +274,12 @@ export class ExportService {
|
|||||||
|
|
||||||
computeLocalPath(tree, format, null, '', slugIdToPath);
|
computeLocalPath(tree, format, null, '', slugIdToPath);
|
||||||
|
|
||||||
|
// Batch resolve attachments once for the whole export so we only run the
|
||||||
|
// owning-page view check a single time, regardless of page count.
|
||||||
|
const allowedAttachments = includeAttachments
|
||||||
|
? await this.resolveAccessibleAttachments(tree, userId, ignorePermissions)
|
||||||
|
: new Map<string, AllowedAttachment>();
|
||||||
|
|
||||||
const stack: { folder: JSZip; parentPageId: string | null }[] = [
|
const stack: { folder: JSZip; parentPageId: string | null }[] = [
|
||||||
{ folder: zip, parentPageId: null },
|
{ folder: zip, parentPageId: null },
|
||||||
];
|
];
|
||||||
@@ -301,7 +309,7 @@ export class ExportService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (includeAttachments) {
|
if (includeAttachments) {
|
||||||
await this.zipAttachments(updatedJsonContent, page.spaceId, folder);
|
await this.zipAttachments(updatedJsonContent, folder, allowedAttachments);
|
||||||
updatedJsonContent =
|
updatedJsonContent =
|
||||||
updateAttachmentUrlsToLocalPaths(updatedJsonContent);
|
updateAttachmentUrlsToLocalPaths(updatedJsonContent);
|
||||||
}
|
}
|
||||||
@@ -347,31 +355,80 @@ export class ExportService {
|
|||||||
zip.file('docmost-metadata.json', JSON.stringify(metadata, null, 2));
|
zip.file('docmost-metadata.json', JSON.stringify(metadata, null, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
async zipAttachments(prosemirrorJson: any, spaceId: string, zip: JSZip) {
|
async zipAttachments(
|
||||||
|
prosemirrorJson: any,
|
||||||
|
zip: JSZip,
|
||||||
|
allowed: Map<string, AllowedAttachment>,
|
||||||
|
) {
|
||||||
const attachmentIds = getAttachmentIds(prosemirrorJson);
|
const attachmentIds = getAttachmentIds(prosemirrorJson);
|
||||||
|
|
||||||
if (attachmentIds.length > 0) {
|
await Promise.all(
|
||||||
const attachments = await this.db
|
attachmentIds.map(async (id) => {
|
||||||
.selectFrom('attachments')
|
const attachment = allowed.get(id);
|
||||||
.select(['id', 'fileName', 'filePath'])
|
if (!attachment) return;
|
||||||
.where('id', 'in', attachmentIds)
|
try {
|
||||||
.where('spaceId', '=', spaceId)
|
const fileBuffer = await this.storageService.read(
|
||||||
.execute();
|
attachment.filePath,
|
||||||
|
);
|
||||||
|
const filePath = `/files/${attachment.id}/${attachment.fileName}`;
|
||||||
|
zip.file(filePath, fileBuffer);
|
||||||
|
} catch (err) {
|
||||||
|
this.logger.debug(`Attachment export error ${attachment.id}`, err);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
await Promise.all(
|
private async resolveAccessibleAttachments(
|
||||||
attachments.map(async (attachment) => {
|
tree: PageExportTree,
|
||||||
try {
|
userId: string | undefined,
|
||||||
const fileBuffer = await this.storageService.read(
|
ignorePermissions: boolean,
|
||||||
attachment.filePath,
|
): Promise<Map<string, AllowedAttachment>> {
|
||||||
);
|
const allAttachmentIds = new Set<string>();
|
||||||
const filePath = `/files/${attachment.id}/${attachment.fileName}`;
|
let spaceId: string | undefined;
|
||||||
zip.file(filePath, fileBuffer);
|
for (const siblings of Object.values(tree)) {
|
||||||
} catch (err) {
|
for (const page of siblings) {
|
||||||
this.logger.debug(`Attachment export error ${attachment.id}`, err);
|
if (!spaceId) spaceId = page.spaceId;
|
||||||
}
|
for (const id of getAttachmentIds(getProsemirrorContent(page.content))) {
|
||||||
}),
|
allAttachmentIds.add(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allAttachmentIds.size === 0 || !spaceId) {
|
||||||
|
return new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
const attachments = await this.db
|
||||||
|
.selectFrom('attachments')
|
||||||
|
.select(['id', 'fileName', 'filePath', 'pageId'])
|
||||||
|
.where('id', 'in', [...allAttachmentIds])
|
||||||
|
.where('spaceId', '=', spaceId)
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
let visible = attachments;
|
||||||
|
if (!ignorePermissions && userId) {
|
||||||
|
const ownerPageIds = [
|
||||||
|
...new Set(
|
||||||
|
attachments
|
||||||
|
.map((a) => a.pageId)
|
||||||
|
.filter((id): id is string => !!id),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
const accessible = ownerPageIds.length
|
||||||
|
? await this.pagePermissionRepo.filterAccessiblePageIds({
|
||||||
|
pageIds: ownerPageIds,
|
||||||
|
userId,
|
||||||
|
spaceId,
|
||||||
|
})
|
||||||
|
: [];
|
||||||
|
const accessibleSet = new Set(accessible);
|
||||||
|
visible = attachments.filter(
|
||||||
|
(a) => a.pageId && accessibleSet.has(a.pageId),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return new Map(visible.map((a) => [a.id, a]));
|
||||||
}
|
}
|
||||||
|
|
||||||
async turnPageMentionsToLinks(
|
async turnPageMentionsToLinks(
|
||||||
|
|||||||
Reference in New Issue
Block a user