Skip to content

Commit

Permalink
First review changes (#595)
Browse files Browse the repository at this point in the history
  • Loading branch information
tarampampam authored Nov 18, 2024
1 parent ea43c33 commit b813138
Show file tree
Hide file tree
Showing 76 changed files with 2,891 additions and 1,849 deletions.
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

0 comments on commit b813138

Please sign in to comment.