mirror of
https://github.com/n8n-io/n8n.git
synced 2026-09-24 23:22:38 +08:00
feat(API): Deprecate workflow activate/deactivate public API endpoints (#34771)
This commit is contained in:
+4
-1
@@ -5,7 +5,10 @@ post:
|
||||
tags:
|
||||
- Workflow
|
||||
summary: Publish a workflow
|
||||
description: Publish a workflow. In n8n v1, this action was termed activating a workflow.
|
||||
description: >-
|
||||
Deprecated: use POST /workflows/{id}/publish instead. Publish a workflow. In n8n v1, this
|
||||
action was termed activating a workflow.
|
||||
deprecated: true
|
||||
parameters:
|
||||
- $ref: '../schemas/parameters/workflowId.yml'
|
||||
requestBody:
|
||||
|
||||
+2
-1
@@ -5,7 +5,8 @@ post:
|
||||
tags:
|
||||
- Workflow
|
||||
summary: Deactivate a workflow
|
||||
description: Deactivate a workflow.
|
||||
description: 'Deprecated: use POST /workflows/{id}/unpublish instead. Deactivate a workflow.'
|
||||
deprecated: true
|
||||
parameters:
|
||||
- $ref: '../schemas/parameters/workflowId.yml'
|
||||
responses:
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
publicApiScope,
|
||||
projectScope,
|
||||
validCursor,
|
||||
deprecated,
|
||||
} from '../../shared/middlewares/global.middleware';
|
||||
import { encodeNextCursor } from '../../shared/services/pagination.service';
|
||||
|
||||
@@ -386,9 +387,11 @@ const workflowHandlers: WorkflowHandlers = {
|
||||
],
|
||||
publishWorkflow,
|
||||
unpublishWorkflow,
|
||||
// `activate`/`deactivate` are legacy aliases; they share the exact same code path.
|
||||
activateWorkflow: publishWorkflow,
|
||||
deactivateWorkflow: unpublishWorkflow,
|
||||
activateWorkflow: [deprecated({ since: new Date('2026-07-23T00:00:00Z') }), ...publishWorkflow],
|
||||
deactivateWorkflow: [
|
||||
deprecated({ since: new Date('2026-07-23T00:00:00Z') }),
|
||||
...unpublishWorkflow,
|
||||
],
|
||||
getWorkflowTags: [
|
||||
publicApiScope('workflowTags:list'),
|
||||
projectScope('workflow:read', 'workflow'),
|
||||
|
||||
@@ -88,6 +88,28 @@ export const validCursor = (
|
||||
return next();
|
||||
};
|
||||
|
||||
export type DeprecationInfo = {
|
||||
/** When the endpoint became deprecated. Emitted as an RFC 9745 `Deprecation` header. */
|
||||
since: Date;
|
||||
};
|
||||
|
||||
/**
|
||||
* Signals that an endpoint is deprecated via the RFC 9745 `Deprecation` response header. Callers
|
||||
* pass a semantic `Date`; this middleware owns the on-the-wire formatting so the wire syntax
|
||||
* (an RFC 9651 structured-field Date, `@<unix-seconds>`) never leaks to call sites.
|
||||
*
|
||||
* `since` is a fixed value owned by the caller — never derive it from `Date.now()`, so the header
|
||||
* stays deterministic across requests.
|
||||
*/
|
||||
export const deprecated = ({ since }: DeprecationInfo) => {
|
||||
const deprecation = `@${Math.floor(since.getTime() / 1000)}`;
|
||||
|
||||
return (_req: Request, res: express.Response, next: express.NextFunction): void => {
|
||||
res.setHeader('Deprecation', deprecation);
|
||||
next();
|
||||
};
|
||||
};
|
||||
|
||||
export type ScopeTaggedMiddleware = Middleware & {
|
||||
__apiKeyScope: ApiKeyScope;
|
||||
};
|
||||
|
||||
@@ -1373,6 +1373,48 @@ describe.each(['deactivate', 'unpublish'])('POST /workflows/:id/%s', (action) =>
|
||||
});
|
||||
});
|
||||
|
||||
describe('Deprecation header on legacy activate/deactivate aliases', () => {
|
||||
// RFC 9745 structured-field Date: "@" followed by unix seconds. Assert the contract, not a
|
||||
// specific date, so the test doesn't couple to the (release-owned) deprecation date.
|
||||
const rfc9745Date = /^@\d+$/;
|
||||
|
||||
test('should return a Deprecation header on activate', async () => {
|
||||
const workflow = await createWorkflowWithTriggerAndHistory({}, member);
|
||||
|
||||
const response = await authMemberAgent.post(`/workflows/${workflow.id}/activate`);
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.headers.deprecation).toMatch(rfc9745Date);
|
||||
});
|
||||
|
||||
test('should return a Deprecation header on deactivate', async () => {
|
||||
const workflow = await createActiveWorkflow({}, member);
|
||||
|
||||
const response = await authMemberAgent.post(`/workflows/${workflow.id}/deactivate`);
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.headers.deprecation).toMatch(rfc9745Date);
|
||||
});
|
||||
|
||||
test('should not return a Deprecation header on publish', async () => {
|
||||
const workflow = await createWorkflowWithTriggerAndHistory({}, member);
|
||||
|
||||
const response = await authMemberAgent.post(`/workflows/${workflow.id}/publish`);
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.headers.deprecation).toBeUndefined();
|
||||
});
|
||||
|
||||
test('should not return a Deprecation header on unpublish', async () => {
|
||||
const workflow = await createActiveWorkflow({}, member);
|
||||
|
||||
const response = await authMemberAgent.post(`/workflows/${workflow.id}/unpublish`);
|
||||
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.headers.deprecation).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('POST /workflows/:id/archive', () => {
|
||||
test('should fail due to missing API Key', testWithAPIKey('post', '/workflows/2/archive', null));
|
||||
|
||||
|
||||
Reference in New Issue
Block a user