Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

First review changes #595

Merged
merged 8 commits into from
Nov 18, 2024
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/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# yaml-language-server: $schema=https://json.schemastore.org/github-release-config.json
# docs: https://docs.github.com/en/repositories/releasing-projects-on-github/automatically-generated-release-notes

changelog:
categories:
- title: 🛠 Fixes
labels: [type:fix, type:bug]
- title: 🚀 Features
labels: [type:feature, type:feature_request]
- title: 📦 Dependency updates
labels: [dependencies]
- title: Other Changes
labels: ['*']
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
*.pb.go

# Temp dirs & trash
*.env
/__old__
/temp
/tmp
Expand Down
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ e2e: ## Run end-to-end tests

up: ## Start the application in watch mode
#docker compose build
docker compose kill app-http --remove-orphans 2>/dev/null || true
docker compose kill app-http app-web-serve --remove-orphans 2>/dev/null || true
docker compose up -d app-web-serve --wait # start the web dev server (vite)
@printf "\n\t\033[33m%s\033[0m\n" "Open http://127.0.0.1:8080 in your browser to view the app in production mode (go server)"
@printf "\t\033[33m%s\033[0m\n\n" " or http://127.0.0.1:8081 to view the app web in development mode (vite, nodejs server)"
docker compose up app-http

down: ## Stop the application
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ For custom configuration options, refer to the CLI help below or execute the app
[link_docker_hub]:https://hub.docker.com/r/tarampampam/webhook-tester/

<!--GENERATED:CLI_DOCS-->
<!-- Documentation inside this block generated by github.com/urfave/cli; DO NOT EDIT -->
<!-- Documentation inside this block generated by github.com/urfave/cli-docs/v3; DO NOT EDIT -->
## CLI interface

webhook tester.
Expand Down
51 changes: 47 additions & 4 deletions api/openapi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,16 @@ paths:
'400': {$ref: '#/components/responses/ErrorResponse'} # Bad request
'5XX': {$ref: '#/components/responses/ErrorResponse'} # Server error

/api/session/check/exists:
post:
summary: Batch check if sessions exist by UUID
tags: [api]
operationId: apiSessionCheckExists
requestBody: {$ref: '#/components/requestBodies/CheckSessionExistsRequest'}
responses:
'200': {$ref: '#/components/responses/CheckSessionExistsResponse'}
'5XX': {$ref: '#/components/responses/ErrorResponse'} # Server error

/api/session/{session_uuid}:
get:
summary: Get session options by UUID
Expand All @@ -54,7 +64,7 @@ paths:
'5XX': {$ref: '#/components/responses/ErrorResponse'} # Server error

/api/session/{session_uuid}/requests:
get:
get: # TODO: add possibility to omit the request body
summary: Get the list of requests for a session by UUID
tags: [api]
operationId: apiSessionListRequests
Expand Down Expand Up @@ -96,7 +106,7 @@ paths:
description: WebSocket connection established
content:
application/json:
schema: {$ref: '#/components/schemas/CapturedRequestShort'}
schema: {$ref: '#/components/schemas/RequestEvent'}
'400': {$ref: '#/components/responses/ErrorResponse'} # Bad request
'5XX': {$ref: '#/components/responses/ErrorResponse'} # Server error

Expand Down Expand Up @@ -272,9 +282,19 @@ components:
required: [uuid, client_address, method, request_payload_base64, headers, url, captured_at_unix_milli]
additionalProperties: false

CapturedRequestShort:
RequestEvent:
type: object
properties:
action:
type: string
enum: [create, delete, clear]
example: create
request: {$ref: '#/components/schemas/RequestEventRequest'}
required: [action]
additionalProperties: false

RequestEventRequest:
type: object
description: The same as CapturedRequest, but without the request payload
properties:
uuid: {$ref: '#/components/schemas/UUID'}
client_address: {type: string, example: '214.184.32.7', description: 'May be IPv6 like 2a0e:4005:1002:ffff:185:40:4:132'}
Expand Down Expand Up @@ -352,6 +372,16 @@ components:
application/json:
schema: {$ref: '#/components/schemas/SessionResponseOptions'}

CheckSessionExistsRequest:
description: Check if a session exists by UUID
content:
application/json:
schema:
type: array
items: {$ref: '#/components/schemas/UUID'}
minItems: 1
maxItems: 100

responses: # ---------------------------------------------- RESPONSES -----------------------------------------------
VersionResponse:
description: Information about the version
Expand Down Expand Up @@ -382,6 +412,19 @@ components:
required: [uuid, response, created_at_unix_milli]
additionalProperties: false

CheckSessionExistsResponse:
description: A hashmap of session UUIDs and their existence
content:
application/json:
schema:
type: object
additionalProperties:
type: boolean
example: true
example:
9b6bbab9-c197-4dd3-bc3f-3cb6253820c7: true
9b6bbab9-c197-4dd3-bc3f-3cb6253820c8: false

CapturedRequestsListResponse:
description: List of captured requests, sorted from newest to oldest
content:
Expand Down
32 changes: 22 additions & 10 deletions compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,21 @@ services:
volumes: [.:/src:rw, app-tmp-data:/tmp:rw, app-modules-cache:/var/tmp/go:rw]
security_opt: [no-new-privileges:true]

app-http:
build: *app-build
env_file: [{path: .env, required: false}]
entrypoint: sh -c 'go build -buildvcs=false -o /var/tmp/app ./cmd/webhook-tester/ && exec $0 "$@"'
command: /var/tmp/app start --use-live-frontend --auto-create-sessions --max-requests 16
volumes: [.:/src:rw]
ports: ['8080:8080/tcp'] # open http://127.0.0.1:8080
healthcheck:
test: ['CMD', '/var/tmp/app', 'start', 'healthcheck']
start_interval: 1s
interval: 10s
start_period: 10s
depends_on: {app-web-dist: {condition: service_healthy}}
security_opt: [no-new-privileges:true]

app-web-dist:
build: *app-build
user: node
Expand All @@ -18,18 +33,15 @@ services:
healthcheck: {test: ['CMD', 'test', '-f', './dist/robots.txt'], start_interval: 1s, interval: 10s, start_period: 20s}
security_opt: [no-new-privileges:true]

app-http:
app-web-serve:
build: *app-build
entrypoint: sh -c 'go build -buildvcs=false -o /var/tmp/app ./cmd/webhook-tester/ && exec $0 "$@"'
command: /var/tmp/app start --use-live-frontend --auto-create-sessions --max-requests 8
user: node
volumes: [.:/src:rw]
ports: ['8080:8080/tcp']
healthcheck:
test: ['CMD', '/var/tmp/app', 'start', 'healthcheck']
start_interval: 1s
interval: 10s
start_period: 10s
depends_on: {app-web-dist: {condition: service_healthy}}
working_dir: /src/web
environment: {DEV_SERVER_PROXY_TO: http://app-http:8080} # tell to vite dev server "where is the API"
command: npm run serve -- --host 0.0.0.0 --port 8080
ports: ['8081:8080/tcp'] # open http://127.0.0.1:8081
healthcheck: {test: ['CMD', 'wget', '--spider', '-q', 'http://127.0.0.1:8080'], start_interval: 1s, interval: 3s, start_period: 10s}
security_opt: [no-new-privileges:true]

redis:
Expand Down
6 changes: 3 additions & 3 deletions internal/cli/start/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -380,14 +380,14 @@ func (cmd *command) Run(parentCtx context.Context, log *zap.Logger) error { //no
return fmt.Errorf("unknown storage driver [%s]", cmd.options.storage.driver)
}

var pubSub pubsub.PubSub[pubsub.CapturedRequest]
var pubSub pubsub.PubSub[pubsub.RequestEvent]

// create the Pub/Sub
switch cmd.options.pubSub.driver {
case pubSubDriverMemory:
pubSub = pubsub.NewInMemory[pubsub.CapturedRequest]()
pubSub = pubsub.NewInMemory[pubsub.RequestEvent]()
case pubSubDriverRedis:
pubSub = pubsub.NewRedis[pubsub.CapturedRequest](rdc, encoding.JSON{})
pubSub = pubsub.NewRedis[pubsub.RequestEvent](rdc, encoding.JSON{})
default:
return fmt.Errorf("unknown Pub/Sub driver [%s]", cmd.options.pubSub.driver)
}
Expand Down
39 changes: 37 additions & 2 deletions internal/http/handlers/request_delete/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,57 @@ import (
"context"

"gh.tarampamp.am/webhook-tester/v2/internal/http/openapi"
"gh.tarampamp.am/webhook-tester/v2/internal/pubsub"
"gh.tarampamp.am/webhook-tester/v2/internal/storage"
)

type (
sID = openapi.SessionUUIDInPath
rID = openapi.RequestUUIDInPath

Handler struct{ db storage.Storage }
Handler struct {
appCtx context.Context
db storage.Storage
pub pubsub.Publisher[pubsub.RequestEvent]
}
)

func New(db storage.Storage) *Handler { return &Handler{db: db} }
func New(appCtx context.Context, db storage.Storage, pub pubsub.Publisher[pubsub.RequestEvent]) *Handler {
return &Handler{appCtx: appCtx, db: db, pub: pub}
}

func (h *Handler) Handle(ctx context.Context, sID sID, rID rID) (*openapi.SuccessfulOperationResponse, error) {
// get the request from the storage to notify the subscribers
req, getErr := h.db.GetRequest(ctx, sID.String(), rID.String())
if getErr != nil {
return nil, getErr
}

// delete it
if err := h.db.DeleteRequest(ctx, sID.String(), rID.String()); err != nil {
return nil, err
}

// convert headers to the pubsub format
var headers = make([]pubsub.HttpHeader, len(req.Headers))
for i, rh := range req.Headers {
headers[i] = pubsub.HttpHeader{Name: rh.Name, Value: rh.Value}
}

// notify the subscribers
if err := h.pub.Publish(h.appCtx, sID.String(), pubsub.RequestEvent{ //nolint:contextcheck
Action: pubsub.RequestActionDelete,
Request: &pubsub.Request{
ID: rID.String(),
ClientAddr: req.ClientAddr,
Method: req.Method,
Headers: headers,
URL: req.URL,
CreatedAtUnixMilli: req.CreatedAtUnixMilli,
},
}); err != nil {
return nil, err
}

return &openapi.SuccessfulOperationResponse{Success: true}, nil
}
16 changes: 14 additions & 2 deletions internal/http/handlers/requests_delete_all/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,33 @@ import (
"context"

"gh.tarampamp.am/webhook-tester/v2/internal/http/openapi"
"gh.tarampamp.am/webhook-tester/v2/internal/pubsub"
"gh.tarampamp.am/webhook-tester/v2/internal/storage"
)

type (
sID = openapi.SessionUUIDInPath

Handler struct{ db storage.Storage }
Handler struct {
appCtx context.Context
db storage.Storage
pub pubsub.Publisher[pubsub.RequestEvent]
}
)

func New(db storage.Storage) *Handler { return &Handler{db: db} }
func New(appCtx context.Context, db storage.Storage, pub pubsub.Publisher[pubsub.RequestEvent]) *Handler {
return &Handler{appCtx: appCtx, db: db, pub: pub}
}

func (h *Handler) Handle(ctx context.Context, sID sID) (*openapi.SuccessfulOperationResponse, error) {
if err := h.db.DeleteAllRequests(ctx, sID.String()); err != nil {
return nil, err
}

// notify the subscribers
if err := h.pub.Publish(h.appCtx, sID.String(), pubsub.RequestEvent{Action: pubsub.RequestActionClear}); err != nil { //nolint:contextcheck,lll
return nil, err
}

return &openapi.SuccessfulOperationResponse{Success: true}, nil
}
54 changes: 37 additions & 17 deletions internal/http/handlers/requests_subscribe/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ type (

Handler struct {
db storage.Storage
sub pubsub.Subscriber[pubsub.CapturedRequest]
sub pubsub.Subscriber[pubsub.RequestEvent]
upgrader websocket.Upgrader
}
)

func New(db storage.Storage, sub pubsub.Subscriber[pubsub.CapturedRequest]) *Handler {
func New(db storage.Storage, sub pubsub.Subscriber[pubsub.RequestEvent]) *Handler {
return &Handler{db: db, sub: sub}
}

Expand Down Expand Up @@ -93,7 +93,7 @@ func (*Handler) reader(ctx context.Context, ws *websocket.Conn) error {
// will block until the context is canceled, the client closes the connection, or an error during the writing occurs.
//
// This function sends the captured requests to the client and pings the client periodically.
func (h *Handler) writer(ctx context.Context, ws *websocket.Conn, sub <-chan pubsub.CapturedRequest) error {
func (h *Handler) writer(ctx context.Context, ws *websocket.Conn, sub <-chan pubsub.RequestEvent) error { //nolint:funlen,lll
const pingInterval, pingDeadline = 10 * time.Second, 5 * time.Second

// create a ticker for the ping messages
Expand All @@ -110,25 +110,45 @@ func (h *Handler) writer(ctx context.Context, ws *websocket.Conn, sub <-chan pub
return nil // this should never happen, but just in case
}

rID, pErr := uuid.Parse(r.ID)
if pErr != nil {
continue
var (
action openapi.RequestEventAction
request *openapi.RequestEventRequest
)

switch r.Action {
case pubsub.RequestActionCreate:
action = openapi.RequestEventActionCreate
case pubsub.RequestActionDelete:
action = openapi.RequestEventActionDelete
case pubsub.RequestActionClear:
action = openapi.RequestEventActionClear
default:
continue // skip the unknown action
}

var rHeaders = make([]openapi.HttpHeader, len(r.Headers))
for i, header := range r.Headers {
rHeaders[i].Name, rHeaders[i].Value = header.Name, header.Value
if r.Request != nil {
rID, pErr := uuid.Parse(r.Request.ID)
if pErr != nil {
continue
}

var rHeaders = make([]openapi.HttpHeader, len(r.Request.Headers))
for i, header := range r.Request.Headers {
rHeaders[i].Name, rHeaders[i].Value = header.Name, header.Value
}

request = &openapi.RequestEventRequest{
Uuid: rID,
CapturedAtUnixMilli: r.Request.CreatedAtUnixMilli,
ClientAddress: r.Request.ClientAddr,
Headers: rHeaders,
Method: strings.ToUpper(r.Request.Method),
Url: r.Request.URL,
}
}

// write the response to the client
if err := ws.WriteJSON(openapi.CapturedRequest{
CapturedAtUnixMilli: r.CreatedAtUnixMilli,
ClientAddress: r.ClientAddr,
Headers: rHeaders,
Method: strings.ToUpper(r.Method),
Url: r.URL,
Uuid: rID,
}); err != nil {
if err := ws.WriteJSON(openapi.RequestEvent{Action: action, Request: request}); err != nil {
return fmt.Errorf("failed to write the message: %w", err)
}

Expand Down
Loading