201 lines
5.7 KiB
JavaScript
201 lines
5.7 KiB
JavaScript
import { filterPrivateRows, PrivacyMode } from "../privacy.mjs";
|
|
import { Database } from "./Database.mjs";
|
|
import { Logger } from "../Logger.mjs";
|
|
import { rowSchema } from "./model.mjs";
|
|
import { validateValue } from "../buckets.mjs";
|
|
|
|
const { hasProperty, mergeObject, randomID } = foundry.utils;
|
|
|
|
const dataFlag = `rows`;
|
|
|
|
export class UserFlagDatabase extends Database {
|
|
// MARK: Row Ops
|
|
static async createRow(tableID, userID, row, { rerender = true } = {}) {
|
|
const table = await this.getTable(tableID);
|
|
let user = game.users.get(userID);
|
|
if (!table || !user) { return };
|
|
|
|
row._id = randomID();
|
|
row.timestamp = new Date().toISOString();
|
|
|
|
const { error, value: corrected } = rowSchema.validate(
|
|
row,
|
|
{ abortEarly: false, convert: true, dateFormat: `iso`, render: false },
|
|
);
|
|
if (error) {
|
|
ui.notifications.error(`Row being created did not conform to required schema, see console for more information.`, { console: false });
|
|
Logger.error(error);
|
|
return false;
|
|
};
|
|
|
|
const userData = user.getFlag(__ID__, dataFlag) ?? {};
|
|
userData[tableID] ??= [];
|
|
userData[tableID].push(corrected);
|
|
await user.setFlag(__ID__, dataFlag, userData);
|
|
|
|
if (rerender) {
|
|
this.render({ userUpdated: userID });
|
|
};
|
|
this.triggerListeners();
|
|
};
|
|
|
|
static async createRows(tableID, userID, rows, { rerender = true } = {}) {
|
|
const table = await this.getTable(tableID);
|
|
let user = game.users.get(userID);
|
|
if (!table || !user) { return };
|
|
|
|
const userData = user.getFlag(__ID__, dataFlag) ?? {};
|
|
userData[tableID] ??= [];
|
|
|
|
let valueErrorPosted = false;
|
|
let validationErrorPosted = false;
|
|
|
|
for (const row of rows) {
|
|
row._id = randomID();
|
|
row.timestamp = new Date().toISOString();
|
|
|
|
const { error, value: corrected } = rowSchema.validate(
|
|
row,
|
|
{ abortEarly: false, convert: true, dateFormat: `iso`, render: false },
|
|
);
|
|
if (error) {
|
|
if (!validationErrorPosted) {
|
|
ui.notifications.error(
|
|
`One or more rows being created did not conform to required schema, skipping row and see console for more information.`,
|
|
{ console: false },
|
|
);
|
|
validationErrorPosted = true;
|
|
};
|
|
Logger.error(`Failing row:`, row);
|
|
Logger.error(error);
|
|
continue;
|
|
};
|
|
|
|
const validValue = validateValue(corrected.value, table.buckets);
|
|
if (!validValue) {
|
|
if (!valueErrorPosted) {
|
|
ui.notifications.warn(
|
|
`One or more rows being created did not contain a valid value, skipping row and see console for more information.`,
|
|
{ console: false },
|
|
);
|
|
valueErrorPosted = true;
|
|
};
|
|
Logger.warn(`Row with invalid value:`, row);
|
|
continue;
|
|
};
|
|
|
|
userData[tableID].push(corrected);
|
|
};
|
|
|
|
await user.setFlag(__ID__, dataFlag, userData);
|
|
|
|
if (rerender) {
|
|
this.render({ userUpdated: userID });
|
|
};
|
|
this.triggerListeners();
|
|
};
|
|
|
|
static async getRows(tableID, userIDs, privacy = [PrivacyMode.PUBLIC]) {
|
|
const table = await this.getTable(tableID);
|
|
if (!table) { return };
|
|
if (!userIDs || userIDs.length === 0) { userIDs = [game.user.id] };
|
|
|
|
const datasets = {};
|
|
for (const userID of userIDs) {
|
|
const user = game.users.get(userID);
|
|
if (!user) {
|
|
Logger.warn(`Failed to find user with ID "${userID}", skipping.`);
|
|
continue;
|
|
};
|
|
|
|
const userData = user.getFlag(__ID__, dataFlag) ?? {};
|
|
datasets[userID] = filterPrivateRows(
|
|
userData[tableID] ?? [],
|
|
userID,
|
|
privacy,
|
|
);
|
|
};
|
|
|
|
return datasets;
|
|
};
|
|
|
|
static async updateRow(tableID, userID, rowID, changes) {
|
|
const table = await this.getTable(tableID);
|
|
if (!table) {
|
|
Logger.error(`Cannot find the table with ID "${tableID}"`);
|
|
return false;
|
|
};
|
|
|
|
const user = game.users.get(userID);
|
|
if (!user) {
|
|
Logger.error(`Can't find the user with ID "${tableID}"`);
|
|
return false;
|
|
};
|
|
|
|
const userData = user.getFlag(__ID__, dataFlag) ?? {};
|
|
let row = userData[tableID]?.find(row => row._id === rowID);
|
|
if (!row) { return false };
|
|
|
|
if (hasProperty(changes, `value`)) {
|
|
const validValue = validateValue(changes.value, table.buckets);
|
|
if (!validValue) {
|
|
ui.notifications.warn(
|
|
`One or more rows being created did not contain a valid value, skipping row and see console for more information.`,
|
|
{ console: false },
|
|
);
|
|
Logger.warn(`Row with invalid value:`, row);
|
|
return false;
|
|
};
|
|
};
|
|
|
|
mergeObject(row, changes);
|
|
await user.setFlag(__ID__, dataFlag, userData);
|
|
this.render({ userUpdated: userID });
|
|
this.triggerListeners();
|
|
return true;
|
|
};
|
|
|
|
static async deleteRow(tableID, userID, rowID) {
|
|
const table = await this.getTable(tableID);
|
|
if (!table) {
|
|
Logger.error(`Cannot find the table with ID "${tableID}"`);
|
|
return;
|
|
};
|
|
|
|
const user = game.users.get(userID);
|
|
if (!user) {
|
|
Logger.error(`Can't find the user with ID "${tableID}"`);
|
|
return;
|
|
};
|
|
|
|
const userData = user.getFlag(__ID__, dataFlag) ?? {};
|
|
let rowIndex = userData[tableID]?.findIndex(row => row._id === rowID);
|
|
if (rowIndex === -1) { return };
|
|
userData[tableID].splice(rowIndex, 1);
|
|
await user.setFlag(__ID__, dataFlag, userData);
|
|
this.render({ userUpdated: userID });
|
|
this.triggerListeners();
|
|
};
|
|
|
|
// MARK: Listeners
|
|
static #listener = null;
|
|
static async registerListeners() {
|
|
if (this.#listener !== null) { return };
|
|
|
|
this.#listener = Hooks.on(`updateUser`, (doc, diff, options, userID) => {
|
|
// Shortcircuit when on the client that triggered the update
|
|
if (userID === game.user.id) { return };
|
|
if (!hasProperty(diff, `flags.${__ID__}.${dataFlag}`)) { return };
|
|
this.render({ userUpdated: doc.id });
|
|
});
|
|
};
|
|
|
|
static async triggerListeners() {
|
|
// No-op because the User document lifecycle takes care of it
|
|
};
|
|
|
|
static async unregisterListeners() {
|
|
Hooks.off(`updateUser`, this.#listener);
|
|
this.#listener = null;
|
|
};
|
|
};
|