Add data validation and a world setting for enabling the Global API
This commit is contained in:
parent
c3d632274a
commit
354b22da57
7 changed files with 180 additions and 56 deletions
|
|
@ -1,6 +1,8 @@
|
|||
/* eslint-disable no-unused-vars */
|
||||
import { BucketTypes, validateBucketConfig } from "../buckets.mjs";
|
||||
import { Logger } from "../Logger.mjs";
|
||||
import { PrivacyMode } from "../privacy.mjs";
|
||||
import { validateBucketConfig } from "../buckets.mjs";
|
||||
import { tableSchema } from "./model.mjs";
|
||||
|
||||
/*
|
||||
NOTE:
|
||||
|
|
@ -28,6 +30,12 @@ Default Subtables:
|
|||
|
||||
const { deleteProperty, diffObject, expandObject, mergeObject } = foundry.utils;
|
||||
|
||||
/**
|
||||
* The generic Database implementation, any subclasses should implement all of
|
||||
* the required methods, optionally overriding the methods provided by this class,
|
||||
* data validation should be used on any and all of the create* methods to ensure
|
||||
* consistency across databases.
|
||||
*/
|
||||
export class Database {
|
||||
// MARK: Table Ops
|
||||
static async createTable(tableConfig) {
|
||||
|
|
@ -36,25 +44,42 @@ export class Database {
|
|||
return false;
|
||||
};
|
||||
|
||||
const name = tableConfig.name;
|
||||
if (name.split(`/`).length > 2) {
|
||||
ui.notifications.error(`Subtables are not able to have subtables`);
|
||||
const { error, value: corrected } = tableSchema.validate(
|
||||
tableConfig,
|
||||
{ abortEarly: false, convert: true, dateFormat: `iso`, render: false },
|
||||
);
|
||||
if (error) {
|
||||
ui.notifications.error(`Table being created did not conform to required schema, see console for more information.`, { console: false });
|
||||
Logger.error(error);
|
||||
return false;
|
||||
};
|
||||
|
||||
const tables = game.settings.get(__ID__, `tables`);
|
||||
const name = tableConfig.name;
|
||||
const [ table, subtable ] = name.split(`/`);
|
||||
|
||||
const tables = game.settings.get(__ID__, `tables`);
|
||||
if (subtable && tables[table]) {
|
||||
ui.notifications.error(`Cannot add subtable for a table that already exists`);
|
||||
return false;
|
||||
};
|
||||
|
||||
if (table === `Dice`) {
|
||||
if (!subtable.match(/^d[0-9]+$/)) {
|
||||
ui.notifications.error(`Cannot create a Dice subtable that doesn't use "dX" as it's subtable name.`);
|
||||
return false;
|
||||
};
|
||||
if (tableConfig.buckets.type === BucketTypes.RANGE) {
|
||||
ui.notifications.error(`Cannot create a Dice subtable with a non-range bucket type`);
|
||||
return false;
|
||||
};
|
||||
};
|
||||
|
||||
if (tables[name]) {
|
||||
ui.notifications.error(`Cannot create table that already exists`);
|
||||
return false;
|
||||
};
|
||||
|
||||
tables[name] = tableConfig;
|
||||
tables[name] = corrected;
|
||||
game.settings.set(__ID__, `tables`, tables);
|
||||
this.render({ tags: [`table`] });
|
||||
return true;
|
||||
|
|
@ -99,7 +124,7 @@ export class Database {
|
|||
try {
|
||||
updated.buckets = validateBucketConfig(updated.buckets);
|
||||
} catch (e) {
|
||||
ui.notifications.error(e);
|
||||
Logger.error(e);
|
||||
return false;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { filterPrivateRows, PrivacyMode } from "../privacy.mjs";
|
||||
import { Database } from "./Database.mjs";
|
||||
import { Logger } from "../Logger.mjs";
|
||||
import { rowSchema } from "./model.mjs";
|
||||
|
||||
const { hasProperty, mergeObject, randomID } = foundry.utils;
|
||||
|
||||
|
|
@ -13,12 +14,22 @@ export class UserFlagDatabase extends Database {
|
|||
let user = game.users.get(userID);
|
||||
if (!table || !user) { return };
|
||||
|
||||
row._id ||= randomID();
|
||||
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(row);
|
||||
userData[tableID].push(corrected);
|
||||
await user.setFlag(__ID__, dataFlag, userData);
|
||||
|
||||
if (rerender) {
|
||||
|
|
@ -36,9 +47,21 @@ export class UserFlagDatabase extends Database {
|
|||
userData[tableID] ??= [];
|
||||
|
||||
for (const row of rows) {
|
||||
row._id ||= randomID();
|
||||
row._id = randomID();
|
||||
row.timestamp = new Date().toISOString();
|
||||
userData[tableID].push(row);
|
||||
|
||||
const { error, value: corrected } = rowSchema.validate(
|
||||
row,
|
||||
{ abortEarly: false, convert: true, dateFormat: `iso`, render: false },
|
||||
);
|
||||
if (error) {
|
||||
ui.notifications.error(`A row being created did not conform to required schema, see console for more information.`, { console: false });
|
||||
Logger.error(`Failing row:`, row);
|
||||
Logger.error(error);
|
||||
continue;
|
||||
};
|
||||
|
||||
userData[tableID].push(corrected);
|
||||
};
|
||||
|
||||
await user.setFlag(__ID__, dataFlag, userData);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import * as Joi from "joi";
|
||||
import { PrivacyMode } from "../privacy.mjs";
|
||||
|
||||
// MARK: Buckets
|
||||
const numberBucketSchema = Joi.object({
|
||||
export const numberBucketSchema = Joi.object({
|
||||
type: Joi.string().valid(`number`, `range`).required(),
|
||||
min: Joi
|
||||
.number()
|
||||
|
|
@ -29,15 +30,18 @@ const numberBucketSchema = Joi.object({
|
|||
}),
|
||||
});
|
||||
|
||||
const stringBucketSchema = Joi.object({
|
||||
export const stringBucketSchema = Joi.object({
|
||||
type: Joi.string().valid(`string`).required(),
|
||||
choices: Joi.array(Joi.string()).optional(),
|
||||
choices: Joi.array().items(Joi.string().trim().invalid(``)).optional(),
|
||||
});
|
||||
|
||||
// MARK: Graphs
|
||||
const barGraphSchema = Joi.object({
|
||||
export const barGraphSchema = Joi.object({
|
||||
type: Joi.string().valid(`bar`).required(),
|
||||
stacked: Joi.boolean().required(),
|
||||
stacked: Joi
|
||||
.boolean()
|
||||
.default(true)
|
||||
.optional(),
|
||||
});
|
||||
|
||||
// MARK: Table
|
||||
|
|
@ -45,16 +49,51 @@ export const tableSchema = Joi.object({
|
|||
name: Joi
|
||||
.string()
|
||||
.trim()
|
||||
.invalid(``)
|
||||
.required()
|
||||
.pattern(/^[a-z \-_]+(\/[a-z \-_]+)?$/i),
|
||||
buckets: Joi.alternatives([
|
||||
numberBucketSchema,
|
||||
stringBucketSchema,
|
||||
]).match(`one`),
|
||||
graph: Joi.alternatives([
|
||||
barGraphSchema,
|
||||
]).match(`one`),
|
||||
.pattern(/^[0-9a-z \-_]+(\/[0-9a-z \-_]+)?$/i),
|
||||
buckets: Joi
|
||||
.alternatives()
|
||||
.try(
|
||||
numberBucketSchema,
|
||||
stringBucketSchema,
|
||||
)
|
||||
.match(`one`)
|
||||
.required(),
|
||||
graph: Joi
|
||||
.alternatives()
|
||||
.try(
|
||||
barGraphSchema,
|
||||
)
|
||||
.match(`one`)
|
||||
.required(),
|
||||
});
|
||||
|
||||
// MARK: Row
|
||||
export const rowSchema = Joi.object({});
|
||||
/**
|
||||
* The schema for the row objects, this does not validate that the
|
||||
* value of the row conforms to the bucket configurations, however
|
||||
* it does validate that the value is at least one of the accepted
|
||||
* types. For validation of the value itself check "validateValue"
|
||||
* in `utils/buckets.mjs`
|
||||
*/
|
||||
export const rowSchema = Joi.object({
|
||||
_id: Joi
|
||||
.string()
|
||||
.alphanum()
|
||||
.required(),
|
||||
timestamp: Joi
|
||||
.string()
|
||||
.isoDate()
|
||||
.required(),
|
||||
value: Joi
|
||||
.alternatives([
|
||||
Joi.string().trim().invalid(``),
|
||||
Joi.number(),
|
||||
])
|
||||
.required(),
|
||||
privacy: Joi
|
||||
.string()
|
||||
.valid(...Object.values(PrivacyMode))
|
||||
.required(),
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue