package service import ( "context" "crypto/sha256" "encoding/hex" "errors" "strings" "time" "backend/internal/model" "backend/internal/repo" ) type APIKeyService struct { keys *repo.APIKeyRepository } func NewAPIKeyService(keys *repo.APIKeyRepository) *APIKeyService { return &APIKeyService{keys: keys} } func (s *APIKeyService) Current(ctx context.Context, userID string) (map[string]any, error) { keys, err := s.keys.ListByUserID(ctx, userID) if err != nil { return nil, err } if len(keys) == 0 { return map[string]any{"key": nil}, nil } key := keys[0] return map[string]any{ "key": map[string]any{ "id": key.ID, "name": key.Name, "key_preview": key.KeyPreview, "created_at": key.CreatedAt, "last_used_at": key.LastUsedAt, }, }, nil } func (s *APIKeyService) Mint(ctx context.Context, userID string) (map[string]any, error) { plain, err := generatePlainAPIKey() if err != nil { return nil, err } key := &model.APIKey{ ID: "k-" + time.Now().Format("150405") + randomSuffix(2), UserID: userID, Name: "default", KeyPreview: previewAPIKey(plain), KeyHash: hashAPIKey(plain), CreatedAt: time.Now(), } if err := s.keys.ReplaceForUser(ctx, userID, key); err != nil { return nil, err } return map[string]any{ "ok": true, "key": plain, "preview": key.KeyPreview, }, nil } func (s *APIKeyService) Revoke(ctx context.Context, userID string) error { return s.keys.DeleteByUserID(ctx, userID) } func (s *APIKeyService) MintNamed(ctx context.Context, userID, name string, replace bool) (map[string]any, error) { name = strings.TrimSpace(name) if name == "" { name = "default" } plain, err := generatePlainAPIKey() if err != nil { return nil, err } key := &model.APIKey{ ID: "k-" + time.Now().Format("150405") + randomSuffix(2), UserID: userID, Name: name, KeyPreview: previewAPIKey(plain), KeyHash: hashAPIKey(plain), CreatedAt: time.Now(), } if replace { if err := s.keys.ReplaceForUser(ctx, userID, key); err != nil { return nil, err } } else { if err := s.keys.Create(ctx, key); err != nil { return nil, err } } return map[string]any{ "ok": true, "key": plain, "preview": key.KeyPreview, "id": key.ID, "name": key.Name, }, nil } func (s *APIKeyService) DeleteOne(ctx context.Context, userID, keyID string) error { if strings.TrimSpace(keyID) == "" { return errors.New("key id required") } return s.keys.DeleteByID(ctx, userID, keyID) } func generatePlainAPIKey() (string, error) { return "sk-" + randomUpper(38), nil } func previewAPIKey(plain string) string { if len(plain) <= 4 { return strings.Repeat("•", len(plain)) } return "…" + plain[len(plain)-4:] } func hashAPIKey(plain string) string { sum := sha256.Sum256([]byte(plain)) return "sha256:" + hex.EncodeToString(sum[:]) } func randomSuffix(n int) string { return randomUpper(n) }