@directus/api 13.1.0-beta.0 → 13.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__utils__/snapshots.js +0 -9
- package/dist/app.js +0 -2
- package/dist/controllers/files.js +1 -1
- package/dist/database/helpers/index.d.ts +2 -0
- package/dist/database/helpers/index.js +2 -0
- package/dist/database/helpers/sequence/dialects/default.d.ts +3 -0
- package/dist/database/helpers/sequence/dialects/default.js +3 -0
- package/dist/database/helpers/sequence/dialects/postgres.d.ts +9 -0
- package/dist/database/helpers/sequence/dialects/postgres.js +10 -0
- package/dist/database/helpers/sequence/index.d.ts +7 -0
- package/dist/database/helpers/sequence/index.js +7 -0
- package/dist/database/helpers/sequence/types.d.ts +5 -0
- package/dist/database/helpers/sequence/types.js +6 -0
- package/dist/database/index.js +8 -0
- package/dist/database/migrations/20230721A-require-shares-fields.js +45 -16
- package/dist/database/system-data/fields/collections.yaml +0 -19
- package/dist/env.d.ts +1 -1
- package/dist/env.js +18 -15
- package/dist/middleware/respond.js +0 -20
- package/dist/services/activity.js +4 -3
- package/dist/services/assets.js +17 -1
- package/dist/services/files.js +58 -1
- package/dist/services/import-export.js +16 -0
- package/dist/services/items.js +28 -3
- package/dist/services/users.d.ts +4 -0
- package/dist/services/users.js +19 -0
- package/dist/types/collection.d.ts +0 -1
- package/dist/types/items.d.ts +1 -0
- package/dist/utils/sanitize-query.js +0 -3
- package/dist/utils/validate-query.js +0 -1
- package/dist/websocket/controllers/base.d.ts +1 -0
- package/dist/websocket/controllers/base.js +16 -0
- package/package.json +15 -15
- package/dist/controllers/branches.d.ts +0 -2
- package/dist/controllers/branches.js +0 -190
- package/dist/database/migrations/20230823A-add-content-versioning.d.ts +0 -3
- package/dist/database/migrations/20230823A-add-content-versioning.js +0 -26
- package/dist/database/system-data/fields/branches.yaml +0 -19
- package/dist/services/branches.d.ts +0 -25
- package/dist/services/branches.js +0 -205
|
@@ -1,205 +0,0 @@
|
|
|
1
|
-
import Joi from 'joi';
|
|
2
|
-
import { assign, pick } from 'lodash-es';
|
|
3
|
-
import objectHash from 'object-hash';
|
|
4
|
-
import getDatabase from '../database/index.js';
|
|
5
|
-
import emitter from '../emitter.js';
|
|
6
|
-
import { InvalidPayloadError, UnprocessableContentError } from '../errors/index.js';
|
|
7
|
-
import { ActivityService } from './activity.js';
|
|
8
|
-
import { AuthorizationService } from './authorization.js';
|
|
9
|
-
import { CollectionsService } from './collections.js';
|
|
10
|
-
import { ItemsService } from './items.js';
|
|
11
|
-
import { PayloadService } from './payload.js';
|
|
12
|
-
import { RevisionsService } from './revisions.js';
|
|
13
|
-
const branchUpdateSchema = Joi.object({
|
|
14
|
-
name: Joi.string(),
|
|
15
|
-
});
|
|
16
|
-
export class BranchesService extends ItemsService {
|
|
17
|
-
authorizationService;
|
|
18
|
-
collectionsService;
|
|
19
|
-
constructor(options) {
|
|
20
|
-
super('directus_branches', options);
|
|
21
|
-
this.authorizationService = new AuthorizationService({
|
|
22
|
-
accountability: this.accountability,
|
|
23
|
-
knex: this.knex,
|
|
24
|
-
schema: this.schema,
|
|
25
|
-
});
|
|
26
|
-
this.collectionsService = new CollectionsService({
|
|
27
|
-
accountability: this.accountability,
|
|
28
|
-
knex: this.knex,
|
|
29
|
-
schema: this.schema,
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
async validateCreateData(data) {
|
|
33
|
-
if (!data['name'])
|
|
34
|
-
throw new InvalidPayloadError({ reason: `"name" is required` });
|
|
35
|
-
// Reserves the "main" branch name for the branch query parameter
|
|
36
|
-
if (data['name'] === 'main')
|
|
37
|
-
throw new InvalidPayloadError({ reason: `"main" is a reserved branch name` });
|
|
38
|
-
if (!data['collection']) {
|
|
39
|
-
throw new InvalidPayloadError({ reason: `"collection" is required` });
|
|
40
|
-
}
|
|
41
|
-
if (!data['item'])
|
|
42
|
-
throw new InvalidPayloadError({ reason: `"item" is required` });
|
|
43
|
-
// will throw an error if the collection does not exist or the accountability does not have permission to read it
|
|
44
|
-
const existingCollection = await this.collectionsService.readOne(data['collection']);
|
|
45
|
-
if (!existingCollection.meta?.branches_enabled) {
|
|
46
|
-
throw new UnprocessableContentError({
|
|
47
|
-
reason: `Branch feature is not enabled for collection "${data['collection']}"`,
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
const existingBranches = await super.readByQuery({
|
|
51
|
-
fields: ['name', 'collection', 'item'],
|
|
52
|
-
filter: { name: { _eq: data['name'] }, collection: { _eq: data['collection'] }, item: { _eq: data['item'] } },
|
|
53
|
-
});
|
|
54
|
-
if (existingBranches.length > 0) {
|
|
55
|
-
throw new UnprocessableContentError({
|
|
56
|
-
reason: `Branch "${data['name']}" already exists for item "${data['item']}" in collection "${data['collection']}"`,
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
// will throw an error if the accountability does not have permission to read the item
|
|
60
|
-
await this.authorizationService.checkAccess('read', data['collection'], data['item']);
|
|
61
|
-
}
|
|
62
|
-
async validateUpdateData(data) {
|
|
63
|
-
// Only allow updates on "name" field
|
|
64
|
-
const { error } = branchUpdateSchema.validate(data);
|
|
65
|
-
if (error)
|
|
66
|
-
throw new InvalidPayloadError({ reason: error.message });
|
|
67
|
-
// Reserves the "main" branch name for the branch query parameter
|
|
68
|
-
if ('name' in data && data['name'] === 'main') {
|
|
69
|
-
throw new InvalidPayloadError({ reason: `"main" is a reserved branch name` });
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
async getMainBranchItem(collection, item, query) {
|
|
73
|
-
// will throw an error if the accountability does not have permission to read the item
|
|
74
|
-
await this.authorizationService.checkAccess('read', collection, item);
|
|
75
|
-
const itemsService = new ItemsService(collection, {
|
|
76
|
-
knex: this.knex,
|
|
77
|
-
accountability: this.accountability,
|
|
78
|
-
schema: this.schema,
|
|
79
|
-
});
|
|
80
|
-
return await itemsService.readOne(item, query);
|
|
81
|
-
}
|
|
82
|
-
async verifyHash(collection, item, hash) {
|
|
83
|
-
const mainBranchItem = await this.getMainBranchItem(collection, item);
|
|
84
|
-
const mainHash = objectHash(mainBranchItem);
|
|
85
|
-
return { outdated: hash !== mainHash, mainHash };
|
|
86
|
-
}
|
|
87
|
-
async getBranchCommits(key) {
|
|
88
|
-
const revisionsService = new RevisionsService({
|
|
89
|
-
knex: this.knex,
|
|
90
|
-
schema: this.schema,
|
|
91
|
-
});
|
|
92
|
-
const result = await revisionsService.readByQuery({
|
|
93
|
-
filter: { branch: { _eq: key } },
|
|
94
|
-
});
|
|
95
|
-
return result.map((revision) => revision['delta']);
|
|
96
|
-
}
|
|
97
|
-
async createOne(data, opts) {
|
|
98
|
-
await this.validateCreateData(data);
|
|
99
|
-
const mainBranchItem = await this.getMainBranchItem(data['collection'], data['item']);
|
|
100
|
-
data['hash'] = objectHash(mainBranchItem);
|
|
101
|
-
return super.createOne(data, opts);
|
|
102
|
-
}
|
|
103
|
-
async createMany(data, opts) {
|
|
104
|
-
if (!Array.isArray(data)) {
|
|
105
|
-
throw new InvalidPayloadError({ reason: 'Input should be an array of items' });
|
|
106
|
-
}
|
|
107
|
-
for (const item of data) {
|
|
108
|
-
await this.validateCreateData(item);
|
|
109
|
-
const mainBranchItem = await this.getMainBranchItem(item['collection'], item['item']);
|
|
110
|
-
item['hash'] = objectHash(mainBranchItem);
|
|
111
|
-
}
|
|
112
|
-
return super.createMany(data, opts);
|
|
113
|
-
}
|
|
114
|
-
async updateBatch(data, opts) {
|
|
115
|
-
if (!Array.isArray(data)) {
|
|
116
|
-
throw new InvalidPayloadError({ reason: 'Input should be an array of items' });
|
|
117
|
-
}
|
|
118
|
-
for (const item of data) {
|
|
119
|
-
await this.validateUpdateData(item);
|
|
120
|
-
}
|
|
121
|
-
return super.updateBatch(data, opts);
|
|
122
|
-
}
|
|
123
|
-
async updateMany(keys, data, opts) {
|
|
124
|
-
await this.validateUpdateData(data);
|
|
125
|
-
return super.updateMany(keys, data, opts);
|
|
126
|
-
}
|
|
127
|
-
async updateByQuery(query, data, opts) {
|
|
128
|
-
await this.validateUpdateData(data);
|
|
129
|
-
return super.updateByQuery(query, data, opts);
|
|
130
|
-
}
|
|
131
|
-
async commit(key, data) {
|
|
132
|
-
const branch = await super.readOne(key);
|
|
133
|
-
const payloadService = new PayloadService(this.collection, {
|
|
134
|
-
accountability: this.accountability,
|
|
135
|
-
knex: this.knex,
|
|
136
|
-
schema: this.schema,
|
|
137
|
-
});
|
|
138
|
-
const activityService = new ActivityService({
|
|
139
|
-
knex: this.knex,
|
|
140
|
-
schema: this.schema,
|
|
141
|
-
});
|
|
142
|
-
const revisionsService = new RevisionsService({
|
|
143
|
-
knex: this.knex,
|
|
144
|
-
schema: this.schema,
|
|
145
|
-
});
|
|
146
|
-
const activity = await activityService.createOne({
|
|
147
|
-
action: 'commit',
|
|
148
|
-
user: this.accountability?.user ?? null,
|
|
149
|
-
collection: branch['collection'],
|
|
150
|
-
ip: this.accountability?.ip ?? null,
|
|
151
|
-
user_agent: this.accountability?.userAgent ?? null,
|
|
152
|
-
origin: this.accountability?.origin ?? null,
|
|
153
|
-
item: branch['item'],
|
|
154
|
-
});
|
|
155
|
-
const revisionDelta = await payloadService.prepareDelta(data);
|
|
156
|
-
await revisionsService.createOne({
|
|
157
|
-
activity,
|
|
158
|
-
branch: key,
|
|
159
|
-
collection: branch['collection'],
|
|
160
|
-
item: branch['item'],
|
|
161
|
-
data: revisionDelta,
|
|
162
|
-
delta: revisionDelta,
|
|
163
|
-
});
|
|
164
|
-
return data;
|
|
165
|
-
}
|
|
166
|
-
async merge(branch, mainHash, fields) {
|
|
167
|
-
const { id, collection, item } = (await this.readOne(branch));
|
|
168
|
-
// will throw an error if the accountability does not have permission to update the item
|
|
169
|
-
await this.authorizationService.checkAccess('update', collection, item);
|
|
170
|
-
const { outdated } = await this.verifyHash(collection, item, mainHash);
|
|
171
|
-
if (outdated) {
|
|
172
|
-
throw new UnprocessableContentError({
|
|
173
|
-
reason: `Main branch has changed since this branch was last updated`,
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
const commits = await this.getBranchCommits(id);
|
|
177
|
-
const branchResult = assign({}, ...commits);
|
|
178
|
-
const payloadToUpdate = fields ? pick(branchResult, fields) : branchResult;
|
|
179
|
-
const itemsService = new ItemsService(collection, {
|
|
180
|
-
accountability: this.accountability,
|
|
181
|
-
schema: this.schema,
|
|
182
|
-
});
|
|
183
|
-
const payloadAfterHooks = await emitter.emitFilter(['items.merge', `${collection}.items.merge`], payloadToUpdate, {
|
|
184
|
-
collection,
|
|
185
|
-
item,
|
|
186
|
-
branch,
|
|
187
|
-
}, {
|
|
188
|
-
database: getDatabase(),
|
|
189
|
-
schema: this.schema,
|
|
190
|
-
accountability: this.accountability,
|
|
191
|
-
});
|
|
192
|
-
const updatedItemKey = await itemsService.updateOne(item, payloadAfterHooks);
|
|
193
|
-
emitter.emitAction(['items.merge', `${collection}.items.merge`], {
|
|
194
|
-
payload: payloadAfterHooks,
|
|
195
|
-
collection,
|
|
196
|
-
item: updatedItemKey,
|
|
197
|
-
branch,
|
|
198
|
-
}, {
|
|
199
|
-
database: getDatabase(),
|
|
200
|
-
schema: this.schema,
|
|
201
|
-
accountability: this.accountability,
|
|
202
|
-
});
|
|
203
|
-
return updatedItemKey;
|
|
204
|
-
}
|
|
205
|
-
}
|