Archives and restore now works to S3 and file system
If a file is stored on the file system and s3 settings later become available it is still correctly fetched from the file system.
This commit is contained in:
@@ -18,6 +18,7 @@ export function getArchiveObj(creatureId){
|
|||||||
const logs = CreatureLogs.find({creatureId}).fetch();
|
const logs = CreatureLogs.find({creatureId}).fetch();
|
||||||
let archiveCreature = {
|
let archiveCreature = {
|
||||||
meta: {
|
meta: {
|
||||||
|
type: 'DiceCloud V2 Creature Archive',
|
||||||
schemaVersion: SCHEMA_VERSION,
|
schemaVersion: SCHEMA_VERSION,
|
||||||
archiveDate: new Date(),
|
archiveDate: new Date(),
|
||||||
},
|
},
|
||||||
@@ -33,7 +34,7 @@ export function getArchiveObj(creatureId){
|
|||||||
export function archiveCreature(creatureId){
|
export function archiveCreature(creatureId){
|
||||||
const archive = getArchiveObj(creatureId);
|
const archive = getArchiveObj(creatureId);
|
||||||
const buffer = Buffer.from(JSON.stringify(archive, null, 2));
|
const buffer = Buffer.from(JSON.stringify(archive, null, 2));
|
||||||
const result = ArchiveCreatureFiles.write(buffer, {
|
ArchiveCreatureFiles.write(buffer, {
|
||||||
fileName: `${archive.creature.name || archive.creature._id}.json`,
|
fileName: `${archive.creature.name || archive.creature._id}.json`,
|
||||||
type: 'application/json',
|
type: 'application/json',
|
||||||
userId: archive.creature.owner,
|
userId: archive.creature.owner,
|
||||||
@@ -42,9 +43,13 @@ export function archiveCreature(creatureId){
|
|||||||
creatureId: archive.creature._id,
|
creatureId: archive.creature._id,
|
||||||
creatureName: archive.creature.name,
|
creatureName: archive.creature.name,
|
||||||
},
|
},
|
||||||
});
|
}, (error) => {
|
||||||
removeCreatureWork(creatureId);
|
if (error){
|
||||||
return result;
|
throw error;
|
||||||
|
} else {
|
||||||
|
removeCreatureWork(creatureId);
|
||||||
|
}
|
||||||
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
const archiveCreatureToFile = new ValidatedMethod({
|
const archiveCreatureToFile = new ValidatedMethod({
|
||||||
|
|||||||
@@ -1,66 +0,0 @@
|
|||||||
import SimpleSchema from 'simpl-schema';
|
|
||||||
import { ValidatedMethod } from 'meteor/mdg:validated-method';
|
|
||||||
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
|
|
||||||
import { assertOwnership } from '/imports/api/creature/creatures/creaturePermissions.js';
|
|
||||||
import Creatures from '/imports/api/creature/creatures/Creatures.js';
|
|
||||||
import CreatureProperties from '/imports/api/creature/creatureProperties/CreatureProperties.js';
|
|
||||||
import CreatureLogs from '/imports/api/creature/log/CreatureLogs.js';
|
|
||||||
import Experiences from '/imports/api/creature/experience/Experiences.js';
|
|
||||||
import { removeCreatureWork } from '/imports/api/creature/creatures/methods/removeCreature.js';
|
|
||||||
import ArchivedCreatures from '/imports/api/creature/archive/ArchivedCreatures.js';
|
|
||||||
|
|
||||||
function archiveCreature(creatureId){
|
|
||||||
// Build the archive document
|
|
||||||
const creature = Creatures.findOne(creatureId);
|
|
||||||
const properties = CreatureProperties.find({'ancestors.id': creatureId}).fetch();
|
|
||||||
const experiences = Experiences.find({creatureId}).fetch();
|
|
||||||
const logs = CreatureLogs.find({creatureId}).fetch();
|
|
||||||
let archiveCreature = {
|
|
||||||
owner: creature.owner,
|
|
||||||
archiveDate: new Date(),
|
|
||||||
creature,
|
|
||||||
properties,
|
|
||||||
experiences,
|
|
||||||
logs,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Insert it
|
|
||||||
let id = ArchivedCreatures.insert(archiveCreature);
|
|
||||||
|
|
||||||
// Remove the original creature
|
|
||||||
removeCreatureWork(creatureId);
|
|
||||||
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
const archiveCreatures = new ValidatedMethod({
|
|
||||||
name: 'Creatures.methods.archiveCreatures',
|
|
||||||
validate: new SimpleSchema({
|
|
||||||
creatureIds: {
|
|
||||||
type: Array,
|
|
||||||
max: 10,
|
|
||||||
},
|
|
||||||
'creatureIds.$': {
|
|
||||||
type: String,
|
|
||||||
regEx: SimpleSchema.RegEx.Id,
|
|
||||||
},
|
|
||||||
}).validator(),
|
|
||||||
mixins: [RateLimiterMixin],
|
|
||||||
rateLimit: {
|
|
||||||
numRequests: 1,
|
|
||||||
timeInterval: 5000,
|
|
||||||
},
|
|
||||||
run({creatureIds}) {
|
|
||||||
for (let id of creatureIds){
|
|
||||||
assertOwnership(id, this.userId)
|
|
||||||
}
|
|
||||||
let archivedIds = [];
|
|
||||||
for (let id of creatureIds){
|
|
||||||
let archivedId = archiveCreature(id);
|
|
||||||
archivedIds.push(archivedId);
|
|
||||||
}
|
|
||||||
return archivedIds;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export default archiveCreatures;
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import { promises as fs } from 'fs';
|
|
||||||
|
|
||||||
// Read a file and return the result
|
|
||||||
export default function read(file){
|
|
||||||
return fs.readFile(file.path, 'utf8');
|
|
||||||
}
|
|
||||||
@@ -8,13 +8,12 @@ import CreatureLogs from '/imports/api/creature/log/CreatureLogs.js';
|
|||||||
import Experiences from '/imports/api/creature/experience/Experiences.js';
|
import Experiences from '/imports/api/creature/experience/Experiences.js';
|
||||||
import { removeCreatureWork } from '/imports/api/creature/creatures/methods/removeCreature.js';
|
import { removeCreatureWork } from '/imports/api/creature/creatures/methods/removeCreature.js';
|
||||||
import ArchiveCreatureFiles from '/imports/api/creature/archive/ArchiveCreatureFiles.js';
|
import ArchiveCreatureFiles from '/imports/api/creature/archive/ArchiveCreatureFiles.js';
|
||||||
import readFile from '/imports/api/creature/archive/methods/readFile.js';
|
|
||||||
let migrateArchive;
|
let migrateArchive;
|
||||||
if (Meteor.isServer){
|
if (Meteor.isServer){
|
||||||
migrateArchive = require('/imports/migrations/server/migrateArchive.js').default;
|
migrateArchive = require('/imports/migrations/server/migrateArchive.js').default;
|
||||||
}
|
}
|
||||||
|
|
||||||
function restoreCreature(file, archive){
|
function restoreCreature(archive){
|
||||||
if (SCHEMA_VERSION < archive.meta.schemaVersion){
|
if (SCHEMA_VERSION < archive.meta.schemaVersion){
|
||||||
throw new Meteor.Error('Incompatible',
|
throw new Meteor.Error('Incompatible',
|
||||||
'The archive file is from a newer version. Update required to read.')
|
'The archive file is from a newer version. Update required to read.')
|
||||||
@@ -72,12 +71,11 @@ const restoreCreaturefromFile = new ValidatedMethod({
|
|||||||
}
|
}
|
||||||
if (Meteor.isServer){
|
if (Meteor.isServer){
|
||||||
// Read the file data
|
// Read the file data
|
||||||
const string = await readFile(file);
|
const archive = await ArchiveCreatureFiles.readJSONFile(file);
|
||||||
const archive = JSON.parse(string);
|
restoreCreature(archive);
|
||||||
restoreCreature(file, archive);
|
|
||||||
//Remove the archive once the restore succeeded
|
|
||||||
ArchiveCreatureFiles.remove({_id: fileId})
|
|
||||||
}
|
}
|
||||||
|
//Remove the archive once the restore succeeded
|
||||||
|
ArchiveCreatureFiles.remove({_id: fileId});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -5,11 +5,12 @@ import { each, clone } from 'lodash';
|
|||||||
import { Random } from 'meteor/random';
|
import { Random } from 'meteor/random';
|
||||||
import { FilesCollection } from 'meteor/ostrio:files';
|
import { FilesCollection } from 'meteor/ostrio:files';
|
||||||
import stream from 'stream';
|
import stream from 'stream';
|
||||||
|
import S3 from 'aws-sdk/clients/s3';
|
||||||
|
|
||||||
import S3 from 'aws-sdk/clients/s3'; /* http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html */
|
|
||||||
/* See fs-extra and graceful-fs NPM packages */
|
/* See fs-extra and graceful-fs NPM packages */
|
||||||
/* For better i/o performance */
|
/* For better i/o performance */
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
import { promises as fsp } from 'fs';
|
||||||
|
|
||||||
/* Example: S3='{"s3":{"key": "xxx", "secret": "xxx", "bucket": "xxx", "endpoint": "xxx""}}' meteor */
|
/* Example: S3='{"s3":{"key": "xxx", "secret": "xxx", "bucket": "xxx", "endpoint": "xxx""}}' meteor */
|
||||||
if (process.env.S3) {
|
if (process.env.S3) {
|
||||||
@@ -17,6 +18,10 @@ if (process.env.S3) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const s3Conf = Meteor.settings.s3 || {};
|
const s3Conf = Meteor.settings.s3 || {};
|
||||||
|
Meteor.settings.useS3 = !!(
|
||||||
|
s3Conf && s3Conf.key && s3Conf.secret && s3Conf.bucket && s3Conf.endpoint
|
||||||
|
);
|
||||||
|
|
||||||
const bound = Meteor.bindEnvironment((callback) => {
|
const bound = Meteor.bindEnvironment((callback) => {
|
||||||
return callback();
|
return callback();
|
||||||
});
|
});
|
||||||
@@ -25,7 +30,7 @@ let createS3FilesCollection;
|
|||||||
|
|
||||||
/* Check settings existence in `Meteor.settings` */
|
/* Check settings existence in `Meteor.settings` */
|
||||||
/* This is the best practice for app security */
|
/* This is the best practice for app security */
|
||||||
if (Meteor.isServer && s3Conf && s3Conf.key && s3Conf.secret && s3Conf.bucket && s3Conf.endpoint) {
|
if (Meteor.isServer && Meteor.settings.useS3) {
|
||||||
// Create a new S3 object
|
// Create a new S3 object
|
||||||
const s3 = new S3({
|
const s3 = new S3({
|
||||||
accessKeyId: s3Conf.key,
|
accessKeyId: s3Conf.key,
|
||||||
@@ -98,7 +103,6 @@ if (Meteor.isServer && s3Conf && s3Conf.key && s3Conf.secret && s3Conf.bucket &&
|
|||||||
interceptDownload(http, fileRef, version) {
|
interceptDownload(http, fileRef, version) {
|
||||||
// Intercept access to the file
|
// Intercept access to the file
|
||||||
// And redirect request to AWS:S3
|
// And redirect request to AWS:S3
|
||||||
|
|
||||||
let path;
|
let path;
|
||||||
|
|
||||||
if (fileRef && fileRef.versions && fileRef.versions[version] && fileRef.versions[version].meta && fileRef.versions[version].meta.pipePath) {
|
if (fileRef && fileRef.versions && fileRef.versions[version] && fileRef.versions[version].meta && fileRef.versions[version].meta.pipePath) {
|
||||||
@@ -189,6 +193,23 @@ if (Meteor.isServer && s3Conf && s3Conf.key && s3Conf.secret && s3Conf.bucket &&
|
|||||||
//remove original file from database
|
//remove original file from database
|
||||||
_origRemove.call(this, search);
|
_origRemove.call(this, search);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
collection.readJSONFile = async function(file){
|
||||||
|
// If there is the pipepath, use s3 to get the file
|
||||||
|
if (file?.versions?.original?.meta?.pipePath){
|
||||||
|
const path = file.versions.original.meta.pipePath;
|
||||||
|
const data = await s3.getObject({
|
||||||
|
Bucket: s3Conf.bucket,
|
||||||
|
Key: path
|
||||||
|
}).promise();
|
||||||
|
return JSON.parse(data.Body.toString('utf-8'));
|
||||||
|
} else {
|
||||||
|
// Otherwise use the normal filesystem
|
||||||
|
const fileString = await fsp.readFile(file.path, 'utf8');
|
||||||
|
return JSON.parse(fileString);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return collection;
|
return collection;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -202,13 +223,23 @@ if (Meteor.isServer && s3Conf && s3Conf.key && s3Conf.secret && s3Conf.bucket &&
|
|||||||
debug = Meteor.isProduction,
|
debug = Meteor.isProduction,
|
||||||
allowClientCode = false,
|
allowClientCode = false,
|
||||||
}){
|
}){
|
||||||
return new FilesCollection({
|
const collection = new FilesCollection({
|
||||||
collectionName,
|
collectionName,
|
||||||
storagePath,
|
storagePath,
|
||||||
onBeforeUpload,
|
onBeforeUpload,
|
||||||
debug,
|
debug,
|
||||||
allowClientCode,
|
allowClientCode,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (Meteor.isServer){
|
||||||
|
// Use the normal file system to read files
|
||||||
|
collection.readJSONFile = async function(file){
|
||||||
|
const fileString = await fsp.readFile(file.path, 'utf8');
|
||||||
|
return JSON.parse(fileString);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return collection;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user