Merge pull request #64 from directus/batch

Add batch create + CSV based batch operations
This commit is contained in:
Rijk van Zanten
2020-07-17 15:22:12 -04:00
committed by GitHub
19 changed files with 322 additions and 232 deletions

View File

@@ -6,6 +6,7 @@ import * as FilesService from '../services/files';
import logger from '../logger';
import { InvalidPayloadException } from '../exceptions';
import useCollection from '../middleware/use-collection';
import { Item } from '../types';
const router = express.Router();
@@ -14,7 +15,7 @@ router.use(useCollection('directus_files'));
const multipartHandler = (operation: 'create' | 'update') =>
asyncHandler(async (req, res, next) => {
const busboy = new Busboy({ headers: req.headers });
const savedFiles: Record<string, any> = [];
const savedFiles: Item[] = [];
/**
* The order of the fields in multipart/form-data is important. We require that all fields
@@ -23,7 +24,7 @@ const multipartHandler = (operation: 'create' | 'update') =>
*/
let disk: string;
let payload: Record<string, any> = {};
let payload: Partial<Item> = {};
busboy.on('field', (fieldname, val) => {
if (fieldname === 'storage') {
@@ -134,7 +135,7 @@ router.patch(
'/:pk',
sanitizeQuery,
asyncHandler(async (req, res, next) => {
let file: Record<string, any>;
let file: Item;
if (req.is('multipart/form-data')) {
file = await multipartHandler('update')(req, res, next);

View File

@@ -5,6 +5,7 @@ import sanitizeQuery from '../middleware/sanitize-query';
import collectionExists from '../middleware/collection-exists';
import * as MetaService from '../services/meta';
import { RouteNotFoundException } from '../exceptions';
import { Accountability } from '../types';
const router = express.Router();
@@ -17,20 +18,24 @@ router.post(
throw new RouteNotFoundException(req.path);
}
const primaryKey = await ItemsService.createItem(req.collection, req.body, {
const accountability: Accountability = {
user: req.user,
role: req.role,
admin: req.admin,
ip: req.ip,
userAgent: req.get('user-agent'),
});
};
const item = await ItemsService.readItem(req.collection, primaryKey, req.sanitizedQuery, {
role: req.role,
admin: req.admin,
});
const primaryKey = await ItemsService.createItem(req.collection, req.body, accountability);
res.json({ data: item || null });
const result = await ItemsService.readItem(
req.collection,
primaryKey,
req.sanitizedQuery,
accountability
);
res.json({ data: result || null });
})
);
@@ -68,15 +73,15 @@ router.get(
throw new RouteNotFoundException(req.path);
}
const record = await ItemsService.readItem(
req.collection,
req.params.pk,
req.sanitizedQuery,
{ role: req.role, admin: req.admin }
);
const pk = req.params.pk.includes(',') ? req.params.pk.split(',') : req.params.pk;
const result = await ItemsService.readItem(req.collection, pk, req.sanitizedQuery, {
role: req.role,
admin: req.admin,
});
return res.json({
data: record || null,
data: result || null,
});
})
);
@@ -86,24 +91,24 @@ router.patch(
collectionExists,
sanitizeQuery,
asyncHandler(async (req, res) => {
if (req.single === false) {
throw new RouteNotFoundException(req.path);
if (req.single === true) {
await ItemsService.upsertSingleton(req.collection, req.body, {
role: req.role,
admin: req.admin,
ip: req.ip,
userAgent: req.get('user-agent'),
user: req.user,
});
const item = await ItemsService.readSingleton(req.collection, req.sanitizedQuery, {
role: req.role,
admin: req.admin,
});
return res.json({ data: item || null });
}
await ItemsService.upsertSingleton(req.collection, req.body, {
role: req.role,
admin: req.admin,
ip: req.ip,
userAgent: req.get('user-agent'),
user: req.user,
});
const item = await ItemsService.readSingleton(req.collection, req.sanitizedQuery, {
role: req.role,
admin: req.admin,
});
return res.json({ data: item || null });
throw new RouteNotFoundException(req.path);
})
);
@@ -116,20 +121,30 @@ router.patch(
throw new RouteNotFoundException(req.path);
}
const primaryKey = await ItemsService.updateItem(req.collection, req.params.pk, req.body, {
const accountability: Accountability = {
user: req.user,
role: req.role,
admin: req.admin,
ip: req.ip,
userAgent: req.get('user-agent'),
user: req.user,
});
};
const item = await ItemsService.readItem(req.collection, primaryKey, req.sanitizedQuery, {
role: req.role,
admin: req.admin,
});
const primaryKey = req.params.pk.includes(',') ? req.params.pk.split(',') : req.params.pk;
const updatedPrimaryKey = await ItemsService.updateItem(
req.collection,
primaryKey,
req.body,
accountability
);
return res.json({ data: item || null });
const result = await ItemsService.readItem(
req.collection,
updatedPrimaryKey,
req.sanitizedQuery,
accountability
);
res.json({ data: result || null });
})
);
@@ -137,13 +152,17 @@ router.delete(
'/:collection/:pk',
collectionExists,
asyncHandler(async (req, res) => {
await ItemsService.deleteItem(req.collection, req.params.pk, {
const accountability: Accountability = {
user: req.user,
role: req.role,
admin: req.admin,
ip: req.ip,
userAgent: req.get('user-agent'),
user: req.user,
});
};
const pk = req.params.pk.includes(',') ? req.params.pk.split(',') : req.params.pk;
await ItemsService.deleteItem(req.collection, pk, accountability);
return res.status(200).end();
})

View File

@@ -1,6 +1,5 @@
import { Query } from '../types/query';
import * as ItemsService from './items';
import { Accountability } from '../types';
import { Accountability, Item, Query } from '../types';
export enum Action {
CREATE = 'create',
@@ -12,7 +11,7 @@ export enum Action {
AUTHENTICATE = 'authenticate',
}
export const createActivity = async (data: Record<string, any>) => {
export const createActivity = async (data: Partial<Item>) => {
return await ItemsService.createItem('directus_activity', data);
};
@@ -30,7 +29,7 @@ export const readActivity = async (
export const updateActivity = async (
pk: string | number,
data: Record<string, any>,
data: Partial<Item>,
accountability: Accountability
) => {
return await ItemsService.updateItem('directus_activity', pk, data, accountability);

View File

@@ -101,12 +101,7 @@ export const readOne = async (
) => {
const [table, collectionInfo] = await Promise.all([
schemaInspector.tableInfo(collection),
ItemsService.readItem<Collection>(
'directus_collections',
collection,
query,
accountability
),
ItemsService.readItem('directus_collections', collection, query, accountability),
]);
return {

View File

@@ -9,17 +9,17 @@ import parseEXIF from 'exif-reader';
import parseIPTC from '../utils/parse-iptc';
import path from 'path';
import { v4 as uuidv4 } from 'uuid';
import { Accountability } from '../types';
import { Accountability, Item } from '../types';
import { Readable } from 'stream';
export const createFile = async (
data: Record<string, any>,
data: Partial<Item>,
stream: NodeJS.ReadableStream,
accountability: Accountability
) => {
const id = uuidv4();
const payload: Record<string, any> = {
const payload: Partial<Item> = {
...data,
id,
};
@@ -70,7 +70,7 @@ export const readFile = async (
export const updateFile = async (
pk: string | number,
data: Record<string, any>,
data: Partial<Item>,
accountability: Accountability,
stream?: NodeJS.ReadableStream
) => {
@@ -98,6 +98,7 @@ export const updateFile = async (
};
export const deleteFile = async (pk: string, accountability: Accountability) => {
/** @todo use ItemsService */
const file = await database
.select('storage', 'filename_disk')
.from('directus_files')

View File

@@ -1,8 +1,8 @@
import * as ItemsService from './items';
import { Accountability, Query } from '../types';
import { Accountability, Query, Item } from '../types';
export const createFolder = async (
data: Record<string, any>,
data: Partial<Item>,
accountability: Accountability
): Promise<string> => {
return (await ItemsService.createItem('directus_folders', data, accountability)) as string;
@@ -18,7 +18,7 @@ export const readFolder = async (pk: string, query: Query, accountability?: Acco
export const updateFolder = async (
pk: string,
data: Record<string, any>,
data: Partial<Item>,
accountability: Accountability
): Promise<string> => {
return (await ItemsService.updateItem('directus_folders', pk, data, accountability)) as string;

View File

@@ -2,35 +2,37 @@ import database, { schemaInspector } from '../database';
import { Query } from '../types/query';
import runAST from '../database/run-ast';
import getASTFromQuery from '../utils/get-ast-from-query';
import { Accountability, Operation } from '../types';
import { Accountability, Operation, Item } from '../types';
import Knex from 'knex';
import * as PayloadService from './payload';
import * as PermissionsService from './permissions';
import * as ActivityService from './activity';
import * as RevisionsService from './revisions';
import { pick } from 'lodash';
import logger from '../logger';
import { pick, clone } from 'lodash';
async function saveActivityAndRevision(
action: ActivityService.Action,
collection: string,
item: string,
payload: Record<string, any>,
accountability: Accountability
payload: Partial<Item>,
accountability: Accountability,
knex: Knex = database
) {
const activityID = await ActivityService.createActivity({
action,
collection,
item,
ip: accountability.ip,
user_agent: accountability.userAgent,
action_by: accountability.user,
});
const activityID = await knex('directus_activity')
.insert({
action,
collection,
item,
ip: accountability.ip,
user_agent: accountability.userAgent,
action_by: accountability.user,
})
.returning('id');
if (action !== ActivityService.Action.DELETE) {
await RevisionsService.createRevision({
activity: activityID,
await knex('directus_revisions').insert({
activity: activityID[0],
collection,
item,
delta: payload,
@@ -41,58 +43,82 @@ async function saveActivityAndRevision(
}
}
export const createItem = async (
export async function createItem(
collection: string,
data: Record<string, any>,
data: Partial<Item>[],
accountability?: Accountability
): Promise<string | number> => {
let payload = data;
): Promise<(string | number)[]>;
export async function createItem(
collection: string,
data: Partial<Item>,
accountability?: Accountability
): Promise<string | number>;
export async function createItem(
collection: string,
data: Partial<Item> | Partial<Item>[],
accountability?: Accountability
): Promise<string | number | (string | number)[]> {
const isBatch = Array.isArray(data);
if (accountability && accountability.admin === false) {
payload = await PermissionsService.processValues(
'create',
collection,
accountability?.role,
data
return database.transaction(async (transaction) => {
let payloads = isBatch ? data : [data];
const primaryKeys: (string | number)[] = await Promise.all(
payloads.map(async (payload: Partial<Item>) => {
if (accountability && accountability.admin === false) {
payload = await PermissionsService.processValues(
'create',
collection,
accountability?.role,
payload
);
}
payload = await PayloadService.processValues('create', collection, payload);
payload = await PayloadService.processM2O(collection, payload);
const primaryKeyField = await schemaInspector.primary(collection);
// Only insert the values that actually save to an existing column. This ensures we ignore aliases etc
const columns = await schemaInspector.columns(collection);
const payloadWithoutAlias = pick(
payload,
columns.map(({ column }) => column)
);
const primaryKeys = await transaction(collection)
.insert(payloadWithoutAlias)
.returning(primaryKeyField);
// This allows the o2m values to be populated correctly
payload[primaryKeyField] = primaryKeys[0];
await PayloadService.processO2M(collection, payload);
if (accountability) {
await saveActivityAndRevision(
ActivityService.Action.CREATE,
collection,
primaryKeys[0],
payloadWithoutAlias,
accountability,
transaction
);
}
return primaryKeys[0];
})
);
}
payload = await PayloadService.processValues('create', collection, payload);
if (isBatch) {
return primaryKeys;
} else {
return primaryKeys[0];
}
});
}
payload = await PayloadService.processM2O(collection, payload);
const primaryKeyField = await schemaInspector.primary(collection);
// Only insert the values that actually save to an existing column. This ensures we ignore aliases etc
const columns = await schemaInspector.columns(collection);
const payloadWithoutAlias = pick(
payload,
columns.map(({ column }) => column)
);
const primaryKeys = await database(collection)
.insert(payloadWithoutAlias)
.returning(primaryKeyField);
// This allows the o2m values to be populated correctly
payload[primaryKeyField] = primaryKeys[0];
await PayloadService.processO2M(collection, payload);
if (accountability) {
// Don't await this. It can run async in the background
saveActivityAndRevision(
ActivityService.Action.CREATE,
collection,
primaryKeys[0],
payloadWithoutAlias,
accountability
).catch((err) => logger.error(err));
}
return primaryKeys[0];
};
export const readItems = async <T = Record<string, any>>(
export const readItems = async <T = Partial<Item>>(
collection: string,
query: Query,
accountability?: Accountability
@@ -107,28 +133,42 @@ export const readItems = async <T = Record<string, any>>(
return await PayloadService.processValues('read', collection, records);
};
export const readItem = async <T = any>(
export const readItem = async <T extends number | string | (number | string)[]>(
collection: string,
pk: number | string,
pk: T,
query: Query = {},
accountability?: Accountability,
operation?: Operation
): Promise<T> => {
): Promise<T extends number | string ? Partial<Item> : Partial<Item>[]> => {
// We allow overriding the operation, so we can use the item read logic to validate permissions
// for update and delete as well
operation = operation || 'read';
const primaryKeyField = await schemaInspector.primary(collection);
const primaryKeys: any[] = Array.isArray(pk) ? pk : [pk];
const isBatch = Array.isArray(pk);
query = {
...query,
filter: {
...(query.filter || {}),
[primaryKeyField]: {
_eq: pk,
if (isBatch) {
query = {
...query,
filter: {
...(query.filter || {}),
[primaryKeyField]: {
_in: primaryKeys,
},
},
},
};
};
} else {
query = {
...query,
filter: {
...(query.filter || {}),
[primaryKeyField]: {
_eq: pk,
},
},
};
}
let ast = await getASTFromQuery(collection, query, accountability, operation);
@@ -137,82 +177,112 @@ export const readItem = async <T = any>(
}
const records = await runAST(ast);
return await PayloadService.processValues('read', collection, records[0]);
const processedRecords = await PayloadService.processValues('read', collection, records);
return isBatch ? processedRecords : processedRecords[0];
};
export const updateItem = async (
export const updateItem = async <T extends number | string | (number | string)[]>(
collection: string,
pk: number | string,
data: Record<string, any>,
pk: T,
data: Partial<Item>,
accountability?: Accountability
): Promise<string | number> => {
let payload = data;
): Promise<T> => {
const primaryKeys: any[] = Array.isArray(pk) ? pk : [pk];
if (accountability && accountability.admin === false) {
await PermissionsService.checkAccess('update', collection, pk, accountability.role);
await database.transaction(async (transaction) => {
let payload = clone(data);
payload = await PermissionsService.processValues(
'validate',
collection,
accountability.role,
data
return await Promise.all(
primaryKeys.map(async (key) => {
if (accountability && accountability.admin === false) {
await PermissionsService.checkAccess(
'update',
collection,
key,
accountability.role
);
payload = await PermissionsService.processValues(
'validate',
collection,
accountability.role,
data
);
}
payload = await PayloadService.processValues('update', collection, payload);
payload = await PayloadService.processM2O(collection, payload);
const primaryKeyField = await schemaInspector.primary(collection);
// Only insert the values that actually save to an existing column. This ensures we ignore aliases etc
const columns = await schemaInspector.columns(collection);
const payloadWithoutAlias = pick(
payload,
columns.map(({ column }) => column)
);
await transaction(collection)
.update(payloadWithoutAlias)
.where({ [primaryKeyField]: key });
if (accountability) {
await saveActivityAndRevision(
ActivityService.Action.UPDATE,
collection,
String(key),
payloadWithoutAlias,
accountability,
transaction
);
}
return pk;
})
);
}
payload = await PayloadService.processValues('update', collection, payload);
payload = await PayloadService.processM2O(collection, payload);
const primaryKeyField = await schemaInspector.primary(collection);
// Only insert the values that actually save to an existing column. This ensures we ignore aliases etc
const columns = await schemaInspector.columns(collection);
const payloadWithoutAlias = pick(
payload,
columns.map(({ column }) => column)
);
await database(collection)
.update(payloadWithoutAlias)
.where({ [primaryKeyField]: pk });
if (accountability) {
// Don't await this. It can run async in the background
saveActivityAndRevision(
ActivityService.Action.UPDATE,
collection,
String(pk),
payloadWithoutAlias,
accountability
).catch((err) => logger.error(err));
}
});
return pk;
};
export const deleteItem = async (
export const deleteItem = async <T extends number | string | (number | string)[]>(
collection: string,
pk: number | string,
pk: T,
accountability?: Accountability
) => {
): Promise<T> => {
const primaryKeyField = await schemaInspector.primary(collection);
const primaryKeys: any[] = Array.isArray(pk) ? pk : [pk];
if (accountability && accountability.admin === false) {
await PermissionsService.checkAccess('delete', collection, pk, accountability.role);
await database.transaction(async (transaction) => {
await Promise.all(
primaryKeys.map(async (key) => {
if (accountability && accountability.admin === false) {
await PermissionsService.checkAccess(
'delete',
collection,
key,
accountability.role
);
}
// Don't await this. It can run async in the background
saveActivityAndRevision(
ActivityService.Action.DELETE,
collection,
String(pk),
{},
accountability
).catch((err) => logger.error(err));
}
await transaction(collection)
.where({ [primaryKeyField]: key })
.delete();
return await database(collection)
.delete()
.where({ [primaryKeyField]: pk });
await saveActivityAndRevision(
ActivityService.Action.DELETE,
collection,
String(key),
{},
accountability,
transaction
);
})
);
});
return pk;
};
export const readSingleton = async (
@@ -241,7 +311,7 @@ export const readSingleton = async (
export const upsertSingleton = async (
collection: string,
data: Record<string, any>,
data: Partial<Item>,
accountability: Accountability
) => {
const primaryKeyField = await schemaInspector.primary(collection);

View File

@@ -8,18 +8,13 @@ import argon2 from 'argon2';
import { v4 as uuidv4 } from 'uuid';
import database from '../database';
import { clone, isObject } from 'lodash';
import { File } from '../types/files';
import { Relation } from '../types/relation';
import { File, Relation, Item } from '../types';
import * as ItemsService from './items';
type Operation = 'create' | 'read' | 'update';
type Transformers = {
[type: string]: (
operation: Operation,
value: any,
payload: Record<string, any>
) => Promise<any>;
[type: string]: (operation: Operation, value: any, payload: Partial<Item>) => Promise<any>;
};
/**
@@ -72,7 +67,7 @@ const transformers: Transformers = {
export const processValues = async (
operation: Operation,
collection: string,
payload: Record<string, any> | Record<string, any>[]
payload: Partial<Item> | Partial<Item>[]
) => {
let processedPayload = clone(payload);
@@ -113,7 +108,7 @@ export const processValues = async (
async function processField(
field: Pick<System, 'field' | 'special'>,
payload: Record<string, any>,
payload: Partial<Item>,
operation: Operation
) {
if (transformers.hasOwnProperty(field.special)) {
@@ -126,7 +121,7 @@ async function processField(
/**
* Recursively checks for nested relational items, and saves them bottom up, to ensure we have IDs etc ready
*/
export const processM2O = async (collection: string, payload: Record<string, any>) => {
export const processM2O = async (collection: string, payload: Partial<Item>) => {
const payloadClone = clone(payload);
const relations = await database
@@ -145,7 +140,7 @@ export const processM2O = async (collection: string, payload: Record<string, any
// Save all nested m2o records
await Promise.all(
relationsToProcess.map(async (relation) => {
const relatedRecord = payloadClone[relation.field_many];
const relatedRecord: Partial<Item> = payloadClone[relation.field_many];
const hasPrimaryKey = relatedRecord.hasOwnProperty(relation.primary_one);
let relatedPrimaryKey: string | number;
@@ -172,7 +167,7 @@ export const processM2O = async (collection: string, payload: Record<string, any
return payloadClone;
};
export const processO2M = async (collection: string, payload: Record<string, any>) => {
export const processO2M = async (collection: string, payload: Partial<Item>) => {
const payloadClone = clone(payload);
const relations = await database
@@ -194,7 +189,7 @@ export const processO2M = async (collection: string, payload: Record<string, any
const relatedRecords = payloadClone[relation.field_one];
await Promise.all(
relatedRecords.map(async (relatedRecord: any, index: number) => {
relatedRecords.map(async (relatedRecord: Partial<Item>, index: number) => {
relatedRecord[relation.field_many] = payloadClone[relation.primary_one];
const hasPrimaryKey = relatedRecord.hasOwnProperty(relation.primary_many);

View File

@@ -6,6 +6,7 @@ import {
Query,
Permission,
Operation,
Item,
} from '../types';
import * as ItemsService from './items';
import database from '../database';
@@ -14,7 +15,7 @@ import { uniq } from 'lodash';
import generateJoi from '../utils/generate-joi';
export const createPermission = async (
data: Record<string, any>,
data: Partial<Item>,
accountability: Accountability
): Promise<number> => {
return (await ItemsService.createItem('directus_permissions', data, accountability)) as number;
@@ -30,7 +31,7 @@ export const readPermission = async (pk: number, query: Query, accountability?:
export const updatePermission = async (
pk: number,
data: Record<string, any>,
data: Partial<Item>,
accountability: Accountability
): Promise<number> => {
return (await ItemsService.updateItem(
@@ -183,7 +184,7 @@ export const processValues = async (
operation: Operation,
collection: string,
role: string | null,
data: Record<string, any>
data: Partial<Item>
) => {
const permission = await database
.select<Permission>('*')
@@ -239,6 +240,8 @@ export const checkAccess = async (
if (!result) throw '';
} catch {
throw new ForbiddenException(`You're not allowed to ${operation} this item.`);
throw new ForbiddenException(
`You're not allowed to ${operation} item "${pk}" in collection "${collection}".`
);
}
};

View File

@@ -1,10 +1,10 @@
import { Accountability, Query } from '../types';
import { Accountability, Query, Item } from '../types';
import * as ItemsService from './items';
/** @todo check if we want to save activity for collection presets */
export const createCollectionPreset = async (
data: Record<string, any>,
data: Partial<Item>,
accountability: Accountability
) => {
return await ItemsService.createItem('directus_presets', data, accountability);
@@ -24,7 +24,7 @@ export const readCollectionPreset = async (
export const updateCollectionPreset = async (
pk: string | number,
data: Record<string, any>,
data: Partial<Item>,
accountability: Accountability
) => {
return await ItemsService.updateItem('directus_presets', pk, data, accountability);

View File

@@ -1,7 +1,7 @@
import { Accountability, Query } from '../types';
import { Accountability, Query, Item } from '../types';
import * as ItemsService from './items';
export const createRelation = async (data: Record<string, any>, accountability: Accountability) => {
export const createRelation = async (data: Partial<Item>, accountability: Accountability) => {
return await ItemsService.createItem('directus_relations', data, accountability);
};
@@ -19,7 +19,7 @@ export const readRelation = async (
export const updateRelation = async (
pk: string | number,
data: Record<string, any>,
data: Partial<Item>,
accountability: Accountability
) => {
return await ItemsService.updateItem('directus_relations', pk, data, accountability);

View File

@@ -1,7 +1,7 @@
import * as ItemsService from './items';
import { Accountability, Query } from '../types';
import { Accountability, Query, Item } from '../types';
export const createRevision = async (data: Record<string, any>) => {
export const createRevision = async (data: Partial<Item>) => {
return await ItemsService.createItem('directus_revisions', data);
};

View File

@@ -1,7 +1,7 @@
import { Accountability, Query } from '../types';
import { Accountability, Query, Item } from '../types';
import * as ItemsService from './items';
export const createRole = async (data: Record<string, any>, accountability: Accountability) => {
export const createRole = async (data: Partial<Item>, accountability: Accountability) => {
return await ItemsService.createItem('directus_roles', data, accountability);
};
@@ -19,7 +19,7 @@ export const readRole = async (
export const updateRole = async (
pk: string | number,
data: Record<string, any>,
data: Partial<Item>,
accountability: Accountability
) => {
return await ItemsService.updateItem('directus_roles', pk, data, accountability);

View File

@@ -1,11 +1,10 @@
import { Query } from '../types/query';
import { Query, Item, Accountability } from '../types';
import * as ItemsService from './items';
import { Accountability } from '../types';
export const readSettings = async (query: Query, accountability?: Accountability) => {
return await ItemsService.readSingleton('directus_settings', query, accountability);
};
export const updateSettings = async (data: Record<string, any>, accountability: Accountability) => {
export const updateSettings = async (data: Partial<Item>, accountability: Accountability) => {
return await ItemsService.upsertSingleton('directus_settings', data, accountability);
};

View File

@@ -4,9 +4,9 @@ import { sendInviteMail } from '../mail';
import database from '../database';
import argon2 from 'argon2';
import { InvalidPayloadException } from '../exceptions';
import { Accountability, Query } from '../types';
import { Accountability, Query, Item } from '../types';
export const createUser = async (data: Record<string, any>, accountability: Accountability) => {
export const createUser = async (data: Partial<Item>, accountability: Accountability) => {
return await ItemsService.createItem('directus_users', data, accountability);
};
@@ -24,7 +24,7 @@ export const readUser = async (
export const updateUser = async (
pk: string | number,
data: Record<string, any>,
data: Partial<Item>,
accountability: Accountability
) => {
/**

View File

@@ -1,7 +1,7 @@
import { Accountability, Query } from '../types';
import { Accountability, Query, Item } from '../types';
import * as ItemsService from './items';
export const createWebhook = async (data: Record<string, any>, accountability: Accountability) => {
export const createWebhook = async (data: Partial<Item>, accountability: Accountability) => {
return await ItemsService.createItem('directus_webhooks', data, accountability);
};
@@ -19,7 +19,7 @@ export const readWebhook = async (
export const updateWebhook = async (
pk: string | number,
data: Record<string, any>,
data: Partial<Item>,
accountability: Accountability
) => {
return await ItemsService.updateItem('directus_webhooks', pk, data, accountability);

View File

@@ -3,6 +3,7 @@
*/
import { Permission } from './permissions';
import { Query } from './query';
export {};
@@ -14,7 +15,7 @@ declare global {
role: string | null;
admin: boolean;
collection?: string;
sanitizedQuery?: Record<string, any>;
sanitizedQuery?: Query;
single?: boolean;
permissions?: Permission;
}

View File

@@ -9,3 +9,4 @@ export * from './permissions';
export * from './query';
export * from './relation';
export * from './sessions';
export * from './items';

6
src/types/items.ts Normal file
View File

@@ -0,0 +1,6 @@
/**
* I know this looks a little silly, but it allows us to explicitly differentiate between when we're
* expecting an item vs any other generic object.
*/
export type Item = Record<string, any>;