record controllers...
This commit is contained in:
parent
6954bf20cc
commit
6c0efea8ea
|
@ -22,10 +22,10 @@ const validateAllFieldParse = (record, model) =>
|
|||
}, []),
|
||||
])
|
||||
|
||||
const validateAllTypeConstraints = async (record, model) => {
|
||||
const validateAllTypeConstraints = (record, model) => {
|
||||
const errors = []
|
||||
for (const field of model.fields) {
|
||||
$(await validateTypeConstraints(field, record), [
|
||||
$(validateTypeConstraints(field, record), [
|
||||
filter(isNonEmptyString),
|
||||
map(m => ({ message: m, fields: [field.name] })),
|
||||
each(e => errors.push(e)),
|
||||
|
@ -55,8 +55,8 @@ const runRecordValidationRules = (record, model) => {
|
|||
])
|
||||
}
|
||||
|
||||
export const validateRecord = async (schema, record) => {
|
||||
const model = schema.findModel(record.modelId)
|
||||
export const validateRecord = (schema, record) => {
|
||||
const model = schema.findModel(record._modelId)
|
||||
const fieldParseFails = validateAllFieldParse(record, model)
|
||||
|
||||
// non parsing would cause further issues - exit here
|
||||
|
@ -65,7 +65,7 @@ export const validateRecord = async (schema, record) => {
|
|||
}
|
||||
|
||||
const recordValidationRuleFails = runRecordValidationRules(record, model)
|
||||
const typeContraintFails = await validateAllTypeConstraints(record, model)
|
||||
const typeContraintFails = validateAllTypeConstraints(record, model)
|
||||
|
||||
if (
|
||||
isEmpty(fieldParseFails) &&
|
||||
|
|
|
@ -47,11 +47,11 @@ const options = {
|
|||
|
||||
const typeConstraints = [
|
||||
makerule(
|
||||
async (val, opts) => val === null || val.length >= opts.minLength,
|
||||
(val, opts) => val === null || val.length >= opts.minLength,
|
||||
(val, opts) => `must choose ${opts.minLength} or more options`
|
||||
),
|
||||
makerule(
|
||||
async (val, opts) => val === null || val.length <= opts.maxLength,
|
||||
(val, opts) => val === null || val.length <= opts.maxLength,
|
||||
(val, opts) => `cannot choose more than ${opts.maxLength} options`
|
||||
),
|
||||
]
|
||||
|
|
|
@ -6,7 +6,12 @@ import {
|
|||
parsedSuccess,
|
||||
getDefaultExport,
|
||||
} from "./typeHelpers"
|
||||
import { switchCase, defaultCase, isOneOf, toBoolOrNull } from "../../common/index.mjs"
|
||||
import {
|
||||
switchCase,
|
||||
defaultCase,
|
||||
isOneOf,
|
||||
toBoolOrNull,
|
||||
} from "../../common/index.mjs"
|
||||
|
||||
const boolFunctions = typeFunctions({
|
||||
default: constant(null),
|
||||
|
@ -31,7 +36,7 @@ const options = {
|
|||
|
||||
const typeConstraints = [
|
||||
makerule(
|
||||
async (val, opts) => opts.allowNulls === true || val !== null,
|
||||
(val, opts) => opts.allowNulls === true || val !== null,
|
||||
() => "field cannot be null"
|
||||
),
|
||||
]
|
||||
|
|
|
@ -50,7 +50,7 @@ const options = {
|
|||
|
||||
const typeConstraints = [
|
||||
makerule(
|
||||
async (val, opts) =>
|
||||
(val, opts) =>
|
||||
val === null || isNullOrEmpty(opts.minValue) || val >= opts.minValue,
|
||||
(val, opts) =>
|
||||
`value (${val.toString()}) must be greater than or equal to ${
|
||||
|
@ -58,7 +58,7 @@ const typeConstraints = [
|
|||
}`
|
||||
),
|
||||
makerule(
|
||||
async (val, opts) =>
|
||||
(val, opts) =>
|
||||
val === null || isNullOrEmpty(opts.maxValue) || val <= opts.maxValue,
|
||||
(val, opts) =>
|
||||
`value (${val.toString()}) must be less than or equal to ${
|
||||
|
|
|
@ -5,7 +5,13 @@ import {
|
|||
parsedSuccess,
|
||||
getDefaultExport,
|
||||
} from "./typeHelpers"
|
||||
import { switchCase, defaultCase, none, $, splitKey } from "../../common/index.mjs"
|
||||
import {
|
||||
switchCase,
|
||||
defaultCase,
|
||||
none,
|
||||
$,
|
||||
splitKey,
|
||||
} from "../../common/index.mjs"
|
||||
|
||||
const illegalCharacters = "*?\\/:<>|\0\b\f\v"
|
||||
|
||||
|
|
|
@ -67,8 +67,8 @@ export const validateFieldParse = (field, record) =>
|
|||
|
||||
export const getDefaultOptions = type => getType(type).getDefaultOptions()
|
||||
|
||||
export const validateTypeConstraints = async (field, record) =>
|
||||
await getType(field.type).validateTypeConstraints(field, record)
|
||||
export const validateTypeConstraints = (field, record) =>
|
||||
getType(field.type).validateTypeConstraints(field, record)
|
||||
|
||||
export const detectType = value => {
|
||||
if (isString(value)) return string
|
||||
|
|
|
@ -1,65 +1,30 @@
|
|||
import { isString, isObjectLike, isNull, has } from "lodash/fp"
|
||||
import {
|
||||
typeFunctions,
|
||||
parsedSuccess,
|
||||
getDefaultExport,
|
||||
parsedFailed,
|
||||
} from "./typeHelpers"
|
||||
import { isString, isUndefined, isNull } from "lodash/fp"
|
||||
import { typeFunctions, parsedSuccess, getDefaultExport } from "./typeHelpers"
|
||||
import {
|
||||
switchCase,
|
||||
defaultCase,
|
||||
isNonEmptyString,
|
||||
isArrayOfString,
|
||||
} from "../../common/index.mjs"
|
||||
|
||||
const linkNothing = () => ({ key: "" })
|
||||
const linkNothing = () => ""
|
||||
|
||||
const linkFunctions = typeFunctions({
|
||||
default: linkNothing,
|
||||
})
|
||||
|
||||
const hasStringValue = (ob, path) => has(path)(ob) && isString(ob[path])
|
||||
|
||||
const isObjectWithKey = v => isObjectLike(v) && hasStringValue(v, "key")
|
||||
|
||||
const tryParseFromString = s => {
|
||||
try {
|
||||
const asObj = JSON.parse(s)
|
||||
if (isObjectWithKey) {
|
||||
return parsedSuccess(asObj)
|
||||
}
|
||||
} catch (_) {
|
||||
// EMPTY
|
||||
}
|
||||
|
||||
return parsedFailed(s)
|
||||
}
|
||||
|
||||
const linkTryParse = v =>
|
||||
switchCase(
|
||||
[isObjectWithKey, parsedSuccess],
|
||||
[isString, tryParseFromString],
|
||||
[isString, s => parsedSuccess(s)],
|
||||
[isNull, () => parsedSuccess(linkNothing())],
|
||||
[defaultCase, parsedFailed]
|
||||
[isUndefined, () => parsedSuccess(linkNothing())],
|
||||
[defaultCase, s => parsedSuccess(s.toString())]
|
||||
)(v)
|
||||
|
||||
const options = {
|
||||
indexNodeKey: {
|
||||
defaultValue: null,
|
||||
isValid: isNonEmptyString,
|
||||
requirementDescription: "must be a non-empty string",
|
||||
parse: s => s,
|
||||
},
|
||||
displayValue: {
|
||||
modelId: {
|
||||
defaultValue: "",
|
||||
isValid: isNonEmptyString,
|
||||
requirementDescription: "must be a non-empty string",
|
||||
parse: s => s,
|
||||
},
|
||||
reverseIndexNodeKeys: {
|
||||
defaultValue: null,
|
||||
isValid: v => isArrayOfString(v) && v.length > 0,
|
||||
requirementDescription: "must be a non-empty array of strings",
|
||||
requirementDescription: "must choose a model",
|
||||
parse: s => s,
|
||||
},
|
||||
}
|
||||
|
@ -72,6 +37,6 @@ export default getDefaultExport(
|
|||
linkFunctions,
|
||||
options,
|
||||
typeConstraints,
|
||||
{ key: "key", value: "value" },
|
||||
"abcd1234",
|
||||
JSON.stringify
|
||||
)
|
||||
|
|
|
@ -58,7 +58,7 @@ const getDecimalPlaces = val => {
|
|||
|
||||
const typeConstraints = [
|
||||
makerule(
|
||||
async (val, opts) =>
|
||||
(val, opts) =>
|
||||
val === null || opts.minValue === null || val >= opts.minValue,
|
||||
(val, opts) =>
|
||||
`value (${val.toString()}) must be greater than or equal to ${
|
||||
|
@ -66,7 +66,7 @@ const typeConstraints = [
|
|||
}`
|
||||
),
|
||||
makerule(
|
||||
async (val, opts) =>
|
||||
(val, opts) =>
|
||||
val === null || opts.maxValue === null || val <= opts.maxValue,
|
||||
(val, opts) =>
|
||||
`value (${val.toString()}) must be less than or equal to ${
|
||||
|
@ -74,7 +74,7 @@ const typeConstraints = [
|
|||
} options`
|
||||
),
|
||||
makerule(
|
||||
async (val, opts) =>
|
||||
(val, opts) =>
|
||||
val === null || opts.decimalPlaces >= getDecimalPlaces(val),
|
||||
(val, opts) =>
|
||||
`value (${val.toString()}) must have ${
|
||||
|
|
|
@ -50,12 +50,12 @@ const options = {
|
|||
|
||||
const typeConstraints = [
|
||||
makerule(
|
||||
async (val, opts) =>
|
||||
(val, opts) =>
|
||||
val === null || opts.maxLength === null || val.length <= opts.maxLength,
|
||||
(val, opts) => `value exceeds maximum length of ${opts.maxLength}`
|
||||
),
|
||||
makerule(
|
||||
async (val, opts) =>
|
||||
(val, opts) =>
|
||||
val === null ||
|
||||
opts.allowDeclaredValuesOnly === false ||
|
||||
includes(val)(opts.values),
|
||||
|
|
|
@ -46,19 +46,16 @@ export const typeFunctions = specificFunctions =>
|
|||
specificFunctions
|
||||
)
|
||||
|
||||
export const validateTypeConstraints = validationRules => async (
|
||||
field,
|
||||
record
|
||||
) => {
|
||||
export const validateTypeConstraints = validationRules => (field, record) => {
|
||||
const fieldValue = record[field.name]
|
||||
const validateRule = async r =>
|
||||
!(await r.isValid(fieldValue, field.typeOptions))
|
||||
const validateRule = r =>
|
||||
!r.isValid(fieldValue, field.typeOptions)
|
||||
? r.getMessage(fieldValue, field.typeOptions)
|
||||
: ""
|
||||
|
||||
const errors = []
|
||||
for (const r of validationRules) {
|
||||
const err = await validateRule(r)
|
||||
const err = validateRule(r)
|
||||
if (isNotEmpty(err)) errors.push(err)
|
||||
}
|
||||
|
||||
|
|
|
@ -2,10 +2,7 @@ import { newModel } from "../src/schema/models.mjs"
|
|||
import { newView } from "../src/schema/views.mjs"
|
||||
import { getNewField } from "../src/schema/fields.mjs"
|
||||
import { fullSchema } from "../src/schema/fullSchema.mjs"
|
||||
import {
|
||||
recordValidationRules,
|
||||
commonRecordValidationRules,
|
||||
} from "../src/schema/recordValidationRules.mjs"
|
||||
import { commonRecordValidationRules } from "../src/records/recordValidationRules.mjs"
|
||||
|
||||
export function testSchema() {
|
||||
const addFieldToModel = (model, { type, name }) => {
|
||||
|
@ -21,9 +18,10 @@ export function testSchema() {
|
|||
addFieldToModel(contactModel, { name: "Name" })
|
||||
addFieldToModel(contactModel, { name: "Is Active", type: "bool" })
|
||||
addFieldToModel(contactModel, { name: "Created", type: "datetime" })
|
||||
addFieldToModel(contactModel, { name: "Status", type: "string" })
|
||||
|
||||
contactModel.validationRules.push(
|
||||
recordValidationRules(commonRecordValidationRules.fieldNotEmpty)
|
||||
commonRecordValidationRules.fieldNotEmpty("Name")
|
||||
)
|
||||
|
||||
const activeContactsView = newView(contactModel.id)
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
import { addHours } from "date-fns"
|
||||
import { events } from "../src/common"
|
||||
import { testSchema } from "./testSchema.mjs"
|
||||
import { validateRecord } from "../src/records/validateRecord.mjs"
|
||||
import { getNewRecord } from "../src/records/getNewRecord.mjs"
|
||||
|
||||
describe("recordApi > validate", () => {
|
||||
describe("validateRecord", () => {
|
||||
it("should return errors when any fields do not parse", () => {
|
||||
const schema = testSchema()
|
||||
const record = getNewRecord(schema, "Contact")
|
||||
|
@ -34,7 +32,7 @@ describe("recordApi > validate", () => {
|
|||
const schema = testSchema()
|
||||
schema.findField("Contact", "Name").typeOptions.maxLength = 5
|
||||
const record = getNewRecord(schema, "Contact")
|
||||
record.name = "more than 5 characters"
|
||||
record.Name = "more than 5 characters"
|
||||
|
||||
const validationResult = validateRecord(schema, record)
|
||||
expect(validationResult.isValid).toBe(false)
|
||||
|
@ -47,213 +45,93 @@ describe("recordApi > validate", () => {
|
|||
const record = getNewRecord(schema, "Deal")
|
||||
record["Estimated Value"] = 10
|
||||
|
||||
const validationResult = recordApi.validate(schema, record)
|
||||
const validationResult = validateRecord(schema, record)
|
||||
expect(validationResult.isValid).toBe(false)
|
||||
expect(validationResult.errors.length).toBe(1)
|
||||
})
|
||||
|
||||
it("should return error when number field is < minValue", async () => {
|
||||
const withFieldWithMaxLength = hierarchy => {
|
||||
const age = find(hierarchy.customerRecord.fields, f => f.name === "age")
|
||||
age.typeOptions.minValue = 5
|
||||
}
|
||||
it("should return error when number field is < minValue", () => {
|
||||
const schema = testSchema()
|
||||
schema.findField("Deal", "Estimated Value").typeOptions.minValue = 5
|
||||
const record = getNewRecord(schema, "Deal")
|
||||
record["Estimated Value"] = 1
|
||||
|
||||
const hierarchyCreator = hierarchyFactory(
|
||||
withFields,
|
||||
withFieldWithMaxLength
|
||||
)
|
||||
const { recordApi } = await setupApphierarchy(hierarchyCreator)
|
||||
|
||||
const tooYoungRecord = recordApi.getNew("/customers", "customer")
|
||||
tooYoungRecord.age = 3
|
||||
|
||||
const tooYoungResult = await recordApi.validate(tooYoungRecord)
|
||||
expect(tooYoungResult.isValid).toBe(false)
|
||||
expect(tooYoungResult.errors.length).toBe(1)
|
||||
})
|
||||
|
||||
it("should return error when number has too many decimal places", async () => {
|
||||
const withFieldWithMaxLength = (hierarchy, templateApi) => {
|
||||
const age = find(hierarchy.customerRecord.fields, f => f.name === "age")
|
||||
age.typeOptions.decimalPlaces = 2
|
||||
}
|
||||
|
||||
const hierarchyCreator = hierarchyFactory(
|
||||
withFields,
|
||||
withFieldWithMaxLength
|
||||
)
|
||||
const { recordApi } = await setupApphierarchy(hierarchyCreator)
|
||||
|
||||
const record = recordApi.getNew("/customers", "customer")
|
||||
record.age = 3.123
|
||||
|
||||
const validationResult = await recordApi.validate(record)
|
||||
expect(validationResult.isValid).toBe(false)
|
||||
expect(validationResult.errors.length).toBe(1)
|
||||
})
|
||||
|
||||
it("should return error when datetime field is > maxValue", async () => {
|
||||
const withFieldWithMaxLength = hierarchy => {
|
||||
const createddate = find(
|
||||
hierarchy.customerRecord.fields,
|
||||
f => f.name === "createddate"
|
||||
)
|
||||
createddate.typeOptions.maxValue = new Date()
|
||||
}
|
||||
|
||||
const hierarchyCreator = hierarchyFactory(
|
||||
withFields,
|
||||
withFieldWithMaxLength
|
||||
)
|
||||
const { recordApi } = await setupApphierarchy(hierarchyCreator)
|
||||
|
||||
const record = recordApi.getNew("/customers", "customer")
|
||||
record.createddate = addHours(new Date(), 1)
|
||||
|
||||
const result = await recordApi.validate(record)
|
||||
const result = validateRecord(schema, record)
|
||||
expect(result.isValid).toBe(false)
|
||||
expect(result.errors.length).toBe(1)
|
||||
})
|
||||
|
||||
it("should return error when number field is < minValue", async () => {
|
||||
const withFieldWithMaxLength = hierarchy => {
|
||||
const createddate = find(
|
||||
hierarchy.customerRecord.fields,
|
||||
f => f.name === "createddate"
|
||||
)
|
||||
createddate.typeOptions.minValue = addHours(new Date(), 1)
|
||||
}
|
||||
it("should return error when number has too many decimal places", () => {
|
||||
const schema = testSchema()
|
||||
schema.findField("Deal", "Estimated Value").typeOptions.decimalPlaces = 2
|
||||
const record = getNewRecord(schema, "Deal")
|
||||
record["Estimated Value"] = 1.123
|
||||
|
||||
const hierarchyCreator = hierarchyFactory(
|
||||
withFields,
|
||||
withFieldWithMaxLength
|
||||
)
|
||||
const { recordApi } = await setupApphierarchy(hierarchyCreator)
|
||||
|
||||
const record = recordApi.getNew("/customers", "customer")
|
||||
record.createddate = new Date()
|
||||
|
||||
const result = await recordApi.validate(record)
|
||||
const result = validateRecord(schema, record)
|
||||
expect(result.isValid).toBe(false)
|
||||
expect(result.errors.length).toBe(1)
|
||||
})
|
||||
|
||||
it("should return error when string IS NOT one of declared values, and only declared values are allowed", async () => {
|
||||
const withFieldWithMaxLength = hierarchy => {
|
||||
const surname = find(
|
||||
hierarchy.customerRecord.fields,
|
||||
f => f.name === "surname"
|
||||
)
|
||||
surname.typeOptions.allowDeclaredValuesOnly = true
|
||||
surname.typeOptions.values = ["thedog"]
|
||||
}
|
||||
it("should return error when datetime field is > maxValue", () => {
|
||||
const schema = testSchema()
|
||||
schema.findField("Contact", "Created").typeOptions.maxValue = new Date(2020, 1, 1)
|
||||
const record = getNewRecord(schema, "Contact")
|
||||
record.Name = "Bob"
|
||||
record.Created = new Date(2020, 1, 2)
|
||||
|
||||
const hierarchyCreator = hierarchyFactory(
|
||||
withFields,
|
||||
withFieldWithMaxLength
|
||||
)
|
||||
const { recordApi } = await setupApphierarchy(hierarchyCreator)
|
||||
|
||||
const record = recordApi.getNew("/customers", "customer")
|
||||
record.surname = "zeecat"
|
||||
|
||||
const result = await recordApi.validate(record)
|
||||
const result = validateRecord(schema, record)
|
||||
expect(result.isValid).toBe(false)
|
||||
expect(result.errors.length).toBe(1)
|
||||
})
|
||||
|
||||
it("should not return error when string IS one of declared values, and only declared values are allowed", async () => {
|
||||
const withFieldWithMaxLength = hierarchy => {
|
||||
const surname = find(
|
||||
hierarchy.customerRecord.fields,
|
||||
f => f.name === "surname"
|
||||
)
|
||||
surname.typeOptions.allowDeclaredValuesOnly = true
|
||||
surname.typeOptions.values = ["thedog"]
|
||||
}
|
||||
it("should return error when number field is < minValue", () => {
|
||||
const schema = testSchema()
|
||||
schema.findField("Contact", "Created").typeOptions.minValue = new Date(2020, 1, 2)
|
||||
const record = getNewRecord(schema, "Contact")
|
||||
record.Name = "Bob"
|
||||
record.Created = new Date(2020, 1, 1)
|
||||
|
||||
const hierarchyCreator = hierarchyFactory(
|
||||
withFields,
|
||||
withFieldWithMaxLength
|
||||
)
|
||||
const { recordApi, appHierarchy } = await setupApphierarchy(
|
||||
hierarchyCreator
|
||||
)
|
||||
const result = validateRecord(schema, record)
|
||||
expect(result.isValid).toBe(false)
|
||||
expect(result.errors.length).toBe(1)
|
||||
})
|
||||
|
||||
const record = recordApi.getNew("/customers", "customer")
|
||||
record.surname = "thedog"
|
||||
it("should return error when string IS NOT one of declared values, and only declared values are allowed", () => {
|
||||
const schema = testSchema()
|
||||
schema.findField("Contact", "Status").typeOptions.allowDeclaredValuesOnly = true
|
||||
schema.findField("Contact", "Status").typeOptions.values = ["thedog"]
|
||||
const record = getNewRecord(schema, "Contact")
|
||||
record.Status = "not allowed"
|
||||
record.Name = "Bob"
|
||||
|
||||
const result = await recordApi.validate(record)
|
||||
const result = validateRecord(schema, record)
|
||||
expect(result.isValid).toBe(false)
|
||||
expect(result.errors.length).toBe(1)
|
||||
})
|
||||
|
||||
it("should not return error when string IS one of declared values, and only declared values are allowed", () => {
|
||||
const schema = testSchema()
|
||||
schema.findField("Contact", "Status").typeOptions.allowDeclaredValuesOnly = true
|
||||
schema.findField("Contact", "Status").typeOptions.values = ["thedog"]
|
||||
const record = getNewRecord(schema, "Contact")
|
||||
record.Status = "thedog"
|
||||
record.Name = "Bob"
|
||||
|
||||
const result = validateRecord(schema, record)
|
||||
expect(result.isValid).toBe(true)
|
||||
expect(result.errors.length).toBe(0)
|
||||
})
|
||||
|
||||
it("should not return error when string IS NOT one of declared values, but any values are allowed", async () => {
|
||||
const withFieldWithMaxLength = (hierarchy, templateApi) => {
|
||||
const surname = find(
|
||||
hierarchy.customerRecord.fields,
|
||||
f => f.name === "surname"
|
||||
)
|
||||
surname.typeOptions.allowDeclaredValuesOnly = false
|
||||
surname.typeOptions.values = ["thedog"]
|
||||
}
|
||||
it("should not return error when string IS NOT one of declared values, but any values are allowed", () => {
|
||||
const schema = testSchema()
|
||||
schema.findField("Contact", "Status").typeOptions.allowDeclaredValuesOnly = false
|
||||
schema.findField("Contact", "Status").typeOptions.values = ["thedog"]
|
||||
const record = getNewRecord(schema, "Contact")
|
||||
record.Status = "not one of the values"
|
||||
record.Name = "Bob"
|
||||
|
||||
const hierarchyCreator = hierarchyFactory(
|
||||
withFields,
|
||||
withFieldWithMaxLength
|
||||
)
|
||||
const { recordApi, appHierarchy } = await setupApphierarchy(
|
||||
hierarchyCreator
|
||||
)
|
||||
|
||||
const record = recordApi.getNew("/customers", "customer")
|
||||
record.surname = "zeecat"
|
||||
|
||||
const result = await recordApi.validate(record)
|
||||
const result = validateRecord(schema, record)
|
||||
expect(result.isValid).toBe(true)
|
||||
expect(result.errors.length).toBe(0)
|
||||
})
|
||||
|
||||
it("should return error when reference field does not exist in options index", async () => {
|
||||
const { recordApi, appHierarchy } = await setupApphierarchy(
|
||||
basicAppHierarchyCreator_WithFields_AndIndexes
|
||||
)
|
||||
|
||||
const partner = recordApi.getNew("/partners", "partner")
|
||||
partner.businessName = "ACME Inc"
|
||||
await recordApi.save(partner)
|
||||
|
||||
const customer = recordApi.getNew("/customers", "customer")
|
||||
customer.partner = { key: "incorrect key", name: partner.businessName }
|
||||
const result = await await recordApi.validate(customer)
|
||||
expect(result.isValid).toBe(false)
|
||||
expect(result.errors.length).toBe(1)
|
||||
})
|
||||
|
||||
it("should publish invalid events", async () => {
|
||||
const withValidationRule = (hierarchy, templateApi) => {
|
||||
templateApi.addRecordValidationRule(hierarchy.customerRecord)(
|
||||
templateApi.commonRecordValidationRules.fieldNotEmpty("surname")
|
||||
)
|
||||
}
|
||||
|
||||
const hierarchyCreator = hierarchyFactory(withFields, withValidationRule)
|
||||
|
||||
const { recordApi, subscribe } = await setupApphierarchy(hierarchyCreator)
|
||||
const handler = stubEventHandler()
|
||||
subscribe(events.recordApi.save.onInvalid, handler.handle)
|
||||
|
||||
const record = recordApi.getNew("/customers", "customer")
|
||||
record.surname = ""
|
||||
|
||||
try {
|
||||
await recordApi.save(record)
|
||||
} catch (e) {}
|
||||
|
||||
const onInvalid = handler.getEvents(events.recordApi.save.onInvalid)
|
||||
expect(onInvalid.length).toBe(1)
|
||||
expect(onInvalid[0].context.record).toBeDefined()
|
||||
expect(onInvalid[0].context.record.key).toBe(record.key)
|
||||
expect(onInvalid[0].context.validationResult).toBeDefined()
|
||||
})
|
||||
})
|
||||
|
|
|
@ -15,17 +15,6 @@ const _events = {
|
|||
uploadFile: common(),
|
||||
downloadFile: common(),
|
||||
},
|
||||
indexApi: {
|
||||
buildIndex: common(),
|
||||
listItems: common(),
|
||||
delete: common(),
|
||||
aggregates: common(),
|
||||
},
|
||||
collectionApi: {
|
||||
getAllowedRecordTypes: common(),
|
||||
initialise: common(),
|
||||
delete: common(),
|
||||
},
|
||||
authApi: {
|
||||
authenticate: common(),
|
||||
authenticateTemporaryAccess: common(),
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
export function allModelsViewName(modelId) {
|
||||
return `all_${modelId}`
|
||||
}
|
||||
export function allModelsDesignDocName(modelId) {
|
||||
return `all_${modelId}`
|
||||
}
|
||||
export function instanceDatabaseId (clientId, instanceId) {
|
||||
return `instance:${clientId}:${instanceId}`
|
||||
}
|
||||
export function clientDatabaseId(clientId) {
|
||||
return `client:${clientId}`
|
||||
}
|
|
@ -1,11 +1,15 @@
|
|||
const couchdb = require("../../db")
|
||||
const { cloneDeep, mapValues, keyBy, filter, includes } = require("lodash/fp")
|
||||
const { cloneDeep, mapValues, keyBy } = require("lodash/fp")
|
||||
const {
|
||||
validateRecord,
|
||||
} = require("../../../common/src/records/validateRecord.mjs")
|
||||
const { events } = require("../../../common/src/common/events.mjs")
|
||||
const { $, isNonEmptyString } = require("../../../common/src/common")
|
||||
const { $ } = require("../../../common/src/common")
|
||||
import { safeParseField } from "../../../common/src/schema/types"
|
||||
import {
|
||||
allModelsViewName,
|
||||
allModelsDesignDocName,
|
||||
} from "./couchdbNamingConventions"
|
||||
|
||||
async function save(ctx) {
|
||||
const db = couchdb.use(ctx.databaseId)
|
||||
|
@ -19,7 +23,7 @@ async function save(ctx) {
|
|||
|
||||
const validationResult = await validateRecord(ctx.schema, record)
|
||||
if (!validationResult.isValid) {
|
||||
await app.publish(events.recordApi.save.onInvalid, {
|
||||
await ctx.publish(events.recordApi.save.onInvalid, {
|
||||
record,
|
||||
validationResult,
|
||||
})
|
||||
|
@ -30,13 +34,13 @@ async function save(ctx) {
|
|||
|
||||
if (!record._rev) {
|
||||
await db.insert(record)
|
||||
await app.publish(events.recordApi.save.onRecordCreated, {
|
||||
await ctx.publish(events.recordApi.save.onRecordCreated, {
|
||||
record: record,
|
||||
})
|
||||
} else {
|
||||
const oldRecord = await _findRecord(db, ctx.schema, record._id)
|
||||
await db.insert(record)
|
||||
await app.publish(events.recordApi.save.onRecordUpdated, {
|
||||
await ctx.publish(events.recordApi.save.onRecordUpdated, {
|
||||
old: oldRecord,
|
||||
new: record,
|
||||
})
|
||||
|
@ -49,16 +53,23 @@ async function save(ctx) {
|
|||
|
||||
async function fetch(ctx) {
|
||||
const db = couchdb.db.use(ctx.params.databaseId)
|
||||
|
||||
ctx.body = await db.view("database", "all_somemodel", {
|
||||
const model = ctx.schema.findModel(ctx.modelName)
|
||||
ctx.body = db.viewAsStream(
|
||||
allModelsDesignDocName(model.id),
|
||||
allModelsViewName(model.id),
|
||||
{
|
||||
include_docs: true,
|
||||
key: ["app"]
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
async function find(ctx) {
|
||||
const db = couchdb.db.use(ctx.params.databaseId)
|
||||
const { body, status } = await _findRecord(db, ctx.schema, ctx.params.id)
|
||||
const { body, status } = await _findRecord(
|
||||
db,
|
||||
ctx.schema,
|
||||
ctx.params.recordId
|
||||
)
|
||||
ctx.status = status
|
||||
ctx.body = body
|
||||
}
|
||||
|
@ -78,28 +89,6 @@ async function _findRecord(db, schema, id) {
|
|||
mapValues(f => safeParseField(f, storedData)),
|
||||
])
|
||||
|
||||
const links = $(model.fields, [
|
||||
filter(
|
||||
f => f.type === "reference" && isNonEmptyString(loadedRecord[f.name].key)
|
||||
),
|
||||
map(f => ({
|
||||
promise: _findRecord(db, schema, loadedRecord[f.name]._id),
|
||||
index: getNode(app.hierarchy, f.typeOptions.indexNodeKey),
|
||||
field: f,
|
||||
})),
|
||||
])
|
||||
|
||||
if (links.length > 0) {
|
||||
const refRecords = await Promise.all(map(p => p.promise)(links))
|
||||
|
||||
for (const ref of links) {
|
||||
loadedRecord[ref.field.name] = mapRecord(
|
||||
refRecords[links.indexOf(ref)],
|
||||
ref.index
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
loadedRecord._rev = storedData._rev
|
||||
loadedRecord._id = storedData._id
|
||||
loadedRecord._modelId = storedData._modelId
|
||||
|
@ -112,4 +101,4 @@ async function destroy(ctx) {
|
|||
ctx.body = await database.destroy(ctx.params.recordId);
|
||||
}
|
||||
|
||||
module.exports = {dave, fetch, destroy, find};
|
||||
module.exports = { save, fetch, destroy, find }
|
||||
|
|
|
@ -4,8 +4,9 @@ const controller = require("../../controllers/record");
|
|||
const router = Router();
|
||||
|
||||
router
|
||||
.get("/api/:databaseId/records", controller.fetch)
|
||||
.post("/api/:databaseId/records", controller.save)
|
||||
.delete("/api/:databaseId/records/:recordId", controller.destroy)
|
||||
.get("/api/:databaseId/records/:modelname", controller.fetch)
|
||||
.post("/api/:databaseId/record", controller.save)
|
||||
.get("/api/:databaseId/record/:recordId", controller.find)
|
||||
.delete("/api/:databaseId/record/:recordId", controller.destroy)
|
||||
|
||||
module.exports = router;
|
Loading…
Reference in New Issue