Skip to content

Commit d91faac

Browse files
authored
example: Quick start Docker Compose setup (#91)
* example: Set up quickstart Docker compose * chore: Remove default network * feat: Setup with RabbitMQ * refactor: Use bind mount for data volume instead of local volume * chore: Use latest image * feat: Implement GET /:tenantId/destinations query params (#87) * chore: Store destination type in summary * chore: Rename destination type "webhooks" to "webhook" * test: Refactor test to better test the API surface behavior * feat: Destination filter * chore: Ensure destination filter * case * feat: Handle type & topics destination filter query params * test: e2e tests for destination filter * chore: Remove log
1 parent 78077b5 commit d91faac

File tree

21 files changed

+576
-64
lines changed

21 files changed

+576
-64
lines changed

cmd/e2e/api_test.go

Lines changed: 176 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ func (suite *basicSuite) TestTenantsAPI() {
123123
Path: "/" + tenantID + "/destinations",
124124
Body: map[string]interface{}{
125125
"id": sampleDestinationID,
126-
"type": "webhooks",
126+
"type": "webhook",
127127
"topics": "*",
128128
"config": map[string]interface{}{
129129
"url": "http://host.docker.internal:4444",
@@ -250,7 +250,7 @@ func (suite *basicSuite) TestDestinationsAPI() {
250250
Method: httpclient.MethodPOST,
251251
Path: "/" + tenantID + "/destinations",
252252
Body: map[string]interface{}{
253-
"type": "webhooks",
253+
"type": "webhook",
254254
"topics": "*",
255255
"config": map[string]interface{}{
256256
"url": "http://host.docker.internal:4444",
@@ -305,7 +305,7 @@ func (suite *basicSuite) TestDestinationsAPI() {
305305
Method: httpclient.MethodPOST,
306306
Path: "/" + tenantID + "/destinations",
307307
Body: map[string]interface{}{
308-
"type": "webhooks",
308+
"type": "webhook",
309309
"topics": "invalid",
310310
"config": map[string]interface{}{
311311
"url": "http://host.docker.internal:4444",
@@ -327,7 +327,7 @@ func (suite *basicSuite) TestDestinationsAPI() {
327327
Method: httpclient.MethodPOST,
328328
Path: "/" + tenantID + "/destinations",
329329
Body: map[string]interface{}{
330-
"type": "webhooks",
330+
"type": "webhook",
331331
"topics": []string{"invalid"},
332332
"config": map[string]interface{}{
333333
"url": "http://host.docker.internal:4444",
@@ -349,7 +349,7 @@ func (suite *basicSuite) TestDestinationsAPI() {
349349
Method: httpclient.MethodPOST,
350350
Path: "/" + tenantID + "/destinations",
351351
Body: map[string]interface{}{
352-
"type": "webhooks",
352+
"type": "webhook",
353353
"topics": []string{"user.created"},
354354
"config": map[string]interface{}{},
355355
},
@@ -370,7 +370,7 @@ func (suite *basicSuite) TestDestinationsAPI() {
370370
Path: "/" + tenantID + "/destinations",
371371
Body: map[string]interface{}{
372372
"id": sampleDestinationID,
373-
"type": "webhooks",
373+
"type": "webhook",
374374
"topics": "*",
375375
"config": map[string]interface{}{
376376
"url": "http://host.docker.internal:4444",
@@ -390,7 +390,7 @@ func (suite *basicSuite) TestDestinationsAPI() {
390390
Path: "/" + tenantID + "/destinations",
391391
Body: map[string]interface{}{
392392
"id": sampleDestinationID,
393-
"type": "webhooks",
393+
"type": "webhook",
394394
"topics": "*",
395395
"config": map[string]interface{}{
396396
"url": "http://host.docker.internal:4444",
@@ -427,7 +427,7 @@ func (suite *basicSuite) TestDestinationsAPI() {
427427
StatusCode: http.StatusOK,
428428
Body: map[string]interface{}{
429429
"id": sampleDestinationID,
430-
"type": "webhooks",
430+
"type": "webhook",
431431
"topics": []string{"*"},
432432
"config": map[string]interface{}{
433433
"url": "http://host.docker.internal:4444",
@@ -451,7 +451,7 @@ func (suite *basicSuite) TestDestinationsAPI() {
451451
StatusCode: http.StatusOK,
452452
Body: map[string]interface{}{
453453
"id": sampleDestinationID,
454-
"type": "webhooks",
454+
"type": "webhook",
455455
"topics": []string{"user.created"},
456456
"config": map[string]interface{}{
457457
"url": "http://host.docker.internal:4444",
@@ -472,7 +472,7 @@ func (suite *basicSuite) TestDestinationsAPI() {
472472
StatusCode: http.StatusOK,
473473
Body: map[string]interface{}{
474474
"id": sampleDestinationID,
475-
"type": "webhooks",
475+
"type": "webhook",
476476
"topics": []string{"user.created"},
477477
"config": map[string]interface{}{
478478
"url": "http://host.docker.internal:4444",
@@ -568,6 +568,172 @@ func (suite *basicSuite) TestDestinationsAPI() {
568568
suite.RunAPITests(suite.T(), tests)
569569
}
570570

571+
func (suite *basicSuite) TestDestinationsListAPI() {
572+
tenantID := uuid.New().String()
573+
tests := []APITest{
574+
{
575+
Name: "PUT /:tenantID",
576+
Request: suite.AuthRequest(httpclient.Request{
577+
Method: httpclient.MethodPUT,
578+
Path: "/" + tenantID,
579+
}),
580+
Expected: APITestExpectation{
581+
Match: &httpclient.Response{
582+
StatusCode: http.StatusCreated,
583+
},
584+
},
585+
},
586+
{
587+
Name: "POST /:tenantID/destinations type=webhook topics=*",
588+
Request: suite.AuthRequest(httpclient.Request{
589+
Method: httpclient.MethodPOST,
590+
Path: "/" + tenantID + "/destinations",
591+
Body: map[string]interface{}{
592+
"type": "webhook",
593+
"topics": "*",
594+
"config": map[string]interface{}{
595+
"url": "http://host.docker.internal:4444",
596+
},
597+
},
598+
}),
599+
Expected: APITestExpectation{
600+
Match: &httpclient.Response{
601+
StatusCode: http.StatusCreated,
602+
},
603+
},
604+
},
605+
{
606+
Name: "POST /:tenantID/destinations type=webhook topics=user.created",
607+
Request: suite.AuthRequest(httpclient.Request{
608+
Method: httpclient.MethodPOST,
609+
Path: "/" + tenantID + "/destinations",
610+
Body: map[string]interface{}{
611+
"type": "webhook",
612+
"topics": []string{"user.created"},
613+
"config": map[string]interface{}{
614+
"url": "http://host.docker.internal:4444",
615+
},
616+
},
617+
}),
618+
Expected: APITestExpectation{
619+
Match: &httpclient.Response{
620+
StatusCode: http.StatusCreated,
621+
},
622+
},
623+
},
624+
{
625+
Name: "POST /:tenantID/destinations type=webhook topics=user.created user.updated",
626+
Request: suite.AuthRequest(httpclient.Request{
627+
Method: httpclient.MethodPOST,
628+
Path: "/" + tenantID + "/destinations",
629+
Body: map[string]interface{}{
630+
"type": "webhook",
631+
"topics": []string{"user.created", "user.updated"},
632+
"config": map[string]interface{}{
633+
"url": "http://host.docker.internal:4444",
634+
},
635+
},
636+
}),
637+
Expected: APITestExpectation{
638+
Match: &httpclient.Response{
639+
StatusCode: http.StatusCreated,
640+
},
641+
},
642+
},
643+
{
644+
Name: "GET /:tenantID/destinations",
645+
Request: suite.AuthRequest(httpclient.Request{
646+
Method: httpclient.MethodGET,
647+
Path: "/" + tenantID + "/destinations",
648+
}),
649+
Expected: APITestExpectation{
650+
Validate: makeDestinationListValidator(3),
651+
},
652+
},
653+
{
654+
Name: "GET /:tenantID/destinations?type=webhook",
655+
Request: suite.AuthRequest(httpclient.Request{
656+
Method: httpclient.MethodGET,
657+
Path: "/" + tenantID + "/destinations?type=webhook",
658+
}),
659+
Expected: APITestExpectation{
660+
Validate: makeDestinationListValidator(3),
661+
},
662+
},
663+
{
664+
Name: "GET /:tenantID/destinations?type=rabbitmq",
665+
Request: suite.AuthRequest(httpclient.Request{
666+
Method: httpclient.MethodGET,
667+
Path: "/" + tenantID + "/destinations?type=rabbitmq",
668+
}),
669+
Expected: APITestExpectation{
670+
Validate: makeDestinationListValidator(0),
671+
},
672+
},
673+
{
674+
Name: "GET /:tenantID/destinations?topics=*",
675+
Request: suite.AuthRequest(httpclient.Request{
676+
Method: httpclient.MethodGET,
677+
Path: "/" + tenantID + "/destinations?topics=*",
678+
}),
679+
Expected: APITestExpectation{
680+
Validate: makeDestinationListValidator(1),
681+
},
682+
},
683+
{
684+
Name: "GET /:tenantID/destinations?topics=user.created",
685+
Request: suite.AuthRequest(httpclient.Request{
686+
Method: httpclient.MethodGET,
687+
Path: "/" + tenantID + "/destinations?topics=user.created",
688+
}),
689+
Expected: APITestExpectation{
690+
Validate: makeDestinationListValidator(3),
691+
},
692+
},
693+
{
694+
Name: "GET /:tenantID/destinations?topics=user.updated",
695+
Request: suite.AuthRequest(httpclient.Request{
696+
Method: httpclient.MethodGET,
697+
Path: "/" + tenantID + "/destinations?topics=user.updated",
698+
}),
699+
Expected: APITestExpectation{
700+
Validate: makeDestinationListValidator(2),
701+
},
702+
},
703+
{
704+
Name: "GET /:tenantID/destinations?topics=user.created&topics=user.updated",
705+
Request: suite.AuthRequest(httpclient.Request{
706+
Method: httpclient.MethodGET,
707+
Path: "/" + tenantID + "/destinations?topics=user.created&topics=user.updated",
708+
}),
709+
Expected: APITestExpectation{
710+
Validate: makeDestinationListValidator(2),
711+
},
712+
},
713+
{
714+
Name: "GET /:tenantID/destinations?type=webhook&topics=user.created&topics=user.updated",
715+
Request: suite.AuthRequest(httpclient.Request{
716+
Method: httpclient.MethodGET,
717+
Path: "/" + tenantID + "/destinations?type=webhook&topics=user.created&topics=user.updated",
718+
}),
719+
Expected: APITestExpectation{
720+
Validate: makeDestinationListValidator(2),
721+
},
722+
},
723+
{
724+
Name: "GET /:tenantID/destinations?type=rabbitmq&topics=user.created&topics=user.updated",
725+
Request: suite.AuthRequest(httpclient.Request{
726+
Method: httpclient.MethodGET,
727+
Path: "/" + tenantID + "/destinations?type=rabbitmq&topics=user.created&topics=user.updated",
728+
}),
729+
Expected: APITestExpectation{
730+
Validate: makeDestinationListValidator(0),
731+
},
732+
},
733+
}
734+
suite.RunAPITests(suite.T(), tests)
735+
}
736+
571737
func makeDestinationListValidator(length int) map[string]any {
572738
return map[string]any{
573739
"type": "object",

cmd/e2e/httpclient/httpclient.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,6 @@ func (r *Response) doMatchBody(mainBody ResponseBody, toMatchedBody ResponseBody
8282
if !ok {
8383
return false
8484
}
85-
log.Println(key, subValue, fullValue, cmp.Equal(fullValue, subValue))
8685

8786
switch subValueTyped := subValue.(type) {
8887
case map[string]interface{}:

docs/contributing/destinations.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ $ curl --location 'localhost:4000/<TENANT_ID>/destinations' \
133133
--header 'Content-Type: application/json' \
134134
--header 'Authorization: ••••••' \
135135
--data '{
136-
"type": "webhooks",
136+
"type": "webhook",
137137
"topics": ["*"],
138138
"config": {
139139
"url": "http://localhost:4444"
@@ -144,7 +144,7 @@ $ curl --location 'localhost:4000/<TENANT_ID>/destinations' \
144144
```json
145145
{
146146
"id": "...",
147-
"type": "webhooks",
147+
"type": "webhook",
148148
"topics": [
149149
"*"
150150
],

docs/contributing/step-by-step.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,15 +94,15 @@ $ curl --location 'localhost:3333/api/v1/123/destinations' \
9494
--header 'Content-Type: application/json' \
9595
--header 'Authorization: Bearer apikey' \
9696
--data '{
97-
"type": "webhooks",
97+
"type": "webhook",
9898
"topics": ["*"],
9999
"config": {
100100
"url": "http://host.docker.internal:4444"
101101
},
102102
"credentials": {
103103
}
104104
}'
105-
{"id":"abcxyz","type":"webhooks","topics":["*"],"config":{"url":"http://host.docker.internal:4444"},"credentials":{},"created_at":"...","disabled_at":null}%
105+
{"id":"abcxyz","type":"webhook","topics":["*"],"config":{"url":"http://host.docker.internal:4444"},"credentials":{},"created_at":"...","disabled_at":null}%
106106
```
107107

108108
Feel free to confirm that the destination is successfully created either in Redis or by listing the destination for tenant 123:
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# API
2+
PORT="3333"
3+
API_PORT="3333"
4+
API_KEY="apikey"
5+
JWT_SECRET="jwtsecret"
6+
ENCRYPTION_SECRET="encryptionsecret"
7+
8+
# Redis
9+
REDIS_HOST="redis"
10+
REDIS_PORT="6379"
11+
REDIS_PASSWORD="password"
12+
REDIS_DATABASE="0"
13+
14+
# Clickhouse
15+
CLICKHOUSE_ADDR="clickhouse:9000"
16+
CLICKHOUSE_USERNAME="default"
17+
CLICKHOUSE_DATABASE="outpost"
18+
CLICKHOUSE_PASSWORD=""
19+
20+
# Misc Configs
21+
PUBLISHMQ_MAX_CONCURRENCY=1
22+
DELIVERYMQ_MAX_CONCURRENCY=1
23+
TOPICS="user.created,user.updated,user.deleted" # comma separated list of topics to subscribe to, replace with your own topics

examples/docker-compose/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/data
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
services:
2+
aws:
3+
image: localstack/localstack:latest
4+
hostname: aws
5+
environment:
6+
- SERVICES=sns,sts,sqs
7+
ports:
8+
- 4566:4566
9+
- 4571:4571
10+
healthcheck:
11+
test: ["CMD", "curl", "-f", "http://localhost:4566/_localstack/health"]
12+
interval: 1s
13+
timeout: 1s
14+
retries: 30
15+
16+
api:
17+
depends_on:
18+
aws:
19+
condition: service_healthy
20+
environment:
21+
- "DELIVERY_AWS_SQS_ENDPOINT=http://aws:4566"
22+
- "DELIVERY_AWS_SQS_REGION=eu-central-1"
23+
- "DELIVERY_AWS_SQS_SERVICE_ACCOUNT_CREDS=test:test:"
24+
- "DELIVERY_AWS_SQS_TOPIC=outpost_delivery"
25+
- "LOG_AWS_SQS_ENDPOINT=http://aws:4566"
26+
- "LOG_AWS_SQS_REGION=eu-central-1"
27+
- "LOG_AWS_SQS_SERVICE_ACCOUNT_CREDS=test:test:"
28+
- "LOG_AWS_SQS_TOPIC=outpost_log"
29+
delivery:
30+
depends_on:
31+
aws:
32+
condition: service_healthy
33+
environment:
34+
- "DELIVERY_AWS_SQS_ENDPOINT=http://aws:4566"
35+
- "DELIVERY_AWS_SQS_REGION=eu-central-1"
36+
- "DELIVERY_AWS_SQS_SERVICE_ACCOUNT_CREDS=test:test:"
37+
- "DELIVERY_AWS_SQS_TOPIC=outpost_delivery"
38+
- "LOG_AWS_SQS_ENDPOINT=http://aws:4566"
39+
- "LOG_AWS_SQS_REGION=eu-central-1"
40+
- "LOG_AWS_SQS_SERVICE_ACCOUNT_CREDS=test:test:"
41+
- "LOG_AWS_SQS_TOPIC=outpost_log"
42+
log:
43+
depends_on:
44+
aws:
45+
condition: service_healthy
46+
environment:
47+
- "DELIVERY_AWS_SQS_ENDPOINT=http://aws:4566"
48+
- "DELIVERY_AWS_SQS_REGION=eu-central-1"
49+
- "DELIVERY_AWS_SQS_SERVICE_ACCOUNT_CREDS=test:test:"
50+
- "DELIVERY_AWS_SQS_TOPIC=outpost_delivery"
51+
- "LOG_AWS_SQS_ENDPOINT=http://aws:4566"
52+
- "LOG_AWS_SQS_REGION=eu-central-1"
53+
- "LOG_AWS_SQS_SERVICE_ACCOUNT_CREDS=test:test:"
54+
- "LOG_AWS_SQS_TOPIC=outpost_log"

0 commit comments

Comments
 (0)