Skip to content

Commit cdd1238

Browse files
committed
Add Vector Store Redis
1 parent 887bb4d commit cdd1238

33 files changed

+9160
-318
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# n8n-nodes-gnr-stack
22

3-
This is a set of n8n community nodes. It may be used in GNR Stack.
3+
This is a set of n8n community nodes. It may be used in GNR(Grist/N8n/Redis) Stack.
44

55
## HTTP Forward Auth Trigger/Response Node
66
It can be used as a HTTP forward auth middleware with reverse proxies like Traefik and Caddy.
@@ -27,15 +27,15 @@ _List the operations supported by your node._
2727

2828
## Credentials
2929

30-
Use [Redis](https://docs.n8n.io/integrations/builtin/credentials/redis/) as session data storage.
30+
Use [Redis](https://docs.n8n.io/integrations/builtin/credentials/redis/) as data storage.
3131

3232
## Compatibility
3333

3434
N8N 1.76.1 and above.
3535

3636
## Usage
3737

38-
TODO
38+
Se examples folder
3939

4040
## Resources
4141

examples/Caddyfile

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,32 @@
1-
:80 {
1+
:8080 {
22

3-
rewrite /n8n /n8n/
4-
5-
handle_path /n8n/* {
3+
handle_path /n8n/webhook/* {
64
uri strip_prefix /n8n
7-
# Fix custom node icons when using N8N_PATH
8-
uri replace icons/CUSTOM/home/node/.n8n/custom/ icons/CUSTOM/
95
reverse_proxy n8n:5678 {
106
header_up X-Real-IP {remote_host}
117
}
128
}
139

14-
handle /protected {
10+
handle /auth/login {
1511
forward_auth n8n:5678 {
1612
uri /webhook/{$WEBHOOK_ID}/check
1713
copy_headers X-Forwarded-User
1814
}
1915
}
2016

21-
handle /login {
22-
uri replace login webhook/{$WEBHOOK_ID}/login
17+
handle /_auth/login {
18+
uri replace /_auth/login webhook/{$WEBHOOK_ID}/login
2319
reverse_proxy n8n:5678 {
2420
header_up X-Real-IP {remote_host}
2521
}
2622
}
2723

28-
handle /logout {
29-
uri replace logout webhook/{$WEBHOOK_ID}/logout
24+
handle /_auth/logout {
25+
uri replace /_auth/logout webhook/{$WEBHOOK_ID}/logout
3026
reverse_proxy n8n:5678
3127
}
3228

33-
reverse_proxy whoami:80
29+
reverse_proxy grist:8484 {
30+
header_up -X-Real-IP
31+
}
3432
}

examples/docker-compose.yml

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,11 @@ services:
44
restart: always
55
environment:
66
- NODE_ENV=production
7-
- N8N_PATH=/n8n/
8-
- WEBHOOK_URL=http://localhost:8080/n8n/
9-
- N8N_PROXY_HOPS=1
107
volumes:
118
- ./data/n8n:/home/node/.n8n
129
- ../dist:/home/node/.n8n/custom
1310
ports:
14-
- 5678:5678
11+
- 8081:5678
1512
depends_on:
1613
build:
1714
condition: service_completed_successfully
@@ -24,22 +21,52 @@ services:
2421
command: ["run", "build-docker"]
2522

2623
redis:
27-
image: redis:7-alpine
24+
image: redis/redis-stack:latest
2825
restart: always
29-
command: redis-server --save 20 1 --loglevel warning
26+
healthcheck:
27+
test: [ "CMD", "redis-cli", "--raw", "incr", "ping" ]
28+
interval: 5s
29+
timeout: 5s
30+
retries: 5
3031
volumes:
3132
- ./data/redis:/data
33+
ports:
34+
- 8082:8001
3235

33-
whoami:
34-
image: traefik/whoami
36+
grist:
37+
image: gristlabs/grist-oss
3538
restart: always
36-
39+
volumes:
40+
- ./data/grist:/persist
41+
environment:
42+
- GRIST_SESSION_SECRET=invent-a-secret-here
43+
44+
- GRIST_SANDBOX_FLAVOR=gvisor
45+
- APP_HOME_URL=http://localhost:8080
46+
- GRIST_SINGLE_ORG=example-com
47+
- COOKIE_MAX_AGE=604800000
48+
- GRIST_HIDE_UI_ELEMENTS=helpCenter,billing,templates,createSite,multiSite,multiAccounts,sendToDrive,tutorials,supportGrist
49+
- GRIST_PAGE_TITLE_SUFFIX=" - Example.com"
50+
- GRIST_WIDGET_LIST_URL="https://github.com/gristlabs/grist-widget/releases/download/latest/manifest.json"
51+
- ALLOWED_WEBHOOK_DOMAINS=n8n:5678
52+
- GRIST_FORWARD_AUTH_HEADER=X-Forwarded-User
53+
- GRIST_FORWARD_AUTH_LOGOUT_PATH=/_auth/logout
54+
# - GRIST_DOCS_MINIO_ACCESS_KEY=grist
55+
# - GRIST_DOCS_MINIO_SECRET_KEY=grist
56+
# - GRIST_DOCS_MINIO_ENDPOINT=s3
57+
# - GRIST_DOCS_MINIO_PORT=8333
58+
# - GRIST_DOCS_MINIO_USE_SSL=0
59+
# - GRIST_DOCS_MINIO_BUCKET=grist
60+
- REDIS_URL=redis://redis:6379
61+
depends_on:
62+
redis:
63+
condition: service_healthy
3764
caddy:
3865
image: caddy:alpine
3966
restart: always
4067
environment:
4168
- WEBHOOK_ID=aa1e42ef-7492-4235-9f22-e3c007358d15
4269
ports:
43-
- 8080:80
70+
- 8080:8080
4471
volumes:
4572
- ./Caddyfile:/etc/caddy/Caddyfile

jest.config.js

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,17 @@
22
module.exports = {
33
preset: 'ts-jest/presets/default-esm',
44
collectCoverage: true,
5-
coverageDirectory: "coverage",
6-
coverageProvider: "v8",
5+
coverageDirectory: 'coverage',
6+
coverageProvider: 'v8',
77
collectCoverageFrom: ['nodes/**/*.ts'],
8+
coveragePathIgnorePatterns: [
9+
'<rootDir>/nodes/common/types.ts',
10+
'<rootDir>/nodes/VectorStoreRedis/VectorStoreRedis.node.ts',
11+
],
812
testPathIgnorePatterns: ['/node_modules/'],
9-
transform: {},
10-
moduleNameMapper: {
11-
'^(\\.{1,2}/.*)\\.js$': '$1'
12-
},
13-
testEnvironment: 'node'
13+
transform: {},
14+
moduleNameMapper: {
15+
'^(\\.{1,2}/.*)\\.js$': '$1',
16+
},
17+
testEnvironment: 'node',
1418
};

nodes/HttpForwardAuth/HttpForwardAuth.node.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,17 @@ import type {
88
INodeType,
99
} from 'n8n-workflow';
1010

11-
import { FORWARDED_USER_HEADER, TRIGGER_NAME } from './constants';
11+
import { FORWARDED_USER_HEADER, TRIGGER_NAME } from '../common/constants';
1212
import { responseDescription } from './descriptions';
13-
import { getRedisClient } from './transport';
14-
import type { RedisCredential } from './types';
13+
import { getRedisClient } from '../common/transport';
14+
import type { RedisCredential } from '../common/types';
1515
import {
1616
createSession,
1717
generateSessionToken,
1818
rateLimitReset,
1919
redisConnectionTest,
2020
setSessionTokenCookie,
21-
} from './utils';
21+
} from '../common/utils';
2222

2323
type TriggerParamsType = {
2424
authURL: string;

nodes/HttpForwardAuth/HttpForwardAuthTrigger.node.ts

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,19 @@ import type {
66
IDataObject,
77
} from 'n8n-workflow';
88

9-
import { FORWARDED_USER_HEADER, REMOTE_IP_HEADER, SESSION_KEY } from './constants';
10-
import { cookieParse } from './cookieParser';
9+
import { FORWARDED_USER_HEADER, REMOTE_IP_HEADER, SESSION_KEY } from '../common/constants';
10+
import { cookieParse } from '../common/cookieParser';
1111
import { triggerDescription } from './descriptions';
1212
import { logoutPageHTMLTemplate } from './templates';
13-
import { getRedisClient } from './transport';
14-
import type { RedisCredential } from './types';
13+
import { getRedisClient } from '../common/transport';
14+
import type { RedisCredential } from '../common/types';
1515
import {
1616
deleteSessionTokenCookie,
1717
rateLimitConsume,
1818
redisConnectionTest,
1919
setSessionTokenCookie,
2020
validateSessionToken,
21-
} from './utils';
21+
} from '../common/utils';
2222

2323
export class HttpForwardAuthTrigger implements INodeType {
2424
description = triggerDescription;
@@ -28,8 +28,7 @@ export class HttpForwardAuthTrigger implements INodeType {
2828
};
2929

3030
async webhook(this: IWebhookFunctions): Promise<IWebhookResponseData> {
31-
const req = this.getRequestObject();
32-
const res = this.getResponseObject();
31+
const [req, res] = [this.getRequestObject(), this.getResponseObject()];
3332
const addResHeader = (key: string, value: string) => res.setHeader(key, value);
3433

3534
const credentials = (await this.getCredentials('redis')) as RedisCredential;
@@ -41,7 +40,7 @@ export class HttpForwardAuthTrigger implements INodeType {
4140
const logoutRedirectURL = this.getNodeParameter('logoutRedirectURL', '') as string;
4241
const enableHTTP = this.getNodeParameter('enableHTTP', false) as boolean;
4342
const rateLimit = this.getNodeParameter('rateLimit', false) as boolean;
44-
const remoteIp = rateLimit ? req.headers[REMOTE_IP_HEADER] as string : undefined;
43+
const remoteIp = rateLimit ? (req.headers[REMOTE_IP_HEADER] as string) : undefined;
4544
const rateLimitErrorMessage = this.getNodeParameter('rateLimitErrorMessage', '') as string;
4645
const loginTemplate = this.getNodeParameter('loginTemplate', '') as string;
4746

nodes/HttpForwardAuth/descriptions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import type { INodeTypeDescription } from 'n8n-workflow';
33
import { NodeConnectionType } from 'n8n-workflow';
44

5-
import { TRIGGER_NAME } from './constants';
5+
import { TRIGGER_NAME } from '../common/constants';
66
import { loginPageHTMLTemplate } from './templates';
77

88
export const responseDescription: INodeTypeDescription = {

nodes/HttpForwardAuth/templates.ts

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -151,33 +151,3 @@ export const logoutPageHTMLTemplate = `<!DOCTYPE html>
151151
<form name="logout" method="post" action="#LOGOUT_URL#"></form>
152152
</body>
153153
</html>`;
154-
155-
export const redisRLScript = `
156-
-- Returns 1 if allowed, 0 if not
157-
local key = KEYS[1]
158-
local now = tonumber(ARGV[1])
159-
160-
local timeoutSeconds = {1, 2, 4, 8, 16, 30, 60, 180, 300}
161-
162-
local fields = redis.call("HGETALL", key)
163-
if #fields == 0 then
164-
redis.call("HSET", key, "index", 1, "updated_at", now)
165-
return {1}
166-
end
167-
local index = 0
168-
local updatedAt = 0
169-
for i = 1, #fields, 2 do
170-
if fields[i] == "index" then
171-
index = tonumber(fields[i+1])
172-
elseif fields[i] == "updated_at" then
173-
updatedAt = tonumber(fields[i+1])
174-
end
175-
end
176-
local allowed = now - updatedAt >= timeoutSeconds[index]
177-
if not allowed then
178-
return {0}
179-
end
180-
index = math.min(index + 1, #timeoutSeconds)
181-
redis.call("HSET", key, "index", index, "updated_at", now)
182-
return {1}
183-
`;
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { RedisVectorStore } from '@langchain/redis';
2+
import { createVectorStoreNode } from '@n8n/n8n-nodes-langchain/dist/nodes/vector_store/shared/createVectorStoreNode';
3+
import type { RedisCredential } from '../common/types';
4+
import { getRedisClient } from '../common/transport';
5+
6+
export class VectorStoreRedis extends createVectorStoreNode({
7+
meta: {
8+
displayName: 'Redis Vector Store',
9+
name: 'vectorStoreRedis',
10+
description: 'Work with your data in Redis Vector Store',
11+
icon: 'file:redis.svg',
12+
docsUrl: 'https://github.com/pedrozadotdev/n8n-nodes-gnr-stack',
13+
credentials: [
14+
{
15+
// eslint-disable-next-line n8n-nodes-base/node-class-description-credentials-name-unsuffixed
16+
name: 'redis',
17+
// @ts-ignore
18+
displayName: 'Redis Credential',
19+
required: true,
20+
testedBy: 'redisConnectionTest',
21+
},
22+
],
23+
},
24+
sharedFields: [
25+
{
26+
displayName: 'Index Name',
27+
name: 'indexName',
28+
type: 'string',
29+
default: '',
30+
required: true,
31+
description: 'The index name that will be use in Redis',
32+
},
33+
],
34+
loadFields: [],
35+
retrieveFields: [],
36+
// @ts-ignore
37+
async getVectorStoreClient(context, _filter, embeddings, itemIndex) {
38+
const credentials = (await context.getCredentials('redis')) as RedisCredential;
39+
const { client } = await getRedisClient(credentials);
40+
const indexName = context.getNodeParameter('indexName', itemIndex) as string;
41+
42+
return new RedisVectorStore(embeddings, {
43+
redisClient: client,
44+
indexName,
45+
});
46+
},
47+
48+
async populateVectorStore(context, embeddings, documents, itemIndex) {
49+
const credentials = (await context.getCredentials('redis')) as RedisCredential;
50+
const { client } = await getRedisClient(credentials);
51+
const indexName = context.getNodeParameter('indexName', itemIndex) as string;
52+
53+
const vectorStore = new RedisVectorStore(embeddings, {
54+
redisClient: client,
55+
indexName,
56+
});
57+
58+
await vectorStore.addDocuments(documents);
59+
},
60+
}) {}

nodes/VectorStoreRedis/redis.svg

Lines changed: 1 addition & 0 deletions
Loading

0 commit comments

Comments
 (0)