mirror of
https://github.com/directus/directus.git
synced 2026-04-25 03:00:53 -04:00
Transform default values for sqlite, add casting for boolean
This commit is contained in:
@@ -563,6 +563,7 @@ rows:
|
||||
# @todo update this to be a versioned link
|
||||
note: '[Learn More](https://docs.directus.io/guides/collections.html#hidden).'
|
||||
sort: 3
|
||||
special: boolean
|
||||
width: half
|
||||
- collection: directus_collections
|
||||
field: singleton
|
||||
@@ -571,6 +572,7 @@ rows:
|
||||
# @todo update this to be a versioned link
|
||||
note: '[Learn More](https://docs.directus.io/guides/collections.html#single).'
|
||||
sort: 4
|
||||
special: boolean
|
||||
width: half
|
||||
- collection: directus_collections
|
||||
field: icon
|
||||
@@ -578,7 +580,7 @@ rows:
|
||||
locked: true
|
||||
sort: 5
|
||||
- collection: directus_collections
|
||||
field: translations
|
||||
field: translation
|
||||
interface: repeater
|
||||
locked: true
|
||||
options:
|
||||
@@ -593,52 +595,10 @@ rows:
|
||||
type: string
|
||||
interface: text-input
|
||||
width: half
|
||||
special: json
|
||||
sort: 6
|
||||
width: full
|
||||
|
||||
- collection: directus_folders
|
||||
field: id
|
||||
interface: text-input
|
||||
locked: true
|
||||
special: uuid
|
||||
|
||||
- collection: directus_files
|
||||
field: id
|
||||
hidden: true
|
||||
interface: text-input
|
||||
locked: true
|
||||
special: uuid
|
||||
- collection: directus_files
|
||||
field: title
|
||||
interface: text-input
|
||||
locked: true
|
||||
options:
|
||||
iconRight: title
|
||||
sort: 1
|
||||
width: full
|
||||
- collection: directus_files
|
||||
field: description
|
||||
interface: wysiwyg
|
||||
locked: true
|
||||
sort: 2
|
||||
width: full
|
||||
- collection: directus_files
|
||||
field: tags
|
||||
interface: tags
|
||||
locked: true
|
||||
options:
|
||||
iconRight: local_offer
|
||||
sort: 3
|
||||
width: half
|
||||
- collection: directus_files
|
||||
field: location
|
||||
interface: text-input
|
||||
locked: true
|
||||
options:
|
||||
iconRight: place
|
||||
sort: 4
|
||||
width: half
|
||||
|
||||
- collection: directus_roles
|
||||
field: id
|
||||
hidden: true
|
||||
@@ -668,6 +628,7 @@ rows:
|
||||
interface: toggle
|
||||
locked: true
|
||||
sort: 4
|
||||
special: boolean
|
||||
width: full
|
||||
- collection: directus_roles
|
||||
field: module_list
|
||||
@@ -698,6 +659,7 @@ rows:
|
||||
options:
|
||||
iconRight: link
|
||||
placeholder: Relative or absolute URL...
|
||||
special: json
|
||||
sort: 5
|
||||
width: full
|
||||
- collection: directus_roles
|
||||
@@ -743,215 +705,24 @@ rows:
|
||||
type: string
|
||||
interface: collections
|
||||
width: full
|
||||
special: json
|
||||
sort: 6
|
||||
width: full
|
||||
- collection: directus_roles
|
||||
field: users
|
||||
interface: one-to-many
|
||||
locked: true
|
||||
sort: 7
|
||||
width: full
|
||||
special: o2m
|
||||
|
||||
- collection: directus_settings
|
||||
field: project_name
|
||||
interface: text-input
|
||||
locked: true
|
||||
options:
|
||||
iconRight: title
|
||||
placeholder: My project...
|
||||
sort: 1
|
||||
translation:
|
||||
locale: en-US
|
||||
translation: Name
|
||||
width: half
|
||||
- collection: directus_settings
|
||||
field: project_url
|
||||
interface: text-input
|
||||
locked: true
|
||||
options:
|
||||
iconRight: link
|
||||
placeholder: https://example.com
|
||||
sort: 2
|
||||
translation:
|
||||
locale: en-US
|
||||
translation: Website
|
||||
width: half
|
||||
- collection: directus_settings
|
||||
field: project_logo
|
||||
interface: file
|
||||
locked: true
|
||||
note: White 40x40 SVG/PNG
|
||||
sort: 3
|
||||
translation:
|
||||
locale: en-US
|
||||
translation: Brand Logo
|
||||
width: half
|
||||
- collection: directus_settings
|
||||
field: project_color
|
||||
interface: color
|
||||
locked: true
|
||||
note: Login & Logo Background
|
||||
sort: 4
|
||||
translation:
|
||||
locale: en-US
|
||||
translation: Brand Color
|
||||
width: half
|
||||
- collection: directus_settings
|
||||
field: project_foreground
|
||||
interface: image
|
||||
locked: true
|
||||
sort: 5
|
||||
translation:
|
||||
locale: en-US
|
||||
translation: Login Foreground
|
||||
width: half
|
||||
- collection: directus_settings
|
||||
field: project_background
|
||||
interface: image
|
||||
locked: true
|
||||
sort: 6
|
||||
translation:
|
||||
locale: en-US
|
||||
translation: Login Background
|
||||
width: half
|
||||
- collection: directus_settings
|
||||
field: project_note
|
||||
interface: text-input
|
||||
locked: true
|
||||
options:
|
||||
placeholder: A short, public message that supports markdown formatting...
|
||||
sort: 7
|
||||
translation:
|
||||
locale: en-US
|
||||
translation: Login Message
|
||||
width: full
|
||||
- collection: directus_settings
|
||||
field: project_telemetry
|
||||
- collection: directus_roles
|
||||
field: admin
|
||||
interface: toggle
|
||||
locked: true
|
||||
options:
|
||||
label: Send Anonymous Diagnostics
|
||||
special: boolean
|
||||
sort: 8
|
||||
width: half
|
||||
|
||||
- collection: directus_settings
|
||||
field: security_divider
|
||||
interface: divider
|
||||
locked: true
|
||||
options:
|
||||
icon: security
|
||||
title: Security
|
||||
color: '#2f80ed'
|
||||
sort: 9
|
||||
width: full
|
||||
- collection: directus_settings
|
||||
field: auth_password_policy
|
||||
interface: dropdown
|
||||
locked: true
|
||||
options:
|
||||
choices:
|
||||
- value: null
|
||||
text: None – Not Recommended
|
||||
- value: "/^.{8,}$/"
|
||||
text: Weak – Minimum 8 Characters
|
||||
- value: "/(?=^.{8,}$)(?=.*\\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%^&*()_+}{';'?>.<,])(?!.*\\s).*$/"
|
||||
text: Strong – Upper / Lowercase / Numbers / Special
|
||||
sort: 10
|
||||
width: half
|
||||
- collection: directus_settings
|
||||
field: auth_idle_timeout
|
||||
interface: numeric
|
||||
locked: true
|
||||
options:
|
||||
iconRight: timer
|
||||
sort: 11
|
||||
width: half
|
||||
- collection: directus_settings
|
||||
field: auth_login_attempts
|
||||
interface: numeric
|
||||
locked: true
|
||||
options:
|
||||
iconRight: lock
|
||||
sort: 12
|
||||
width: half
|
||||
- collection: directus_settings
|
||||
field: files_divider
|
||||
interface: divider
|
||||
locked: true
|
||||
options:
|
||||
icon: storage
|
||||
title: Files & Thumbnails
|
||||
color: '#2f80ed'
|
||||
sort: 13
|
||||
width: full
|
||||
- collection: directus_settings
|
||||
field: storage_asset_presets
|
||||
interface: repeater
|
||||
locked: true
|
||||
options:
|
||||
fields:
|
||||
- field: key
|
||||
interface: slug
|
||||
name: Key
|
||||
options:
|
||||
onlyOnCreate: false
|
||||
required: true
|
||||
type: string
|
||||
width: half
|
||||
- field: fit
|
||||
interface: dropdown
|
||||
name: Fit
|
||||
options:
|
||||
choices:
|
||||
- value: contain
|
||||
text: Contain (preserve aspect ratio)
|
||||
- value: crop
|
||||
text: Crop (forces exact size)
|
||||
required: true
|
||||
type: string
|
||||
width: half
|
||||
- field: width
|
||||
interface: numeric
|
||||
name: Width
|
||||
required: true
|
||||
type: integer
|
||||
width: half
|
||||
- field: height
|
||||
interface: numeric
|
||||
name: Height
|
||||
required: true
|
||||
type: integer
|
||||
width: half
|
||||
- field: quality
|
||||
default_value: 80
|
||||
interface: slider
|
||||
name: Quality
|
||||
options:
|
||||
max: 100
|
||||
min: 0
|
||||
step: 1
|
||||
required: true
|
||||
type: integer
|
||||
width: full
|
||||
template: "{{key}}"
|
||||
sort: 13
|
||||
width: full
|
||||
- collection: directus_settings
|
||||
field: storage_asset_transform
|
||||
interface: dropdown
|
||||
locked: true
|
||||
options:
|
||||
choices:
|
||||
- value: all
|
||||
text: All
|
||||
- value: none
|
||||
text: None
|
||||
- value: presets
|
||||
text: Presets Only\
|
||||
sort: 14
|
||||
width: half
|
||||
|
||||
- collection: directus_users
|
||||
field: id
|
||||
hidden: true
|
||||
@@ -1467,6 +1238,335 @@ rows:
|
||||
sort: 16
|
||||
width: full
|
||||
|
||||
# directus_fields isn't surfaced in the app
|
||||
- collection: directus_fields
|
||||
field: options
|
||||
hidden: true
|
||||
locked: true
|
||||
special: json
|
||||
- collection: directus_fields
|
||||
field: display_options
|
||||
hidden: true
|
||||
locked: true
|
||||
special: json
|
||||
- collection: directus_fields
|
||||
field: locked
|
||||
hidden: true
|
||||
locked: true
|
||||
special: boolean
|
||||
- collection: directus_fields
|
||||
field: readonly
|
||||
hidden: true
|
||||
locked: true
|
||||
special: boolean
|
||||
- collection: directus_fields
|
||||
field: hidden
|
||||
hidden: true
|
||||
locked: true
|
||||
special: boolean
|
||||
- collection: directus_fields
|
||||
field: translation
|
||||
hidden: true
|
||||
locked: true
|
||||
special: json
|
||||
|
||||
# directus_activity isn't surfaced in the app
|
||||
|
||||
- collection: directus_folders
|
||||
field: id
|
||||
interface: text-input
|
||||
locked: true
|
||||
special: uuid
|
||||
|
||||
- collection: directus_files
|
||||
field: id
|
||||
hidden: true
|
||||
interface: text-input
|
||||
locked: true
|
||||
special: uuid
|
||||
- collection: directus_files
|
||||
field: title
|
||||
interface: text-input
|
||||
locked: true
|
||||
options:
|
||||
iconRight: title
|
||||
sort: 1
|
||||
width: full
|
||||
- collection: directus_files
|
||||
field: description
|
||||
interface: wysiwyg
|
||||
locked: true
|
||||
sort: 2
|
||||
width: full
|
||||
- collection: directus_files
|
||||
field: tags
|
||||
interface: tags
|
||||
locked: true
|
||||
options:
|
||||
iconRight: local_offer
|
||||
sort: 3
|
||||
width: half
|
||||
- collection: directus_files
|
||||
field: location
|
||||
interface: text-input
|
||||
locked: true
|
||||
options:
|
||||
iconRight: place
|
||||
sort: 4
|
||||
width: half
|
||||
- collection: directus_files
|
||||
field: metadata
|
||||
hidden: true
|
||||
locked: true
|
||||
special: json
|
||||
|
||||
# directus_permissions isn't surfaced in the app
|
||||
- collection: directus_permissions
|
||||
field: permissions
|
||||
hidden: true
|
||||
locked: true
|
||||
special: json
|
||||
- collection: directus_permissions
|
||||
field: presets
|
||||
hidden: true
|
||||
locked: true
|
||||
special: json
|
||||
|
||||
# directus_presets isn't surfaced in the app
|
||||
- collection: directus_presets
|
||||
field: filters
|
||||
hidden: true
|
||||
locked: true
|
||||
special: json
|
||||
- collection: directus_presets
|
||||
field: view_query
|
||||
hidden: true
|
||||
locked: true
|
||||
special: json
|
||||
- collection: directus_presets
|
||||
field: view_options
|
||||
hidden: true
|
||||
locked: true
|
||||
special: json
|
||||
|
||||
# directus_relations isn't surfaced in the app
|
||||
# directus_revisions isn't surfaced in the app
|
||||
- collection: directus_revisions
|
||||
field: data
|
||||
hidden: true
|
||||
locked: true
|
||||
special: json
|
||||
- collection: directus_revisions
|
||||
field: delta
|
||||
hidden: true
|
||||
locked: true
|
||||
special: json
|
||||
|
||||
# diretus_sessions isn't surfaced in the app
|
||||
|
||||
- collection: directus_settings
|
||||
field: project_name
|
||||
interface: text-input
|
||||
locked: true
|
||||
options:
|
||||
iconRight: title
|
||||
placeholder: My project...
|
||||
sort: 1
|
||||
translation:
|
||||
locale: en-US
|
||||
translation: Name
|
||||
width: half
|
||||
- collection: directus_settings
|
||||
field: project_url
|
||||
interface: text-input
|
||||
locked: true
|
||||
options:
|
||||
iconRight: link
|
||||
placeholder: https://example.com
|
||||
sort: 2
|
||||
translation:
|
||||
locale: en-US
|
||||
translation: Website
|
||||
width: half
|
||||
- collection: directus_settings
|
||||
field: project_logo
|
||||
interface: file
|
||||
locked: true
|
||||
note: White 40x40 SVG/PNG
|
||||
sort: 3
|
||||
translation:
|
||||
locale: en-US
|
||||
translation: Brand Logo
|
||||
width: half
|
||||
- collection: directus_settings
|
||||
field: project_color
|
||||
interface: color
|
||||
locked: true
|
||||
note: Login & Logo Background
|
||||
sort: 4
|
||||
translation:
|
||||
locale: en-US
|
||||
translation: Brand Color
|
||||
width: half
|
||||
- collection: directus_settings
|
||||
field: project_foreground
|
||||
interface: image
|
||||
locked: true
|
||||
sort: 5
|
||||
translation:
|
||||
locale: en-US
|
||||
translation: Login Foreground
|
||||
width: half
|
||||
- collection: directus_settings
|
||||
field: project_background
|
||||
interface: image
|
||||
locked: true
|
||||
sort: 6
|
||||
translation:
|
||||
locale: en-US
|
||||
translation: Login Background
|
||||
width: half
|
||||
- collection: directus_settings
|
||||
field: project_note
|
||||
interface: text-input
|
||||
locked: true
|
||||
options:
|
||||
placeholder: A short, public message that supports markdown formatting...
|
||||
sort: 7
|
||||
translation:
|
||||
locale: en-US
|
||||
translation: Login Message
|
||||
width: full
|
||||
- collection: directus_settings
|
||||
field: project_telemetry
|
||||
interface: toggle
|
||||
locked: true
|
||||
options:
|
||||
label: Send Anonymous Diagnostics
|
||||
special: boolean
|
||||
sort: 8
|
||||
width: half
|
||||
|
||||
- collection: directus_settings
|
||||
field: security_divider
|
||||
interface: divider
|
||||
locked: true
|
||||
options:
|
||||
icon: security
|
||||
title: Security
|
||||
color: '#2f80ed'
|
||||
sort: 9
|
||||
width: full
|
||||
- collection: directus_settings
|
||||
field: auth_password_policy
|
||||
interface: dropdown
|
||||
locked: true
|
||||
options:
|
||||
choices:
|
||||
- value: null
|
||||
text: None – Not Recommended
|
||||
- value: "/^.{8,}$/"
|
||||
text: Weak – Minimum 8 Characters
|
||||
- value: "/(?=^.{8,}$)(?=.*\\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%^&*()_+}{';'?>.<,])(?!.*\\s).*$/"
|
||||
text: Strong – Upper / Lowercase / Numbers / Special
|
||||
sort: 10
|
||||
width: half
|
||||
- collection: directus_settings
|
||||
field: auth_idle_timeout
|
||||
interface: numeric
|
||||
locked: true
|
||||
options:
|
||||
iconRight: timer
|
||||
sort: 11
|
||||
width: half
|
||||
- collection: directus_settings
|
||||
field: auth_login_attempts
|
||||
interface: numeric
|
||||
locked: true
|
||||
options:
|
||||
iconRight: lock
|
||||
sort: 12
|
||||
width: half
|
||||
- collection: directus_settings
|
||||
field: files_divider
|
||||
interface: divider
|
||||
locked: true
|
||||
options:
|
||||
icon: storage
|
||||
title: Files & Thumbnails
|
||||
color: '#2f80ed'
|
||||
sort: 13
|
||||
width: full
|
||||
- collection: directus_settings
|
||||
field: storage_asset_presets
|
||||
interface: repeater
|
||||
locked: true
|
||||
options:
|
||||
fields:
|
||||
- field: key
|
||||
interface: slug
|
||||
name: Key
|
||||
options:
|
||||
onlyOnCreate: false
|
||||
required: true
|
||||
type: string
|
||||
width: half
|
||||
- field: fit
|
||||
interface: dropdown
|
||||
name: Fit
|
||||
options:
|
||||
choices:
|
||||
- value: contain
|
||||
text: Contain (preserve aspect ratio)
|
||||
- value: crop
|
||||
text: Crop (forces exact size)
|
||||
required: true
|
||||
type: string
|
||||
width: half
|
||||
- field: width
|
||||
interface: numeric
|
||||
name: Width
|
||||
required: true
|
||||
type: integer
|
||||
width: half
|
||||
- field: height
|
||||
interface: numeric
|
||||
name: Height
|
||||
required: true
|
||||
type: integer
|
||||
width: half
|
||||
- field: quality
|
||||
default_value: 80
|
||||
interface: slider
|
||||
name: Quality
|
||||
options:
|
||||
max: 100
|
||||
min: 0
|
||||
step: 1
|
||||
required: true
|
||||
type: integer
|
||||
width: full
|
||||
template: "{{key}}"
|
||||
special: json
|
||||
sort: 13
|
||||
width: full
|
||||
- collection: directus_settings
|
||||
field: storage_asset_transform
|
||||
interface: dropdown
|
||||
locked: true
|
||||
options:
|
||||
choices:
|
||||
- value: all
|
||||
text: All
|
||||
- value: none
|
||||
text: None
|
||||
- value: presets
|
||||
text: Presets Only\
|
||||
sort: 14
|
||||
width: half
|
||||
|
||||
# directus_webhooks TBD
|
||||
|
||||
directus_permissions:
|
||||
defaults:
|
||||
id: null
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Router } from 'express';
|
||||
import asyncHandler from 'express-async-handler';
|
||||
import * as FieldsService from '../services/fields';
|
||||
import FieldsService from '../services/fields';
|
||||
import validateCollection from '../middleware/collection-exists';
|
||||
import { schemaInspector } from '../database';
|
||||
import { FieldNotFoundException, InvalidPayloadException } from '../exceptions';
|
||||
@@ -21,7 +21,9 @@ router.get(
|
||||
'/',
|
||||
useCollection('directus_fields'),
|
||||
asyncHandler(async (req, res) => {
|
||||
const fields = await FieldsService.readAll();
|
||||
const service = new FieldsService({ accountability: req.accountability });
|
||||
|
||||
const fields = await service.readAll();
|
||||
return res.json({ data: fields || null });
|
||||
})
|
||||
);
|
||||
@@ -31,7 +33,9 @@ router.get(
|
||||
validateCollection,
|
||||
useCollection('directus_fields'),
|
||||
asyncHandler(async (req, res) => {
|
||||
const fields = await FieldsService.readAll(req.collection);
|
||||
const service = new FieldsService({ accountability: req.accountability });
|
||||
|
||||
const fields = await service.readAll(req.collection);
|
||||
return res.json({ data: fields || null });
|
||||
})
|
||||
);
|
||||
@@ -41,10 +45,12 @@ router.get(
|
||||
validateCollection,
|
||||
useCollection('directus_fields'),
|
||||
asyncHandler(async (req, res) => {
|
||||
const service = new FieldsService({ accountability: req.accountability });
|
||||
|
||||
const exists = await schemaInspector.hasColumn(req.collection, req.params.field);
|
||||
if (exists === false) throw new FieldNotFoundException(req.collection, req.params.field);
|
||||
|
||||
const field = await FieldsService.readOne(req.collection, req.params.field);
|
||||
const field = await service.readOne(req.collection, req.params.field);
|
||||
return res.json({ data: field || null });
|
||||
})
|
||||
);
|
||||
@@ -70,6 +76,8 @@ router.post(
|
||||
validateCollection,
|
||||
useCollection('directus_fields'),
|
||||
asyncHandler(async (req, res) => {
|
||||
const service = new FieldsService({ accountability: req.accountability });
|
||||
|
||||
const { error } = newFieldSchema.validate(req.body);
|
||||
|
||||
if (error) {
|
||||
@@ -78,9 +86,9 @@ router.post(
|
||||
|
||||
const field: Partial<Field> & { field: string; type: typeof types[number] } = req.body;
|
||||
|
||||
await FieldsService.createField(req.params.collection, field, req.accountability);
|
||||
await service.createField(req.params.collection, field, req.accountability);
|
||||
|
||||
const createdField = await FieldsService.readOne(
|
||||
const createdField = await service.readOne(
|
||||
req.params.collection,
|
||||
field.field,
|
||||
req.accountability
|
||||
@@ -95,15 +103,17 @@ router.patch(
|
||||
validateCollection,
|
||||
useCollection('directus_fields'),
|
||||
asyncHandler(async (req, res) => {
|
||||
const service = new FieldsService({ accountability: req.accountability });
|
||||
|
||||
if (Array.isArray(req.body) === false)
|
||||
throw new InvalidPayloadException('Submitted body has to be an array.');
|
||||
|
||||
let results: any = [];
|
||||
|
||||
for (const field of req.body) {
|
||||
await FieldsService.updateField(req.params.collection, field, req.accountability);
|
||||
await service.updateField(req.params.collection, field, req.accountability);
|
||||
|
||||
const updatedField = await FieldsService.readOne(
|
||||
const updatedField = await service.readOne(
|
||||
req.params.collection,
|
||||
field.field,
|
||||
req.accountability
|
||||
@@ -122,13 +132,15 @@ router.patch(
|
||||
useCollection('directus_fields'),
|
||||
// @todo: validate field
|
||||
asyncHandler(async (req, res) => {
|
||||
const service = new FieldsService({ accountability: req.accountability });
|
||||
|
||||
const fieldData: Partial<Field> & { field: string; type: typeof types[number] } = req.body;
|
||||
|
||||
if (!fieldData.field) fieldData.field = req.params.field;
|
||||
|
||||
await FieldsService.updateField(req.params.collection, fieldData, req.accountability);
|
||||
await service.updateField(req.params.collection, fieldData, req.accountability);
|
||||
|
||||
const updatedField = await FieldsService.readOne(
|
||||
const updatedField = await service.readOne(
|
||||
req.params.collection,
|
||||
req.params.field,
|
||||
req.accountability
|
||||
@@ -143,11 +155,9 @@ router.delete(
|
||||
validateCollection,
|
||||
useCollection('directus_fields'),
|
||||
asyncHandler(async (req, res) => {
|
||||
await FieldsService.deleteField(
|
||||
req.params.collection,
|
||||
req.params.field,
|
||||
req.accountability
|
||||
);
|
||||
const service = new FieldsService({ accountability: req.accountability });
|
||||
|
||||
await service.deleteField(req.params.collection, req.params.field, req.accountability);
|
||||
|
||||
res.status(200).end();
|
||||
})
|
||||
|
||||
@@ -1,206 +1,198 @@
|
||||
import database, { schemaInspector } from '../database';
|
||||
import { Field } from '../types/field';
|
||||
import { uniq } from 'lodash';
|
||||
import { Accountability } from '../types';
|
||||
import { Accountability, AbstractServiceOptions } from '../types';
|
||||
import ItemsService from '../services/items';
|
||||
import { ColumnBuilder } from 'knex';
|
||||
import getLocalType from '../utils/get-local-type';
|
||||
import { types } from '../types';
|
||||
import { InvalidPayloadException, FieldNotFoundException } from '../exceptions';
|
||||
|
||||
/**
|
||||
* @TODO turn into class
|
||||
*/
|
||||
|
||||
export const fieldsInCollection = async (collection: string) => {
|
||||
const [fields, columns] = await Promise.all([
|
||||
database.select('field').from('directus_fields').where({ collection }),
|
||||
schemaInspector.columns(collection),
|
||||
]);
|
||||
|
||||
return uniq([...fields.map(({ field }) => field), ...columns.map(({ column }) => column)]);
|
||||
};
|
||||
|
||||
/**
|
||||
* @TODO
|
||||
* update read to use ItemsService instead of direct to db
|
||||
*/
|
||||
|
||||
export const readAll = async (collection?: string) => {
|
||||
const fieldsQuery = database.select('*').from('directus_fields');
|
||||
|
||||
if (collection) {
|
||||
fieldsQuery.where({ collection });
|
||||
}
|
||||
|
||||
const [columns, fields] = await Promise.all([
|
||||
schemaInspector.columnInfo(collection),
|
||||
fieldsQuery,
|
||||
]);
|
||||
|
||||
return columns.map((column) => {
|
||||
const field = fields.find(
|
||||
(field) => field.field === column.name && field.collection === column.table
|
||||
);
|
||||
|
||||
const data = {
|
||||
collection: column.table,
|
||||
field: column.name,
|
||||
type: column ? getLocalType(column.type) : 'alias',
|
||||
database: column,
|
||||
system: field || null,
|
||||
};
|
||||
|
||||
return data;
|
||||
});
|
||||
};
|
||||
|
||||
/** @todo add accountability */
|
||||
export const readOne = async (
|
||||
collection: string,
|
||||
field: string,
|
||||
accountability?: Accountability
|
||||
) => {
|
||||
let column;
|
||||
const fieldInfo = await database
|
||||
.select('*')
|
||||
.from('directus_fields')
|
||||
.where({ collection, field })
|
||||
.first();
|
||||
|
||||
try {
|
||||
column = await schemaInspector.columnInfo(collection, field);
|
||||
} catch {}
|
||||
|
||||
const data = {
|
||||
collection,
|
||||
field,
|
||||
type: column ? getLocalType(column.type) : 'alias',
|
||||
database: column || null,
|
||||
system: fieldInfo || null,
|
||||
};
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
export const createField = async (
|
||||
collection: string,
|
||||
field: Partial<Field> & { field: string; type: typeof types[number] },
|
||||
accountability?: Accountability
|
||||
) => {
|
||||
const itemsService = new ItemsService('directus_fields', { accountability });
|
||||
|
||||
/**
|
||||
* @todo
|
||||
* Check if table / directus_fields row already exists
|
||||
*/
|
||||
|
||||
if (field.database) {
|
||||
await database.schema.alterTable(collection, (table) => {
|
||||
let column: ColumnBuilder;
|
||||
|
||||
if (!field.database) return;
|
||||
|
||||
if (field.type === 'string') {
|
||||
column = table.string(
|
||||
field.field,
|
||||
field.database.max_length !== null ? field.database.max_length : undefined
|
||||
);
|
||||
} else if (['float', 'decimal'].includes(field.type)) {
|
||||
const type = field.type as 'float' | 'decimal';
|
||||
/** @todo add precision and scale support */
|
||||
column = table[type](field.field /* precision, scale */);
|
||||
} else {
|
||||
column = table[field.type](field.field);
|
||||
}
|
||||
|
||||
if (field.database.default_value) {
|
||||
column.defaultTo(field.database.default_value);
|
||||
}
|
||||
|
||||
if (field.database.is_nullable && field.database.is_nullable === true) {
|
||||
column.nullable();
|
||||
} else {
|
||||
column.notNullable();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (field.system) {
|
||||
await itemsService.create({
|
||||
...field.system,
|
||||
collection: collection,
|
||||
field: field.field,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/** @todo research how to make this happen in SQLite / Redshift */
|
||||
import { FieldNotFoundException } from '../exceptions';
|
||||
import Knex from 'knex';
|
||||
|
||||
type RawField = Partial<Field> & { field: string; type: typeof types[number] };
|
||||
|
||||
export const updateField = async (
|
||||
collection: string,
|
||||
field: RawField,
|
||||
accountability?: Accountability
|
||||
) => {
|
||||
if (field.database) {
|
||||
await database.schema.alterTable(collection, (table) => {
|
||||
let column: ColumnBuilder;
|
||||
export default class FieldsService {
|
||||
knex: Knex;
|
||||
accountability: Accountability | null;
|
||||
service: ItemsService;
|
||||
|
||||
if (!field.database) return;
|
||||
constructor(options?: AbstractServiceOptions) {
|
||||
this.knex = options?.knex || database;
|
||||
this.accountability = options?.accountability || null;
|
||||
this.service = new ItemsService('directus_fields', options);
|
||||
}
|
||||
|
||||
if (field.type === 'string') {
|
||||
column = table.string(
|
||||
field.field,
|
||||
field.database.max_length !== null ? field.database.max_length : undefined
|
||||
);
|
||||
} else if (['float', 'decimal'].includes(field.type)) {
|
||||
const type = field.type as 'float' | 'decimal';
|
||||
/** @todo add precision and scale support */
|
||||
column = table[type](field.field /* precision, scale */);
|
||||
} else {
|
||||
column = table[field.type](field.field);
|
||||
}
|
||||
async fieldsInCollection(collection: string) {
|
||||
const [fields, columns] = await Promise.all([
|
||||
this.service.readByQuery({ filter: { collection: { _eq: collection } } }),
|
||||
schemaInspector.columns(collection),
|
||||
]);
|
||||
|
||||
if (field.database.default_value) {
|
||||
column.defaultTo(field.database.default_value);
|
||||
}
|
||||
return uniq([...fields.map(({ field }) => field), ...columns.map(({ column }) => column)]);
|
||||
}
|
||||
|
||||
if (field.database.is_nullable && field.database.is_nullable === true) {
|
||||
column.nullable();
|
||||
} else {
|
||||
column.notNullable();
|
||||
}
|
||||
async readAll(collection?: string) {
|
||||
const fieldsQuery = this.knex.select('*').from('directus_fields');
|
||||
|
||||
column.alter();
|
||||
if (collection) {
|
||||
fieldsQuery.where({ collection });
|
||||
}
|
||||
|
||||
let [columns, fields] = await Promise.all([
|
||||
schemaInspector.columnInfo(collection),
|
||||
fieldsQuery,
|
||||
]);
|
||||
|
||||
return columns.map((column) => {
|
||||
const field = fields.find(
|
||||
(field) => field.field === column.name && field.collection === column.table
|
||||
);
|
||||
|
||||
const data = {
|
||||
collection: column.table,
|
||||
field: column.name,
|
||||
type: column ? getLocalType(column.type) : 'alias',
|
||||
database: column,
|
||||
system: field || null,
|
||||
};
|
||||
|
||||
return data;
|
||||
});
|
||||
}
|
||||
|
||||
if (field.system) {
|
||||
const record = await database
|
||||
.select<{ id: number }>('id')
|
||||
/** @todo add accountability */
|
||||
async readOne(collection: string, field: string, accountability?: Accountability) {
|
||||
let column;
|
||||
const fieldInfo = await this.knex
|
||||
.select('*')
|
||||
.from('directus_fields')
|
||||
.where({ collection, field: field.field })
|
||||
.where({ collection, field })
|
||||
.first();
|
||||
if (!record) throw new FieldNotFoundException(collection, field.field);
|
||||
await database('directus_fields')
|
||||
.update(field.system)
|
||||
.where({ collection, field: field.field });
|
||||
|
||||
try {
|
||||
column = await schemaInspector.columnInfo(collection, field);
|
||||
} catch {}
|
||||
|
||||
const data = {
|
||||
collection,
|
||||
field,
|
||||
type: column ? getLocalType(column.type) : 'alias',
|
||||
database: column || null,
|
||||
system: fieldInfo || null,
|
||||
};
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
return field.field;
|
||||
};
|
||||
async createField(
|
||||
collection: string,
|
||||
field: Partial<Field> & { field: string; type: typeof types[number] },
|
||||
accountability?: Accountability
|
||||
) {
|
||||
const itemsService = new ItemsService('directus_fields', { accountability });
|
||||
|
||||
/** @todo save accountability */
|
||||
export const deleteField = async (
|
||||
collection: string,
|
||||
field: string,
|
||||
accountability?: Accountability
|
||||
) => {
|
||||
await database('directus_fields').delete().where({ collection, field });
|
||||
/**
|
||||
* @todo
|
||||
* Check if table / directus_fields row already exists
|
||||
*/
|
||||
|
||||
await database.schema.table(collection, (table) => {
|
||||
table.dropColumn(field);
|
||||
});
|
||||
};
|
||||
if (field.database) {
|
||||
await database.schema.alterTable(collection, (table) => {
|
||||
let column: ColumnBuilder;
|
||||
|
||||
if (!field.database) return;
|
||||
|
||||
if (field.type === 'string') {
|
||||
column = table.string(
|
||||
field.field,
|
||||
field.database.max_length !== null ? field.database.max_length : undefined
|
||||
);
|
||||
} else if (['float', 'decimal'].includes(field.type)) {
|
||||
const type = field.type as 'float' | 'decimal';
|
||||
/** @todo add precision and scale support */
|
||||
column = table[type](field.field /* precision, scale */);
|
||||
} else {
|
||||
column = table[field.type](field.field);
|
||||
}
|
||||
|
||||
if (field.database.default_value) {
|
||||
column.defaultTo(field.database.default_value);
|
||||
}
|
||||
|
||||
if (field.database.is_nullable && field.database.is_nullable === true) {
|
||||
column.nullable();
|
||||
} else {
|
||||
column.notNullable();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (field.system) {
|
||||
await itemsService.create({
|
||||
...field.system,
|
||||
collection: collection,
|
||||
field: field.field,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/** @todo research how to make this happen in SQLite / Redshift */
|
||||
|
||||
async updateField(collection: string, field: RawField, accountability?: Accountability) {
|
||||
if (field.database) {
|
||||
await database.schema.alterTable(collection, (table) => {
|
||||
let column: ColumnBuilder;
|
||||
|
||||
if (!field.database) return;
|
||||
|
||||
if (field.type === 'string') {
|
||||
column = table.string(
|
||||
field.field,
|
||||
field.database.max_length !== null ? field.database.max_length : undefined
|
||||
);
|
||||
} else if (['float', 'decimal'].includes(field.type)) {
|
||||
const type = field.type as 'float' | 'decimal';
|
||||
/** @todo add precision and scale support */
|
||||
column = table[type](field.field /* precision, scale */);
|
||||
} else {
|
||||
column = table[field.type](field.field);
|
||||
}
|
||||
|
||||
if (field.database.default_value) {
|
||||
column.defaultTo(field.database.default_value);
|
||||
}
|
||||
|
||||
if (field.database.is_nullable && field.database.is_nullable === true) {
|
||||
column.nullable();
|
||||
} else {
|
||||
column.notNullable();
|
||||
}
|
||||
|
||||
column.alter();
|
||||
});
|
||||
}
|
||||
|
||||
if (field.system) {
|
||||
const record = await database
|
||||
.select<{ id: number }>('id')
|
||||
.from('directus_fields')
|
||||
.where({ collection, field: field.field })
|
||||
.first();
|
||||
if (!record) throw new FieldNotFoundException(collection, field.field);
|
||||
await database('directus_fields')
|
||||
.update(field.system)
|
||||
.where({ collection, field: field.field });
|
||||
}
|
||||
|
||||
return field.field;
|
||||
}
|
||||
|
||||
/** @todo save accountability */
|
||||
async deleteField(collection: string, field: string, accountability?: Accountability) {
|
||||
await database('directus_fields').delete().where({ collection, field });
|
||||
|
||||
await database.schema.table(collection, (table) => {
|
||||
table.dropColumn(field);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,9 +15,9 @@ import Knex from 'knex';
|
||||
|
||||
import PayloadService from './payload';
|
||||
import AuthService from './auth';
|
||||
import ActivityService from './activity';
|
||||
|
||||
import { pick, clone } from 'lodash';
|
||||
import getDefaultValue from '../utils/get-default-value';
|
||||
|
||||
export default class ItemsService implements AbstractService {
|
||||
collection: string;
|
||||
@@ -202,7 +202,7 @@ export default class ItemsService implements AbstractService {
|
||||
const defaults: Record<string, any> = {};
|
||||
|
||||
for (const column of columns) {
|
||||
defaults[column.name] = column.default_value;
|
||||
defaults[column.name] = getDefaultValue(column);
|
||||
}
|
||||
|
||||
return defaults;
|
||||
|
||||
@@ -67,6 +67,13 @@ export default class PayloadService {
|
||||
// This is an non-existing column, so there isn't any data to save
|
||||
return undefined;
|
||||
},
|
||||
async boolean(operation, value) {
|
||||
if (operation === 'read') {
|
||||
return value === true || value === 1 || value === '1';
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
};
|
||||
|
||||
processValues(operation: Operation, payloads: Partial<Item>[]): Promise<Partial<Item>[]>;
|
||||
|
||||
27
src/utils/get-default-value.ts
Normal file
27
src/utils/get-default-value.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Column } from 'knex-schema-inspector/dist/types/column';
|
||||
import getLocalType from './get-local-type';
|
||||
|
||||
export default function getDefaultValue(column: Column) {
|
||||
const type = getLocalType(column.type);
|
||||
|
||||
let defaultValue = column.default_value || null;
|
||||
|
||||
if (defaultValue === null) return null;
|
||||
|
||||
// Check if the default is wrapped in an extra pair of quotes, this happens in SQLite
|
||||
if (defaultValue.startsWith(`'`) && defaultValue.endsWith(`'`)) {
|
||||
defaultValue = defaultValue.slice(1, -1);
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case 'bigInteger':
|
||||
case 'integer':
|
||||
case 'decimal':
|
||||
case 'float':
|
||||
return Number(defaultValue);
|
||||
case 'boolean':
|
||||
return !!Number(defaultValue);
|
||||
default:
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user