Files
outline/server/utils/zip.js
Huss 8e2b19dc7a feat: private content (#1137)
* save images as private and serve via signed url from images.info api

* download private images to directory on export

* fix lint errors

* private s3 default, AWS.s3 module level scope, default s3 url expiry

* combine regex to one, and only replace when there are matches

* fix lint

* code not needed anymore, remove

* updates after pulling master

* revert the uploadToS3FromUrl url return

* use model gettr to compact code, rename to attachments api

* basic checking of document read permission to allow attachment viewing

* fix: Continue to upload avatars as public
fix: Allow redirect for non-private attachments

* add support for publicly shared documents

* catch errors which crash the app during zip export and user creation

* add tests

* enable AWS signature v4 for s3

* switch to use factories to build models for testing

* add isDocker flag for local serving of attachment redirect url

* fix redirect tests

Co-authored-by: Tom Moor <tom.moor@gmail.com>
2020-02-12 19:40:44 -08:00

82 lines
2.2 KiB
JavaScript

// @flow
import fs from 'fs';
import JSZip from 'jszip';
import tmp from 'tmp';
import unescape from '../../shared/utils/unescape';
import { Attachment, Collection, Document } from '../models';
import { getImageByKey } from './s3';
import bugsnag from 'bugsnag';
async function addToArchive(zip, documents) {
for (const doc of documents) {
const document = await Document.findByPk(doc.id);
let text = unescape(document.text);
const attachments = await Attachment.findAll({
where: { documentId: document.id },
});
for (const attachment of attachments) {
await addImageToArchive(zip, attachment.key);
text = text.replace(attachment.redirectUrl, encodeURI(attachment.key));
}
zip.file(`${document.title}.md`, text);
if (doc.children && doc.children.length) {
const folder = zip.folder(document.title);
await addToArchive(folder, doc.children);
}
}
}
async function addImageToArchive(zip, key) {
try {
const img = await getImageByKey(key);
zip.file(key, img, { createFolders: true });
} catch (err) {
if (process.env.NODE_ENV === 'production') {
bugsnag.notify(err);
} else {
// error during file retrieval
console.error(err);
}
}
}
async function archiveToPath(zip) {
return new Promise((resolve, reject) => {
tmp.file({ prefix: 'export-', postfix: '.zip' }, (err, path) => {
if (err) return reject(err);
zip
.generateNodeStream({ type: 'nodebuffer', streamFiles: true })
.pipe(fs.createWriteStream(path))
.on('finish', () => resolve(path))
.on('error', reject);
});
});
}
export async function archiveCollection(collection: Collection) {
const zip = new JSZip();
if (collection.documentStructure) {
await addToArchive(zip, collection.documentStructure);
}
return archiveToPath(zip);
}
export async function archiveCollections(collections: Collection[]) {
const zip = new JSZip();
for (const collection of collections) {
if (collection.documentStructure) {
const folder = zip.folder(collection.name);
await addToArchive(folder, collection.documentStructure);
}
}
return archiveToPath(zip);
}