mirror of https://github.com/Budibase/budibase.git
160 changed files with 3990 additions and 3221 deletions
@ -1,46 +0,0 @@ |
|||
<script> |
|||
import { backendUiStore } from "builderStore" |
|||
import { notifier } from "builderStore/store/notifications" |
|||
import RecordFieldControl from "../RecordFieldControl.svelte" |
|||
import * as api from "../api" |
|||
import { ModalContent } from "@budibase/bbui" |
|||
import ErrorsBox from "components/common/ErrorsBox.svelte" |
|||
|
|||
export let record = {} |
|||
|
|||
let errors = [] |
|||
|
|||
$: creating = record?._id == null |
|||
$: model = record.modelId |
|||
? $backendUiStore.models.find(model => model._id === record?.modelId) |
|||
: $backendUiStore.selectedModel |
|||
$: modelSchema = Object.entries(model?.schema ?? {}) |
|||
|
|||
async function saveRecord() { |
|||
const recordResponse = await api.saveRecord( |
|||
{ ...record, modelId: model._id }, |
|||
model._id |
|||
) |
|||
if (recordResponse.errors) { |
|||
errors = Object.keys(recordResponse.errors) |
|||
.map(k => ({ dataPath: k, message: recordResponse.errors[k] })) |
|||
.flat() |
|||
// Prevent modal closing if there were errors |
|||
return false |
|||
} |
|||
notifier.success("Record saved successfully.") |
|||
backendUiStore.actions.records.save(recordResponse) |
|||
} |
|||
</script> |
|||
|
|||
<ModalContent |
|||
title={creating ? 'Create Row' : 'Edit Row'} |
|||
confirmText={creating ? 'Create Row' : 'Save Row'} |
|||
onConfirm={saveRecord}> |
|||
<ErrorsBox {errors} /> |
|||
{#each modelSchema as [key, meta]} |
|||
<div> |
|||
<RecordFieldControl {meta} bind:value={record[key]} /> |
|||
</div> |
|||
{/each} |
|||
</ModalContent> |
|||
@ -0,0 +1,46 @@ |
|||
<script> |
|||
import { backendUiStore } from "builderStore" |
|||
import { notifier } from "builderStore/store/notifications" |
|||
import RowFieldControl from "../RowFieldControl.svelte" |
|||
import * as api from "../api" |
|||
import { ModalContent } from "@budibase/bbui" |
|||
import ErrorsBox from "components/common/ErrorsBox.svelte" |
|||
|
|||
export let row = {} |
|||
|
|||
let errors = [] |
|||
|
|||
$: creating = row?._id == null |
|||
$: table = row.tableId |
|||
? $backendUiStore.tables.find(table => table._id === row?.tableId) |
|||
: $backendUiStore.selectedTable |
|||
$: tableSchema = Object.entries(table?.schema ?? {}) |
|||
|
|||
async function saveRow() { |
|||
const rowResponse = await api.saveRow( |
|||
{ ...row, tableId: table._id }, |
|||
table._id |
|||
) |
|||
if (rowResponse.errors) { |
|||
errors = Object.keys(rowResponse.errors) |
|||
.map(k => ({ dataPath: k, message: rowResponse.errors[k] })) |
|||
.flat() |
|||
// Prevent modal closing if there were errors |
|||
return false |
|||
} |
|||
notifier.success("Row saved successfully.") |
|||
backendUiStore.actions.rows.save(rowResponse) |
|||
} |
|||
</script> |
|||
|
|||
<ModalContent |
|||
title={creating ? 'Create Row' : 'Edit Row'} |
|||
confirmText={creating ? 'Create Row' : 'Save Row'} |
|||
onConfirm={saveRow}> |
|||
<ErrorsBox {errors} /> |
|||
{#each tableSchema as [key, meta]} |
|||
<div> |
|||
<RowFieldControl {meta} bind:value={row[key]} /> |
|||
</div> |
|||
{/each} |
|||
</ModalContent> |
|||
@ -1,53 +0,0 @@ |
|||
<script> |
|||
import { onMount } from "svelte" |
|||
import { backendUiStore } from "builderStore" |
|||
import api from "builderStore/api" |
|||
import { Select, Label, Multiselect } from "@budibase/bbui" |
|||
import { capitalise } from "../../helpers" |
|||
|
|||
export let schema |
|||
export let linkedRecords = [] |
|||
|
|||
let records = [] |
|||
|
|||
$: label = capitalise(schema.name) |
|||
$: linkedModelId = schema.modelId |
|||
$: linkedModel = $backendUiStore.models.find( |
|||
model => model._id === linkedModelId |
|||
) |
|||
$: fetchRecords(linkedModelId) |
|||
|
|||
async function fetchRecords(linkedModelId) { |
|||
const FETCH_RECORDS_URL = `/api/${linkedModelId}/records` |
|||
try { |
|||
const response = await api.get(FETCH_RECORDS_URL) |
|||
records = await response.json() |
|||
} catch (error) { |
|||
console.log(error) |
|||
records = [] |
|||
} |
|||
} |
|||
|
|||
function getPrettyName(record) { |
|||
return record[linkedModel.primaryDisplay || "_id"] |
|||
} |
|||
</script> |
|||
|
|||
{#if linkedModel.primaryDisplay == null} |
|||
<Label extraSmall grey>{label}</Label> |
|||
<Label small black> |
|||
Please choose a primary display column for the |
|||
<b>{linkedModel.name}</b> |
|||
table. |
|||
</Label> |
|||
{:else} |
|||
<Multiselect |
|||
secondary |
|||
bind:value={linkedRecords} |
|||
{label} |
|||
placeholder="Choose some options"> |
|||
{#each records as record} |
|||
<option value={record._id}>{getPrettyName(record)}</option> |
|||
{/each} |
|||
</Multiselect> |
|||
{/if} |
|||
@ -0,0 +1,53 @@ |
|||
<script> |
|||
import { onMount } from "svelte" |
|||
import { backendUiStore } from "builderStore" |
|||
import api from "builderStore/api" |
|||
import { Select, Label, Multiselect } from "@budibase/bbui" |
|||
import { capitalise } from "../../helpers" |
|||
|
|||
export let schema |
|||
export let linkedRows = [] |
|||
|
|||
let rows = [] |
|||
|
|||
$: label = capitalise(schema.name) |
|||
$: linkedTableId = schema.tableId |
|||
$: linkedTable = $backendUiStore.tables.find( |
|||
table => table._id === linkedTableId |
|||
) |
|||
$: fetchRows(linkedTableId) |
|||
|
|||
async function fetchRows(linkedTableId) { |
|||
const FETCH_ROWS_URL = `/api/${linkedTableId}/rows` |
|||
try { |
|||
const response = await api.get(FETCH_ROWS_URL) |
|||
rows = await response.json() |
|||
} catch (error) { |
|||
console.log(error) |
|||
rows = [] |
|||
} |
|||
} |
|||
|
|||
function getPrettyName(row) { |
|||
return row[linkedTable.primaryDisplay || "_id"] |
|||
} |
|||
</script> |
|||
|
|||
{#if linkedTable.primaryDisplay == null} |
|||
<Label extraSmall grey>{label}</Label> |
|||
<Label small black> |
|||
Please choose a primary display column for the |
|||
<b>{linkedTable.name}</b> |
|||
table. |
|||
</Label> |
|||
{:else} |
|||
<Multiselect |
|||
secondary |
|||
bind:value={linkedRows} |
|||
{label} |
|||
placeholder="Choose some options"> |
|||
{#each rows as row} |
|||
<option value={row._id}>{getPrettyName(row)}</option> |
|||
{/each} |
|||
</Multiselect> |
|||
{/if} |
|||
@ -1,6 +1,6 @@ |
|||
<script> |
|||
import { goto } from "@sveltech/routify" |
|||
$goto("../model") |
|||
$goto("../table") |
|||
</script> |
|||
|
|||
<!-- routify:options index=false --> |
|||
|
|||
@ -1,15 +0,0 @@ |
|||
<script> |
|||
import { params } from "@sveltech/routify" |
|||
import { backendUiStore } from "builderStore" |
|||
|
|||
if ($params.selectedModel) { |
|||
const model = $backendUiStore.models.find( |
|||
m => m._id === $params.selectedModel |
|||
) |
|||
if (model) { |
|||
backendUiStore.actions.models.select(model) |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<slot /> |
|||
@ -1,32 +0,0 @@ |
|||
<script> |
|||
import { backendUiStore } from "builderStore" |
|||
import { goto, leftover } from "@sveltech/routify" |
|||
import { onMount } from "svelte" |
|||
|
|||
async function selectModel(model) { |
|||
backendUiStore.actions.models.select(model) |
|||
} |
|||
|
|||
onMount(async () => { |
|||
// navigate to first model in list, if not already selected |
|||
// and this is the final url (i.e. no selectedModel) |
|||
if ( |
|||
!$leftover && |
|||
$backendUiStore.models.length > 0 && |
|||
(!$backendUiStore.selectedModel || !$backendUiStore.selectedModel._id) |
|||
) { |
|||
$goto(`./${$backendUiStore.models[0]._id}`) |
|||
} |
|||
}) |
|||
</script> |
|||
|
|||
<div class="root"> |
|||
<slot /> |
|||
</div> |
|||
|
|||
<style> |
|||
.root { |
|||
height: 100%; |
|||
position: relative; |
|||
} |
|||
</style> |
|||
@ -1,35 +0,0 @@ |
|||
<script> |
|||
import { store, backendUiStore } from "builderStore" |
|||
import { goto, leftover } from "@sveltech/routify" |
|||
import { onMount } from "svelte" |
|||
|
|||
async function selectModel(model) { |
|||
backendUiStore.actions.models.select(model) |
|||
} |
|||
|
|||
onMount(async () => { |
|||
// navigate to first model in list, if not already selected |
|||
// and this is the final url (i.e. no selectedModel) |
|||
if ( |
|||
!$leftover && |
|||
$backendUiStore.models.length > 0 && |
|||
(!$backendUiStore.selectedModel || !$backendUiStore.selectedModel._id) |
|||
) { |
|||
// this file routes as .../models/index, so, go up one. |
|||
$goto(`../${$backendUiStore.models[0]._id}`) |
|||
} |
|||
}) |
|||
</script> |
|||
|
|||
{#if $backendUiStore.models.length === 0} |
|||
<i>Create your first table to start building</i> |
|||
{:else} |
|||
<i>Select a table to edit</i> |
|||
{/if} |
|||
|
|||
<style> |
|||
i { |
|||
font-size: var(--font-size-xl); |
|||
color: var(--grey-4); |
|||
} |
|||
</style> |
|||
@ -0,0 +1,15 @@ |
|||
<script> |
|||
import { params } from "@sveltech/routify" |
|||
import { backendUiStore } from "builderStore" |
|||
|
|||
if ($params.selectedTable) { |
|||
const table = $backendUiStore.tables.find( |
|||
m => m._id === $params.selectedTable |
|||
) |
|||
if (table) { |
|||
backendUiStore.actions.tables.select(table) |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<slot /> |
|||
@ -1,12 +1,12 @@ |
|||
<script> |
|||
import ModelDataTable from "components/backend/DataTable/ModelDataTable.svelte" |
|||
import TableDataTable from "components/backend/DataTable/DataTable.svelte" |
|||
import { backendUiStore } from "builderStore" |
|||
|
|||
$: selectedModel = $backendUiStore.selectedModel |
|||
$: selectedTable = $backendUiStore.selectedTable |
|||
</script> |
|||
|
|||
{#if $backendUiStore.selectedDatabase._id && selectedModel.name} |
|||
<ModelDataTable /> |
|||
{#if $backendUiStore.selectedDatabase._id && selectedTable.name} |
|||
<TableDataTable /> |
|||
{:else} |
|||
<i>Create your first table to start building</i> |
|||
{/if} |
|||
@ -0,0 +1,32 @@ |
|||
<script> |
|||
import { backendUiStore } from "builderStore" |
|||
import { goto, leftover } from "@sveltech/routify" |
|||
import { onMount } from "svelte" |
|||
|
|||
async function selectTable(table) { |
|||
backendUiStore.actions.tables.select(table) |
|||
} |
|||
|
|||
onMount(async () => { |
|||
// navigate to first table in list, if not already selected |
|||
// and this is the final url (i.e. no selectedTable) |
|||
if ( |
|||
!$leftover && |
|||
$backendUiStore.tables.length > 0 && |
|||
(!$backendUiStore.selectedTable || !$backendUiStore.selectedTable._id) |
|||
) { |
|||
$goto(`./${$backendUiStore.tables[0]._id}`) |
|||
} |
|||
}) |
|||
</script> |
|||
|
|||
<div class="root"> |
|||
<slot /> |
|||
</div> |
|||
|
|||
<style> |
|||
.root { |
|||
height: 100%; |
|||
position: relative; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,35 @@ |
|||
<script> |
|||
import { store, backendUiStore } from "builderStore" |
|||
import { goto, leftover } from "@sveltech/routify" |
|||
import { onMount } from "svelte" |
|||
|
|||
async function selectTable(table) { |
|||
backendUiStore.actions.tables.select(table) |
|||
} |
|||
|
|||
onMount(async () => { |
|||
// navigate to first table in list, if not already selected |
|||
// and this is the final url (i.e. no selectedTable) |
|||
if ( |
|||
!$leftover && |
|||
$backendUiStore.tables.length > 0 && |
|||
(!$backendUiStore.selectedTable || !$backendUiStore.selectedTable._id) |
|||
) { |
|||
// this file routes as .../tables/index, so, go up one. |
|||
$goto(`../${$backendUiStore.tables[0]._id}`) |
|||
} |
|||
}) |
|||
</script> |
|||
|
|||
{#if $backendUiStore.tables.length === 0} |
|||
<i>Create your first table to start building</i> |
|||
{:else} |
|||
<i>Select a table to edit</i> |
|||
{/if} |
|||
|
|||
<style> |
|||
i { |
|||
font-size: var(--font-size-xl); |
|||
color: var(--grey-4); |
|||
} |
|||
</style> |
|||
@ -1,146 +0,0 @@ |
|||
const CouchDB = require("../../db") |
|||
const linkRecords = require("../../db/linkedRecords") |
|||
const csvParser = require("../../utilities/csvParser") |
|||
const { |
|||
getRecordParams, |
|||
getModelParams, |
|||
generateModelID, |
|||
generateRecordID, |
|||
} = require("../../db/utils") |
|||
|
|||
exports.fetch = async function(ctx) { |
|||
const db = new CouchDB(ctx.user.instanceId) |
|||
const body = await db.allDocs( |
|||
getModelParams(null, { |
|||
include_docs: true, |
|||
}) |
|||
) |
|||
ctx.body = body.rows.map(row => row.doc) |
|||
} |
|||
|
|||
exports.find = async function(ctx) { |
|||
const db = new CouchDB(ctx.user.instanceId) |
|||
ctx.body = await db.get(ctx.params.id) |
|||
} |
|||
|
|||
exports.save = async function(ctx) { |
|||
const instanceId = ctx.user.instanceId |
|||
const db = new CouchDB(instanceId) |
|||
const { dataImport, ...rest } = ctx.request.body |
|||
const modelToSave = { |
|||
type: "model", |
|||
_id: generateModelID(), |
|||
views: {}, |
|||
...rest, |
|||
} |
|||
let renameDocs = [] |
|||
|
|||
// if the model obj had an _id then it will have been retrieved
|
|||
const oldModel = ctx.preExisting |
|||
|
|||
// rename record fields when table column is renamed
|
|||
const { _rename } = modelToSave |
|||
if (_rename && modelToSave.schema[_rename.updated].type === "link") { |
|||
throw "Cannot rename a linked field." |
|||
} else if (_rename && modelToSave.primaryDisplay === _rename.old) { |
|||
throw "Cannot rename the primary display field." |
|||
} else if (_rename) { |
|||
const records = await db.allDocs( |
|||
getRecordParams(modelToSave._id, null, { |
|||
include_docs: true, |
|||
}) |
|||
) |
|||
renameDocs = records.rows.map(({ doc }) => { |
|||
doc[_rename.updated] = doc[_rename.old] |
|||
delete doc[_rename.old] |
|||
return doc |
|||
}) |
|||
delete modelToSave._rename |
|||
} |
|||
|
|||
// update schema of non-statistics views when new columns are added
|
|||
for (let view in modelToSave.views) { |
|||
const modelView = modelToSave.views[view] |
|||
if (!modelView) continue |
|||
|
|||
if (modelView.schema.group || modelView.schema.field) continue |
|||
modelView.schema = modelToSave.schema |
|||
} |
|||
|
|||
// update linked records
|
|||
await linkRecords.updateLinks({ |
|||
instanceId, |
|||
eventType: oldModel |
|||
? linkRecords.EventType.MODEL_UPDATED |
|||
: linkRecords.EventType.MODEL_SAVE, |
|||
model: modelToSave, |
|||
oldModel: oldModel, |
|||
}) |
|||
|
|||
// don't perform any updates until relationships have been
|
|||
// checked by the updateLinks function
|
|||
if (renameDocs.length !== 0) { |
|||
await db.bulkDocs(renameDocs) |
|||
} |
|||
const result = await db.post(modelToSave) |
|||
modelToSave._rev = result.rev |
|||
|
|||
ctx.eventEmitter && |
|||
ctx.eventEmitter.emitModel(`model:save`, instanceId, modelToSave) |
|||
|
|||
if (dataImport && dataImport.path) { |
|||
// Populate the table with records imported from CSV in a bulk update
|
|||
const data = await csvParser.transform(dataImport) |
|||
|
|||
for (let row of data) { |
|||
row._id = generateRecordID(modelToSave._id) |
|||
row.modelId = modelToSave._id |
|||
} |
|||
|
|||
await db.bulkDocs(data) |
|||
} |
|||
|
|||
ctx.status = 200 |
|||
ctx.message = `Model ${ctx.request.body.name} saved successfully.` |
|||
ctx.body = modelToSave |
|||
} |
|||
|
|||
exports.destroy = async function(ctx) { |
|||
const instanceId = ctx.user.instanceId |
|||
const db = new CouchDB(instanceId) |
|||
const modelToDelete = await db.get(ctx.params.modelId) |
|||
|
|||
// Delete all records for that model
|
|||
const records = await db.allDocs( |
|||
getRecordParams(ctx.params.modelId, null, { |
|||
include_docs: true, |
|||
}) |
|||
) |
|||
await db.bulkDocs( |
|||
records.rows.map(record => ({ ...record.doc, _deleted: true })) |
|||
) |
|||
|
|||
// update linked records
|
|||
await linkRecords.updateLinks({ |
|||
instanceId, |
|||
eventType: linkRecords.EventType.MODEL_DELETE, |
|||
model: modelToDelete, |
|||
}) |
|||
|
|||
// don't remove the table itself until very end
|
|||
await db.remove(modelToDelete) |
|||
|
|||
ctx.eventEmitter && |
|||
ctx.eventEmitter.emitModel(`model:delete`, instanceId, modelToDelete) |
|||
ctx.status = 200 |
|||
ctx.message = `Model ${ctx.params.modelId} deleted.` |
|||
} |
|||
|
|||
exports.validateCSVSchema = async function(ctx) { |
|||
const { file, schema = {} } = ctx.request.body |
|||
const result = await csvParser.parse(file.path, schema) |
|||
ctx.body = { |
|||
schema: result, |
|||
path: file.path, |
|||
} |
|||
} |
|||
@ -1,382 +0,0 @@ |
|||
const CouchDB = require("../../db") |
|||
const validateJs = require("validate.js") |
|||
const linkRecords = require("../../db/linkedRecords") |
|||
const { |
|||
getRecordParams, |
|||
generateRecordID, |
|||
DocumentTypes, |
|||
SEPARATOR, |
|||
} = require("../../db/utils") |
|||
const { cloneDeep } = require("lodash") |
|||
|
|||
const MODEL_VIEW_BEGINS_WITH = `all${SEPARATOR}${DocumentTypes.MODEL}${SEPARATOR}` |
|||
|
|||
validateJs.extend(validateJs.validators.datetime, { |
|||
parse: function(value) { |
|||
return new Date(value).getTime() |
|||
}, |
|||
// Input is a unix timestamp
|
|||
format: function(value) { |
|||
return new Date(value).toISOString() |
|||
}, |
|||
}) |
|||
|
|||
exports.patch = async function(ctx) { |
|||
const instanceId = ctx.user.instanceId |
|||
const db = new CouchDB(instanceId) |
|||
let record = await db.get(ctx.params.id) |
|||
const model = await db.get(record.modelId) |
|||
const patchfields = ctx.request.body |
|||
record = coerceRecordValues(record, model) |
|||
|
|||
for (let key of Object.keys(patchfields)) { |
|||
if (!model.schema[key]) continue |
|||
record[key] = patchfields[key] |
|||
} |
|||
|
|||
const validateResult = await validate({ |
|||
record, |
|||
model, |
|||
}) |
|||
|
|||
if (!validateResult.valid) { |
|||
ctx.status = 400 |
|||
ctx.body = { |
|||
status: 400, |
|||
errors: validateResult.errors, |
|||
} |
|||
return |
|||
} |
|||
|
|||
// returned record is cleaned and prepared for writing to DB
|
|||
record = await linkRecords.updateLinks({ |
|||
instanceId, |
|||
eventType: linkRecords.EventType.RECORD_UPDATE, |
|||
record, |
|||
modelId: record.modelId, |
|||
model, |
|||
}) |
|||
const response = await db.put(record) |
|||
record._rev = response.rev |
|||
record.type = "record" |
|||
|
|||
ctx.eventEmitter && |
|||
ctx.eventEmitter.emitRecord(`record:update`, instanceId, record, model) |
|||
ctx.body = record |
|||
ctx.status = 200 |
|||
ctx.message = `${model.name} updated successfully.` |
|||
} |
|||
|
|||
exports.save = async function(ctx) { |
|||
if (ctx.request.body.type === "delete") { |
|||
await bulkDelete(ctx) |
|||
} else { |
|||
await saveRecord(ctx) |
|||
} |
|||
} |
|||
|
|||
exports.fetchView = async function(ctx) { |
|||
const instanceId = ctx.user.instanceId |
|||
const db = new CouchDB(instanceId) |
|||
const { stats, group, field } = ctx.query |
|||
const viewName = ctx.params.viewName |
|||
|
|||
// if this is a model view being looked for just transfer to that
|
|||
if (viewName.indexOf(MODEL_VIEW_BEGINS_WITH) === 0) { |
|||
ctx.params.modelId = viewName.substring(4) |
|||
await exports.fetchModelRecords(ctx) |
|||
return |
|||
} |
|||
|
|||
const response = await db.query(`database/${viewName}`, { |
|||
include_docs: !stats, |
|||
group, |
|||
}) |
|||
|
|||
if (stats) { |
|||
response.rows = response.rows.map(row => ({ |
|||
group: row.key, |
|||
field, |
|||
...row.value, |
|||
avg: row.value.sum / row.value.count, |
|||
})) |
|||
} else { |
|||
response.rows = response.rows.map(row => row.doc) |
|||
} |
|||
|
|||
ctx.body = await linkRecords.attachLinkInfo(instanceId, response.rows) |
|||
} |
|||
|
|||
exports.fetchModelRecords = async function(ctx) { |
|||
const instanceId = ctx.user.instanceId |
|||
const db = new CouchDB(instanceId) |
|||
const response = await db.allDocs( |
|||
getRecordParams(ctx.params.modelId, null, { |
|||
include_docs: true, |
|||
}) |
|||
) |
|||
ctx.body = response.rows.map(row => row.doc) |
|||
ctx.body = await linkRecords.attachLinkInfo( |
|||
instanceId, |
|||
response.rows.map(row => row.doc) |
|||
) |
|||
} |
|||
|
|||
exports.search = async function(ctx) { |
|||
const instanceId = ctx.user.instanceId |
|||
const db = new CouchDB(instanceId) |
|||
const response = await db.allDocs({ |
|||
include_docs: true, |
|||
...ctx.request.body, |
|||
}) |
|||
ctx.body = await linkRecords.attachLinkInfo( |
|||
instanceId, |
|||
response.rows.map(row => row.doc) |
|||
) |
|||
} |
|||
|
|||
exports.find = async function(ctx) { |
|||
const instanceId = ctx.user.instanceId |
|||
const db = new CouchDB(instanceId) |
|||
const record = await db.get(ctx.params.recordId) |
|||
if (record.modelId !== ctx.params.modelId) { |
|||
ctx.throw(400, "Supplied modelId does not match the records modelId") |
|||
return |
|||
} |
|||
ctx.body = await linkRecords.attachLinkInfo(instanceId, record) |
|||
} |
|||
|
|||
exports.destroy = async function(ctx) { |
|||
const instanceId = ctx.user.instanceId |
|||
const db = new CouchDB(instanceId) |
|||
const record = await db.get(ctx.params.recordId) |
|||
if (record.modelId !== ctx.params.modelId) { |
|||
ctx.throw(400, "Supplied modelId doesn't match the record's modelId") |
|||
return |
|||
} |
|||
await linkRecords.updateLinks({ |
|||
instanceId, |
|||
eventType: linkRecords.EventType.RECORD_DELETE, |
|||
record, |
|||
modelId: record.modelId, |
|||
}) |
|||
ctx.body = await db.remove(ctx.params.recordId, ctx.params.revId) |
|||
ctx.status = 200 |
|||
|
|||
// for automations include the record that was deleted
|
|||
ctx.record = record |
|||
ctx.eventEmitter && |
|||
ctx.eventEmitter.emitRecord(`record:delete`, instanceId, record) |
|||
} |
|||
|
|||
exports.validate = async function(ctx) { |
|||
const errors = await validate({ |
|||
instanceId: ctx.user.instanceId, |
|||
modelId: ctx.params.modelId, |
|||
record: ctx.request.body, |
|||
}) |
|||
ctx.status = 200 |
|||
ctx.body = errors |
|||
} |
|||
|
|||
async function validate({ instanceId, modelId, record, model }) { |
|||
if (!model) { |
|||
const db = new CouchDB(instanceId) |
|||
model = await db.get(modelId) |
|||
} |
|||
const errors = {} |
|||
for (let fieldName of Object.keys(model.schema)) { |
|||
const res = validateJs.single( |
|||
record[fieldName], |
|||
model.schema[fieldName].constraints |
|||
) |
|||
if (res) errors[fieldName] = res |
|||
} |
|||
return { valid: Object.keys(errors).length === 0, errors } |
|||
} |
|||
|
|||
exports.fetchEnrichedRecord = async function(ctx) { |
|||
const instanceId = ctx.user.instanceId |
|||
const db = new CouchDB(instanceId) |
|||
const modelId = ctx.params.modelId |
|||
const recordId = ctx.params.recordId |
|||
if (instanceId == null || modelId == null || recordId == null) { |
|||
ctx.status = 400 |
|||
ctx.body = { |
|||
status: 400, |
|||
error: |
|||
"Cannot handle request, URI params have not been successfully prepared.", |
|||
} |
|||
return |
|||
} |
|||
// need model to work out where links go in record
|
|||
const [model, record] = await Promise.all([db.get(modelId), db.get(recordId)]) |
|||
// get the link docs
|
|||
const linkVals = await linkRecords.getLinkDocuments({ |
|||
instanceId, |
|||
modelId, |
|||
recordId, |
|||
}) |
|||
// look up the actual records based on the ids
|
|||
const response = await db.allDocs({ |
|||
include_docs: true, |
|||
keys: linkVals.map(linkVal => linkVal.id), |
|||
}) |
|||
// need to include the IDs in these records for any links they may have
|
|||
let linkedRecords = await linkRecords.attachLinkInfo( |
|||
instanceId, |
|||
response.rows.map(row => row.doc) |
|||
) |
|||
// insert the link records in the correct place throughout the main record
|
|||
for (let fieldName of Object.keys(model.schema)) { |
|||
let field = model.schema[fieldName] |
|||
if (field.type === "link") { |
|||
record[fieldName] = linkedRecords.filter( |
|||
linkRecord => linkRecord.modelId === field.modelId |
|||
) |
|||
} |
|||
} |
|||
ctx.body = record |
|||
ctx.status = 200 |
|||
} |
|||
|
|||
function coerceRecordValues(rec, model) { |
|||
const record = cloneDeep(rec) |
|||
for (let [key, value] of Object.entries(record)) { |
|||
const field = model.schema[key] |
|||
if (!field) continue |
|||
|
|||
// eslint-disable-next-line no-prototype-builtins
|
|||
if (TYPE_TRANSFORM_MAP[field.type].hasOwnProperty(value)) { |
|||
record[key] = TYPE_TRANSFORM_MAP[field.type][value] |
|||
} else if (TYPE_TRANSFORM_MAP[field.type].parse) { |
|||
record[key] = TYPE_TRANSFORM_MAP[field.type].parse(value) |
|||
} |
|||
} |
|||
return record |
|||
} |
|||
|
|||
const TYPE_TRANSFORM_MAP = { |
|||
link: { |
|||
"": [], |
|||
[null]: [], |
|||
[undefined]: undefined, |
|||
}, |
|||
options: { |
|||
"": "", |
|||
[null]: "", |
|||
[undefined]: undefined, |
|||
}, |
|||
string: { |
|||
"": "", |
|||
[null]: "", |
|||
[undefined]: undefined, |
|||
}, |
|||
number: { |
|||
"": null, |
|||
[null]: null, |
|||
[undefined]: undefined, |
|||
parse: n => parseFloat(n), |
|||
}, |
|||
datetime: { |
|||
"": null, |
|||
[undefined]: undefined, |
|||
[null]: null, |
|||
}, |
|||
attachment: { |
|||
"": [], |
|||
[null]: [], |
|||
[undefined]: undefined, |
|||
}, |
|||
boolean: { |
|||
"": null, |
|||
[null]: null, |
|||
[undefined]: undefined, |
|||
true: true, |
|||
false: false, |
|||
}, |
|||
} |
|||
|
|||
async function bulkDelete(ctx) { |
|||
const instanceId = ctx.user.instanceId |
|||
const { records } = ctx.request.body |
|||
const db = new CouchDB(ctx.user.instanceId) |
|||
|
|||
await db.bulkDocs( |
|||
records.map( |
|||
record => ({ ...record, _deleted: true }), |
|||
err => { |
|||
if (err) { |
|||
ctx.status = 500 |
|||
} else { |
|||
records.forEach(record => { |
|||
ctx.eventEmitter && |
|||
ctx.eventEmitter.emitRecord(`record:delete`, instanceId, record) |
|||
}) |
|||
ctx.status = 200 |
|||
} |
|||
} |
|||
) |
|||
) |
|||
} |
|||
|
|||
async function saveRecord(ctx) { |
|||
const instanceId = ctx.user.instanceId |
|||
const db = new CouchDB(instanceId) |
|||
let record = ctx.request.body |
|||
record.modelId = ctx.params.modelId |
|||
|
|||
if (!record._rev && !record._id) { |
|||
record._id = generateRecordID(record.modelId) |
|||
} |
|||
|
|||
// if the record obj had an _id then it will have been retrieved
|
|||
const existingRecord = ctx.preExisting |
|||
|
|||
const model = await db.get(record.modelId) |
|||
|
|||
record = coerceRecordValues(record, model) |
|||
|
|||
const validateResult = await validate({ |
|||
record, |
|||
model, |
|||
}) |
|||
|
|||
if (!validateResult.valid) { |
|||
ctx.status = 400 |
|||
ctx.body = { |
|||
status: 400, |
|||
errors: validateResult.errors, |
|||
} |
|||
return |
|||
} |
|||
|
|||
// make sure link records are up to date
|
|||
record = await linkRecords.updateLinks({ |
|||
instanceId, |
|||
eventType: linkRecords.EventType.RECORD_SAVE, |
|||
record, |
|||
modelId: record.modelId, |
|||
model, |
|||
}) |
|||
|
|||
if (existingRecord) { |
|||
const response = await db.put(record) |
|||
record._rev = response.rev |
|||
record.type = "record" |
|||
ctx.body = record |
|||
ctx.status = 200 |
|||
ctx.message = `${model.name} updated successfully.` |
|||
return |
|||
} |
|||
|
|||
record.type = "record" |
|||
const response = await db.post(record) |
|||
record._rev = response.rev |
|||
|
|||
ctx.eventEmitter && |
|||
ctx.eventEmitter.emitRecord(`record:save`, instanceId, record, model) |
|||
ctx.body = record |
|||
ctx.status = 200 |
|||
ctx.message = `${model.name} created successfully` |
|||
} |
|||
@ -0,0 +1,350 @@ |
|||
const CouchDB = require("../../db") |
|||
const validateJs = require("validate.js") |
|||
const linkRows = require("../../db/linkedRows") |
|||
const { |
|||
getRowParams, |
|||
generateRowID, |
|||
DocumentTypes, |
|||
SEPARATOR, |
|||
} = require("../../db/utils") |
|||
const { cloneDeep } = require("lodash") |
|||
|
|||
const TABLE_VIEW_BEGINS_WITH = `all${SEPARATOR}${DocumentTypes.TABLE}${SEPARATOR}` |
|||
|
|||
validateJs.extend(validateJs.validators.datetime, { |
|||
parse: function(value) { |
|||
return new Date(value).getTime() |
|||
}, |
|||
// Input is a unix timestamp
|
|||
format: function(value) { |
|||
return new Date(value).toISOString() |
|||
}, |
|||
}) |
|||
|
|||
exports.patch = async function(ctx) { |
|||
const instanceId = ctx.user.instanceId |
|||
const db = new CouchDB(instanceId) |
|||
let row = await db.get(ctx.params.id) |
|||
const table = await db.get(row.tableId) |
|||
const patchfields = ctx.request.body |
|||
row = coerceRowValues(row, table) |
|||
|
|||
for (let key of Object.keys(patchfields)) { |
|||
if (!table.schema[key]) continue |
|||
row[key] = patchfields[key] |
|||
} |
|||
|
|||
const validateResult = await validate({ |
|||
row, |
|||
table, |
|||
}) |
|||
|
|||
if (!validateResult.valid) { |
|||
ctx.status = 400 |
|||
ctx.body = { |
|||
status: 400, |
|||
errors: validateResult.errors, |
|||
} |
|||
return |
|||
} |
|||
|
|||
// returned row is cleaned and prepared for writing to DB
|
|||
row = await linkRows.updateLinks({ |
|||
instanceId, |
|||
eventType: linkRows.EventType.ROW_UPDATE, |
|||
row, |
|||
tableId: row.tableId, |
|||
table, |
|||
}) |
|||
const response = await db.put(row) |
|||
row._rev = response.rev |
|||
row.type = "row" |
|||
|
|||
ctx.eventEmitter && |
|||
ctx.eventEmitter.emitRow(`row:update`, instanceId, row, table) |
|||
ctx.body = row |
|||
ctx.status = 200 |
|||
ctx.message = `${table.name} updated successfully.` |
|||
} |
|||
|
|||
exports.save = async function(ctx) { |
|||
const instanceId = ctx.user.instanceId |
|||
const db = new CouchDB(instanceId) |
|||
let row = ctx.request.body |
|||
row.tableId = ctx.params.tableId |
|||
|
|||
if (!row._rev && !row._id) { |
|||
row._id = generateRowID(row.tableId) |
|||
} |
|||
|
|||
// if the row obj had an _id then it will have been retrieved
|
|||
const existingRow = ctx.preExisting |
|||
|
|||
const table = await db.get(row.tableId) |
|||
|
|||
row = coerceRowValues(row, table) |
|||
|
|||
const validateResult = await validate({ |
|||
row, |
|||
table, |
|||
}) |
|||
|
|||
if (!validateResult.valid) { |
|||
ctx.status = 400 |
|||
ctx.body = { |
|||
status: 400, |
|||
errors: validateResult.errors, |
|||
} |
|||
return |
|||
} |
|||
|
|||
// make sure link rows are up to date
|
|||
row = await linkRows.updateLinks({ |
|||
instanceId, |
|||
eventType: linkRows.EventType.ROW_SAVE, |
|||
row, |
|||
tableId: row.tableId, |
|||
table, |
|||
}) |
|||
|
|||
if (existingRow) { |
|||
const response = await db.put(row) |
|||
row._rev = response.rev |
|||
row.type = "row" |
|||
ctx.body = row |
|||
ctx.status = 200 |
|||
ctx.message = `${table.name} updated successfully.` |
|||
return |
|||
} |
|||
|
|||
row.type = "row" |
|||
const response = await db.post(row) |
|||
row._rev = response.rev |
|||
|
|||
ctx.eventEmitter && |
|||
ctx.eventEmitter.emitRow(`row:save`, instanceId, row, table) |
|||
ctx.body = row |
|||
ctx.status = 200 |
|||
ctx.message = `${table.name} created successfully` |
|||
} |
|||
|
|||
exports.fetchView = async function(ctx) { |
|||
const instanceId = ctx.user.instanceId |
|||
const db = new CouchDB(instanceId) |
|||
const { stats, group, field } = ctx.query |
|||
const viewName = ctx.params.viewName |
|||
|
|||
// if this is a table view being looked for just transfer to that
|
|||
if (viewName.indexOf(TABLE_VIEW_BEGINS_WITH) === 0) { |
|||
ctx.params.tableId = viewName.substring(4) |
|||
await exports.fetchTableRows(ctx) |
|||
return |
|||
} |
|||
|
|||
const response = await db.query(`database/${viewName}`, { |
|||
include_docs: !stats, |
|||
group, |
|||
}) |
|||
|
|||
if (stats) { |
|||
response.rows = response.rows.map(row => ({ |
|||
group: row.key, |
|||
field, |
|||
...row.value, |
|||
avg: row.value.sum / row.value.count, |
|||
})) |
|||
} else { |
|||
response.rows = response.rows.map(row => row.doc) |
|||
} |
|||
|
|||
ctx.body = await linkRows.attachLinkInfo(instanceId, response.rows) |
|||
} |
|||
|
|||
exports.fetchTableRows = async function(ctx) { |
|||
const instanceId = ctx.user.instanceId |
|||
const db = new CouchDB(instanceId) |
|||
const response = await db.allDocs( |
|||
getRowParams(ctx.params.tableId, null, { |
|||
include_docs: true, |
|||
}) |
|||
) |
|||
ctx.body = response.rows.map(row => row.doc) |
|||
ctx.body = await linkRows.attachLinkInfo( |
|||
instanceId, |
|||
response.rows.map(row => row.doc) |
|||
) |
|||
} |
|||
|
|||
exports.search = async function(ctx) { |
|||
const instanceId = ctx.user.instanceId |
|||
const db = new CouchDB(instanceId) |
|||
const response = await db.allDocs({ |
|||
include_docs: true, |
|||
...ctx.request.body, |
|||
}) |
|||
ctx.body = await linkRows.attachLinkInfo( |
|||
instanceId, |
|||
response.rows.map(row => row.doc) |
|||
) |
|||
} |
|||
|
|||
exports.find = async function(ctx) { |
|||
const instanceId = ctx.user.instanceId |
|||
const db = new CouchDB(instanceId) |
|||
const row = await db.get(ctx.params.rowId) |
|||
if (row.tableId !== ctx.params.tableId) { |
|||
ctx.throw(400, "Supplied tableId does not match the rows tableId") |
|||
return |
|||
} |
|||
ctx.body = await linkRows.attachLinkInfo(instanceId, row) |
|||
} |
|||
|
|||
exports.destroy = async function(ctx) { |
|||
const instanceId = ctx.user.instanceId |
|||
const db = new CouchDB(instanceId) |
|||
const row = await db.get(ctx.params.rowId) |
|||
if (row.tableId !== ctx.params.tableId) { |
|||
ctx.throw(400, "Supplied tableId doesn't match the row's tableId") |
|||
return |
|||
} |
|||
await linkRows.updateLinks({ |
|||
instanceId, |
|||
eventType: linkRows.EventType.ROW_DELETE, |
|||
row, |
|||
tableId: row.tableId, |
|||
}) |
|||
ctx.body = await db.remove(ctx.params.rowId, ctx.params.revId) |
|||
ctx.status = 200 |
|||
|
|||
// for automations include the row that was deleted
|
|||
ctx.row = row |
|||
ctx.eventEmitter && ctx.eventEmitter.emitRow(`row:delete`, instanceId, row) |
|||
} |
|||
|
|||
exports.validate = async function(ctx) { |
|||
const errors = await validate({ |
|||
instanceId: ctx.user.instanceId, |
|||
tableId: ctx.params.tableId, |
|||
row: ctx.request.body, |
|||
}) |
|||
ctx.status = 200 |
|||
ctx.body = errors |
|||
} |
|||
|
|||
async function validate({ instanceId, tableId, row, table }) { |
|||
if (!table) { |
|||
const db = new CouchDB(instanceId) |
|||
table = await db.get(tableId) |
|||
} |
|||
const errors = {} |
|||
for (let fieldName of Object.keys(table.schema)) { |
|||
const res = validateJs.single( |
|||
row[fieldName], |
|||
table.schema[fieldName].constraints |
|||
) |
|||
if (res) errors[fieldName] = res |
|||
} |
|||
return { valid: Object.keys(errors).length === 0, errors } |
|||
} |
|||
|
|||
exports.fetchEnrichedRow = async function(ctx) { |
|||
const instanceId = ctx.user.instanceId |
|||
const db = new CouchDB(instanceId) |
|||
const tableId = ctx.params.tableId |
|||
const rowId = ctx.params.rowId |
|||
if (instanceId == null || tableId == null || rowId == null) { |
|||
ctx.status = 400 |
|||
ctx.body = { |
|||
status: 400, |
|||
error: |
|||
"Cannot handle request, URI params have not been successfully prepared.", |
|||
} |
|||
return |
|||
} |
|||
// need table to work out where links go in row
|
|||
const [table, row] = await Promise.all([db.get(tableId), db.get(rowId)]) |
|||
// get the link docs
|
|||
const linkVals = await linkRows.getLinkDocuments({ |
|||
instanceId, |
|||
tableId, |
|||
rowId, |
|||
}) |
|||
// look up the actual rows based on the ids
|
|||
const response = await db.allDocs({ |
|||
include_docs: true, |
|||
keys: linkVals.map(linkVal => linkVal.id), |
|||
}) |
|||
// need to include the IDs in these rows for any links they may have
|
|||
let linkedRows = await linkRows.attachLinkInfo( |
|||
instanceId, |
|||
response.rows.map(row => row.doc) |
|||
) |
|||
// insert the link rows in the correct place throughout the main row
|
|||
for (let fieldName of Object.keys(table.schema)) { |
|||
let field = table.schema[fieldName] |
|||
if (field.type === "link") { |
|||
row[fieldName] = linkedRows.filter( |
|||
linkRow => linkRow.tableId === field.tableId |
|||
) |
|||
} |
|||
} |
|||
ctx.body = row |
|||
ctx.status = 200 |
|||
} |
|||
|
|||
function coerceRowValues(rec, table) { |
|||
const row = cloneDeep(rec) |
|||
for (let [key, value] of Object.entries(row)) { |
|||
const field = table.schema[key] |
|||
if (!field) continue |
|||
|
|||
// eslint-disable-next-line no-prototype-builtins
|
|||
if (TYPE_TRANSFORM_MAP[field.type].hasOwnProperty(value)) { |
|||
row[key] = TYPE_TRANSFORM_MAP[field.type][value] |
|||
} else if (TYPE_TRANSFORM_MAP[field.type].parse) { |
|||
row[key] = TYPE_TRANSFORM_MAP[field.type].parse(value) |
|||
} |
|||
} |
|||
return row |
|||
} |
|||
|
|||
const TYPE_TRANSFORM_MAP = { |
|||
link: { |
|||
"": [], |
|||
[null]: [], |
|||
[undefined]: undefined, |
|||
}, |
|||
options: { |
|||
"": "", |
|||
[null]: "", |
|||
[undefined]: undefined, |
|||
}, |
|||
string: { |
|||
"": "", |
|||
[null]: "", |
|||
[undefined]: undefined, |
|||
}, |
|||
number: { |
|||
"": null, |
|||
[null]: null, |
|||
[undefined]: undefined, |
|||
parse: n => parseFloat(n), |
|||
}, |
|||
datetime: { |
|||
"": null, |
|||
[undefined]: undefined, |
|||
[null]: null, |
|||
}, |
|||
attachment: { |
|||
"": [], |
|||
[null]: [], |
|||
[undefined]: undefined, |
|||
}, |
|||
boolean: { |
|||
"": null, |
|||
[null]: null, |
|||
[undefined]: undefined, |
|||
true: true, |
|||
false: false, |
|||
}, |
|||
} |
|||
@ -0,0 +1,144 @@ |
|||
const CouchDB = require("../../db") |
|||
const linkRows = require("../../db/linkedRows") |
|||
const csvParser = require("../../utilities/csvParser") |
|||
const { |
|||
getRowParams, |
|||
getTableParams, |
|||
generateTableID, |
|||
generateRowID, |
|||
} = require("../../db/utils") |
|||
|
|||
exports.fetch = async function(ctx) { |
|||
const db = new CouchDB(ctx.user.instanceId) |
|||
const body = await db.allDocs( |
|||
getTableParams(null, { |
|||
include_docs: true, |
|||
}) |
|||
) |
|||
ctx.body = body.rows.map(row => row.doc) |
|||
} |
|||
|
|||
exports.find = async function(ctx) { |
|||
const db = new CouchDB(ctx.user.instanceId) |
|||
ctx.body = await db.get(ctx.params.id) |
|||
} |
|||
|
|||
exports.save = async function(ctx) { |
|||
const instanceId = ctx.user.instanceId |
|||
const db = new CouchDB(instanceId) |
|||
const { dataImport, ...rest } = ctx.request.body |
|||
const tableToSave = { |
|||
type: "table", |
|||
_id: generateTableID(), |
|||
views: {}, |
|||
...rest, |
|||
} |
|||
let renameDocs = [] |
|||
|
|||
// if the table obj had an _id then it will have been retrieved
|
|||
const oldTable = ctx.preExisting |
|||
|
|||
// rename row fields when table column is renamed
|
|||
const { _rename } = tableToSave |
|||
if (_rename && tableToSave.schema[_rename.updated].type === "link") { |
|||
throw "Cannot rename a linked field." |
|||
} else if (_rename && tableToSave.primaryDisplay === _rename.old) { |
|||
throw "Cannot rename the primary display field." |
|||
} else if (_rename) { |
|||
const rows = await db.allDocs( |
|||
getRowParams(tableToSave._id, null, { |
|||
include_docs: true, |
|||
}) |
|||
) |
|||
renameDocs = rows.rows.map(({ doc }) => { |
|||
doc[_rename.updated] = doc[_rename.old] |
|||
delete doc[_rename.old] |
|||
return doc |
|||
}) |
|||
delete tableToSave._rename |
|||
} |
|||
|
|||
// update schema of non-statistics views when new columns are added
|
|||
for (let view in tableToSave.views) { |
|||
const tableView = tableToSave.views[view] |
|||
if (!tableView) continue |
|||
|
|||
if (tableView.schema.group || tableView.schema.field) continue |
|||
tableView.schema = tableToSave.schema |
|||
} |
|||
|
|||
// update linked rows
|
|||
await linkRows.updateLinks({ |
|||
instanceId, |
|||
eventType: oldTable |
|||
? linkRows.EventType.TABLE_UPDATED |
|||
: linkRows.EventType.TABLE_SAVE, |
|||
table: tableToSave, |
|||
oldTable: oldTable, |
|||
}) |
|||
|
|||
// don't perform any updates until relationships have been
|
|||
// checked by the updateLinks function
|
|||
if (renameDocs.length !== 0) { |
|||
await db.bulkDocs(renameDocs) |
|||
} |
|||
const result = await db.post(tableToSave) |
|||
tableToSave._rev = result.rev |
|||
|
|||
ctx.eventEmitter && |
|||
ctx.eventEmitter.emitTable(`table:save`, instanceId, tableToSave) |
|||
|
|||
if (dataImport && dataImport.path) { |
|||
// Populate the table with rows imported from CSV in a bulk update
|
|||
const data = await csvParser.transform(dataImport) |
|||
|
|||
for (let row of data) { |
|||
row._id = generateRowID(tableToSave._id) |
|||
row.tableId = tableToSave._id |
|||
} |
|||
|
|||
await db.bulkDocs(data) |
|||
} |
|||
|
|||
ctx.status = 200 |
|||
ctx.message = `Table ${ctx.request.body.name} saved successfully.` |
|||
ctx.body = tableToSave |
|||
} |
|||
|
|||
exports.destroy = async function(ctx) { |
|||
const instanceId = ctx.user.instanceId |
|||
const db = new CouchDB(instanceId) |
|||
const tableToDelete = await db.get(ctx.params.tableId) |
|||
|
|||
// Delete all rows for that table
|
|||
const rows = await db.allDocs( |
|||
getRowParams(ctx.params.tableId, null, { |
|||
include_docs: true, |
|||
}) |
|||
) |
|||
await db.bulkDocs(rows.rows.map(row => ({ ...row.doc, _deleted: true }))) |
|||
|
|||
// update linked rows
|
|||
await linkRows.updateLinks({ |
|||
instanceId, |
|||
eventType: linkRows.EventType.TABLE_DELETE, |
|||
table: tableToDelete, |
|||
}) |
|||
|
|||
// don't remove the table itself until very end
|
|||
await db.remove(tableToDelete) |
|||
|
|||
ctx.eventEmitter && |
|||
ctx.eventEmitter.emitTable(`table:delete`, instanceId, tableToDelete) |
|||
ctx.status = 200 |
|||
ctx.message = `Table ${ctx.params.tableId} deleted.` |
|||
} |
|||
|
|||
exports.validateCSVSchema = async function(ctx) { |
|||
const { file, schema = {} } = ctx.request.body |
|||
const result = await csvParser.parse(file.path, schema) |
|||
ctx.body = { |
|||
schema: result, |
|||
path: file.path, |
|||
} |
|||
} |
|||
@ -1,27 +0,0 @@ |
|||
const Router = require("@koa/router") |
|||
const modelController = require("../controllers/model") |
|||
const authorized = require("../../middleware/authorized") |
|||
const { BUILDER, READ_MODEL } = require("../../utilities/accessLevels") |
|||
|
|||
const router = Router() |
|||
|
|||
router |
|||
.get("/api/models", authorized(BUILDER), modelController.fetch) |
|||
.get( |
|||
"/api/models/:id", |
|||
authorized(READ_MODEL, ctx => ctx.params.id), |
|||
modelController.find |
|||
) |
|||
.post("/api/models", authorized(BUILDER), modelController.save) |
|||
.post( |
|||
"/api/models/csv/validate", |
|||
authorized(BUILDER), |
|||
modelController.validateCSVSchema |
|||
) |
|||
.delete( |
|||
"/api/models/:modelId/:revId", |
|||
authorized(BUILDER), |
|||
modelController.destroy |
|||
) |
|||
|
|||
module.exports = router |
|||
@ -1,49 +0,0 @@ |
|||
const Router = require("@koa/router") |
|||
const recordController = require("../controllers/record") |
|||
const authorized = require("../../middleware/authorized") |
|||
const usage = require("../../middleware/usageQuota") |
|||
const { READ_MODEL, WRITE_MODEL } = require("../../utilities/accessLevels") |
|||
|
|||
const router = Router() |
|||
|
|||
router |
|||
.get( |
|||
"/api/:modelId/:recordId/enrich", |
|||
authorized(READ_MODEL, ctx => ctx.params.modelId), |
|||
recordController.fetchEnrichedRecord |
|||
) |
|||
.get( |
|||
"/api/:modelId/records", |
|||
authorized(READ_MODEL, ctx => ctx.params.modelId), |
|||
recordController.fetchModelRecords |
|||
) |
|||
.get( |
|||
"/api/:modelId/records/:recordId", |
|||
authorized(READ_MODEL, ctx => ctx.params.modelId), |
|||
recordController.find |
|||
) |
|||
.post("/api/records/search", recordController.search) |
|||
.post( |
|||
"/api/:modelId/records", |
|||
authorized(WRITE_MODEL, ctx => ctx.params.modelId), |
|||
usage, |
|||
recordController.save |
|||
) |
|||
.patch( |
|||
"/api/:modelId/records/:id", |
|||
authorized(WRITE_MODEL, ctx => ctx.params.modelId), |
|||
recordController.patch |
|||
) |
|||
.post( |
|||
"/api/:modelId/records/validate", |
|||
authorized(WRITE_MODEL, ctx => ctx.params.modelId), |
|||
recordController.validate |
|||
) |
|||
.delete( |
|||
"/api/:modelId/records/:recordId/:revId", |
|||
authorized(WRITE_MODEL, ctx => ctx.params.modelId), |
|||
usage, |
|||
recordController.destroy |
|||
) |
|||
|
|||
module.exports = router |
|||
@ -0,0 +1,49 @@ |
|||
const Router = require("@koa/router") |
|||
const rowController = require("../controllers/row") |
|||
const authorized = require("../../middleware/authorized") |
|||
const usage = require("../../middleware/usageQuota") |
|||
const { READ_TABLE, WRITE_TABLE } = require("../../utilities/accessLevels") |
|||
|
|||
const router = Router() |
|||
|
|||
router |
|||
.get( |
|||
"/api/:tableId/:rowId/enrich", |
|||
authorized(READ_TABLE, ctx => ctx.params.tableId), |
|||
rowController.fetchEnrichedRow |
|||
) |
|||
.get( |
|||
"/api/:tableId/rows", |
|||
authorized(READ_TABLE, ctx => ctx.params.tableId), |
|||
rowController.fetchTableRows |
|||
) |
|||
.get( |
|||
"/api/:tableId/rows/:rowId", |
|||
authorized(READ_TABLE, ctx => ctx.params.tableId), |
|||
rowController.find |
|||
) |
|||
.post("/api/rows/search", rowController.search) |
|||
.post( |
|||
"/api/:tableId/rows", |
|||
authorized(WRITE_TABLE, ctx => ctx.params.tableId), |
|||
usage, |
|||
rowController.save |
|||
) |
|||
.patch( |
|||
"/api/:tableId/rows/:id", |
|||
authorized(WRITE_TABLE, ctx => ctx.params.tableId), |
|||
rowController.patch |
|||
) |
|||
.post( |
|||
"/api/:tableId/rows/validate", |
|||
authorized(WRITE_TABLE, ctx => ctx.params.tableId), |
|||
rowController.validate |
|||
) |
|||
.delete( |
|||
"/api/:tableId/rows/:rowId/:revId", |
|||
authorized(WRITE_TABLE, ctx => ctx.params.tableId), |
|||
usage, |
|||
rowController.destroy |
|||
) |
|||
|
|||
module.exports = router |
|||
@ -0,0 +1,27 @@ |
|||
const Router = require("@koa/router") |
|||
const tableController = require("../controllers/table") |
|||
const authorized = require("../../middleware/authorized") |
|||
const { BUILDER, READ_TABLE } = require("../../utilities/accessLevels") |
|||
|
|||
const router = Router() |
|||
|
|||
router |
|||
.get("/api/tables", authorized(BUILDER), tableController.fetch) |
|||
.get( |
|||
"/api/tables/:id", |
|||
authorized(READ_TABLE, ctx => ctx.params.id), |
|||
tableController.find |
|||
) |
|||
.post("/api/tables", authorized(BUILDER), tableController.save) |
|||
.post( |
|||
"/api/tables/csv/validate", |
|||
authorized(BUILDER), |
|||
tableController.validateCSVSchema |
|||
) |
|||
.delete( |
|||
"/api/tables/:tableId/:revId", |
|||
authorized(BUILDER), |
|||
tableController.destroy |
|||
) |
|||
|
|||
module.exports = router |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue