From d9ffd7f8de110ebd03fa95c9e2c0faf7a434a033 Mon Sep 17 00:00:00 2001 From: rijkvanzanten Date: Thu, 9 Jul 2020 12:02:53 -0400 Subject: [PATCH 01/10] Track revisions and activity on the item level --- src/routes/collections.ts | 14 +++- src/routes/items.ts | 41 ++++------- src/services/activity.ts | 3 +- src/services/collection-presets.ts | 2 + src/services/collections.ts | 33 +++++---- src/services/items.ts | 112 ++++++++++++++++++++++++----- src/services/payload.ts | 15 ++-- src/services/revisions.ts | 4 ++ src/services/users.ts | 4 +- src/types/accountability.ts | 7 ++ src/types/files.ts | 1 + 11 files changed, 167 insertions(+), 69 deletions(-) create mode 100644 src/types/accountability.ts diff --git a/src/routes/collections.ts b/src/routes/collections.ts index 41dabb66df..0185e0d2ca 100644 --- a/src/routes/collections.ts +++ b/src/routes/collections.ts @@ -29,7 +29,12 @@ router.post( const { error } = collectionSchema.validate(req.body); if (error) throw new InvalidPayloadException(error.message); - const createdCollection = await CollectionsService.create(req.body); + const createdCollection = await CollectionsService.create(req.body, { + ip: req.ip, + userAgent: req.get('user-agent'), + user: req.user, + }); + res.json({ data: createdCollection }); }) ); @@ -65,7 +70,12 @@ router.delete( throw new CollectionNotFoundException(req.params.collection); } - await CollectionsService.deleteCollection(req.params.collection); + await CollectionsService.deleteCollection(req.params.collection, { + ip: req.ip, + userAgent: req.get('user-agent'), + user: req.user, + }); + res.end(); }) ); diff --git a/src/routes/items.ts b/src/routes/items.ts index fd03af3694..c9716e1f90 100644 --- a/src/routes/items.ts +++ b/src/routes/items.ts @@ -6,7 +6,6 @@ import validateCollection from '../middleware/validate-collection'; import validateSingleton from '../middleware/validate-singleton'; import validateQuery from '../middleware/validate-query'; import * as MetaService from '../services/meta'; -import * as ActivityService from '../services/activity'; const router = express.Router(); @@ -17,19 +16,14 @@ router.post( sanitizeQuery, validateQuery, asyncHandler(async (req, res) => { - const primaryKey = await ItemsService.createItem(req.collection, req.body); - const item = await ItemsService.readItem(req.collection, primaryKey, req.sanitizedQuery); - - ActivityService.createActivity({ - action: ActivityService.Action.CREATE, - collection: req.collection, - /** @TODO don't forget to use real primary key here */ - item: item.id, + const primaryKey = await ItemsService.createItem(req.collection, req.body, { ip: req.ip, - user_agent: req.get('user-agent'), - action_by: req.user, + userAgent: req.get('user-agent'), + user: req.user, }); + const item = await ItemsService.readItem(req.collection, primaryKey, req.sanitizedQuery); + res.json({ data: item }); }) ); @@ -76,18 +70,14 @@ router.patch( sanitizeQuery, validateQuery, asyncHandler(async (req, res) => { - const primaryKey = await ItemsService.updateItem(req.collection, req.params.pk, req.body); - const item = await ItemsService.readItem(req.collection, primaryKey, req.sanitizedQuery); - - ActivityService.createActivity({ - action: ActivityService.Action.UPDATE, - collection: req.collection, - item: item.id, + const primaryKey = await ItemsService.updateItem(req.collection, req.params.pk, req.body, { ip: req.ip, - user_agent: req.get('user-agent'), - action_by: req.user, + userAgent: req.get('user-agent'), + user: req.user, }); + const item = await ItemsService.readItem(req.collection, primaryKey, req.sanitizedQuery); + return res.json({ data: item }); }) ); @@ -96,15 +86,10 @@ router.delete( '/:collection/:pk', validateCollection, asyncHandler(async (req, res) => { - await ItemsService.deleteItem(req.collection, req.params.pk); - - ActivityService.createActivity({ - action: ActivityService.Action.DELETE, - collection: req.collection, - item: req.params.pk, + await ItemsService.deleteItem(req.collection, req.params.pk, { ip: req.ip, - user_agent: req.get('user-agent'), - action_by: req.user, + userAgent: req.get('user-agent'), + user: req.user, }); return res.status(200).end(); diff --git a/src/services/activity.ts b/src/services/activity.ts index aded494c81..552f006b8f 100644 --- a/src/services/activity.ts +++ b/src/services/activity.ts @@ -12,8 +12,7 @@ export enum Action { } export const createActivity = async (data: Record, query?: Query) => { - const primaryKey = await ItemsService.createItem('directus_activity', data); - return await ItemsService.readItem('directus_activity', primaryKey, query); + return await ItemsService.createItem('directus_activity', data); }; export const readActivities = async (query?: Query) => { diff --git a/src/services/collection-presets.ts b/src/services/collection-presets.ts index 81ea03b4d2..a3ade0dc93 100644 --- a/src/services/collection-presets.ts +++ b/src/services/collection-presets.ts @@ -1,6 +1,8 @@ import { Query } from '../types/query'; import * as ItemsService from './items'; +/** @todo check if we want to save activity for collection presets */ + export const createCollectionPreset = async (data: Record, query: Query) => { const primaryKey = await ItemsService.createItem('directus_collection_presets', data); return await ItemsService.readItem('directus_collection_presets', primaryKey, query); diff --git a/src/services/collections.ts b/src/services/collections.ts index 009080ebe2..3bba16f8bb 100644 --- a/src/services/collections.ts +++ b/src/services/collections.ts @@ -3,9 +3,10 @@ import * as ItemsService from '../services/items'; import { Collection } from '../types/collection'; import { Query } from '../types/query'; import { ColumnBuilder } from 'knex'; +import { Accountability } from '../types/accountability'; /** @Todo properly type this */ -export const create = async (payload: any) => { +export const create = async (payload: any, accountability: Accountability) => { await database.schema.createTable(payload.collection, (table) => { if (payload.note) { table.comment(payload.note); @@ -13,6 +14,8 @@ export const create = async (payload: any) => { /** @todo move this into fields service */ + /** @todo adhere to new nested structure system vs database */ + payload.fields?.forEach((field: any) => { let column: ColumnBuilder; @@ -36,16 +39,18 @@ export const create = async (payload: any) => { }); }); - const primaryKey = await ItemsService.createItem('directus_collections', { - collection: payload.collection, - hidden: payload.hidden || false, - single: payload.single || false, - icon: payload.icon || null, - note: payload.note || null, - translation: payload.translation || null, - }); - - const collection = await ItemsService.createItem('directus_collections', primaryKey); + const primaryKey = await ItemsService.createItem( + 'directus_collections', + { + collection: payload.collection, + hidden: payload.hidden || false, + single: payload.single || false, + icon: payload.icon || null, + note: payload.note || null, + translation: payload.translation || null, + }, + accountability + ); /** * @TODO make this flexible and based on payload @@ -62,7 +67,7 @@ export const create = async (payload: any) => { })) ); - return collection; + return await ItemsService.readItem('directus_collections', primaryKey); }; export const readAll = async (query?: Query) => { @@ -105,10 +110,10 @@ export const readOne = async (collection: string, query?: Query) => { }; }; -export const deleteCollection = async (collection: string) => { +export const deleteCollection = async (collection: string, accountability: Accountability) => { await Promise.all([ database.schema.dropTable(collection), - ItemsService.deleteItem('directus_collections', collection), + ItemsService.deleteItem('directus_collections', collection, accountability), database.delete().from('directus_fields').where({ collection }), database .delete() diff --git a/src/services/items.ts b/src/services/items.ts index bcce640fa7..420452be48 100644 --- a/src/services/items.ts +++ b/src/services/items.ts @@ -3,9 +3,46 @@ import { Query } from '../types/query'; import runAST from '../database/run-ast'; import getAST from '../utils/get-ast'; import * as PayloadService from './payload'; -import { pick } from 'lodash'; +import { Accountability } from '../types/accountability'; -export const createItem = async (collection: string, data: Record) => { +import * as ActivityService from './activity'; +import * as RevisionsService from './revisions'; + +import { pick } from 'lodash'; +import logger from '../logger'; + +async function saveActivityAndRevision( + action: ActivityService.Action, + collection: string, + item: string, + payload: Record, + accountability: Accountability +) { + const activityID = await ActivityService.createActivity({ + action, + collection, + item, + ip: accountability.ip, + user_agent: accountability.userAgent, + action_by: accountability.user, + }); + + await RevisionsService.createRevision({ + activity: activityID, + collection, + item, + delta: payload, + /** @todo make this configurable */ + data: await readItem(collection, item, { fields: ['*'] }), + parent: accountability.parent, + }); +} + +export const createItem = async ( + collection: string, + data: Record, + accountability?: Accountability +) => { let payload = await PayloadService.processValues('create', collection, data); payload = await PayloadService.processM2O(collection, payload); @@ -14,13 +51,14 @@ export const createItem = async (collection: string, data: Record) // 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( - pick( - payload, - columns.map(({ column }) => column) - ) - ) + .insert(payloadWithoutAlias) .returning(primaryKeyField); // This allows the o2m values to be populated correctly @@ -28,6 +66,17 @@ export const createItem = async (collection: string, data: Record) 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]; }; @@ -67,9 +116,10 @@ export const readItem = async ( export const updateItem = async ( collection: string, pk: number | string, - data: Record + data: Record, + accountability?: Accountability ) => { - let payload = await PayloadService.processValues('create', collection, data); + let payload = await PayloadService.processValues('update', collection, data); payload = await PayloadService.processM2O(collection, payload); @@ -77,21 +127,49 @@ export const updateItem = async ( // 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) - .update( - pick( - payload, - columns.map(({ column }) => column) - ) - ) + .update(payloadWithoutAlias) .where({ [primaryKeyField]: pk }) .returning(primaryKeyField); + if (accountability) { + // Don't await this. It can run async in the background + saveActivityAndRevision( + ActivityService.Action.UPDATE, + collection, + primaryKeys[0], + payloadWithoutAlias, + accountability + ).catch((err) => logger.error(err)); + } + return primaryKeys[0]; }; -export const deleteItem = async (collection: string, pk: number | string) => { +export const deleteItem = async ( + collection: string, + pk: number | string, + accountability?: Accountability +) => { const primaryKeyField = await schemaInspector.primary(collection); + + if (accountability) { + // Don't await this. It can run async in the background + saveActivityAndRevision( + ActivityService.Action.DELETE, + collection, + String(pk), + {}, + accountability + ).catch((err) => logger.error(err)); + } + return await database(collection) .delete() .where({ [primaryKeyField]: pk }); diff --git a/src/services/payload.ts b/src/services/payload.ts index addf3d3743..cbd6336d9c 100644 --- a/src/services/payload.ts +++ b/src/services/payload.ts @@ -7,7 +7,7 @@ import { System } from '../types/field'; import argon2 from 'argon2'; import { v4 as uuidv4 } from 'uuid'; import database from '../database'; -import { clone } from 'lodash'; +import { clone, isObject } from 'lodash'; import { File } from '../types/files'; import { Relation } from '../types/relation'; import * as ItemsService from './items'; @@ -24,6 +24,10 @@ type Transformers = { /** * @todo allow this to be extended + * + * @todo allow these extended special types to have "field dependencies"? + * f.e. the file-links transformer needs the id and filename_download to be fetched from the DB + * in order to work */ const transformers: Transformers = { async hash(operation, value) { @@ -42,10 +46,13 @@ const transformers: Transformers = { return value; }, - async 'file-info'(operation, value, payload: File) { - if (operation === 'read' && payload) { + async 'file-links'(operation, value, payload: File) { + if (operation === 'read' && payload && payload.storage && payload.filename_disk) { + const publicKey = `STORAGE_${payload.storage.toUpperCase()}_PUBLIC_URL`; + return { asset_url: new URL(`/assets/${payload.id}`, process.env.PUBLIC_URL), + public_url: new URL(payload.filename_disk, process.env[publicKey]), }; } @@ -131,7 +138,7 @@ export const processM2O = async (collection: string, payload: Record { return ( payloadClone.hasOwnProperty(relation.field_many) && - typeof payloadClone[relation.field_many] === 'object' + isObject(payloadClone[relation.field_many]) ); }); diff --git a/src/services/revisions.ts b/src/services/revisions.ts index 0062a1c312..29041cee0e 100644 --- a/src/services/revisions.ts +++ b/src/services/revisions.ts @@ -1,6 +1,10 @@ import { Query } from '../types/query'; import * as ItemsService from './items'; +export const createRevision = async (data: Record) => { + return await ItemsService.createItem('directus_revisions', data); +}; + export const readRevisions = async (query: Query) => { return await ItemsService.readItems('directus_revisions', query); }; diff --git a/src/services/users.ts b/src/services/users.ts index e7ff44d4a9..2c01b39711 100644 --- a/src/services/users.ts +++ b/src/services/users.ts @@ -8,7 +8,7 @@ import { InvalidPayloadException } from '../exceptions'; export const createUser = async (data: Record, query?: Query) => { const primaryKey = await ItemsService.createItem('directus_users', data); - return await ItemsService.readItem('directus_user', primaryKey, query); + return await ItemsService.readItem('directus_users', primaryKey, query); }; export const readUsers = async (query?: Query) => { @@ -27,7 +27,7 @@ export const updateUser = async (pk: string | number, data: Record, * Maybe make this an option? */ const primaryKey = await ItemsService.updateItem('directus_users', pk, data); - return await ItemsService.readItem('directus_user', primaryKey, query); + return await ItemsService.readItem('directus_users', primaryKey, query); }; export const deleteUser = async (pk: string | number) => { diff --git a/src/types/accountability.ts b/src/types/accountability.ts new file mode 100644 index 0000000000..8bdebbea82 --- /dev/null +++ b/src/types/accountability.ts @@ -0,0 +1,7 @@ +export type Accountability = { + ip?: string; + userAgent?: string; + user?: string; + + parent?: number; +}; diff --git a/src/types/files.ts b/src/types/files.ts index e3dec251a9..b64d831191 100644 --- a/src/types/files.ts +++ b/src/types/files.ts @@ -2,4 +2,5 @@ export type File = { id: string; // uuid filename_disk: string; + storage: string; }; From 1871a1e2badf90927196c61fa4b5beb9a8e11989 Mon Sep 17 00:00:00 2001 From: rijkvanzanten Date: Thu, 9 Jul 2020 12:09:04 -0400 Subject: [PATCH 02/10] Export all types from index --- src/types/index.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/types/index.ts diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000000..9435b54884 --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1,10 @@ +export * from './accountability'; +export * from './assets'; +export * from './ast'; +export * from './collection'; +export * from './field'; +export * from './files'; +export * from './meta'; +export * from './query'; +export * from './relation'; +export * from './sessions'; From f980e3d89eb50e3afbb1b4b3afab6afc1eaa0bb7 Mon Sep 17 00:00:00 2001 From: rijkvanzanten Date: Thu, 9 Jul 2020 12:09:20 -0400 Subject: [PATCH 03/10] Track field creation in activity/revisions --- src/routes/fields.ts | 6 +++++- src/services/fields.ts | 26 +++++++++++++++++++------- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/routes/fields.ts b/src/routes/fields.ts index c1dbb0554e..658a168d6b 100644 --- a/src/routes/fields.ts +++ b/src/routes/fields.ts @@ -61,7 +61,11 @@ router.post( const field: Partial = req.body; - const createdField = await FieldsService.createField(req.collection, field); + const createdField = await FieldsService.createField(req.collection, field, { + ip: req.ip, + userAgent: req.get('user-agent'), + user: req.user, + }); res.json({ data: createdField }); }) diff --git a/src/services/fields.ts b/src/services/fields.ts index 4050d68737..ce0f0f784f 100644 --- a/src/services/fields.ts +++ b/src/services/fields.ts @@ -1,6 +1,8 @@ import database, { schemaInspector } from '../database'; import { Field } from '../types/field'; import { uniq } from 'lodash'; +import { Accountability } from '../types'; +import * as ItemsService from '../services/items'; export const fieldsInCollection = async (collection: string) => { const [fields, columns] = await Promise.all([ @@ -55,20 +57,30 @@ export const readOne = async (collection: string, field: string) => { return data; }; -export const createField = async (collection: string, field: Partial) => { - await database.schema.alterTable('articles', (table) => { +export const createField = async ( + collection: string, + field: Partial, + accountability: Accountability +) => { + await database.schema.alterTable(collection, (table) => { table.specificType(field.field, field.database.type); /** @todo add support for other database info (length etc) */ }); if (field.system) { - await database('directus_fields').insert({ - ...field.system, - collection: collection, - field: field.field, - }); + await ItemsService.createItem( + 'directus_fields', + { + ...field.system, + collection: collection, + field: field.field, + }, + accountability + ); } const createdField = await readOne(collection, field.field); return createdField; }; + +/** @todo add update / delete field */ From 7e1d386e62775c5c0ed7d939e1fadb39e389930a Mon Sep 17 00:00:00 2001 From: rijkvanzanten Date: Thu, 9 Jul 2020 12:29:04 -0400 Subject: [PATCH 04/10] Add revisions to files --- package.json | 32 ++++++++++++++++---------------- src/routes/files.ts | 39 +++++++++++++++++++++------------------ src/services/files.ts | 20 +++++++++----------- 3 files changed, 46 insertions(+), 45 deletions(-) diff --git a/package.json b/package.json index eb75e5302a..eac8852cf9 100644 --- a/package.json +++ b/package.json @@ -31,12 +31,12 @@ "@types/atob": "^2.1.2", "@types/busboy": "^0.2.3", "@types/cookie-parser": "^1.4.2", - "@types/express": "^4.17.6", + "@types/express": "^4.17.7", "@types/express-pino-logger": "^4.0.2", "@types/express-session": "^1.17.0", - "@types/hapi__joi": "^17.1.2", + "@types/hapi__joi": "^17.1.3", "@types/jsonwebtoken": "^8.5.0", - "@types/lodash": "^4.14.156", + "@types/lodash": "^4.14.157", "@types/ms": "^0.7.31", "@types/nodemailer": "^6.4.0", "@types/pino": "^6.3.0", @@ -44,16 +44,16 @@ "@types/uuid": "^8.0.0", "@types/uuid-validate": "0.0.1", "copyfiles": "^2.3.0", - "eslint": "^7.3.1", + "eslint": "^7.4.0", "eslint-plugin-prettier": "^3.1.4", "husky": "^4.2.5", - "lint-staged": "^10.2.10", - "pino-colada": "^1.6.1", + "lint-staged": "^10.2.11", + "pino-colada": "^2.0.1", "prettier": "^2.0.5", "rimraf": "^3.0.2", "ts-node": "^8.10.2", "tslint": "^6.1.2", - "typescript": "^3.9.5" + "typescript": "^3.9.6" }, "husky": { "hooks": { @@ -67,9 +67,9 @@ }, "dependencies": { "@hapi/joi": "^17.1.1", - "@slynova/flydrive": "^1.0.1", - "@slynova/flydrive-gcs": "^1.0.1", - "@slynova/flydrive-s3": "^1.0.1", + "@slynova/flydrive": "^1.0.2", + "@slynova/flydrive-gcs": "^1.0.2", + "@slynova/flydrive-s3": "^1.0.2", "argon2": "^0.26.2", "atob": "^2.1.2", "body-parser": "^1.19.0", @@ -87,19 +87,19 @@ "icc": "^2.0.0", "jsonwebtoken": "^8.5.1", "knex": "^0.21.1", - "liquidjs": "^9.12.0", - "lodash": "^4.17.15", + "liquidjs": "^9.14.1", + "lodash": "^4.17.19", "ms": "^2.1.2", "mssql": "^6.2.0", "mysql": "^2.18.1", "nanoid": "^3.1.10", "nodemailer": "^6.4.10", - "oracledb": "^4.2.0", - "pg": "^8.2.1", + "oracledb": "^5.0.0", + "pg": "^8.3.0", "pino": "^6.3.2", "sharp": "^0.25.4", - "sqlite3": "^4.2.0", - "ts-node-dev": "^1.0.0-pre.49", + "sqlite3": "^5.0.0", + "ts-node-dev": "^1.0.0-pre.51", "uuid": "^8.2.0", "uuid-validate": "0.0.3" } diff --git a/src/routes/files.ts b/src/routes/files.ts index b6b2e32758..8134e9fe75 100644 --- a/src/routes/files.ts +++ b/src/routes/files.ts @@ -55,20 +55,24 @@ const multipartHandler = (operation: 'create' | 'update') => try { if (operation === 'create') { - const file = await FilesService.createFile(payload, fileStream); - - ActivityService.createActivity({ - action: ActivityService.Action.UPLOAD, - collection: 'directus_files', - item: file.id, + const file = await FilesService.createFile(payload, fileStream, { ip: req.ip, - user_agent: req.get('user-agent'), - action_by: req.user, + userAgent: req.get('user-agent'), + user: req.user, }); savedFiles.push(file); } else { - const file = await FilesService.updateFile(req.params.pk, payload, fileStream); + const file = await FilesService.updateFile( + req.params.pk, + payload, + { + ip: req.ip, + userAgent: req.get('user-agent'), + user: req.user, + }, + fileStream + ); ActivityService.createActivity({ action: ActivityService.Action.UPDATE, @@ -130,15 +134,10 @@ router.patch( if (req.is('multipart/form-data')) { file = await multipartHandler('update')(req, res, next); } else { - file = await FilesService.updateFile(req.params.pk, req.body); - - ActivityService.createActivity({ - action: ActivityService.Action.UPDATE, - collection: 'directus_files', - item: file.id, + file = await FilesService.updateFile(req.params.pk, req.body, { ip: req.ip, - user_agent: req.get('user-agent'), - action_by: req.user, + userAgent: req.get('user-agent'), + user: req.user, }); } @@ -150,7 +149,11 @@ router.delete( '/:pk', useCollection('directus_files'), asyncHandler(async (req, res) => { - await FilesService.deleteFile(req.params.pk); + await FilesService.deleteFile(req.params.pk, { + ip: req.ip, + userAgent: req.get('user-agent'), + user: req.user, + }); return res.status(200).end(); }) ); diff --git a/src/services/files.ts b/src/services/files.ts index eed9e53893..bcf7035c6f 100644 --- a/src/services/files.ts +++ b/src/services/files.ts @@ -9,11 +9,13 @@ 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 { Readable } from 'stream'; export const createFile = async ( data: Record, stream: NodeJS.ReadableStream, - query?: Query + accountability: Accountability ) => { const id = uuidv4(); @@ -51,8 +53,7 @@ export const createFile = async ( } await storage.disk(data.storage).put(payload.filename_disk, stream.pipe(pipeline)); - const primaryKey = await ItemsService.createItem('directus_files', payload); - return await ItemsService.readItem('directus_files', primaryKey, query); + return await ItemsService.createItem('directus_files', payload, accountability); }; export const readFiles = async (query: Query) => { @@ -63,12 +64,11 @@ export const readFile = async (pk: string | number, query: Query) => { return await ItemsService.readItem('directus_files', pk, query); }; -// @todo Add query support export const updateFile = async ( pk: string | number, data: Record, - stream?: NodeJS.ReadableStream, - query?: Query + accountability: Accountability, + stream?: NodeJS.ReadableStream ) => { /** * @TODO @@ -87,15 +87,13 @@ export const updateFile = async ( .where({ id: pk }) .first(); - // @todo type of stream in flydrive is wrong: https://github.com/Slynova-Org/flydrive/issues/145 - await storage.disk(file.storage).put(file.filename_disk, stream as any); + await storage.disk(file.storage).put(file.filename_disk, stream as Readable); } - const primaryKey = await ItemsService.updateItem('directus_files', pk, data); - return await ItemsService.readItem('directus_files', primaryKey, query); + return await ItemsService.updateItem('directus_files', pk, data, accountability); }; -export const deleteFile = async (pk: string | number) => { +export const deleteFile = async (pk: string, accountability: Accountability) => { const file = await database .select('storage', 'filename_disk') .from('directus_files') From 24b801582fdd8720013d93e2d567bb372919afba Mon Sep 17 00:00:00 2001 From: rijkvanzanten Date: Thu, 9 Jul 2020 12:37:23 -0400 Subject: [PATCH 05/10] Add revisions to folders --- src/routes/folders.ts | 35 ++++++++++++++--------------------- src/services/folders.ts | 20 +++++++++----------- 2 files changed, 23 insertions(+), 32 deletions(-) diff --git a/src/routes/folders.ts b/src/routes/folders.ts index 42baf311e2..1652e393da 100644 --- a/src/routes/folders.ts +++ b/src/routes/folders.ts @@ -12,17 +12,13 @@ router.post( '/', useCollection('directus_folders'), asyncHandler(async (req, res) => { - const record = await FoldersService.createFolder(req.body, req.sanitizedQuery); - - ActivityService.createActivity({ - action: ActivityService.Action.CREATE, - collection: req.collection, - item: record.id, + const primaryKey = await FoldersService.createFolder(req.body, { ip: req.ip, - user_agent: req.get('user-agent'), - action_by: req.user, + userAgent: req.get('user-agent'), + user: req.user, }); + const record = await FoldersService.readFolder(primaryKey, req.sanitizedQuery); return res.json({ data: record }); }) ); @@ -53,21 +49,14 @@ router.patch( '/:pk', useCollection('directus_folders'), asyncHandler(async (req, res) => { - const record = await FoldersService.updateFolder( - req.params.pk, - req.body, - req.sanitizedQuery - ); - - ActivityService.createActivity({ - action: ActivityService.Action.UPDATE, - collection: req.collection, - item: record.id, + const primaryKey = await FoldersService.updateFolder(req.params.pk, req.body, { ip: req.ip, - user_agent: req.get('user-agent'), - action_by: req.user, + userAgent: req.get('user-agent'), + user: req.user, }); + const record = await FoldersService.readFolder(primaryKey, req.sanitizedQuery); + return res.json({ data: record }); }) ); @@ -76,7 +65,11 @@ router.delete( '/:pk', useCollection('directus_folders'), asyncHandler(async (req, res) => { - await FoldersService.deleteFolder(req.params.pk); + await FoldersService.deleteFolder(req.params.pk, { + ip: req.ip, + userAgent: req.get('user-agent'), + user: req.user, + }); ActivityService.createActivity({ action: ActivityService.Action.DELETE, diff --git a/src/services/folders.ts b/src/services/folders.ts index aa7d2417f3..a966d4e437 100644 --- a/src/services/folders.ts +++ b/src/services/folders.ts @@ -1,28 +1,26 @@ -import { Query } from '../types/query'; import * as ItemsService from './items'; +import { Accountability, Query } from '../types'; -export const createFolder = async (data: Record, query: Query) => { - const primaryKey = await ItemsService.createItem('directus_folders', data); - return await ItemsService.readItem('directus_folders', primaryKey, query); +export const createFolder = async (data: Record, accountability: Accountability) => { + return await ItemsService.createItem('directus_folders', data, accountability); }; export const readFolders = async (query: Query) => { return await ItemsService.readItems('directus_folders', query); }; -export const readFolder = async (pk: string | number, query: Query) => { +export const readFolder = async (pk: string, query: Query) => { return await ItemsService.readItem('directus_folders', pk, query); }; export const updateFolder = async ( - pk: string | number, + pk: string, data: Record, - query: Query + accountability: Accountability ) => { - const primaryKey = await ItemsService.updateItem('directus_folders', pk, data); - return await ItemsService.readItem('directus_folders', primaryKey, query); + return await ItemsService.updateItem('directus_folders', pk, data, accountability); }; -export const deleteFolder = async (pk: string | number) => { - await ItemsService.deleteItem('directus_folders', pk); +export const deleteFolder = async (pk: string, accountability: Accountability) => { + await ItemsService.deleteItem('directus_folders', pk, accountability); }; From ea7647355edb15bce63679cc7f5ab69e8d07da4c Mon Sep 17 00:00:00 2001 From: rijkvanzanten Date: Thu, 9 Jul 2020 12:51:35 -0400 Subject: [PATCH 06/10] Use revisions for permissions --- src/routes/permissions.ts | 49 +++++++++++++++---------------------- src/services/items.ts | 4 +-- src/services/permissions.ts | 30 ++++++++++++++--------- 3 files changed, 40 insertions(+), 43 deletions(-) diff --git a/src/routes/permissions.ts b/src/routes/permissions.ts index ab20962e9a..17b6f5cf0d 100644 --- a/src/routes/permissions.ts +++ b/src/routes/permissions.ts @@ -3,7 +3,6 @@ import asyncHandler from 'express-async-handler'; import sanitizeQuery from '../middleware/sanitize-query'; import validateQuery from '../middleware/validate-query'; import * as PermissionsService from '../services/permissions'; -import * as ActivityService from '../services/activity'; import useCollection from '../middleware/use-collection'; const router = express.Router(); @@ -12,17 +11,14 @@ router.post( '/', useCollection('directus_permissions'), asyncHandler(async (req, res) => { - const item = await PermissionsService.createPermission(req.body, req.sanitizedQuery); - - ActivityService.createActivity({ - action: ActivityService.Action.CREATE, - collection: req.collection, - item: item.id, + const primaryKey = await PermissionsService.createPermission(req.body, { ip: req.ip, - user_agent: req.get('user-agent'), - action_by: req.user, + userAgent: req.get('user-agent'), + user: req.user, }); + const item = await PermissionsService.readPermission(primaryKey, req.sanitizedQuery); + return res.json({ data: item }); }) ); @@ -44,7 +40,10 @@ router.get( sanitizeQuery, validateQuery, asyncHandler(async (req, res) => { - const record = await PermissionsService.readPermission(req.params.pk, req.sanitizedQuery); + const record = await PermissionsService.readPermission( + Number(req.params.pk), + req.sanitizedQuery + ); return res.json({ data: record }); }) ); @@ -53,20 +52,17 @@ router.patch( '/:pk', useCollection('directus_permissions'), asyncHandler(async (req, res) => { - const item = await PermissionsService.updatePermission( - req.params.pk, + const primaryKey = await PermissionsService.updatePermission( + Number(req.params.pk), req.body, - req.sanitizedQuery + { + ip: req.ip, + userAgent: req.get('user-agent'), + user: req.user, + } ); - ActivityService.createActivity({ - action: ActivityService.Action.UPDATE, - collection: req.collection, - item: item.id, - ip: req.ip, - user_agent: req.get('user-agent'), - action_by: req.user, - }); + const item = await PermissionsService.readPermission(primaryKey, req.sanitizedQuery); return res.json({ data: item }); }) @@ -76,15 +72,10 @@ router.delete( '/:pk', useCollection('directus_permissions'), asyncHandler(async (req, res) => { - await PermissionsService.deletePermission(req.params.pk); - - ActivityService.createActivity({ - action: ActivityService.Action.DELETE, - collection: req.collection, - item: req.params.pk, + await PermissionsService.deletePermission(Number(req.params.pk), { ip: req.ip, - user_agent: req.get('user-agent'), - action_by: req.user, + userAgent: req.get('user-agent'), + user: req.user, }); return res.status(200).end(); diff --git a/src/services/items.ts b/src/services/items.ts index 420452be48..c87436e121 100644 --- a/src/services/items.ts +++ b/src/services/items.ts @@ -42,7 +42,7 @@ export const createItem = async ( collection: string, data: Record, accountability?: Accountability -) => { +): Promise => { let payload = await PayloadService.processValues('create', collection, data); payload = await PayloadService.processM2O(collection, payload); @@ -118,7 +118,7 @@ export const updateItem = async ( pk: number | string, data: Record, accountability?: Accountability -) => { +): Promise => { let payload = await PayloadService.processValues('update', collection, data); payload = await PayloadService.processM2O(collection, payload); diff --git a/src/services/permissions.ts b/src/services/permissions.ts index 737b75db08..877c74275b 100644 --- a/src/services/permissions.ts +++ b/src/services/permissions.ts @@ -1,28 +1,34 @@ -import { Query } from '../types/query'; +import { Accountability, Query } from '../types'; import * as ItemsService from './items'; -export const createPermission = async (data: Record, query: Query) => { - const primaryKey = await ItemsService.createItem('directus_permissions', data); - return await ItemsService.readItem('directus_permissions', primaryKey, query); +export const createPermission = async ( + data: Record, + accountability: Accountability +): Promise => { + return (await ItemsService.createItem('directus_permissions', data, accountability)) as number; }; export const readPermissions = async (query: Query) => { return await ItemsService.readItems('directus_permissions', query); }; -export const readPermission = async (pk: string | number, query: Query) => { +export const readPermission = async (pk: number, query: Query) => { return await ItemsService.readItem('directus_permissions', pk, query); }; export const updatePermission = async ( - pk: string | number, + pk: number, data: Record, - query: Query -) => { - const primaryKey = await ItemsService.updateItem('directus_permissions', pk, data); - return await ItemsService.readItem('directus_permissions', primaryKey, query); + accountability: Accountability +): Promise => { + return (await ItemsService.updateItem( + 'directus_permissions', + pk, + data, + accountability + )) as number; }; -export const deletePermission = async (pk: string | number) => { - await ItemsService.deleteItem('directus_permissions', pk); +export const deletePermission = async (pk: number, accountability: Accountability) => { + await ItemsService.deleteItem('directus_permissions', pk, accountability); }; From 9f2d1316f22af4dcb76553e2eda724e6f894cf12 Mon Sep 17 00:00:00 2001 From: rijkvanzanten Date: Thu, 9 Jul 2020 13:01:45 -0400 Subject: [PATCH 07/10] Add revisions to relations --- src/routes/relations.ts | 45 +++++++++++++-------------------------- src/services/activity.ts | 2 +- src/services/relations.ts | 16 ++++++-------- 3 files changed, 23 insertions(+), 40 deletions(-) diff --git a/src/routes/relations.ts b/src/routes/relations.ts index cddd364dfe..558afd7f4c 100644 --- a/src/routes/relations.ts +++ b/src/routes/relations.ts @@ -11,18 +11,15 @@ const router = express.Router(); router.post( '/', useCollection('directus_relations'), + sanitizeQuery, + validateQuery, asyncHandler(async (req, res) => { - const item = await RelationsService.createRelation(req.body, req.sanitizedQuery); - - ActivityService.createActivity({ - action: ActivityService.Action.CREATE, - collection: req.collection, - item: item.id, + const primaryKey = await RelationsService.createRelation(req.body, { ip: req.ip, - user_agent: req.get('user-agent'), - action_by: req.user, + userAgent: req.get('user-agent'), + user: req.user, }); - + const item = await RelationsService.readRelation(primaryKey, req.sanitizedQuery); return res.json({ data: item }); }) ); @@ -52,22 +49,15 @@ router.get( router.patch( '/:pk', useCollection('directus_relations'), + sanitizeQuery, + validateQuery, asyncHandler(async (req, res) => { - const item = await RelationsService.updateRelation( - req.params.pk, - req.body, - req.sanitizedQuery - ); - - ActivityService.createActivity({ - action: ActivityService.Action.UPDATE, - collection: req.collection, - item: item.id, + const primaryKey = await RelationsService.updateRelation(req.params.pk, req.body, { ip: req.ip, - user_agent: req.get('user-agent'), - action_by: req.user, + userAgent: req.get('user-agent'), + user: req.user, }); - + const item = await RelationsService.readRelation(primaryKey, req.sanitizedQuery); return res.json({ data: item }); }) ); @@ -76,15 +66,10 @@ router.delete( '/:pk', useCollection('directus_relations'), asyncHandler(async (req, res) => { - await RelationsService.deleteRelation(req.params.pk); - - ActivityService.createActivity({ - action: ActivityService.Action.DELETE, - collection: req.collection, - item: req.params.pk, + await RelationsService.deleteRelation(Number(req.params.pk), { ip: req.ip, - user_agent: req.get('user-agent'), - action_by: req.user, + userAgent: req.get('user-agent'), + user: req.user, }); return res.status(200).end(); diff --git a/src/services/activity.ts b/src/services/activity.ts index 552f006b8f..ca80fa23d8 100644 --- a/src/services/activity.ts +++ b/src/services/activity.ts @@ -11,7 +11,7 @@ export enum Action { AUTHENTICATE = 'authenticate', } -export const createActivity = async (data: Record, query?: Query) => { +export const createActivity = async (data: Record) => { return await ItemsService.createItem('directus_activity', data); }; diff --git a/src/services/relations.ts b/src/services/relations.ts index 9997fab2bf..9c359f2d24 100644 --- a/src/services/relations.ts +++ b/src/services/relations.ts @@ -1,9 +1,8 @@ -import { Query } from '../types/query'; +import { Accountability, Query } from '../types'; import * as ItemsService from './items'; -export const createRelation = async (data: Record, query: Query) => { - const primaryKey = await ItemsService.createItem('directus_relations', data); - return ItemsService.readItem('directus_relations', primaryKey, query); +export const createRelation = async (data: Record, accountability: Accountability) => { + return await ItemsService.createItem('directus_relations', data, accountability); }; export const readRelations = async (query: Query) => { @@ -17,12 +16,11 @@ export const readRelation = async (pk: string | number, query: Query) => { export const updateRelation = async ( pk: string | number, data: Record, - query: Query + accountability: Accountability ) => { - const primaryKey = await ItemsService.updateItem('directus_relations', pk, data); - return ItemsService.readItem('directus_relations', primaryKey, query); + return await ItemsService.updateItem('directus_relations', pk, data, accountability); }; -export const deleteRelation = async (pk: string | number) => { - await ItemsService.deleteItem('directus_relations', pk); +export const deleteRelation = async (pk: number, accountability: Accountability) => { + await ItemsService.deleteItem('directus_relations', pk, accountability); }; From b8849bcafb9fd2a59ebd33dfef188a523223fbd9 Mon Sep 17 00:00:00 2001 From: rijkvanzanten Date: Thu, 9 Jul 2020 13:31:45 -0400 Subject: [PATCH 08/10] Add revisions for settings, fix types in files/folders --- src/routes/files.ts | 18 ++++++------------ src/routes/relations.ts | 1 - src/routes/roles.ts | 41 +++++++++++++++------------------------- src/routes/settings.ts | 17 +++++++++++++---- src/services/folders.ts | 11 +++++++---- src/services/roles.ts | 20 +++++++++++--------- src/services/settings.ts | 8 ++++---- 7 files changed, 56 insertions(+), 60 deletions(-) diff --git a/src/routes/files.ts b/src/routes/files.ts index 8134e9fe75..52a016a134 100644 --- a/src/routes/files.ts +++ b/src/routes/files.ts @@ -55,15 +55,16 @@ const multipartHandler = (operation: 'create' | 'update') => try { if (operation === 'create') { - const file = await FilesService.createFile(payload, fileStream, { + const pk = await FilesService.createFile(payload, fileStream, { ip: req.ip, userAgent: req.get('user-agent'), user: req.user, }); + const file = await FilesService.readFile(pk, req.sanitizedQuery); savedFiles.push(file); } else { - const file = await FilesService.updateFile( + const pk = await FilesService.updateFile( req.params.pk, payload, { @@ -73,15 +74,7 @@ const multipartHandler = (operation: 'create' | 'update') => }, fileStream ); - - ActivityService.createActivity({ - action: ActivityService.Action.UPDATE, - collection: 'directus_files', - item: file.id, - ip: req.ip, - user_agent: req.get('user-agent'), - action_by: req.user, - }); + const file = await FilesService.readFile(pk, req.sanitizedQuery); savedFiles.push(file); } @@ -134,11 +127,12 @@ router.patch( if (req.is('multipart/form-data')) { file = await multipartHandler('update')(req, res, next); } else { - file = await FilesService.updateFile(req.params.pk, req.body, { + const pk = await FilesService.updateFile(req.params.pk, req.body, { ip: req.ip, userAgent: req.get('user-agent'), user: req.user, }); + file = await FilesService.readFile(pk, req.sanitizedQuery); } return res.status(200).json({ data: file }); diff --git a/src/routes/relations.ts b/src/routes/relations.ts index 558afd7f4c..2460812358 100644 --- a/src/routes/relations.ts +++ b/src/routes/relations.ts @@ -4,7 +4,6 @@ import sanitizeQuery from '../middleware/sanitize-query'; import validateQuery from '../middleware/validate-query'; import * as RelationsService from '../services/relations'; import useCollection from '../middleware/use-collection'; -import * as ActivityService from '../services/activity'; const router = express.Router(); diff --git a/src/routes/roles.ts b/src/routes/roles.ts index c43267b6d1..4f3b0c006c 100644 --- a/src/routes/roles.ts +++ b/src/routes/roles.ts @@ -11,18 +11,15 @@ const router = express.Router(); router.post( '/', useCollection('directus_roles'), + sanitizeQuery, + validateQuery, asyncHandler(async (req, res) => { - const item = await RolesService.createRole(req.body, req.sanitizedQuery); - - ActivityService.createActivity({ - action: ActivityService.Action.CREATE, - collection: req.collection, - item: item.id, + const primaryKey = await RolesService.createRole(req.body, { ip: req.ip, - user_agent: req.get('user-agent'), - action_by: req.user, + userAgent: req.get('user-agent'), + user: req.user, }); - + const item = await RolesService.readRole(primaryKey, req.sanitizedQuery); return res.json({ data: item }); }) ); @@ -52,18 +49,15 @@ router.get( router.patch( '/:pk', useCollection('directus_roles'), + sanitizeQuery, + validateQuery, asyncHandler(async (req, res) => { - const item = await RolesService.updateRole(req.params.pk, req.body, req.sanitizedQuery); - - ActivityService.createActivity({ - action: ActivityService.Action.UPDATE, - collection: req.collection, - item: item.id, + const primaryKey = await RolesService.updateRole(req.params.pk, req.body, { ip: req.ip, - user_agent: req.get('user-agent'), - action_by: req.user, + userAgent: req.get('user-agent'), + user: req.user, }); - + const item = await RolesService.readRole(primaryKey, req.sanitizedQuery); return res.json({ data: item }); }) ); @@ -72,15 +66,10 @@ router.delete( '/:pk', useCollection('directus_roles'), asyncHandler(async (req, res) => { - await RolesService.deleteRole(req.params.pk); - - ActivityService.createActivity({ - action: ActivityService.Action.DELETE, - collection: req.collection, - item: req.params.pk, + await RolesService.deleteRole(req.params.pk, { ip: req.ip, - user_agent: req.get('user-agent'), - action_by: req.user, + userAgent: req.get('user-agent'), + user: req.user, }); return res.status(200).end(); diff --git a/src/routes/settings.ts b/src/routes/settings.ts index a347df4507..7770dfa4c1 100644 --- a/src/routes/settings.ts +++ b/src/routes/settings.ts @@ -13,7 +13,7 @@ router.get( sanitizeQuery, validateQuery, asyncHandler(async (req, res) => { - const records = await SettingsService.readSettings(1, req.sanitizedQuery); + const records = await SettingsService.readSettings(req.sanitizedQuery); return res.json({ data: records }); }) ); @@ -21,13 +21,22 @@ router.get( router.patch( '/', useCollection('directus_settings'), + sanitizeQuery, + validateQuery, asyncHandler(async (req, res) => { - const records = await SettingsService.updateSettings( + const primaryKey = await SettingsService.updateSettings( req.params.pk /** @TODO Singleton */, req.body, - req.sanitizedQuery + { + ip: req.ip, + userAgent: req.get('user-agent'), + user: req.user, + } ); - return res.json({ data: records }); + + const record = await SettingsService.readSettings(req.sanitizedQuery); + + return res.json({ data: record }); }) ); diff --git a/src/services/folders.ts b/src/services/folders.ts index a966d4e437..fd471b7682 100644 --- a/src/services/folders.ts +++ b/src/services/folders.ts @@ -1,8 +1,11 @@ import * as ItemsService from './items'; import { Accountability, Query } from '../types'; -export const createFolder = async (data: Record, accountability: Accountability) => { - return await ItemsService.createItem('directus_folders', data, accountability); +export const createFolder = async ( + data: Record, + accountability: Accountability +): Promise => { + return (await ItemsService.createItem('directus_folders', data, accountability)) as string; }; export const readFolders = async (query: Query) => { @@ -17,8 +20,8 @@ export const updateFolder = async ( pk: string, data: Record, accountability: Accountability -) => { - return await ItemsService.updateItem('directus_folders', pk, data, accountability); +): Promise => { + return (await ItemsService.updateItem('directus_folders', pk, data, accountability)) as string; }; export const deleteFolder = async (pk: string, accountability: Accountability) => { diff --git a/src/services/roles.ts b/src/services/roles.ts index 63e9a77871..30b18f5f86 100644 --- a/src/services/roles.ts +++ b/src/services/roles.ts @@ -1,9 +1,8 @@ -import { Query } from '../types/query'; +import { Accountability, Query } from '../types'; import * as ItemsService from './items'; -export const createRole = async (data: Record, query: Query) => { - const primaryKey = await ItemsService.createItem('directus_roles', data); - return await ItemsService.readItem('directus_roles', primaryKey, query); +export const createRole = async (data: Record, accountability: Accountability) => { + return await ItemsService.createItem('directus_roles', data, accountability); }; export const readRoles = async (query: Query) => { @@ -14,11 +13,14 @@ export const readRole = async (pk: string | number, query: Query) => { return await ItemsService.readItem('directus_roles', pk, query); }; -export const updateRole = async (pk: string | number, data: Record, query: Query) => { - const primaryKey = await ItemsService.updateItem('directus_roles', pk, data); - return await ItemsService.readItem('directus_roles', primaryKey, query); +export const updateRole = async ( + pk: string | number, + data: Record, + accountability: Accountability +) => { + return await ItemsService.updateItem('directus_roles', pk, data, accountability); }; -export const deleteRole = async (pk: string | number) => { - await ItemsService.deleteItem('directus_roles', pk); +export const deleteRole = async (pk: string | number, accountability: Accountability) => { + await ItemsService.deleteItem('directus_roles', pk, accountability); }; diff --git a/src/services/settings.ts b/src/services/settings.ts index 82c0b4befa..32837bce8a 100644 --- a/src/services/settings.ts +++ b/src/services/settings.ts @@ -1,7 +1,8 @@ import { Query } from '../types/query'; import * as ItemsService from './items'; +import { Accountability } from '../types'; -export const readSettings = async (pk: string | number, query: Query) => { +export const readSettings = async (query: Query) => { const settings = await ItemsService.readItems('directus_settings', { ...query, limit: 1, @@ -13,9 +14,8 @@ export const readSettings = async (pk: string | number, query: Query) => { export const updateSettings = async ( pk: string | number, data: Record, - query: Query + accountability: Accountability ) => { /** @NOTE I guess this can technically update _all_ items, as we expect there to only be one */ - const primaryKey = await ItemsService.updateItem('directus_settings', pk, data); - return await ItemsService.readItem('directus_settings', primaryKey, query); + return await ItemsService.updateItem('directus_settings', pk, data, accountability); }; From 306a7be75d395b392ae959ed6bda6d10b2ef8bc9 Mon Sep 17 00:00:00 2001 From: rijkvanzanten Date: Thu, 9 Jul 2020 13:38:08 -0400 Subject: [PATCH 09/10] Users too --- src/routes/users.ts | 61 +++++++++++++++++-------------------------- src/services/users.ts | 26 +++++++++--------- 2 files changed, 38 insertions(+), 49 deletions(-) diff --git a/src/routes/users.ts b/src/routes/users.ts index bc1b8bc8a0..21248f0859 100644 --- a/src/routes/users.ts +++ b/src/routes/users.ts @@ -6,25 +6,21 @@ import * as UsersService from '../services/users'; import Joi from '@hapi/joi'; import { InvalidPayloadException, InvalidCredentialsException } from '../exceptions'; import useCollection from '../middleware/use-collection'; -import * as ActivityService from '../services/activity'; const router = express.Router(); router.post( '/', useCollection('directus_users'), + sanitizeQuery, + validateQuery, asyncHandler(async (req, res) => { - const item = await UsersService.createUser(req.body, req.sanitizedQuery); - - ActivityService.createActivity({ - action: ActivityService.Action.CREATE, - collection: req.collection, - item: item.id, + const primaryKey = await UsersService.createUser(req.body, { ip: req.ip, - user_agent: req.get('user-agent'), - action_by: req.user, + userAgent: req.get('user-agent'), + user: req.user, }); - + const item = await UsersService.readUser(primaryKey, req.sanitizedQuery); return res.json({ data: item }); }) ); @@ -36,7 +32,6 @@ router.get( validateQuery, asyncHandler(async (req, res) => { const item = await UsersService.readUsers(req.sanitizedQuery); - return res.json({ data: item }); }) ); @@ -77,17 +72,13 @@ router.patch( throw new InvalidCredentialsException(); } - const item = await UsersService.updateUser(req.user, req.body, req.sanitizedQuery); - - ActivityService.createActivity({ - action: ActivityService.Action.UPDATE, - collection: req.collection, - item: item.id, + const primaryKey = await UsersService.updateUser(req.user, req.body, { ip: req.ip, - user_agent: req.get('user-agent'), - action_by: req.user, + userAgent: req.get('user-agent'), + user: req.user, }); + const item = await UsersService.readUser(primaryKey, req.sanitizedQuery); return res.json({ data: item }); }) ); @@ -95,18 +86,15 @@ router.patch( router.patch( '/:pk', useCollection('directus_users'), + sanitizeQuery, + validateQuery, asyncHandler(async (req, res) => { - const item = await UsersService.updateUser(req.params.pk, req.body, req.sanitizedQuery); - - ActivityService.createActivity({ - action: ActivityService.Action.UPDATE, - collection: req.collection, - item: item.id, + const primaryKey = await UsersService.updateUser(req.params.pk, req.body, { ip: req.ip, - user_agent: req.get('user-agent'), - action_by: req.user, + userAgent: req.get('user-agent'), + user: req.user, }); - + const item = await UsersService.readUser(primaryKey, req.sanitizedQuery); return res.json({ data: item }); }) ); @@ -115,15 +103,10 @@ router.delete( '/:pk', useCollection('directus_users'), asyncHandler(async (req, res) => { - await UsersService.deleteUser(req.params.pk); - - ActivityService.createActivity({ - action: ActivityService.Action.DELETE, - collection: req.collection, - item: req.params.pk, + await UsersService.deleteUser(req.params.pk, { ip: req.ip, - user_agent: req.get('user-agent'), - action_by: req.user, + userAgent: req.get('user-agent'), + user: req.user, }); return res.status(200).end(); @@ -141,7 +124,11 @@ router.post( asyncHandler(async (req, res) => { const { error } = inviteSchema.validate(req.body); if (error) throw new InvalidPayloadException(error.message); - await UsersService.inviteUser(req.body.email, req.body.role); + await UsersService.inviteUser(req.body.email, req.body.role, { + ip: req.ip, + userAgent: req.get('user-agent'), + user: req.user, + }); res.end(); }) ); diff --git a/src/services/users.ts b/src/services/users.ts index 2c01b39711..50afd35af9 100644 --- a/src/services/users.ts +++ b/src/services/users.ts @@ -1,14 +1,13 @@ -import { Query } from '../types/query'; import * as ItemsService from './items'; import jwt from 'jsonwebtoken'; import { sendInviteMail } from '../mail'; import database from '../database'; import argon2 from 'argon2'; import { InvalidPayloadException } from '../exceptions'; +import { Accountability, Query } from '../types'; -export const createUser = async (data: Record, query?: Query) => { - const primaryKey = await ItemsService.createItem('directus_users', data); - return await ItemsService.readItem('directus_users', primaryKey, query); +export const createUser = async (data: Record, accountability: Accountability) => { + return await ItemsService.createItem('directus_users', data, accountability); }; export const readUsers = async (query?: Query) => { @@ -19,23 +18,26 @@ export const readUser = async (pk: string | number, query?: Query) => { return await ItemsService.readItem('directus_users', pk, query); }; -export const updateUser = async (pk: string | number, data: Record, query?: Query) => { +export const updateUser = async ( + pk: string | number, + data: Record, + accountability: Accountability +) => { /** * @todo * Remove "other" refresh token sessions when changing password to enforce "logout everywhere" on password change * * Maybe make this an option? */ - const primaryKey = await ItemsService.updateItem('directus_users', pk, data); - return await ItemsService.readItem('directus_users', primaryKey, query); + return await ItemsService.updateItem('directus_users', pk, data, accountability); }; -export const deleteUser = async (pk: string | number) => { - await ItemsService.deleteItem('directus_users', pk); +export const deleteUser = async (pk: string | number, accountability: Accountability) => { + await ItemsService.deleteItem('directus_users', pk, accountability); }; -export const inviteUser = async (email: string, role: string) => { - await createUser({ email, role, status: 'invited' }); +export const inviteUser = async (email: string, role: string, accountability: Accountability) => { + await createUser({ email, role, status: 'invited' }, accountability); const payload = { email }; const token = jwt.sign(payload, process.env.SECRET, { expiresIn: '7d' }); @@ -45,7 +47,7 @@ export const inviteUser = async (email: string, role: string) => { }; export const acceptInvite = async (token: string, password: string) => { - const { email } = jwt.verify(token, process.env.SECRET) as Record; + const { email } = jwt.verify(token, process.env.SECRET) as { email: string }; const user = await database .select('id', 'status') .from('directus_users') From 1dceff8ccbaa5a6d0aa0ab54d9f79a04ce1aaedb Mon Sep 17 00:00:00 2001 From: rijkvanzanten Date: Thu, 9 Jul 2020 13:40:19 -0400 Subject: [PATCH 10/10] Finish revisions tracking --- src/routes/webhooks.ts | 41 ++++++++++++---------------------------- src/services/webhooks.ts | 16 +++++++--------- 2 files changed, 19 insertions(+), 38 deletions(-) diff --git a/src/routes/webhooks.ts b/src/routes/webhooks.ts index 6689630378..71150e66c3 100644 --- a/src/routes/webhooks.ts +++ b/src/routes/webhooks.ts @@ -12,17 +12,14 @@ router.post( '/', useCollection('directus_webhooks'), asyncHandler(async (req, res) => { - const item = await WebhooksService.createWebhook(req.body, req.sanitizedQuery); - - ActivityService.createActivity({ - action: ActivityService.Action.CREATE, - collection: req.collection, - item: item.id, + const primaryKey = await WebhooksService.createWebhook(req.body, { ip: req.ip, - user_agent: req.get('user-agent'), - action_by: req.user, + userAgent: req.get('user-agent'), + user: req.user, }); + const item = await WebhooksService.readWebhook(primaryKey, req.sanitizedQuery); + return res.json({ data: item }); }) ); @@ -53,21 +50,12 @@ router.patch( '/:pk', useCollection('directus_webhooks'), asyncHandler(async (req, res) => { - const item = await WebhooksService.updateWebhook( - req.params.pk, - req.body, - req.sanitizedQuery - ); - - ActivityService.createActivity({ - action: ActivityService.Action.UPDATE, - collection: req.collection, - item: item.id, + const primaryKey = await WebhooksService.updateWebhook(req.params.pk, req.body, { ip: req.ip, - user_agent: req.get('user-agent'), - action_by: req.user, + userAgent: req.get('user-agent'), + user: req.user, }); - + const item = await WebhooksService.readWebhook(primaryKey, req.sanitizedQuery); return res.json({ data: item }); }) ); @@ -76,15 +64,10 @@ router.delete( '/:pk', useCollection('directus_webhooks'), asyncHandler(async (req, res) => { - await WebhooksService.deleteWebhook(req.params.pk); - - ActivityService.createActivity({ - action: ActivityService.Action.DELETE, - collection: req.collection, - item: req.params.pk, + await WebhooksService.deleteWebhook(req.params.pk, { ip: req.ip, - user_agent: req.get('user-agent'), - action_by: req.user, + userAgent: req.get('user-agent'), + user: req.user, }); return res.status(200).end(); diff --git a/src/services/webhooks.ts b/src/services/webhooks.ts index 3b7abb9a6a..5de126e65f 100644 --- a/src/services/webhooks.ts +++ b/src/services/webhooks.ts @@ -1,9 +1,8 @@ -import { Query } from '../types/query'; +import { Accountability, Query } from '../types'; import * as ItemsService from './items'; -export const createWebhook = async (data: Record, query: Query) => { - const primaryKey = await ItemsService.createItem('directus_webhooks', data); - return await ItemsService.readItem('directus_webhooks', primaryKey, query); +export const createWebhook = async (data: Record, accountability: Accountability) => { + return await ItemsService.createItem('directus_webhooks', data, accountability); }; export const readWebhooks = async (query: Query) => { @@ -17,12 +16,11 @@ export const readWebhook = async (pk: string | number, query: Query) => { export const updateWebhook = async ( pk: string | number, data: Record, - query: Query + accountability: Accountability ) => { - const primaryKey = await ItemsService.updateItem('directus_webhooks', pk, data); - return await ItemsService.readItem('directus_webhooks', primaryKey, query); + return await ItemsService.updateItem('directus_webhooks', pk, data, accountability); }; -export const deleteWebhook = async (pk: string | number) => { - await ItemsService.deleteItem('directus_webhooks', pk); +export const deleteWebhook = async (pk: string | number, accountability: Accountability) => { + await ItemsService.deleteItem('directus_webhooks', pk, accountability); };