Initial MVP implementation of Claudia Docs CLI
- Auth commands: login, logout, status - Document commands: create, list, get, update, delete - Project commands: list, get - Folder commands: list, create - Tag commands: list, create, add - Search command - Reasoning save command - JSON output format with --output text option - Viper-based configuration management - Cobra CLI framework
This commit is contained in:
169
internal/auth/auth.go
Normal file
169
internal/auth/auth.go
Normal file
@@ -0,0 +1,169 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/claudia/docs-cli/internal/api"
|
||||
"github.com/claudia/docs-cli/internal/config"
|
||||
"github.com/claudia/docs-cli/pkg/types"
|
||||
)
|
||||
|
||||
// AuthClient handles authentication
|
||||
type AuthClient struct {
|
||||
client *api.Client
|
||||
}
|
||||
|
||||
// NewAuthClient creates a new auth client
|
||||
func NewAuthClient(c *api.Client) *AuthClient {
|
||||
return &AuthClient{client: c}
|
||||
}
|
||||
|
||||
// Login authenticates a user and returns the token
|
||||
func (a *AuthClient) Login(username, password string) (*types.AuthResponse, error) {
|
||||
req := types.LoginRequest{
|
||||
Username: username,
|
||||
Password: password,
|
||||
}
|
||||
|
||||
resp, err := a.client.DoRequest(http.MethodPost, "/auth/login", req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read response: %w", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusCreated {
|
||||
return nil, handleAuthError(resp, body)
|
||||
}
|
||||
|
||||
var authResp types.Response
|
||||
if err := json.Unmarshal(body, &authResp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mapToAuthResponse(authResp.Data), nil
|
||||
}
|
||||
|
||||
// Register registers a new user
|
||||
func (a *AuthClient) Register(username, password string) (*types.AuthResponse, error) {
|
||||
req := types.RegisterRequest{
|
||||
Username: username,
|
||||
Password: password,
|
||||
}
|
||||
|
||||
resp, err := a.client.DoRequest(http.MethodPost, "/auth/register", req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read response: %w", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusOK {
|
||||
return nil, handleAuthError(resp, body)
|
||||
}
|
||||
|
||||
var authResp types.Response
|
||||
if err := json.Unmarshal(body, &authResp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return mapToAuthResponse(authResp.Data), nil
|
||||
}
|
||||
|
||||
// Me returns the current user info
|
||||
func (a *AuthClient) Me() (map[string]interface{}, error) {
|
||||
resp, err := a.client.DoRequest(http.MethodGet, "/auth/me", nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read response: %w", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, handleAuthError(resp, body)
|
||||
}
|
||||
|
||||
var authResp types.Response
|
||||
if err := json.Unmarshal(body, &authResp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if m, ok := authResp.Data.(map[string]interface{}); ok {
|
||||
return m, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unexpected response format")
|
||||
}
|
||||
|
||||
// Logout clears the saved token
|
||||
func Logout() error {
|
||||
return config.ClearToken()
|
||||
}
|
||||
|
||||
// Status returns the current auth status
|
||||
func Status() (map[string]interface{}, error) {
|
||||
cfg := config.GetConfig()
|
||||
if cfg == nil {
|
||||
return map[string]interface{}{
|
||||
"authenticated": false,
|
||||
"server": "http://localhost:8000",
|
||||
}, nil
|
||||
}
|
||||
|
||||
token := cfg.Token
|
||||
if token == "" {
|
||||
return map[string]interface{}{
|
||||
"authenticated": false,
|
||||
"server": cfg.Server,
|
||||
}, nil
|
||||
}
|
||||
|
||||
return map[string]interface{}{
|
||||
"authenticated": true,
|
||||
"server": cfg.Server,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func handleAuthError(resp *http.Response, body []byte) error {
|
||||
var authResp types.Response
|
||||
if err := json.Unmarshal(body, &authResp); err != nil {
|
||||
return fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
if authResp.Error != nil {
|
||||
return fmt.Errorf("%s: %s", authResp.Error.Code, authResp.Error.Message)
|
||||
}
|
||||
return fmt.Errorf("HTTP %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
func mapToAuthResponse(data interface{}) *types.AuthResponse {
|
||||
resp := &types.AuthResponse{}
|
||||
if m, ok := data.(map[string]interface{}); ok {
|
||||
if v, ok := m["access_token"].(string); ok {
|
||||
resp.Token = v
|
||||
}
|
||||
if v, ok := m["token_type"].(string); ok {
|
||||
resp.TokenType = v
|
||||
}
|
||||
if v, ok := m["expires_at"].(string); ok {
|
||||
resp.ExpiresAt, _ = time.Parse(time.RFC3339, v)
|
||||
} else if v, ok := m["expires_at"].(float64); ok {
|
||||
resp.ExpiresAt = time.Unix(int64(v), 0)
|
||||
}
|
||||
}
|
||||
return resp
|
||||
}
|
||||
Reference in New Issue
Block a user