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:
@@ -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)
|
||||
|
||||
@@ -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"`
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user