@@ -7,11 +7,10 @@ import sanitize from 'sanitize-html';
77import { Readable } from 'stream' ;
88import { DataSource } from 'typeorm' ;
99import util from 'util' ;
10+ import { v4 } from 'uuid' ;
1011import { ZipFile } from 'yazl' ;
1112
12- import { FastifyReply } from 'fastify' ;
13-
14- import { ItemType , getMimetype } from '@graasp/sdk' ;
13+ import { ItemSettings , ItemType , ItemTypeUnion , getMimetype } from '@graasp/sdk' ;
1514
1615import { BaseLogger } from '../../../../logger' ;
1716import { Repositories , buildRepositories } from '../../../../utils/repositories' ;
@@ -25,6 +24,7 @@ import { H5PService } from '../html/h5p/service';
2524import {
2625 DESCRIPTION_EXTENSION ,
2726 GRAASP_DOCUMENT_EXTENSION ,
27+ GRAASP_MANIFEST_FILENAME ,
2828 HTML_EXTENSION ,
2929 LINK_EXTENSION ,
3030 TXT_EXTENSION ,
@@ -33,6 +33,22 @@ import {
3333import { UnexpectedExportError } from './errors' ;
3434import { buildTextContent , getFilenameFromItem } from './utils' ;
3535
36+ /**
37+ * Defines the properties of an individual item in the graasp export format.
38+ * @property children Children items, if the item if of type FOLDER.
39+ * @property mimetype Mimetype of the item. Present if the item is not of type FOLDER.
40+ */
41+ export type GraaspExportItem = {
42+ id : string ;
43+ name : string ;
44+ type : ItemTypeUnion ;
45+ description : string | null ;
46+ settings : ItemSettings ;
47+ thumbnailFilename ?: string ;
48+ children ?: GraaspExportItem [ ] ;
49+ mimetype ?: string ;
50+ } ;
51+
3652const magic = new Magic ( MAGIC_MIME_TYPE ) ;
3753const asyncDetectFile = util . promisify ( magic . detectFile . bind ( magic ) ) ;
3854
@@ -281,14 +297,12 @@ export class ImportExportService {
281297 actor : Actor ,
282298 repositories : Repositories ,
283299 args : {
284- reply ;
285300 item : Item ;
286301 archiveRootPath : string ;
287302 archive : ZipFile ;
288303 } ,
289- logger : BaseLogger ,
290304 ) {
291- const { item, archiveRootPath, archive, reply } = args ;
305+ const { item, archiveRootPath, archive } = args ;
292306
293307 // save description in file
294308 if ( item . description ) {
@@ -304,17 +318,11 @@ export class ImportExportService {
304318 const children = await this . itemService . getChildren ( actor , repositories , item . id ) ;
305319 const result = await Promise . all (
306320 children . map ( ( child ) =>
307- this . _addItemToZip (
308- actor ,
309- repositories ,
310- {
311- item : child ,
312- archiveRootPath : folderPath ,
313- archive,
314- reply,
315- } ,
316- logger ,
317- ) ,
321+ this . _addItemToZip ( actor , repositories , {
322+ item : child ,
323+ archiveRootPath : folderPath ,
324+ archive,
325+ } ) ,
318326 ) ,
319327 ) ;
320328 // add empty folder
@@ -329,40 +337,133 @@ export class ImportExportService {
329337 return archive . addReadStream ( stream , path . join ( archiveRootPath , name ) ) ;
330338 }
331339
332- async export (
340+ /**
341+ * Recursively add items to the Graasp export file.
342+ * Note that the shortcut items are excluded for now, they will be included in a later release.
343+ * @param args item - the item to add
344+ * archive - reference to the zip file to which the files will be written
345+ * itemManifest - reference to the item manifest list
346+ * @returns A full manifest promise for the given item
347+ */
348+ private async addItemToGraaspExport (
333349 actor : Actor ,
334350 repositories : Repositories ,
335- { item, reply } : { item : Item ; reply : FastifyReply } ,
336- logger : BaseLogger ,
351+ args : {
352+ item : Item ;
353+ archive : ZipFile ;
354+ itemManifest : GraaspExportItem [ ] ;
355+ } ,
337356 ) {
357+ const { item, archive, itemManifest } = args ;
358+
359+ // assign the uuid to the exported items
360+ const exportItemId = v4 ( ) ;
361+ const itemPath = path . join ( path . dirname ( './' ) , exportItemId ) ;
362+
363+ // TODO EXPORT treat the shortcut items correctly
364+ // ignore the shortcuts
365+ if ( isItemType ( item , ItemType . SHORTCUT ) ) {
366+ return itemManifest ;
367+ }
368+
369+ // treat folder items recursively
370+ const childrenManifest : GraaspExportItem [ ] = [ ] ;
371+ if ( isItemType ( item , ItemType . FOLDER ) ) {
372+ const childrenItems = await this . itemService . getChildren ( actor , repositories , item . id , {
373+ ordered : true ,
374+ } ) ;
375+ for ( const child of childrenItems ) {
376+ await this . addItemToGraaspExport ( actor , repositories , {
377+ item : child ,
378+ archive,
379+ itemManifest : childrenManifest ,
380+ } ) ;
381+ }
382+
383+ itemManifest . push ( {
384+ id : exportItemId ,
385+ name : item . name ,
386+ description : item . description ,
387+ type : item . type ,
388+ settings : item . settings ,
389+ children : childrenManifest ,
390+ } ) ;
391+ return itemManifest ;
392+ }
393+
394+ // treat single items
395+ const { stream, name, mimetype } = await this . fetchItemData ( actor , repositories , item ) ;
396+
397+ itemManifest . push ( {
398+ id : exportItemId ,
399+ name,
400+ description : item . description ,
401+ type : item . type ,
402+ settings : item . settings ,
403+ mimetype,
404+ } ) ;
405+ archive . addReadStream ( stream , itemPath ) ;
406+ return itemManifest ;
407+ }
408+
409+ /**
410+ * Export the items recursively
411+ * @param item The root item
412+ * @returns A zip file promise
413+ */
414+ async exportRaw ( actor : Actor , repositories : Repositories , item : Item ) {
338415 // init archive
339416 const archive = new ZipFile ( ) ;
340417 archive . outputStream . on ( 'error' , function ( err ) {
341418 throw new UnexpectedExportError ( err ) ;
342419 } ) ;
343-
344420 // path used to index files in archive
345421 const rootPath = path . dirname ( './' ) ;
346422
347423 // import items in zip recursively
348- await this . _addItemToZip (
349- actor ,
350- repositories ,
351- {
352- item,
353- reply,
354- archiveRootPath : rootPath ,
355- archive,
356- } ,
357- logger ,
358- ) . catch ( ( error ) => {
424+ await this . _addItemToZip ( actor , repositories , {
425+ item,
426+ archiveRootPath : rootPath ,
427+ archive,
428+ } ) . catch ( ( error ) => {
359429 throw new UnexpectedExportError ( error ) ;
360430 } ) ;
361431
362432 archive . end ( ) ;
363433 return archive ;
364434 }
365435
436+ /**
437+ * Export the items recursively in the Graasp export format
438+ * @param item The root item
439+ * @returns A zip file promise
440+ */
441+ async exportGraasp ( actor : Actor , repositories : Repositories , item : Item ) {
442+ // init archive
443+ const archive = new ZipFile ( ) ;
444+ archive . outputStream . on ( 'error' , function ( err ) {
445+ throw new UnexpectedExportError ( err ) ;
446+ } ) ;
447+ // path used to index files in archive
448+ const rootPath = path . dirname ( './' ) ;
449+
450+ const manifest = await this . addItemToGraaspExport ( actor , repositories , {
451+ item,
452+ archive,
453+ itemManifest : [ ] ,
454+ } ) . catch ( ( error ) => {
455+ throw new UnexpectedExportError ( error ) ;
456+ } ) ;
457+
458+ archive . addReadStream (
459+ Readable . from ( JSON . stringify ( manifest ) ) ,
460+ path . join ( rootPath , GRAASP_MANIFEST_FILENAME ) ,
461+ ) ;
462+
463+ archive . end ( ) ;
464+ return archive ;
465+ }
466+
366467 /**
367468 * Util recursive function that create graasp item given folder content
368469 * @param actor
0 commit comments