feat: Allow export of collections as sync zip (#1013)
* feat: Allow export of collections as sync zip * test: Add spec
This commit is contained in:
@ -7,7 +7,7 @@ import { presentCollection, presentUser } from '../presenters';
|
|||||||
import { Collection, CollectionUser, Team, Event, User } from '../models';
|
import { Collection, CollectionUser, Team, Event, User } from '../models';
|
||||||
import { ValidationError, InvalidRequestError } from '../errors';
|
import { ValidationError, InvalidRequestError } from '../errors';
|
||||||
import { exportCollections } from '../logistics';
|
import { exportCollections } from '../logistics';
|
||||||
import { archiveCollection } from '../utils/zip';
|
import { archiveCollection, archiveCollections } from '../utils/zip';
|
||||||
import policy from '../policies';
|
import policy from '../policies';
|
||||||
|
|
||||||
const { authorize } = policy;
|
const { authorize } = policy;
|
||||||
@ -168,13 +168,12 @@ router.post('collections.export', auth(), async ctx => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
router.post('collections.exportAll', auth(), async ctx => {
|
router.post('collections.exportAll', auth(), async ctx => {
|
||||||
|
const { download = false } = ctx.body;
|
||||||
|
|
||||||
const user = ctx.state.user;
|
const user = ctx.state.user;
|
||||||
const team = await Team.findByPk(user.teamId);
|
const team = await Team.findByPk(user.teamId);
|
||||||
authorize(user, 'export', team);
|
authorize(user, 'export', team);
|
||||||
|
|
||||||
// async operation to create zip archive and email user
|
|
||||||
exportCollections(user.teamId, user.email);
|
|
||||||
|
|
||||||
await Event.create({
|
await Event.create({
|
||||||
name: 'collections.export',
|
name: 'collections.export',
|
||||||
teamId: user.teamId,
|
teamId: user.teamId,
|
||||||
@ -182,9 +181,24 @@ router.post('collections.exportAll', auth(), async ctx => {
|
|||||||
ip: ctx.request.ip,
|
ip: ctx.request.ip,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (download) {
|
||||||
|
const collections = await Collection.findAll({
|
||||||
|
where: { teamId: team.id },
|
||||||
|
order: [['name', 'ASC']],
|
||||||
|
});
|
||||||
|
const filePath = await archiveCollections(collections);
|
||||||
|
|
||||||
|
ctx.attachment(`${team.name}.zip`);
|
||||||
|
ctx.set('Content-Type', 'application/force-download');
|
||||||
|
ctx.body = fs.createReadStream(filePath);
|
||||||
|
} else {
|
||||||
|
// async operation to create zip archive and email user
|
||||||
|
exportCollections(user.teamId, user.email);
|
||||||
|
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
success: true,
|
success: true,
|
||||||
};
|
};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post('collections.update', auth(), async ctx => {
|
router.post('collections.update', auth(), async ctx => {
|
||||||
|
@ -120,6 +120,18 @@ describe('#collections.exportAll', async () => {
|
|||||||
|
|
||||||
expect(res.status).toEqual(200);
|
expect(res.status).toEqual(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should allow downloading directly', async () => {
|
||||||
|
const { admin } = await seed();
|
||||||
|
const res = await server.post('/api/collections.exportAll', {
|
||||||
|
body: { token: admin.getJwtToken(), download: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(res.status).toEqual(200);
|
||||||
|
expect(res.headers.get('content-type')).toEqual(
|
||||||
|
'application/force-download'
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('#collections.add_user', async () => {
|
describe('#collections.add_user', async () => {
|
||||||
|
@ -3,7 +3,7 @@ import Queue from 'bull';
|
|||||||
import debug from 'debug';
|
import debug from 'debug';
|
||||||
import mailer from './mailer';
|
import mailer from './mailer';
|
||||||
import { Collection, Team } from './models';
|
import { Collection, Team } from './models';
|
||||||
import { archiveCollection, archiveCollections } from './utils/zip';
|
import { archiveCollections } from './utils/zip';
|
||||||
|
|
||||||
const log = debug('logistics');
|
const log = debug('logistics');
|
||||||
const logisticsQueue = new Queue('logistics', process.env.REDIS_URL);
|
const logisticsQueue = new Queue('logistics', process.env.REDIS_URL);
|
||||||
@ -16,24 +16,6 @@ const queueOptions = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
async function exportAndEmailCollection(collectionId: string, email: string) {
|
|
||||||
log('Archiving collection', collectionId);
|
|
||||||
const collection = await Collection.findByPk(collectionId);
|
|
||||||
const filePath = await archiveCollection(collection);
|
|
||||||
|
|
||||||
log('Archive path', filePath);
|
|
||||||
|
|
||||||
mailer.export({
|
|
||||||
to: email,
|
|
||||||
attachments: [
|
|
||||||
{
|
|
||||||
filename: `${collection.name} Export.zip`,
|
|
||||||
path: filePath,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function exportAndEmailCollections(teamId: string, email: string) {
|
async function exportAndEmailCollections(teamId: string, email: string) {
|
||||||
log('Archiving team', teamId);
|
log('Archiving team', teamId);
|
||||||
const team = await Team.findByPk(teamId);
|
const team = await Team.findByPk(teamId);
|
||||||
@ -60,28 +42,12 @@ logisticsQueue.process(async job => {
|
|||||||
log('Process', job.data);
|
log('Process', job.data);
|
||||||
|
|
||||||
switch (job.data.type) {
|
switch (job.data.type) {
|
||||||
case 'export-collection':
|
|
||||||
return await exportAndEmailCollection(
|
|
||||||
job.data.collectionId,
|
|
||||||
job.data.email
|
|
||||||
);
|
|
||||||
case 'export-collections':
|
case 'export-collections':
|
||||||
return await exportAndEmailCollections(job.data.teamId, job.data.email);
|
return await exportAndEmailCollections(job.data.teamId, job.data.email);
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export const exportCollection = (collectionId: string, email: string) => {
|
|
||||||
logisticsQueue.add(
|
|
||||||
{
|
|
||||||
type: 'export-collection',
|
|
||||||
collectionId,
|
|
||||||
email,
|
|
||||||
},
|
|
||||||
queueOptions
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export const exportCollections = (teamId: string, email: string) => {
|
export const exportCollections = (teamId: string, email: string) => {
|
||||||
logisticsQueue.add(
|
logisticsQueue.add(
|
||||||
{
|
{
|
||||||
|
@ -169,6 +169,21 @@ export default function Api() {
|
|||||||
</Arguments>
|
</Arguments>
|
||||||
</Method>
|
</Method>
|
||||||
|
|
||||||
|
<Method method="collections.exportAll" label="Export all collections">
|
||||||
|
<Description>
|
||||||
|
Returns a zip file of all the collections or creates an async job
|
||||||
|
to send a zip file via email to the authenticated user. If
|
||||||
|
documents are nested then they will be nested in folders inside
|
||||||
|
the zip file.
|
||||||
|
</Description>
|
||||||
|
<Arguments>
|
||||||
|
<Argument
|
||||||
|
id="download"
|
||||||
|
description="Download as zip (default is email)"
|
||||||
|
/>
|
||||||
|
</Arguments>
|
||||||
|
</Method>
|
||||||
|
|
||||||
<Method method="collections.update" label="Update a collection">
|
<Method method="collections.update" label="Update a collection">
|
||||||
<Description>
|
<Description>
|
||||||
This method allows you to modify an already created collection.
|
This method allows you to modify an already created collection.
|
||||||
|
Reference in New Issue
Block a user