diff --git a/internal/core/hook_core.go b/internal/core/hook_core.go index 5457319..0488a69 100644 --- a/internal/core/hook_core.go +++ b/internal/core/hook_core.go @@ -14,6 +14,7 @@ type TokenGenerator func() string type HookRepository interface { CreateHook(ctx context.Context, token string) (domain.Hook, error) GetHookByToken(ctx context.Context, token string) (domain.Hook, error) + ListHooks(ctx context.Context) ([]domain.Hook, error) CreateWebhookRequest(ctx context.Context, params domain.CreateWebhookRequestParams) (domain.WebhookRequest, error) ListWebhookRequests(ctx context.Context, hookID int64) ([]domain.WebhookRequest, error) GetHookResponse(ctx context.Context, hookID int64) (domain.HookResponse, error) @@ -36,6 +37,10 @@ func NewHook(repo HookRepository, generateToken TokenGenerator) *Hook { } } +func (s *Hook) ListHooks(ctx context.Context) ([]domain.Hook, error) { + return s.repo.ListHooks(ctx) +} + func (s *Hook) CreateHook(ctx context.Context, token string) (domain.Hook, error) { if token == "" { token = s.generateToken() diff --git a/internal/repos/hook.go b/internal/repos/hook.go index 5cf2973..99d39d0 100644 --- a/internal/repos/hook.go +++ b/internal/repos/hook.go @@ -78,6 +78,20 @@ func (r *Hook) ListWebhookRequests(ctx context.Context, hookID int64) ([]domain. return result, nil } +func (r *Hook) ListHooks(ctx context.Context) ([]domain.Hook, error) { + rows, err := r.q.ListHooks(ctx) + if err != nil { + return nil, err + } + + result := make([]domain.Hook, len(rows)) + for i, row := range rows { + result[i] = toDomainHook(row) + } + + return result, nil +} + func (r *Hook) GetHookResponse(ctx context.Context, hookID int64) (domain.HookResponse, error) { row, err := r.q.GetHookResponseByHookID(ctx, hookID) if err != nil { diff --git a/internal/server/contract.go b/internal/server/contract.go index 4a993ec..11f5f73 100644 --- a/internal/server/contract.go +++ b/internal/server/contract.go @@ -19,6 +19,14 @@ type CreateEndpointResponseContract struct { URL string `json:"url"` } +type EndpointListItemContract struct { + ID int64 `json:"id"` + Token string `json:"token"` + Name string `json:"name,omitempty"` + URL string `json:"url"` + CreatedAt time.Time `json:"createdAt"` +} + type WebhookRequestContract struct { ID int64 `json:"id"` HookID int64 `json:"hookId"` diff --git a/internal/server/handler.go b/internal/server/handler.go index 9f91d69..1364e48 100644 --- a/internal/server/handler.go +++ b/internal/server/handler.go @@ -15,6 +15,7 @@ import ( const DefaultMaxBodySize int64 = 5 << 20 // 5MB type HookService interface { + ListHooks(ctx context.Context) ([]domain.Hook, error) CreateHook(ctx context.Context, token string) (domain.Hook, error) ReceiveWebhook(ctx context.Context, token string, params domain.CreateWebhookRequestParams) (domain.WebhookRequest, domain.HookResponse, error) ListWebhookRequests(ctx context.Context, token string) ([]domain.WebhookRequest, error) @@ -54,6 +55,7 @@ func NewHook(deps *HookDeps) *Hook { } func (h *Hook) RegisterRoutes() { + h.deps.Mux.HandleFunc("GET /api/endpoints", h.ListEndpoints) h.deps.Mux.HandleFunc("POST /api/endpoints", h.CreateEndpoint) h.deps.Mux.HandleFunc("GET /api/endpoints/{token}/requests", h.ListRequests) h.deps.Mux.HandleFunc("GET /api/endpoints/{token}/events", h.StreamEvents) @@ -62,6 +64,35 @@ func (h *Hook) RegisterRoutes() { h.deps.Mux.HandleFunc("/r/{token}", h.ReceiveWebhook) } +func (h *Hook) ListEndpoints(w http.ResponseWriter, r *http.Request) { + hooks, err := h.deps.Service.ListHooks(r.Context()) + if err != nil { + slog.Error("list endpoints", "err", err) + SendError(w, http.StatusInternalServerError, ErrInternal) + return + } + + contracts := make([]EndpointListItemContract, len(hooks)) + for i, hook := range hooks { + contracts[i] = EndpointListItemContract{ + ID: hook.ID, + Token: hook.Token, + Name: hook.Name, + URL: h.deps.Opts.BaseURL + "/r/" + hook.Token, + CreatedAt: hook.CreatedAt, + } + } + + data, err := json.Marshal(contracts) + if err != nil { + slog.Error("marshal endpoints", "err", err) + SendError(w, http.StatusInternalServerError, ErrInternal) + return + } + + SendSuccess(w, http.StatusOK, data) +} + func (h *Hook) CreateEndpoint(w http.ResponseWriter, r *http.Request) { if h.readOnly(w) { return diff --git a/internal/store/query/hooks.sql b/internal/store/query/hooks.sql index 5672d69..49ea469 100644 --- a/internal/store/query/hooks.sql +++ b/internal/store/query/hooks.sql @@ -66,6 +66,11 @@ ON CONFLICT (hook_id) DO UPDATE SET updated_at = CURRENT_TIMESTAMP RETURNING id, hook_id, status_code, headers, body, created_at, updated_at; +-- name: ListHooks :many +SELECT id, token, name, created_at, updated_at +FROM hooks +ORDER BY created_at DESC; + -- name: GetHookResponseByHookID :one SELECT id, hook_id, status_code, headers, body, created_at, updated_at FROM hook_responses diff --git a/internal/store/sqlc/hooks.sql.go b/internal/store/sqlc/hooks.sql.go index 3d7a6c5..8a792b6 100644 --- a/internal/store/sqlc/hooks.sql.go +++ b/internal/store/sqlc/hooks.sql.go @@ -153,6 +153,40 @@ func (q *Queries) GetHookResponseByHookID(ctx context.Context, hookID int64) (Ho return i, err } +const listHooks = `-- name: ListHooks :many +SELECT id, token, name, created_at, updated_at +FROM hooks +ORDER BY created_at DESC` + +func (q *Queries) ListHooks(ctx context.Context) ([]Hook, error) { + rows, err := q.db.QueryContext(ctx, listHooks) + if err != nil { + return nil, err + } + defer rows.Close() + var items []Hook + for rows.Next() { + var i Hook + if err := rows.Scan( + &i.ID, + &i.Token, + &i.Name, + &i.CreatedAt, + &i.UpdatedAt, + ); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const listWebhookRequestsByHookID = `-- name: ListWebhookRequestsByHookID :many SELECT id, hook_id, method, path, query, headers, body, remote_addr, content_type, body_size, received_at FROM webhook_requests diff --git a/internal/web/ui/index.html b/internal/web/ui/index.html index a734f00..2f6f448 100644 --- a/internal/web/ui/index.html +++ b/internal/web/ui/index.html @@ -82,14 +82,9 @@