feat(docker-watcher): phases 3+4 - Docker client & NPM client
Phase 3: Docker Engine API wrapper — pull/inspect images, container lifecycle (create/start/stop/remove/restart), network management, label-based container tracking, deterministic naming. Phase 4: Nginx Proxy Manager API client — JWT auth with auto-refresh, CRUD for proxy hosts, domain-based host lookup.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Phase 4: NPM Client
|
||||
|
||||
**Status:** ⬜ Not Started
|
||||
**Status:** ✅ Complete
|
||||
**Parent plan:** [PLAN.md](./PLAN.md)
|
||||
**Domain:** backend
|
||||
|
||||
@@ -9,15 +9,15 @@ Implement the Nginx Proxy Manager API client — JWT authentication, CRUD for pr
|
||||
|
||||
## Tasks
|
||||
|
||||
- [ ] Task 1: Create NPM client struct with base URL, cached JWT token, and auto-refresh
|
||||
- [ ] Task 2: Implement `Authenticate(ctx, email, password)` — POST /api/tokens, store JWT
|
||||
- [ ] Task 3: Implement `CreateProxyHost(ctx, config)` — POST /api/nginx/proxy-hosts
|
||||
- [ ] Task 4: Implement `UpdateProxyHost(ctx, id, config)` — PUT /api/nginx/proxy-hosts/{id}
|
||||
- [ ] Task 5: Implement `DeleteProxyHost(ctx, id)` — DELETE /api/nginx/proxy-hosts/{id}
|
||||
- [ ] Task 6: Implement `ListProxyHosts(ctx)` — GET /api/nginx/proxy-hosts
|
||||
- [ ] Task 7: Implement `FindProxyHostByDomain(ctx, domain)` — search existing hosts by domain name
|
||||
- [ ] Task 8: Define proxy host config struct (domain, forward host/port, SSL settings, etc.)
|
||||
- [ ] Task 9: Handle JWT token expiry — re-authenticate automatically on 401
|
||||
- [x] Task 1: Create NPM client struct with base URL, cached JWT token, and auto-refresh
|
||||
- [x] Task 2: Implement `Authenticate(ctx, email, password)` — POST /api/tokens, store JWT
|
||||
- [x] Task 3: Implement `CreateProxyHost(ctx, config)` — POST /api/nginx/proxy-hosts
|
||||
- [x] Task 4: Implement `UpdateProxyHost(ctx, id, config)` — PUT /api/nginx/proxy-hosts/{id}
|
||||
- [x] Task 5: Implement `DeleteProxyHost(ctx, id)` — DELETE /api/nginx/proxy-hosts/{id}
|
||||
- [x] Task 6: Implement `ListProxyHosts(ctx)` — GET /api/nginx/proxy-hosts
|
||||
- [x] Task 7: Implement `FindProxyHostByDomain(ctx, domain)` — search existing hosts by domain name
|
||||
- [x] Task 8: Define proxy host config struct (domain, forward host/port, SSL settings, etc.)
|
||||
- [x] Task 9: Handle JWT token expiry — re-authenticate automatically on 401
|
||||
|
||||
## Files to Modify/Create
|
||||
- `internal/npm/client.go` — NPM API client, auth, HTTP helpers
|
||||
@@ -45,4 +45,34 @@ Implement the Nginx Proxy Manager API client — JWT authentication, CRUD for pr
|
||||
- [ ] Struct types match NPM API contract
|
||||
|
||||
## Handoff to Next Phase
|
||||
<!-- Filled in by the implementation agent after completing this phase. -->
|
||||
|
||||
### What was built
|
||||
|
||||
- `internal/npm/types.go` — `ProxyHostConfig` (create/update input), `ProxyHost` (API response), `Meta`, auth types, and `boolInt` custom JSON type for NPM's 0/1 boolean fields.
|
||||
- `internal/npm/client.go` — Full NPM API client with JWT auth, auto-refresh, and CRUD.
|
||||
|
||||
### Public API surface
|
||||
|
||||
```go
|
||||
npm.New(baseURL string) *Client
|
||||
(*Client).Authenticate(ctx, email, password string) error
|
||||
(*Client).CreateProxyHost(ctx, config ProxyHostConfig) (ProxyHost, error)
|
||||
(*Client).UpdateProxyHost(ctx, id int, config ProxyHostConfig) (ProxyHost, error)
|
||||
(*Client).DeleteProxyHost(ctx, id int) error
|
||||
(*Client).ListProxyHosts(ctx) ([]ProxyHost, error)
|
||||
(*Client).FindProxyHostByDomain(ctx, domain string) (ProxyHost, bool, error)
|
||||
```
|
||||
|
||||
### Key design decisions
|
||||
|
||||
- JWT token is cached with expiry; auto-refreshed 5 minutes before expiry or on 401.
|
||||
- Credentials are stored in memory after `Authenticate` to enable transparent re-auth.
|
||||
- All HTTP errors include the response body text for debugging.
|
||||
- Credentials are never included in error messages.
|
||||
- `boolInt` type handles NPM API's inconsistent 0/1 vs true/false for boolean fields.
|
||||
- `FindProxyHostByDomain` does case-insensitive matching against all domain names.
|
||||
|
||||
### Dependencies for next phase
|
||||
|
||||
- Caller must provide decrypted NPM credentials (email + password from settings via `crypto.Decrypt`).
|
||||
- `ProxyHost.ID` (int) maps to `Instance.NpmProxyID` in the store for tracking.
|
||||
|
||||
Reference in New Issue
Block a user