Archive now uses file system instead of collection

This commit is contained in:
Stefan Zermatten
2021-12-19 12:20:09 +02:00
parent 211659f759
commit 1e10d8751b
11 changed files with 223 additions and 24 deletions

View File

@@ -0,0 +1,18 @@
import { FilesCollection } from 'meteor/ostrio:files';
const ArchiveCreatureFiles = new FilesCollection({
collectionName: 'archiveCreatureFiles',
allowClientCode: false, // Disallow remove files from Client
storagePath: '/DiceCloud/uploads/',
onBeforeUpload(file) {
// Allow upload files under 10MB, and only in json format
if (file.size > 10485760) {
return 'Please upload with size equal or less than 10MB';
}
if (!/json/i.test(file.extension)){
return 'Please upload only a JSON file';
}
}
});
export default ArchiveCreatureFiles;

View File

@@ -0,0 +1,70 @@
import SCHEMA_VERSION from '/imports/constants/SCHEMA_VERSION.js';
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 ArchiveCreatureFiles from '/imports/api/creature/archive/ArchiveCreatureFiles.js';
export function getArchiveObj(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 = {
meta: {
archiveDate: new Date(),
schemaVersion: SCHEMA_VERSION,
},
creature,
properties,
experiences,
logs,
};
return archiveCreature;
}
export function writeArchiveCreatureFile(archive){
const buffer = Buffer.from(JSON.stringify(archive, null, 2));
return ArchiveCreatureFiles.write(buffer, {
fileName: `${archive.creature.name || archive.creature._id}.json`,
type: 'application/json',
userId: archive.creature.owner,
meta: {
schemaVersion: SCHEMA_VERSION,
creatureId: archive.creature._id,
creatureName: archive.creature.name,
},
});
}
const archiveCreatureToFile = new ValidatedMethod({
name: 'Creatures.methods.archiveCreatureToFile',
validate: new SimpleSchema({
'creatureId': {
type: String,
regEx: SimpleSchema.RegEx.Id,
},
}).validator(),
mixins: [RateLimiterMixin],
rateLimit: {
numRequests: 10,
timeInterval: 5000,
},
async run({creatureId}) {
assertOwnership(creatureId, this.userId);
if (Meteor.isServer){
const archive = getArchiveObj(creatureId);
writeArchiveCreatureFile(archive);
}
removeCreatureWork(creatureId);
},
});
export default archiveCreatureToFile;

View File

@@ -1,2 +1,4 @@
import '/imports/api/creature/archive/methods/archiveCreatures.js';
import '/imports/api/creature/archive/methods/archiveCreatureToFile.js';
import '/imports/api/creature/archive/methods/restoreCreatures.js';
import '/imports/api/creature/archive/methods/restoreCreatureFromFile.js';

View File

@@ -0,0 +1,6 @@
import { promises as fs } from 'fs';
// Read a file and return the result
export default function read(file){
return fs.readFile(file.path, 'utf8');
}

View File

@@ -0,0 +1,80 @@
import SCHEMA_VERSION from '/imports/constants/SCHEMA_VERSION.js';
import SimpleSchema from 'simpl-schema';
import { ValidatedMethod } from 'meteor/mdg:validated-method';
import { RateLimiterMixin } from 'ddp-rate-limiter-mixin';
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 ArchiveCreatureFiles from '/imports/api/creature/archive/ArchiveCreatureFiles.js';
import readFile from '/imports/api/creature/archive/methods/readFile.js';
function restoreCreature(file, archive){
if (SCHEMA_VERSION < file.meta.schemaVersion){
throw new Meteor.Error('Incompatible',
'The archive file is from a newer version. Update required to read.')
}
// Migrate and verify the archive meets the current schema
// migrateArchive(archive, file.meta.schemaVersion);
// Insert the creature sub documents
// They still have their original _id's
Creatures.insert(archive.creature);
try {
// Add all the properties
if (archive.properties && archive.properties.length){
CreatureProperties.batchInsert(archive.properties);
}
if (archive.experiences && archive.experiences.length){
Experiences.batchInsert(archive.experiences);
}
if (archive.logs && archive.logs.length){
CreatureLogs.batchInsert(archive.logs);
}
} catch (e) {
// If the above fails, delete the inserted creature
removeCreatureWork(archive.creature._id);
throw e;
}
}
const restoreCreaturefromFile = new ValidatedMethod({
name: 'Creatures.methods.restoreCreaturefromFile',
validate: new SimpleSchema({
'fileId': {
type: String,
regEx: SimpleSchema.RegEx.Id,
},
}).validator(),
mixins: [RateLimiterMixin],
rateLimit: {
numRequests: 10,
timeInterval: 5000,
},
async run({fileId}) {
// fetch the file
const file = ArchiveCreatureFiles.findOne({_id: fileId}).get();
if (!file){
throw new Meteor.Error('File not found',
'The requested creature archive does not exist');
}
// Assert ownership
const userId = file?.userId;
if (!userId || userId !== this.userId){
throw new Meteor.Error('Permission denied',
'You can only restore creatures you own');
}
if (Meteor.isServer){
// Read the file data
const string = await readFile(file);
const archive = JSON.parse(string);
restoreCreature(file, archive);
//Remove the archive once the restore succeeded
ArchiveCreatureFiles.remove({_id: fileId})
}
},
});
export default restoreCreaturefromFile;