Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
name: Deploy to Maven Central

on:
release:
types: [ "released" ]

jobs:
release:
uses: cap-java/.github/.github/workflows/release.yml@b5eb1ad6341e9fd1d681c42f3b36c4b750a7dac2 #main
with:
blackduck-project-name: com.sap.cds.feature.n8n
blackduck-included-modules: cds-feature-n8n
Comment thread
lisajulia marked this conversation as resolved.
secrets: inherit
28 changes: 13 additions & 15 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,19 @@
- The format is based on [Keep a Changelog](https://keepachangelog.com/).
- This project adheres to [Semantic Versioning](https://semver.org/).

## Version 1.0.0 - TBD
## Version 0.0.1

### Added

- Configurable HTTP method per webhook trigger: the `method` property on `@n8n.process.start` entries selects the HTTP verb used when calling the n8n webhook (`GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD`). Defaults to `POST` when omitted — existing annotations are unaffected. The method must match the HTTP Method configured on the corresponding n8n Webhook node.
- BTP destination support: configure `n8n.destination` with a BTP destination name to resolve the n8n base URL and proxy auth headers (e.g. `Authorization: Bearer …`) automatically from the BTP destination service. The destination can also carry the `X-N8N-API-KEY` header for webhook authentication — this way, when the n8n instance changes (URL or key), only the BTP destination needs to be updated; no redeployment of the application is required. Requires `cloudplatform-connectivity` on the classpath (`com.sap.cloud.sdk.cloudplatform:cloudplatform-connectivity`, optional). An explicit `n8n.api-key` always overrides any `X-N8N-API-KEY` from the destination.
- Webhook authentication via `X-N8N-API-KEY`: the plugin sends `X-N8N-API-KEY` on every outbound webhook call. Configure the matching n8n Webhook node with Header Auth (`Name: X-N8N-API-KEY`, `Value: <key>`) to protect individual workflows. The same header name also covers the n8n admin REST API, so one credential serves both purposes. Set via `n8n.api-key` / `N8N_API_KEY`.
- Association path inputs: `inputs` entries like `$self.category.name` now expand one-level to-one associations and include the resolved field in the webhook payload. Supported for DELETE and UPDATE events; CREATE events use the raw request data and do not support association paths.
- `@n8n.process.start.inputs` is now optional. When omitted, all scalar fields of the entity row are sent to n8n automatically. Associations and compositions (which expand as nested objects) are excluded. Explicit `inputs` still work as before for selective or aliased payloads.
- `ConsoleN8NWebhookService`: opt-in offline mode that logs webhook calls instead of making HTTP requests. Enable with `n8n.use-console=true` — no `base-url` required. Each call is stored in memory and accessible via `getExecutions()` for use in tests. Delivery is synchronous and skips the persistent outbox entirely — no rows are written to `cds_outbox_Messages`.
- Profile-aware startup validation: when `n8n.base-url` is missing and the `development` profile is active, the plugin warns and falls back to `http://localhost:5678`. In all other profiles it fails fast at startup with a clear error pointing to `N8N_BASE_URL`.

### Changed

- `n8n.base-url` now expects the bare n8n host URL without a `/webhook` suffix (e.g. `http://localhost:5678`). The plugin appends `/webhook` automatically, or `/webhook-test` when `n8n.use-test-webhook=true`. This aligns with the Node.js plugin behaviour. Existing configs that included `/webhook` in the URL need to remove that suffix.
Comment thread
lisajulia marked this conversation as resolved.
- Upgraded to CAP Java 5.0.0
- Replaced Spring Retry (`@Retryable`/`@Backoff`) with CAP persistent outbox for reliable n8n webhook delivery
- Updated all modules to use JDK 21, Spring Boot 4.1.0, and CDS Services 5.0.0
- Annotation-driven n8n webhook triggers via `@n8n.process.start` — no boilerplate service handler code required
- Supports CDS entity events (`CREATE`, `READ`, `UPDATE`, `DELETE`) and custom actions/functions as trigger sources
- Configurable HTTP method per trigger (`GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD`) via the `method` property — defaults to `POST`
- Reliable webhook delivery via CAP persistent outbox with configurable retry
- Webhook authentication via `X-N8N-API-KEY` header, configured through `n8n.api-key` / `N8N_API_KEY`
- BTP destination support: configure `n8n.destination` to resolve the n8n base URL and authentication headers from the BTP Destination Service — no redeployment needed when the n8n instance changes
- Optional `inputs` payload selection on `@n8n.process.start`; when omitted, all direct entity fields are sent automatically
- Association path inputs (e.g. `$self.category.name`) for including resolved to-one association fields in the webhook payload
- `ConsoleN8NWebhookService`: offline/test mode that logs webhook calls instead of making HTTP requests — enable with `n8n.use-console=true`
- Profile-aware startup validation: warns and falls back to `http://localhost:5678` in the `development` profile; fails fast in all other profiles when `n8n.base-url` is not set
- `n8n.base-url` expects the bare n8n host (e.g. `http://localhost:5678`) — the plugin appends `/webhook` automatically, or `/webhook-test` when `n8n.use-test-webhook=true`
- Programmatic API via the `n8n` service for triggering workflows from custom handler code
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,9 @@ mvn clean install

```xml
<dependency>
<groupId>sap.capire</groupId>
<groupId>com.sap.cds</groupId>
<artifactId>cds-feature-n8n</artifactId>
<version>0.0.1-alpha</version>
<version>0.0.1</version>
</dependency>
```

Expand Down
2 changes: 1 addition & 1 deletion cds-feature-n8n/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>sap.capire</groupId>
<groupId>com.sap.cds</groupId>
<artifactId>cds-feature-n8n-parent</artifactId>
<version>${revision}</version>
</parent>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
/*
* © 2026 SAP SE or an SAP affiliate company and cds-feature-n8n contributors.
*/
package sap.capire.n8n_plugin.configuration;

package com.sap.cds.feature.n8n.configuration;

import com.sap.cds.feature.n8n.handlers.N8nAnnotationValidator;
import com.sap.cds.feature.n8n.handlers.N8nHandler;
import com.sap.cds.feature.n8n.handlers.N8nOutboxHandler;
import com.sap.cds.feature.n8n.handlers.N8nServiceHandler;
import com.sap.cds.feature.n8n.services.ConsoleN8NWebhookService;
import com.sap.cds.feature.n8n.services.N8nService;
import com.sap.cds.feature.n8n.services.N8nServiceImpl;
import com.sap.cds.feature.n8n.services.N8nWebhookService;
import com.sap.cds.reflect.CdsModel;
import com.sap.cds.services.outbox.OutboxService;
import com.sap.cds.services.persistence.PersistenceService;
Expand All @@ -22,22 +30,15 @@
import org.springframework.core.env.Environment;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestClient;
import sap.capire.n8n_plugin.handlers.N8nAnnotationValidator;
import sap.capire.n8n_plugin.handlers.N8nHandler;
import sap.capire.n8n_plugin.handlers.N8nOutboxHandler;
import sap.capire.n8n_plugin.handlers.N8nServiceHandler;
import sap.capire.n8n_plugin.services.ConsoleN8NWebhookService;
import sap.capire.n8n_plugin.services.N8nService;
import sap.capire.n8n_plugin.services.N8nServiceImpl;
import sap.capire.n8n_plugin.services.N8nWebhookService;

/**
* Spring Boot auto-configuration for the cds-feature-n8n plugin.
*
* <p>Registers all plugin beans ({@link sap.capire.n8n_plugin.services.N8nWebhookService}, {@link
* sap.capire.n8n_plugin.handlers.N8nOutboxHandler}, {@link
* sap.capire.n8n_plugin.handlers.N8nHandler}, {@link sap.capire.n8n_plugin.services.N8nService},
* {@link sap.capire.n8n_plugin.handlers.N8nServiceHandler}) and binds plugin configuration from the
* <p>Registers all plugin beans ({@link com.sap.cds.feature.n8n.services.N8nWebhookService}, {@link
* com.sap.cds.feature.n8n.handlers.N8nOutboxHandler}, {@link
* com.sap.cds.feature.n8n.handlers.N8nHandler}, {@link
* com.sap.cds.feature.n8n.services.N8nService}, {@link
* com.sap.cds.feature.n8n.handlers.N8nServiceHandler}) and binds plugin configuration from the
* {@code n8n.*} namespace.
*/
@Configuration
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* © 2026 SAP SE or an SAP affiliate company and cds-feature-n8n contributors.
*/
package sap.capire.n8n_plugin.handlers;
package com.sap.cds.feature.n8n.handlers;

import com.sap.cds.reflect.CdsModel;
import java.util.List;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
/*
* © 2026 SAP SE or an SAP affiliate company and cds-feature-n8n contributors.
*/
package sap.capire.n8n_plugin.handlers;
package com.sap.cds.feature.n8n.handlers;

import com.sap.cds.feature.n8n.configuration.N8nAutoConfiguration.N8nProperties;
import com.sap.cds.feature.n8n.services.N8nWebhookService;
import com.sap.cds.feature.n8n.utils.ConditionEvaluator;
import com.sap.cds.feature.n8n.utils.InputExtractor;
import com.sap.cds.ql.Select;
import com.sap.cds.ql.Selectable;
import com.sap.cds.ql.StructuredType;
Expand All @@ -28,10 +32,6 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpMethod;
import sap.capire.n8n_plugin.configuration.N8nAutoConfiguration.N8nProperties;
import sap.capire.n8n_plugin.services.N8nWebhookService;
import sap.capire.n8n_plugin.utils.ConditionEvaluator;
import sap.capire.n8n_plugin.utils.InputExtractor;

/**
* CAP event handler that detects {@code @n8n.process.start} annotations and queues webhook calls.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
/*
* © 2026 SAP SE or an SAP affiliate company and cds-feature-n8n contributors.
*/
package sap.capire.n8n_plugin.handlers;
package com.sap.cds.feature.n8n.handlers;

import com.sap.cds.feature.n8n.services.N8nWebhookService;
import com.sap.cds.services.handler.EventHandler;
import com.sap.cds.services.handler.annotations.On;
import com.sap.cds.services.handler.annotations.ServiceName;
Expand All @@ -13,7 +14,6 @@
import org.springframework.http.HttpMethod;
import org.springframework.web.client.HttpStatusCodeException;
import org.springframework.web.client.ResourceAccessException;
import sap.capire.n8n_plugin.services.N8nWebhookService;

/**
* CAP outbox handler that delivers queued n8n webhook calls over HTTP.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
/*
* © 2026 SAP SE or an SAP affiliate company and cds-feature-n8n contributors.
*/
package sap.capire.n8n_plugin.handlers;
package com.sap.cds.feature.n8n.handlers;

import com.sap.cds.feature.n8n.configuration.N8nAutoConfiguration.N8nProperties;
import com.sap.cds.feature.n8n.services.N8nService;
import com.sap.cds.feature.n8n.services.N8nWebhookService;
import com.sap.cds.services.EventContext;
import com.sap.cds.services.handler.EventHandler;
import com.sap.cds.services.handler.annotations.On;
Expand All @@ -11,16 +14,13 @@
import com.sap.cds.services.outbox.OutboxService;
import java.util.Map;
import org.springframework.http.HttpMethod;
import sap.capire.n8n_plugin.configuration.N8nAutoConfiguration.N8nProperties;
import sap.capire.n8n_plugin.services.N8nService;
import sap.capire.n8n_plugin.services.N8nWebhookService;

/**
* CAP event handler that bridges {@link sap.capire.n8n_plugin.services.N8nService#trigger} calls to
* the persistent outbox.
* CAP event handler that bridges {@link com.sap.cds.feature.n8n.services.N8nService#trigger} calls
* to the persistent outbox.
*
* <p>Listens on the {@code N8nService} CAP service for the {@code trigger} event emitted by {@link
* sap.capire.n8n_plugin.services.N8nServiceImpl}. The actual HTTP call to n8n happens in {@link
* com.sap.cds.feature.n8n.services.N8nServiceImpl}. The actual HTTP call to n8n happens in {@link
* N8nOutboxHandler} after the transaction commits.
*/
@ServiceName(N8nService.DEFAULT_NAME)
Expand Down Expand Up @@ -51,7 +51,7 @@ public N8nServiceHandler(
* N8nWebhookService#notify}; otherwise submits an outbox message for deferred HTTP delivery.
*
* @param ctx event context carrying {@code path} and {@code data} set by {@link
* sap.capire.n8n_plugin.services.N8nServiceImpl#trigger}
* com.sap.cds.feature.n8n.services.N8nServiceImpl#trigger}
*/
@On(event = "trigger")
public void onTrigger(EventContext ctx) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* © 2026 SAP SE or an SAP affiliate company and cds-feature-n8n contributors.
*/
package sap.capire.n8n_plugin.services;
package com.sap.cds.feature.n8n.services;

import java.time.Instant;
import java.util.ArrayList;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* © 2026 SAP SE or an SAP affiliate company and cds-feature-n8n contributors.
*/
package sap.capire.n8n_plugin.services;
package com.sap.cds.feature.n8n.services;

import com.sap.cds.services.Service;
import java.util.Map;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* © 2026 SAP SE or an SAP affiliate company and cds-feature-n8n contributors.
*/
package sap.capire.n8n_plugin.services;
package com.sap.cds.feature.n8n.services;

import com.sap.cds.services.EventContext;
import com.sap.cds.services.ServiceDelegator;
Expand All @@ -25,7 +25,7 @@ public N8nServiceImpl(String name) {

/**
* Emits a {@code trigger} event on the CAP event bus carrying {@code path}, {@code method}, and
* {@code data}. {@link sap.capire.n8n_plugin.handlers.N8nServiceHandler} listens for this event
* {@code data}. {@link com.sap.cds.feature.n8n.handlers.N8nServiceHandler} listens for this event
* and forwards it to the outbox.
*/
@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* © 2026 SAP SE or an SAP affiliate company and cds-feature-n8n contributors.
*/
package sap.capire.n8n_plugin.services;
package com.sap.cds.feature.n8n.services;

import java.util.Collections;
import java.util.Map;
Expand All @@ -18,7 +18,7 @@
* <p>Sends a {@code POST} to {@code baseUrl/path} with the payload as JSON. Auth headers are
* layered in order: {@code authHeaders} first (e.g. {@code Authorization: Bearer …} from a BTP
* destination), then {@code X-N8N-API-KEY}. Constructed by {@link
* sap.capire.n8n_plugin.configuration.N8nAutoConfiguration} with pre-configured timeouts.
* com.sap.cds.feature.n8n.configuration.N8nAutoConfiguration} with pre-configured timeouts.
*/
public class N8nWebhookService {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* © 2026 SAP SE or an SAP affiliate company and cds-feature-n8n contributors.
*/
package sap.capire.n8n_plugin.utils;
package com.sap.cds.feature.n8n.utils;

import com.fasterxml.jackson.databind.ObjectMapper;
import java.util.Collection;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* © 2026 SAP SE or an SAP affiliate company and cds-feature-n8n contributors.
*/
package sap.capire.n8n_plugin.utils;
package com.sap.cds.feature.n8n.utils;

import com.sap.cds.ql.CQL;
import com.sap.cds.ql.Selectable;
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
sap.capire.n8n_plugin.configuration.N8nAutoConfiguration
com.sap.cds.feature.n8n.configuration.N8nAutoConfiguration
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* © 2026 SAP SE or an SAP affiliate company and cds-feature-n8n contributors.
*/
package sap.capire.n8n_plugin.configuration;
package com.sap.cds.feature.n8n.configuration;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
Expand All @@ -10,6 +10,12 @@
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;

import com.sap.cds.feature.n8n.configuration.N8nAutoConfiguration.DestinationConfiguration;
import com.sap.cds.feature.n8n.configuration.N8nAutoConfiguration.N8nProperties;
import com.sap.cds.feature.n8n.handlers.N8nHandler;
import com.sap.cds.feature.n8n.handlers.N8nServiceHandler;
import com.sap.cds.feature.n8n.services.ConsoleN8NWebhookService;
import com.sap.cds.feature.n8n.services.N8nWebhookService;
import com.sap.cds.services.persistence.PersistenceService;
import com.sap.cloud.sdk.cloudplatform.connectivity.Destination;
import com.sap.cloud.sdk.cloudplatform.connectivity.DestinationAccessor;
Expand All @@ -27,12 +33,6 @@
import org.springframework.core.env.SystemEnvironmentPropertySource;
import org.springframework.mock.env.MockEnvironment;
import org.springframework.web.client.RestClient;
import sap.capire.n8n_plugin.configuration.N8nAutoConfiguration.DestinationConfiguration;
import sap.capire.n8n_plugin.configuration.N8nAutoConfiguration.N8nProperties;
import sap.capire.n8n_plugin.handlers.N8nHandler;
import sap.capire.n8n_plugin.handlers.N8nServiceHandler;
import sap.capire.n8n_plugin.services.ConsoleN8NWebhookService;
import sap.capire.n8n_plugin.services.N8nWebhookService;

class N8nAutoConfigurationTest {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* © 2026 SAP SE or an SAP affiliate company and cds-feature-n8n contributors.
*/
package sap.capire.n8n_plugin.handlers;
package com.sap.cds.feature.n8n.handlers;

import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* © 2026 SAP SE or an SAP affiliate company and cds-feature-n8n contributors.
*/
package sap.capire.n8n_plugin.handlers;
package com.sap.cds.feature.n8n.handlers;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
Expand All @@ -10,6 +10,8 @@

import com.sap.cds.CdsData;
import com.sap.cds.Result;
import com.sap.cds.feature.n8n.configuration.N8nAutoConfiguration.N8nProperties;
import com.sap.cds.feature.n8n.services.N8nWebhookService;
import com.sap.cds.ql.cqn.CqnInsert;
import com.sap.cds.ql.cqn.CqnSelect;
import com.sap.cds.ql.cqn.CqnUpdate;
Expand Down Expand Up @@ -37,8 +39,6 @@
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.HttpMethod;
import sap.capire.n8n_plugin.configuration.N8nAutoConfiguration.N8nProperties;
import sap.capire.n8n_plugin.services.N8nWebhookService;

@ExtendWith(MockitoExtension.class)
class N8nHandlerTest {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
/*
* © 2026 SAP SE or an SAP affiliate company and cds-feature-n8n contributors.
*/
package sap.capire.n8n_plugin.handlers;
package com.sap.cds.feature.n8n.handlers;

import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.*;

import com.sap.cds.feature.n8n.services.N8nWebhookService;
import com.sap.cds.services.outbox.OutboxMessage;
import com.sap.cds.services.outbox.OutboxMessageEventContext;
import java.util.Map;
Expand All @@ -21,7 +22,6 @@
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.HttpServerErrorException;
import org.springframework.web.client.ResourceAccessException;
import sap.capire.n8n_plugin.services.N8nWebhookService;

@ExtendWith(MockitoExtension.class)
class N8nOutboxHandlerTest {
Expand Down
Loading
Loading