1 line
7.6 KiB
JavaScript
1 line
7.6 KiB
JavaScript
import DatabaseBackend from"../../../common/abstract/backend.mjs";import{isEmpty,filterObject,parseUuid,setProperty}from"../../../common/utils/helpers.mjs";import ServerError from"../../server/error.mjs";import DocumentSocketResponse from"../../../common/abstract/socket.mjs";export default class ServerDatabaseBackend extends DatabaseBackend{socketListeners(e){e.on("modifyDocument",this.#e.bind(this,e))}async#e(e,t,a){const o=e.user;if(!o)throw new Error("Unrecognized User for modifyDocument socket operation");t.userId=o.id;const{action:r,type:n,operation:i}=t;if(!(this[r]instanceof Function))throw new Error(`Invalid action "${r}" provided to "modifyDocument" operation`);if(!db[n])throw new Error(`Invalid Document "${n}" provided to "modifyDocument" operation`);const d=new DocumentSocketResponse(t);try{d.result=await this.modifyDocument(t,o),d.broadcast=i.broadcast??!0}catch(e){d.error=new ServerError(e.message,e.stack),d.broadcast=!1,logger.error(d.error)}a(d),d.broadcast&&e.broadcast.emit("modifyDocument",d)}async modifyDocument(e,t){const a=global.db,{action:o,type:r,operation:n}=e,{pack:i,parentUuid:d}=n,s=this[o];if(!(s instanceof Function))throw new Error(`Invalid Document modification action "${o}" requested`);let c,l=a[r];if(!l)throw new Error(`Invalid Document "${r}" provided for database modification operation`);if(i){const e=a.packs.get(i);if("Folder"===r)l=e.folderClass;else if(!d){if(r!==e.packData.type)throw new Error(`${r} Documents cannot be created in a compendium that only allows for ${e.packData.type} Documents.`);l=e}e.connected||await e.connect()}c=i?a.packs.get(i).db:d?parseUuid(d)?.collection.db:l.db;const p=await c.semaphore.add(s.bind(this),l,n,t),u=n._result??p;return delete n._result,u}emit(e,t,a=[],o={},r){const n=new DocumentSocketResponse({type:e,action:t,operation:o,broadcast:!0,result:a,userId:r?.id});global.express.io.emit("modifyDocument",n)}async _getDocuments(e,t,a){const{query:o,index:r,parent:n}=t;if(n){const a=t.parent.getEmbeddedCollection(e.documentName);return Array.from(a.values())}return r?this.#t(e,t):e.find(o,{safe:!0})}async#t(e,t){let{query:a,indexFields:o}=t;o||=e.metadata.compendiumIndexFields;const r=["password","passwordSalt"],n={};for(const e of o){if(r.includes(e))throw new Error(`You are not allowed to request the "${e}" field as a compendium index.`);setProperty(n,e,1)}const i={};for(const[t,a]of Object.entries(n)){const o=e.hierarchy[t];if(o){if(i[t]={},"object"==typeof a)i[t]=a;else for(const e of o.model.metadata.compendiumIndexFields)i[t][e]=1;i[t]._id=1,n[t]=1}}const d=await e.sublevel.find(a,{project:n});if(isEmpty(i))return d;for(const t of d){await e.expandEmbedded(t,{partial:!0});for(const[e,a]of Object.entries(i)){const o=t[e];Array.isArray(o)?t[e]=o.map((e=>filterObject(e,a))):t[e]=filterObject(o,a)}}return d}async _createDocuments(e,t,a){const{data:o,parent:r,parentUuid:n,pack:i,restoreDelta:d,...s}=t,c=e.documentName,l=e.collectionName,p=r?r.getSublevel(l):e.sublevel,u=r?.getEmbeddedCollection(l);u&&(e=u.documentClass);const m=[];await Promise.all(o.map((async t=>{if(s.keepId&&t._id||(t._id=await p.createNewId()),u?.has(t._id))throw new Error(`The _id [${t._id}] already exists within the parent collection: ${r.documentName} [${r.id}] ${l}`);await e.preprocessData(t,{user:a,documentId:t._id});const o=e.fromSource(t,{parent:r,pack:i});if(await o.loadRelatedDocuments(),a&&!o.canUserModify(a,"create",t))throw new Error(this._logError(a,"create",o,{parent:r,pack:i}));!1!==await o._preCreate(t,s,a)?m.push(o):global.logger.debug(`${c} creation prevented by _preCreate`)}))),Object.assign(t,s);if(!1===await e._preCreateOperation(m,t,a))return global.logger.debug(`${c} creation operation prevented by _preCreateOperation`),[];Object.assign(s,t),delete s.data,delete s.parent,delete s.parentUuid,delete s.pack,delete s.restoreDelta;const b=p.db.batch(),f=[];for(const e of m)e.validate({fields:!0,joint:!0,strict:!0}),r&&u.set(e.id,e,{restoreDelta:d}),e.batchWrite(b,{restoreDelta:d}),f.push((async()=>{const t=e.toObject();return await e._onCreate(t,s,a),t}));return r&&r.batchWrite(b,{writeEmbedded:!1,childModified:!0}),await b.write(),t.data=t._result=await Promise.all(f.map((e=>e()))),Object.assign(t,s),await e._onCreateOperation(m,t,a),this._logOperation("Created",c,m,{level:"info",parent:r,pack:i}),m}async _updateDocuments(e,t,a){const{updates:o,parent:r,parentUuid:n,pack:i,restoreDelta:d,...s}=t,c=e.documentName,l=e.collectionName,p=r?r.getSublevel(l):e.sublevel,u=r?.getEmbeddedCollection(l),m={};let b=[];const f=o.map((e=>{const t=e._id;if(!t)throw new Error(`You cannot update a ${c} without providing an _id`);return m[t]=e,r&&b.push(u.get(t,{invalid:!0})),t}));r||(b=await e.getMany(f));const w=[],g=[];await Promise.all(b.map((async(t,o)=>{if(!t)throw new Error(`${c} "${f[o]}" does not exist!`);const n=m[t.id];await e.preprocessData(n,{document:t,user:a}),await t.loadRelatedDocuments();for(const a of Object.keys(e.hierarchy))if(a in n)for(const e of t.getEmbeddedCollection(a))await e.loadRelatedDocuments();if(a&&!t.canUserModify(a,"update",n))throw new Error(this._logError(a,"update",t,{parent:r,pack:i}));!1!==await t._preUpdate(n,s,a)?(w.push(t),g.push(n)):global.logger.debug(`${c} update prevented by _preUpdate`)}))),t.updates=g,Object.assign(t,s);if(!1===await e._preUpdateOperation(w,t,a))return global.logger.debug(`${c} update operation prevented by _preUpdateOperation`),[];Object.assign(s,t),delete s.updates,delete s.parent,delete s.parentUuid,delete s.pack,delete s.restoreDelta;const h=p.db.batch(),y=[];for(const e of w){const o=m[e.id];delete o._id;const n=e.updateSource(o,s);n._id=e.id;const i=await this.#a(e,o,h,t);r&&u.set(e.id,e,{restoreDelta:d}),e.batchWrite(h,{writeEmbedded:i,restoreDelta:d}),y.push((async()=>(await e._onUpdate(n,s,a),n)))}return r?r.batchWrite(h,{writeEmbedded:!1,childModified:!0}):e.isCached&&game.documentCache.addAll(w),await h.write(),t.updates=t._result=await Promise.all(y.map((e=>e()))),Object.assign(t,s),await e._onUpdateOperation(w,t,a),global.options.debug&&this._logOperation("Updated",c,w,{level:"debug",parent:r,pack:i}),w}async#a(e,t,a,o){const{recursive:r,restoreDelta:n}=o;let i=!1;for(const[o,d]of Object.entries(e.constructor.hierarchy))o in t&&(i=!0,!1===r&&d._dbDeleteBranch?.(e,a,{restoreDelta:n}));return i&&await e._generateEmbeddedDocumentIds(),i}async _deleteDocuments(e,t,a){const{ids:o,deleteAll:r,parent:n,parentUuid:i,pack:d,...s}=t,c=e.documentName,l=e.collectionName,p=n?n.getSublevel(l):e.sublevel,u=n?.getEmbeddedCollection(l),m=n?o.map((e=>u.get(e,{invalid:!0}))):await e.getMany(o),b=[];await Promise.all(m.map((async(e,t)=>{if(!e)throw new Error(`${c} "${o[t]}" does not exist!`);if(await e.loadRelatedDocuments(),e.invalid&&a.isGM)return void b.push(e);if(a&&!e.canUserModify(a,"delete"))throw new Error(this._logError(a,"delete",e,{parent:n,pack:d}));!1!==await e._preDelete(s,a)?b.push(e):global.logger.debug(`${c} deletion prevented by _preDelete`)}))),Object.assign(t,s);if(!1===await e._preDeleteOperation(b,t,a))return global.logger.debug(`${c} update operation prevented by _preDeleteOperation`),[];Object.assign(s,t),delete s.ids,delete s.deleteAll,delete s.parent,delete s.parentUuid,delete s.pack;const f=p.db.batch(),w=[];for(const e of b)n&&u.delete(e.id),e.batchDelete(f),w.push((async()=>(await e._onDelete(s,a),e.id)));return n?n.batchWrite(f,{writeEmbedded:!1,childModified:!0}):e.isCached&&b.forEach((e=>game.documentCache.delete(e))),await f.write(),t.ids=t._result=await Promise.all(w.map((e=>e()))),Object.assign(t,s),await e._onDeleteOperation(b,t,a),this._logOperation("Deleted",c,b,{level:"info",parent:n,pack:d}),b}getCompendiumScopes(){return Array.from(global.db.packs.keys())}_log(e,t){global.logger[e](t)}} |