feat: Stage 5 — user management and API key lifecycle

Add full user management section (tab to Users):
- User list table with username, email, created date
- n: create user (username + email form)
- d: delete user with confirmation (cascades API keys)
- k: API key management for selected user
  - n: create new key with name input; one-time reveal
    showing key value and integer key ID prominently
  - r: revoke by integer ID (no list endpoint on server)
- c: copy revealed key to clipboard (atotto/clipboard)

API key listing is unavailable server-side, so the reveal
screen displays the key ID prominently for future revocation.
This commit is contained in:
Niklas Ye
2026-05-21 14:14:07 +02:00
parent ba7a862789
commit 9a50770538
7 changed files with 556 additions and 34 deletions
+41
View File
@@ -255,6 +255,47 @@ func (c *Client) ListUsers() ([]User, error) {
return users, c.do(req, &users)
}
func (c *Client) CreateUser(username, email string) (*User, error) {
body := struct {
Username string `json:"username"`
Email string `json:"email"`
}{Username: username, Email: email}
req, err := c.newRequestWithBody(http.MethodPost, "/api/users", body)
if err != nil {
return nil, err
}
var user User
return &user, c.do(req, &user)
}
func (c *Client) DeleteUser(id int64) error {
req, err := c.newRequest(http.MethodDelete, fmt.Sprintf("/api/users/%d", id))
if err != nil {
return err
}
return c.do(req, nil)
}
func (c *Client) CreateAPIKey(userID int64, name string) (*APIKey, error) {
body := struct {
Name string `json:"name"`
}{Name: name}
req, err := c.newRequestWithBody(http.MethodPost, fmt.Sprintf("/api/users/%d/api-keys", userID), body)
if err != nil {
return nil, err
}
var key APIKey
return &key, c.do(req, &key)
}
func (c *Client) DeleteAPIKey(userID, keyID int64) error {
req, err := c.newRequest(http.MethodDelete, fmt.Sprintf("/api/users/%d/api-keys/%d", userID, keyID))
if err != nil {
return err
}
return c.do(req, nil)
}
// HealthCheck calls GET /healthz (unauthenticated path, no auth needed but we send it anyway).
func (c *Client) HealthCheck() error {
req, err := http.NewRequest(http.MethodGet, c.baseURL+"/healthz", nil)
+9
View File
@@ -63,3 +63,12 @@ type User struct {
Email string `json:"email"`
CreatedAt time.Time `json:"created_at"`
}
type APIKey struct {
ID int64 `json:"id"`
UserID int64 `json:"user_id"`
Name string `json:"name"`
Key string `json:"key,omitempty"` // only present in create response
CreatedAt time.Time `json:"created_at"`
LastUsedAt *time.Time `json:"last_used_at"`
}