adding save & load controllers
This commit is contained in:
parent
195275fc9e
commit
ebc1c44343
|
@ -27,7 +27,7 @@ import {
|
|||
includes,
|
||||
filter,
|
||||
} from "lodash/fp"
|
||||
import { events, eventsList } from "./events"
|
||||
import { events, eventsList } from "./events.mjs"
|
||||
|
||||
// this is the combinator function
|
||||
export const $$ = (...funcs) => arg => flow(funcs)(arg)
|
||||
|
@ -241,7 +241,7 @@ export const retry = async (fn, retries, delay, ...args) => {
|
|||
}
|
||||
}
|
||||
|
||||
export { events } from "./events"
|
||||
export { events } from "./events.mjs"
|
||||
|
||||
export default {
|
||||
ifExists,
|
|
@ -0,0 +1,32 @@
|
|||
export const getNewRecordValidationRule = (
|
||||
invalidField,
|
||||
messageWhenInvalid,
|
||||
expressionWhenValid
|
||||
) => ({
|
||||
invalidField,
|
||||
messageWhenInvalid,
|
||||
expressionWhenValid,
|
||||
})
|
||||
|
||||
export const commonRecordValidationRules = {
|
||||
fieldNotEmpty: fieldName =>
|
||||
getNewRecordValidationRule(
|
||||
fieldName,
|
||||
`${fieldName} is empty`,
|
||||
`record['${fieldName}'] && record['${fieldName}'].length > 0`
|
||||
),
|
||||
|
||||
fieldBetween: (fieldName, min, max) =>
|
||||
getNewRecordValidationRule(
|
||||
fieldName,
|
||||
`${fieldName} must be between ${min.toString()} and ${max.toString()}`,
|
||||
`record['${fieldName}'] >= ${min} && record['${fieldName}'] <= ${max} `
|
||||
),
|
||||
|
||||
fieldGreaterThan: (fieldName, min, max) =>
|
||||
getNewRecordValidationRule(
|
||||
fieldName,
|
||||
`${fieldName} must be greater than ${min.toString()} and ${max.toString()}`,
|
||||
`record['${fieldName}'] >= ${min} `
|
||||
),
|
||||
}
|
|
@ -1,18 +1,19 @@
|
|||
import { map, reduce, filter, isEmpty, flatten, each } from "lodash/fp"
|
||||
import { compileCode } from "../common/compileCode"
|
||||
import { compileCode } from "../common/compileCode.mjs"
|
||||
import _ from "lodash"
|
||||
import { getExactNodeForKey } from "../templateApi/hierarchy"
|
||||
import { validateFieldParse, validateTypeConstraints } from "../types"
|
||||
import { $, isNothing, isNonEmptyString } from "../common"
|
||||
import { _getContext } from "./getContext"
|
||||
import {
|
||||
validateFieldParse,
|
||||
validateTypeConstraints,
|
||||
} from "../schema/types/index.mjs"
|
||||
import { $, isNonEmptyString } from "../common/index.mjs"
|
||||
|
||||
const fieldParseError = (fieldName, value) => ({
|
||||
fields: [fieldName],
|
||||
message: `Could not parse field ${fieldName}:${value}`,
|
||||
})
|
||||
|
||||
const validateAllFieldParse = (record, recordNode) =>
|
||||
$(recordNode.fields, [
|
||||
const validateAllFieldParse = (record, model) =>
|
||||
$(model.fields, [
|
||||
map(f => ({ name: f.name, parseResult: validateFieldParse(f, record) })),
|
||||
reduce((errors, f) => {
|
||||
if (f.parseResult.success) return errors
|
||||
|
@ -21,10 +22,10 @@ const validateAllFieldParse = (record, recordNode) =>
|
|||
}, []),
|
||||
])
|
||||
|
||||
const validateAllTypeConstraints = async (record, recordNode, context) => {
|
||||
const validateAllTypeConstraints = async (record, model) => {
|
||||
const errors = []
|
||||
for (const field of recordNode.fields) {
|
||||
$(await validateTypeConstraints(field, record, context), [
|
||||
for (const field of model.fields) {
|
||||
$(await validateTypeConstraints(field, record), [
|
||||
filter(isNonEmptyString),
|
||||
map(m => ({ message: m, fields: [field.name] })),
|
||||
each(e => errors.push(e)),
|
||||
|
@ -33,10 +34,10 @@ const validateAllTypeConstraints = async (record, recordNode, context) => {
|
|||
return errors
|
||||
}
|
||||
|
||||
const runRecordValidationRules = (record, recordNode) => {
|
||||
const runRecordValidationRules = (record, model) => {
|
||||
const runValidationRule = rule => {
|
||||
const isValid = compileCode(rule.expressionWhenValid)
|
||||
const expressionContext = { record, _ }
|
||||
const expressionContext = { record }
|
||||
return isValid(expressionContext)
|
||||
? { valid: true }
|
||||
: {
|
||||
|
@ -46,7 +47,7 @@ const runRecordValidationRules = (record, recordNode) => {
|
|||
}
|
||||
}
|
||||
|
||||
return $(recordNode.validationRules, [
|
||||
return $(model.validationRules, [
|
||||
map(runValidationRule),
|
||||
flatten,
|
||||
filter(r => r.valid === false),
|
||||
|
@ -54,23 +55,17 @@ const runRecordValidationRules = (record, recordNode) => {
|
|||
])
|
||||
}
|
||||
|
||||
export const validate = app => async (record, context) => {
|
||||
context = isNothing(context) ? _getContext(app, record.key) : context
|
||||
|
||||
const recordNode = getExactNodeForKey(app.hierarchy)(record.key)
|
||||
const fieldParseFails = validateAllFieldParse(record, recordNode)
|
||||
export const validateRecord = async (schema, record) => {
|
||||
const model = schema.findModel(record.modelId)
|
||||
const fieldParseFails = validateAllFieldParse(record, model)
|
||||
|
||||
// non parsing would cause further issues - exit here
|
||||
if (!isEmpty(fieldParseFails)) {
|
||||
return { isValid: false, errors: fieldParseFails }
|
||||
}
|
||||
|
||||
const recordValidationRuleFails = runRecordValidationRules(record, recordNode)
|
||||
const typeContraintFails = await validateAllTypeConstraints(
|
||||
record,
|
||||
recordNode,
|
||||
context
|
||||
)
|
||||
const recordValidationRuleFails = runRecordValidationRules(record, model)
|
||||
const typeContraintFails = await validateAllTypeConstraints(record, model)
|
||||
|
||||
if (
|
||||
isEmpty(fieldParseFails) &&
|
|
@ -1,15 +1,23 @@
|
|||
export const fullSchema = (models, views) => {
|
||||
const findModel = idOrName =>
|
||||
models.find(m => m.id === idOrName || m.name === idOrName)
|
||||
models.find(
|
||||
m => m.id === idOrName || m.name.toLowerCase() === idOrName.toLowerCase()
|
||||
)
|
||||
|
||||
const findView = idOrName =>
|
||||
views.find(m => m.id === idOrName || m.name === idOrName)
|
||||
views.find(
|
||||
m => m.id === idOrName || m.name.toLowerCase() === idOrName.toLowerCase()
|
||||
)
|
||||
|
||||
const findField = (modelIdOrName, fieldName) => {
|
||||
const model = models.find(
|
||||
m => m.id === modelIdOrName || m.name === modelIdOrName
|
||||
m =>
|
||||
m.id === modelIdOrName ||
|
||||
m.name.toLowerCase() === modelIdOrName.toLowerCase()
|
||||
)
|
||||
return model.fields.find(
|
||||
f => f.name.toLowerCase() === fieldName.toLowerCase()
|
||||
)
|
||||
return model.fields.find(f => f.name === fieldName)
|
||||
}
|
||||
|
||||
const viewsForModel = modelId => views.filter(v => v.modelId === modelId)
|
||||
|
|
|
@ -12,7 +12,7 @@ import {
|
|||
toNumberOrNull,
|
||||
$$,
|
||||
isSafeInteger,
|
||||
} from "../../common"
|
||||
} from "../../common/index.mjs"
|
||||
|
||||
const arrayFunctions = () =>
|
||||
typeFunctions({
|
||||
|
|
|
@ -6,7 +6,7 @@ import {
|
|||
parsedSuccess,
|
||||
getDefaultExport,
|
||||
} from "./typeHelpers"
|
||||
import { switchCase, defaultCase, isOneOf, toBoolOrNull } from "../../common"
|
||||
import { switchCase, defaultCase, isOneOf, toBoolOrNull } from "../../common/index.mjs"
|
||||
|
||||
const boolFunctions = typeFunctions({
|
||||
default: constant(null),
|
||||
|
|
|
@ -6,7 +6,7 @@ import {
|
|||
parsedSuccess,
|
||||
getDefaultExport,
|
||||
} from "./typeHelpers"
|
||||
import { switchCase, defaultCase, toDateOrNull, isNonEmptyArray } from "../../common"
|
||||
import { switchCase, defaultCase, toDateOrNull } from "../../common"
|
||||
|
||||
const dateFunctions = typeFunctions({
|
||||
default: constant(null),
|
||||
|
@ -21,13 +21,9 @@ const parseStringToDate = s =>
|
|||
[defaultCase, parsedFailed]
|
||||
)(new Date(s))
|
||||
|
||||
const isNullOrEmpty = d =>
|
||||
isNull(d)
|
||||
|| (d || "").toString() === ""
|
||||
const isNullOrEmpty = d => isNull(d) || (d || "").toString() === ""
|
||||
|
||||
const isDateOrEmpty = d =>
|
||||
isDate(d)
|
||||
|| isNullOrEmpty(d)
|
||||
const isDateOrEmpty = d => isDate(d) || isNullOrEmpty(d)
|
||||
|
||||
const dateTryParse = switchCase(
|
||||
[isDateOrEmpty, parsedSuccess],
|
||||
|
|
|
@ -5,7 +5,7 @@ import {
|
|||
parsedSuccess,
|
||||
getDefaultExport,
|
||||
} from "./typeHelpers"
|
||||
import { switchCase, defaultCase, none, $, splitKey } from "../../common"
|
||||
import { switchCase, defaultCase, none, $, splitKey } from "../../common/index.mjs"
|
||||
|
||||
const illegalCharacters = "*?\\/:<>|\0\b\f\v"
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ import {
|
|||
isArray,
|
||||
has,
|
||||
} from "lodash/fp"
|
||||
import { $ } from "../../common"
|
||||
import { $ } from "../../common/index.mjs"
|
||||
import { parsedSuccess } from "./typeHelpers"
|
||||
import string from "./string"
|
||||
import bool from "./bool"
|
||||
|
@ -19,7 +19,7 @@ import datetime from "./datetime"
|
|||
import array from "./array"
|
||||
import link from "./link"
|
||||
import file from "./file"
|
||||
import { BadRequestError } from "../../common/errors"
|
||||
import { BadRequestError } from "../../common/errors.mjs"
|
||||
|
||||
const allTypes = () => {
|
||||
const basicTypes = {
|
||||
|
@ -67,8 +67,8 @@ export const validateFieldParse = (field, record) =>
|
|||
|
||||
export const getDefaultOptions = type => getType(type).getDefaultOptions()
|
||||
|
||||
export const validateTypeConstraints = async (field, record, context) =>
|
||||
await getType(field.type).validateTypeConstraints(field, record, context)
|
||||
export const validateTypeConstraints = async (field, record) =>
|
||||
await getType(field.type).validateTypeConstraints(field, record)
|
||||
|
||||
export const detectType = value => {
|
||||
if (isString(value)) return string
|
||||
|
@ -76,8 +76,7 @@ export const detectType = value => {
|
|||
if (isNumber(value)) return number
|
||||
if (isDate(value)) return datetime
|
||||
if (isArray(value)) return array(detectType(value[0]))
|
||||
if (isObject(value) && has("key")(value) && has("value")(value))
|
||||
return link
|
||||
if (isObject(value) && has("key")(value) && has("value")(value)) return link
|
||||
if (isObject(value) && has("relativePath")(value) && has("size")(value))
|
||||
return file
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { isString, isObjectLike, isNull, has, isEmpty } from "lodash/fp"
|
||||
import { isString, isObjectLike, isNull, has } from "lodash/fp"
|
||||
import {
|
||||
typeFunctions,
|
||||
makerule,
|
||||
parsedSuccess,
|
||||
getDefaultExport,
|
||||
parsedFailed,
|
||||
|
@ -11,7 +10,7 @@ import {
|
|||
defaultCase,
|
||||
isNonEmptyString,
|
||||
isArrayOfString,
|
||||
} from "../../common"
|
||||
} from "../../common/index.mjs"
|
||||
|
||||
const linkNothing = () => ({ key: "" })
|
||||
|
||||
|
@ -65,20 +64,7 @@ const options = {
|
|||
},
|
||||
}
|
||||
|
||||
const isEmptyString = s => isString(s) && isEmpty(s)
|
||||
|
||||
const ensurelinkExists = async (val, opts, context) =>
|
||||
isEmptyString(val.key) || (await context.linkExists(opts, val.key))
|
||||
|
||||
const typeConstraints = [
|
||||
makerule(
|
||||
ensurelinkExists,
|
||||
(val, opts) =>
|
||||
`"${val[opts.displayValue]}" does not exist in options list (key: ${
|
||||
val.key
|
||||
})`
|
||||
),
|
||||
]
|
||||
const typeConstraints = []
|
||||
|
||||
export default getDefaultExport(
|
||||
"link",
|
||||
|
|
|
@ -11,7 +11,7 @@ import {
|
|||
defaultCase,
|
||||
toNumberOrNull,
|
||||
isSafeInteger,
|
||||
} from "../../common"
|
||||
} from "../../common/index.mjs"
|
||||
|
||||
const numberFunctions = typeFunctions({
|
||||
default: constant(null),
|
||||
|
|
|
@ -5,7 +5,7 @@ import {
|
|||
parsedSuccess,
|
||||
getDefaultExport,
|
||||
} from "./typeHelpers"
|
||||
import { switchCase, defaultCase, $ } from "../../common"
|
||||
import { switchCase, defaultCase, $ } from "../../common/index.mjs"
|
||||
|
||||
const objectFunctions = (definition, allTypes) =>
|
||||
typeFunctions({
|
||||
|
|
|
@ -12,7 +12,7 @@ import {
|
|||
toNumberOrNull,
|
||||
isSafeInteger,
|
||||
isArrayOfString,
|
||||
} from "../../common"
|
||||
} from "../../common/index.mjs"
|
||||
|
||||
const stringFunctions = typeFunctions({
|
||||
default: constant(null),
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { merge } from "lodash"
|
||||
import { constant, isUndefined, has, mapValues, cloneDeep } from "lodash/fp"
|
||||
import { isNotEmpty } from "../../common"
|
||||
import { isNotEmpty } from "../../common/index.mjs"
|
||||
|
||||
export const getSafeFieldParser = (tryParse, defaultValueFunctions) => (
|
||||
field,
|
||||
|
@ -48,12 +48,11 @@ export const typeFunctions = specificFunctions =>
|
|||
|
||||
export const validateTypeConstraints = validationRules => async (
|
||||
field,
|
||||
record,
|
||||
context
|
||||
record
|
||||
) => {
|
||||
const fieldValue = record[field.name]
|
||||
const validateRule = async r =>
|
||||
!(await r.isValid(fieldValue, field.typeOptions, context))
|
||||
!(await r.isValid(fieldValue, field.typeOptions))
|
||||
? r.getMessage(fieldValue, field.typeOptions)
|
||||
: ""
|
||||
|
||||
|
|
|
@ -2,6 +2,10 @@ 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"
|
||||
|
||||
export function testSchema() {
|
||||
const addFieldToModel = (model, { type, name }) => {
|
||||
|
@ -18,6 +22,10 @@ export function testSchema() {
|
|||
addFieldToModel(contactModel, { name: "Is Active", type: "bool" })
|
||||
addFieldToModel(contactModel, { name: "Created", type: "datetime" })
|
||||
|
||||
contactModel.validationRules.push(
|
||||
recordValidationRules(commonRecordValidationRules.fieldNotEmpty)
|
||||
)
|
||||
|
||||
const activeContactsView = newView(contactModel.id)
|
||||
activeContactsView.name = "Active Contacts"
|
||||
activeContactsView.map = "if (doc['Is Active']) emit(doc.Name, doc)"
|
||||
|
|
|
@ -1,95 +1,55 @@
|
|||
import {
|
||||
setupApphierarchy,
|
||||
stubEventHandler,
|
||||
basicAppHierarchyCreator_WithFields,
|
||||
basicAppHierarchyCreator_WithFields_AndIndexes,
|
||||
hierarchyFactory,
|
||||
withFields,
|
||||
} from "./specHelpers"
|
||||
import { find } from "lodash"
|
||||
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", () => {
|
||||
it("should return errors when any fields do not parse", async () => {
|
||||
const { recordApi } = await setupApphierarchy(
|
||||
basicAppHierarchyCreator_WithFields
|
||||
)
|
||||
const record = recordApi.getNew("/customers", "customer")
|
||||
it("should return errors when any fields do not parse", () => {
|
||||
const schema = testSchema()
|
||||
const record = getNewRecord(schema, "Contact")
|
||||
|
||||
record.surname = "Ledog"
|
||||
record.isalive = "hello"
|
||||
record.age = "nine"
|
||||
record.createddate = "blah"
|
||||
record.Name = "Ledog"
|
||||
record["Is Active"] = "hello"
|
||||
record.Created = "not a date"
|
||||
|
||||
const validationResult = await recordApi.validate(record)
|
||||
const validationResult = validateRecord(schema, record)
|
||||
|
||||
expect(validationResult.isValid).toBe(false)
|
||||
expect(validationResult.errors.length).toBe(3)
|
||||
expect(validationResult.errors.length).toBe(2)
|
||||
})
|
||||
|
||||
it("should return errors when mandatory field is empty", async () => {
|
||||
const withValidationRule = (hierarchy, templateApi) => {
|
||||
templateApi.addRecordValidationRule(hierarchy.customerRecord)(
|
||||
templateApi.commonRecordValidationRules.fieldNotEmpty("surname")
|
||||
)
|
||||
}
|
||||
it("should return errors when mandatory field is empty", () => {
|
||||
const schema = testSchema()
|
||||
const record = getNewRecord(schema, "Contact")
|
||||
record.Name = ""
|
||||
|
||||
const hierarchyCreator = hierarchyFactory(withFields, withValidationRule)
|
||||
const { recordApi } = await setupApphierarchy(hierarchyCreator)
|
||||
|
||||
const record = recordApi.getNew("/customers", "customer")
|
||||
|
||||
record.surname = ""
|
||||
|
||||
const validationResult = await recordApi.validate(record)
|
||||
const validationResult = validateRecord(schema, record)
|
||||
|
||||
expect(validationResult.isValid).toBe(false)
|
||||
expect(validationResult.errors.length).toBe(1)
|
||||
})
|
||||
|
||||
it("should return error when string field is beyond maxLength", async () => {
|
||||
const withFieldWithMaxLength = hierarchy => {
|
||||
const surname = find(
|
||||
hierarchy.customerRecord.fields,
|
||||
f => f.name === "surname"
|
||||
)
|
||||
surname.typeOptions.maxLength = 5
|
||||
}
|
||||
it("should return error when string field is beyond maxLength", () => {
|
||||
const schema = testSchema()
|
||||
schema.findField("Contact", "Name").typeOptions.maxLength = 5
|
||||
const record = getNewRecord(schema, "Contact")
|
||||
record.name = "more than 5 characters"
|
||||
|
||||
const hierarchyCreator = hierarchyFactory(
|
||||
withFields,
|
||||
withFieldWithMaxLength
|
||||
)
|
||||
const { recordApi } = await setupApphierarchy(hierarchyCreator)
|
||||
|
||||
const record = recordApi.getNew("/customers", "customer")
|
||||
record.surname = "more than 5 chars"
|
||||
|
||||
const validationResult = await recordApi.validate(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 > maxValue", async () => {
|
||||
const withFieldWithMaxLength = hierarchy => {
|
||||
const age = find(hierarchy.customerRecord.fields, f => f.name === "age")
|
||||
age.typeOptions.maxValue = 10
|
||||
age.typeOptions.minValue = 5
|
||||
}
|
||||
it("should return error when number field is > maxValue", () => {
|
||||
const schema = testSchema()
|
||||
schema.findField("Deal", "Estimated Value").typeOptions.maxValue = 5
|
||||
const record = getNewRecord(schema, "Deal")
|
||||
record["Estimated Value"] = 10
|
||||
|
||||
const hierarchyCreator = hierarchyFactory(
|
||||
withFields,
|
||||
withFieldWithMaxLength
|
||||
)
|
||||
const { recordApi } = await setupApphierarchy(hierarchyCreator)
|
||||
|
||||
const tooOldRecord = recordApi.getNew("/customers", "customer")
|
||||
tooOldRecord.age = 11
|
||||
|
||||
const tooOldResult = await recordApi.validate(tooOldRecord)
|
||||
expect(tooOldResult.isValid).toBe(false)
|
||||
expect(tooOldResult.errors.length).toBe(1)
|
||||
const validationResult = recordApi.validate(schema, record)
|
||||
expect(validationResult.isValid).toBe(false)
|
||||
expect(validationResult.errors.length).toBe(1)
|
||||
})
|
||||
|
||||
it("should return error when number field is < minValue", async () => {
|
|
@ -1,21 +1,115 @@
|
|||
const couchdb = require("../../db");
|
||||
const couchdb = require("../../db")
|
||||
const { cloneDeep, mapValues, keyBy, filter, includes } = 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")
|
||||
import { safeParseField } from "../../../common/src/schema/types"
|
||||
|
||||
const controller = {
|
||||
save: async ctx => {
|
||||
},
|
||||
fetch: async ctx => {
|
||||
async function save(ctx) {
|
||||
const db = couchdb.use(ctx.databaseId)
|
||||
const record = cloneDeep(ctx.body)
|
||||
|
||||
if (!ctx.schema.findModel(record._modelId)) {
|
||||
ctx.status = 400
|
||||
ctx.message = `do not recognise modelId : ${record._modelId}`
|
||||
return
|
||||
}
|
||||
|
||||
const validationResult = await validateRecord(ctx.schema, record)
|
||||
if (!validationResult.isValid) {
|
||||
await app.publish(events.recordApi.save.onInvalid, {
|
||||
record,
|
||||
validationResult,
|
||||
})
|
||||
ctx.status = 400
|
||||
ctx.message = "record failed validation rules"
|
||||
ctx.body = validationResult
|
||||
}
|
||||
|
||||
if (!record._rev) {
|
||||
await db.insert(record)
|
||||
await app.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, {
|
||||
old: oldRecord,
|
||||
new: record,
|
||||
})
|
||||
}
|
||||
|
||||
const savedHead = await db.head(record._id)
|
||||
record._rev = savedHead._rev
|
||||
return record
|
||||
}
|
||||
|
||||
async function fetch(ctx) {
|
||||
const db = couchdb.db.use(ctx.params.databaseId)
|
||||
|
||||
ctx.body = await db.view("database", "all_somemodel", {
|
||||
include_docs: true,
|
||||
key: ["app"]
|
||||
})
|
||||
},
|
||||
destroy: async ctx => {
|
||||
}
|
||||
|
||||
async function find(ctx) {
|
||||
const db = couchdb.db.use(ctx.params.databaseId)
|
||||
const { body, status } = await _findRecord(db, ctx.schema, ctx.params.id)
|
||||
ctx.status = status
|
||||
ctx.body = body
|
||||
}
|
||||
|
||||
async function _findRecord(db, schema, id) {
|
||||
let storedData
|
||||
try {
|
||||
storedData = await db.get(id)
|
||||
} catch (err) {
|
||||
return err
|
||||
}
|
||||
|
||||
const model = schema.findModel(storedData._modelId)
|
||||
|
||||
const loadedRecord = $(model.fields, [
|
||||
keyBy("name"),
|
||||
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
|
||||
return loadedRecord
|
||||
}
|
||||
|
||||
async function destroy(ctx) {
|
||||
const databaseId = ctx.params.databaseId;
|
||||
const database = couchdb.db.use(databaseId)
|
||||
ctx.body = await database.destroy(ctx.params.recordId);
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = controller;
|
||||
module.exports = {dave, fetch, destroy, find};
|
|
@ -0,0 +1,9 @@
|
|||
const { testSchema } = require("../../common/test/testSchema")
|
||||
|
||||
describe("record persistence", () => {
|
||||
it("should save a record", async () => {
|
||||
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
const { testSchema } = require("../../common/test/testSchema")
|
||||
|
||||
describe("record persistence", async () => {
|
||||
it("should ")
|
||||
})
|
||||
|
||||
|
|
@ -2983,7 +2983,7 @@ lodash.sortby@^4.7.0:
|
|||
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
|
||||
integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=
|
||||
|
||||
lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.15, lodash@^4.17.4, lodash@^4.2.1:
|
||||
lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.4, lodash@^4.2.1:
|
||||
version "4.17.15"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
|
||||
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
|
||||
|
|
Loading…
Reference in New Issue