@shipfox/api-triggers 10.1.0 → 11.0.0
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/.turbo/turbo-build.log +4 -4
- package/CHANGELOG.md +22 -0
- package/dist/presentation/routes/fire-manual.d.ts.map +1 -1
- package/dist/presentation/routes/fire-manual.js +11 -3
- package/dist/presentation/routes/fire-manual.js.map +1 -1
- package/dist/presentation/routes/get-trigger-event.d.ts.map +1 -1
- package/dist/presentation/routes/get-trigger-event.js +11 -4
- package/dist/presentation/routes/get-trigger-event.js.map +1 -1
- package/dist/presentation/routes/list-trigger-event-facets.d.ts.map +1 -1
- package/dist/presentation/routes/list-trigger-event-facets.js +6 -8
- package/dist/presentation/routes/list-trigger-event-facets.js.map +1 -1
- package/dist/presentation/routes/list-trigger-events.d.ts.map +1 -1
- package/dist/presentation/routes/list-trigger-events.js +5 -7
- package/dist/presentation/routes/list-trigger-events.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +6 -6
- package/src/presentation/routes/fire-manual.test.ts +22 -2
- package/src/presentation/routes/fire-manual.ts +11 -3
- package/src/presentation/routes/get-trigger-event.test.ts +16 -2
- package/src/presentation/routes/get-trigger-event.ts +9 -4
- package/src/presentation/routes/list-trigger-event-facets.test.ts +18 -2
- package/src/presentation/routes/list-trigger-event-facets.ts +3 -6
- package/src/presentation/routes/list-trigger-events.test.ts +18 -2
- package/src/presentation/routes/list-trigger-events.ts +2 -5
- package/tsconfig.build.tsbuildinfo +1 -1
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shipfox/api-triggers",
|
|
3
3
|
"license": "MIT",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "11.0.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/ShipfoxHQ/shipfox.git",
|
|
@@ -22,19 +22,19 @@
|
|
|
22
22
|
"cron-parser": "^5.6.1",
|
|
23
23
|
"drizzle-orm": "^0.45.2",
|
|
24
24
|
"zod": "^4.4.3",
|
|
25
|
-
"@shipfox/api-auth-context": "
|
|
26
|
-
"@shipfox/api-definitions-dto": "
|
|
25
|
+
"@shipfox/api-auth-context": "11.0.0",
|
|
26
|
+
"@shipfox/api-definitions-dto": "11.0.0",
|
|
27
27
|
"@shipfox/api-integration-core-dto": "9.0.2",
|
|
28
28
|
"@shipfox/api-triggers-dto": "9.0.2",
|
|
29
29
|
"@shipfox/api-workflows-dto": "10.0.0",
|
|
30
|
-
"@shipfox/expression": "1.2.0",
|
|
31
30
|
"@shipfox/config": "1.2.4",
|
|
31
|
+
"@shipfox/expression": "1.2.1",
|
|
32
32
|
"@shipfox/inter-module": "0.2.2",
|
|
33
33
|
"@shipfox/node-drizzle": "0.3.4",
|
|
34
|
-
"@shipfox/node-fastify": "0.4.0",
|
|
35
34
|
"@shipfox/node-error-monitoring": "0.3.0",
|
|
36
|
-
"@shipfox/node-
|
|
35
|
+
"@shipfox/node-fastify": "0.4.0",
|
|
37
36
|
"@shipfox/node-module": "1.0.4",
|
|
37
|
+
"@shipfox/node-opentelemetry": "0.6.3",
|
|
38
38
|
"@shipfox/node-outbox": "0.2.6",
|
|
39
39
|
"@shipfox/node-postgres": "0.4.4",
|
|
40
40
|
"@shipfox/node-temporal": "0.4.4",
|
|
@@ -22,7 +22,11 @@ const workflows = {} as WorkflowsModuleClient;
|
|
|
22
22
|
describe('POST /:definitionId/fire-manual', () => {
|
|
23
23
|
let app: FastifyInstance;
|
|
24
24
|
let workspaceId: string;
|
|
25
|
-
let memberships: Array<{
|
|
25
|
+
let memberships: Array<{
|
|
26
|
+
workspaceId: string;
|
|
27
|
+
role: 'admin';
|
|
28
|
+
workspaceStatus: 'active' | 'suspended' | 'deleted';
|
|
29
|
+
}>;
|
|
26
30
|
|
|
27
31
|
beforeAll(async () => {
|
|
28
32
|
app = Fastify();
|
|
@@ -41,7 +45,7 @@ describe('POST /:definitionId/fire-manual', () => {
|
|
|
41
45
|
|
|
42
46
|
beforeEach(() => {
|
|
43
47
|
workspaceId = crypto.randomUUID();
|
|
44
|
-
memberships = [{workspaceId, role: 'admin'}];
|
|
48
|
+
memberships = [{workspaceId, role: 'admin', workspaceStatus: 'active'}];
|
|
45
49
|
fireManualSubscriptionMock.mockReset();
|
|
46
50
|
});
|
|
47
51
|
|
|
@@ -89,6 +93,22 @@ describe('POST /:definitionId/fire-manual', () => {
|
|
|
89
93
|
});
|
|
90
94
|
});
|
|
91
95
|
|
|
96
|
+
test('returns workspace-suspended for a suspended membership claim', async () => {
|
|
97
|
+
const definitionId = crypto.randomUUID();
|
|
98
|
+
await triggerSubscriptionFactory.create({workspaceId, workflowDefinitionId: definitionId});
|
|
99
|
+
memberships = [{workspaceId, role: 'admin', workspaceStatus: 'suspended'}];
|
|
100
|
+
|
|
101
|
+
const res = await app.inject({
|
|
102
|
+
method: 'POST',
|
|
103
|
+
url: `/${definitionId}/fire-manual`,
|
|
104
|
+
payload: {},
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
expect(res.statusCode).toBe(409);
|
|
108
|
+
expect(res.json().code).toBe('workspace-suspended');
|
|
109
|
+
expect(fireManualSubscriptionMock).not.toHaveBeenCalled();
|
|
110
|
+
});
|
|
111
|
+
|
|
92
112
|
test('maps suspended workspace to 409', async () => {
|
|
93
113
|
const definitionId = crypto.randomUUID();
|
|
94
114
|
await triggerSubscriptionFactory.create({workspaceId, workflowDefinitionId: definitionId});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {requireUserContext} from '@shipfox/api-auth-context';
|
|
1
|
+
import {requireUserContext, requireWorkspaceResourceAccess} from '@shipfox/api-auth-context';
|
|
2
2
|
import {
|
|
3
3
|
fireManualTriggerBodySchema,
|
|
4
4
|
fireManualTriggerResponseSchema,
|
|
@@ -81,10 +81,18 @@ export function createFireManualTriggerRoute(workflows: WorkflowsModuleClient) {
|
|
|
81
81
|
const userContext = requireUserContext(request);
|
|
82
82
|
|
|
83
83
|
const subscription = await getManualSubscriptionByDefinitionId(definitionId);
|
|
84
|
-
//
|
|
85
|
-
|
|
84
|
+
// Missing triggers and memberships remain 404 to avoid leaking existence; lifecycle claims
|
|
85
|
+
// propagate their stable access errors.
|
|
86
|
+
if (!subscription) {
|
|
86
87
|
throw new ManualTriggerNotFoundError(definitionId);
|
|
87
88
|
}
|
|
89
|
+
requireWorkspaceResourceAccess({
|
|
90
|
+
request,
|
|
91
|
+
workspaceId: subscription.workspaceId,
|
|
92
|
+
notFoundError: new ClientError('Manual trigger not found', 'manual-trigger-not-found', {
|
|
93
|
+
status: 404,
|
|
94
|
+
}),
|
|
95
|
+
});
|
|
88
96
|
|
|
89
97
|
const run = await fireManualSubscription({
|
|
90
98
|
workflows,
|
|
@@ -10,7 +10,11 @@ import {getTriggerEventRoute} from './get-trigger-event.js';
|
|
|
10
10
|
describe('GET /trigger-events/:id', () => {
|
|
11
11
|
let app: FastifyInstance;
|
|
12
12
|
let workspaceId: string;
|
|
13
|
-
let memberships: Array<{
|
|
13
|
+
let memberships: Array<{
|
|
14
|
+
workspaceId: string;
|
|
15
|
+
role: 'admin';
|
|
16
|
+
workspaceStatus: 'active' | 'suspended' | 'deleted';
|
|
17
|
+
}>;
|
|
14
18
|
|
|
15
19
|
beforeAll(async () => {
|
|
16
20
|
app = Fastify();
|
|
@@ -29,7 +33,7 @@ describe('GET /trigger-events/:id', () => {
|
|
|
29
33
|
|
|
30
34
|
beforeEach(() => {
|
|
31
35
|
workspaceId = crypto.randomUUID();
|
|
32
|
-
memberships = [{workspaceId, role: 'admin'}];
|
|
36
|
+
memberships = [{workspaceId, role: 'admin', workspaceStatus: 'active'}];
|
|
33
37
|
});
|
|
34
38
|
|
|
35
39
|
test('returns the event with its decisions and full payload', async () => {
|
|
@@ -160,6 +164,16 @@ describe('GET /trigger-events/:id', () => {
|
|
|
160
164
|
expect(res.json().decisions).toEqual([]);
|
|
161
165
|
});
|
|
162
166
|
|
|
167
|
+
test('returns workspace-suspended for a suspended membership claim', async () => {
|
|
168
|
+
const event = await receivedEventFactory.create({workspaceId});
|
|
169
|
+
memberships = [{workspaceId, role: 'admin', workspaceStatus: 'suspended'}];
|
|
170
|
+
|
|
171
|
+
const res = await app.inject({method: 'GET', url: `/trigger-events/${event.id}`});
|
|
172
|
+
|
|
173
|
+
expect(res.statusCode).toBe(409);
|
|
174
|
+
expect(res.json().code).toBe('workspace-suspended');
|
|
175
|
+
});
|
|
176
|
+
|
|
163
177
|
test('returns 404 for an unknown event id', async () => {
|
|
164
178
|
const res = await app.inject({method: 'GET', url: `/trigger-events/${crypto.randomUUID()}`});
|
|
165
179
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {requireWorkspaceResourceAccess} from '@shipfox/api-auth-context';
|
|
2
2
|
import {triggerEventDetailResponseSchema} from '@shipfox/api-triggers-dto';
|
|
3
3
|
import {ClientError, defineRoute} from '@shipfox/node-fastify';
|
|
4
4
|
import {z} from 'zod';
|
|
@@ -19,13 +19,18 @@ export const getTriggerEventRoute = defineRoute({
|
|
|
19
19
|
},
|
|
20
20
|
handler: async (request) => {
|
|
21
21
|
const {id} = request.params;
|
|
22
|
-
const userContext = requireUserContext(request);
|
|
23
22
|
|
|
24
23
|
const event = await getTriggerEventById(id);
|
|
25
|
-
//
|
|
26
|
-
|
|
24
|
+
// Missing events and memberships remain 404 to avoid leaking existence; lifecycle claims
|
|
25
|
+
// propagate their stable access errors.
|
|
26
|
+
if (!event) {
|
|
27
27
|
throw new ClientError('Trigger event not found', 'not-found', {status: 404});
|
|
28
28
|
}
|
|
29
|
+
requireWorkspaceResourceAccess({
|
|
30
|
+
request,
|
|
31
|
+
workspaceId: event.workspaceId,
|
|
32
|
+
notFoundError: new ClientError('Trigger event not found', 'not-found', {status: 404}),
|
|
33
|
+
});
|
|
29
34
|
|
|
30
35
|
const decisions = await listDecisionsByReceivedEventId(event.id);
|
|
31
36
|
|
|
@@ -13,7 +13,11 @@ const facets = (res: {
|
|
|
13
13
|
describe('GET /trigger-events/facets', () => {
|
|
14
14
|
let app: FastifyInstance;
|
|
15
15
|
let workspaceId: string;
|
|
16
|
-
let memberships: Array<{
|
|
16
|
+
let memberships: Array<{
|
|
17
|
+
workspaceId: string;
|
|
18
|
+
role: 'admin';
|
|
19
|
+
workspaceStatus: 'active' | 'suspended' | 'deleted';
|
|
20
|
+
}>;
|
|
17
21
|
|
|
18
22
|
beforeAll(async () => {
|
|
19
23
|
app = Fastify();
|
|
@@ -32,7 +36,7 @@ describe('GET /trigger-events/facets', () => {
|
|
|
32
36
|
|
|
33
37
|
beforeEach(() => {
|
|
34
38
|
workspaceId = crypto.randomUUID();
|
|
35
|
-
memberships = [{workspaceId, role: 'admin'}];
|
|
39
|
+
memberships = [{workspaceId, role: 'admin', workspaceStatus: 'active'}];
|
|
36
40
|
});
|
|
37
41
|
|
|
38
42
|
test('returns distinct sources and events with counts, ordered by count desc', async () => {
|
|
@@ -93,6 +97,18 @@ describe('GET /trigger-events/facets', () => {
|
|
|
93
97
|
expect(res.json()).toEqual({sources: [], events: []});
|
|
94
98
|
});
|
|
95
99
|
|
|
100
|
+
test('returns workspace-suspended for a suspended membership claim', async () => {
|
|
101
|
+
memberships = [{workspaceId, role: 'admin', workspaceStatus: 'suspended'}];
|
|
102
|
+
|
|
103
|
+
const res = await app.inject({
|
|
104
|
+
method: 'GET',
|
|
105
|
+
url: `/trigger-events/facets?workspace_id=${workspaceId}`,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
expect(res.statusCode).toBe(409);
|
|
109
|
+
expect(res.json().code).toBe('workspace-suspended');
|
|
110
|
+
});
|
|
111
|
+
|
|
96
112
|
test('denies a workspace the caller does not belong to', async () => {
|
|
97
113
|
const otherWorkspaceId = crypto.randomUUID();
|
|
98
114
|
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {requireWorkspaceAccess} from '@shipfox/api-auth-context';
|
|
2
2
|
import {
|
|
3
3
|
triggerEventFacetsQuerySchema,
|
|
4
4
|
triggerEventFacetsResponseSchema,
|
|
5
5
|
} from '@shipfox/api-triggers-dto';
|
|
6
|
-
import {
|
|
6
|
+
import {defineRoute} from '@shipfox/node-fastify';
|
|
7
7
|
import {listTriggerEventFacets} from '#db/index.js';
|
|
8
8
|
|
|
9
9
|
export const listTriggerEventFacetsRoute = defineRoute({
|
|
@@ -19,10 +19,7 @@ export const listTriggerEventFacetsRoute = defineRoute({
|
|
|
19
19
|
handler: async (request) => {
|
|
20
20
|
const {workspace_id: workspaceId} = request.query;
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
if (!userContext.canAccess(workspaceId)) {
|
|
24
|
-
throw new ClientError('Not a member of this workspace', 'forbidden', {status: 403});
|
|
25
|
-
}
|
|
22
|
+
requireWorkspaceAccess({request, workspaceId});
|
|
26
23
|
|
|
27
24
|
return await listTriggerEventFacets({workspaceId});
|
|
28
25
|
},
|
|
@@ -12,7 +12,11 @@ const eventIds = (res: {json: () => {trigger_events: Array<{id: string}>}}) =>
|
|
|
12
12
|
describe('GET /trigger-events', () => {
|
|
13
13
|
let app: FastifyInstance;
|
|
14
14
|
let workspaceId: string;
|
|
15
|
-
let memberships: Array<{
|
|
15
|
+
let memberships: Array<{
|
|
16
|
+
workspaceId: string;
|
|
17
|
+
role: 'admin';
|
|
18
|
+
workspaceStatus: 'active' | 'suspended' | 'deleted';
|
|
19
|
+
}>;
|
|
16
20
|
|
|
17
21
|
beforeAll(async () => {
|
|
18
22
|
app = Fastify();
|
|
@@ -31,7 +35,7 @@ describe('GET /trigger-events', () => {
|
|
|
31
35
|
|
|
32
36
|
beforeEach(() => {
|
|
33
37
|
workspaceId = crypto.randomUUID();
|
|
34
|
-
memberships = [{workspaceId, role: 'admin'}];
|
|
38
|
+
memberships = [{workspaceId, role: 'admin', workspaceStatus: 'active'}];
|
|
35
39
|
});
|
|
36
40
|
|
|
37
41
|
test('returns a workspace events newest first, isolated and payload-free', async () => {
|
|
@@ -318,6 +322,18 @@ describe('GET /trigger-events', () => {
|
|
|
318
322
|
expect(res.json()).toEqual({trigger_events: [], next_cursor: null});
|
|
319
323
|
});
|
|
320
324
|
|
|
325
|
+
test('returns workspace-suspended for a suspended membership claim', async () => {
|
|
326
|
+
memberships = [{workspaceId, role: 'admin', workspaceStatus: 'suspended'}];
|
|
327
|
+
|
|
328
|
+
const res = await app.inject({
|
|
329
|
+
method: 'GET',
|
|
330
|
+
url: `/trigger-events?workspace_id=${workspaceId}`,
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
expect(res.statusCode).toBe(409);
|
|
334
|
+
expect(res.json().code).toBe('workspace-suspended');
|
|
335
|
+
});
|
|
336
|
+
|
|
321
337
|
test('denies a workspace the caller does not belong to', async () => {
|
|
322
338
|
const otherWorkspaceId = crypto.randomUUID();
|
|
323
339
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {requireWorkspaceAccess} from '@shipfox/api-auth-context';
|
|
2
2
|
import {
|
|
3
3
|
triggerEventListQuerySchema,
|
|
4
4
|
triggerEventListResponseSchema,
|
|
@@ -30,10 +30,7 @@ export const listTriggerEventsRoute = defineRoute({
|
|
|
30
30
|
cursor,
|
|
31
31
|
} = request.query;
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
if (!userContext.canAccess(workspaceId)) {
|
|
35
|
-
throw new ClientError('Not a member of this workspace', 'forbidden', {status: 403});
|
|
36
|
-
}
|
|
33
|
+
requireWorkspaceAccess({request, workspaceId});
|
|
37
34
|
|
|
38
35
|
const decodedCursor = decodeTimestampIdCursor(cursor);
|
|
39
36
|
if (cursor && !decodedCursor) {
|