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 assertHasCharactersSlots from '/imports/api/creature/creatures/methods/assertHasCharacterSlots.js'; import { incrementFileStorageUsed } from '/imports/api/users/methods/updateFileStorageUsed.js'; import verifyArchiveSafety from '/imports/api/creature/archive/methods/verifyArchiveSafety.js'; let migrateArchive; if (Meteor.isServer) { migrateArchive = require('/imports/migrations/archive/migrateArchive.js').default; } function restoreCreature(archive, userId) { if (SCHEMA_VERSION < archive.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); // Asset that the archive is safe verifyArchiveSafety(archive); // Don't upload creatures twice const existingCreature = Creatures.findOne(archive.creature._id, { fields: { _id: 1 } }); if (existingCreature) throw new Meteor.Error('Already exists', 'The creature you are trying to restore already exists.') // Ensure the user owns the restored creature archive.creature.owner = userId; // 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'); } assertHasCharactersSlots(this.userId); if (Meteor.isServer) { // Read the file data const archive = await ArchiveCreatureFiles.readJSONFile(file); restoreCreature(archive, this.userId); } //Remove the archive once the restore succeeded ArchiveCreatureFiles.remove({ _id: fileId }); // Update the user's file storage limits incrementFileStorageUsed(userId, -file.size); }, }); export default restoreCreaturefromFile;