(ctx context.Context, name string)
| 51 | } |
| 52 | |
| 53 | func (s *APIKeyStore) Create(ctx context.Context, name string) (string, *APIKey, error) { |
| 54 | // Generate new API key |
| 55 | rawKey, err := GenerateAPIKey() |
| 56 | if err != nil { |
| 57 | return "", nil, fmt.Errorf("failed to generate API key: %w", err) |
| 58 | } |
| 59 | |
| 60 | // Hash the key for storage |
| 61 | keyHash := HashAPIKey(rawKey) |
| 62 | |
| 63 | // Start a transaction |
| 64 | tx, err := s.db.BeginTx(ctx, nil) |
| 65 | if err != nil { |
| 66 | return "", nil, fmt.Errorf("failed to begin transaction: %w", err) |
| 67 | } |
| 68 | defer tx.Rollback() |
| 69 | |
| 70 | // Intern the name |
| 71 | ids, err := dbinterface.InternStringNullable(ctx, tx, &name) |
| 72 | if err != nil { |
| 73 | return "", nil, fmt.Errorf("failed to intern name: %w", err) |
| 74 | } |
| 75 | |
| 76 | // Insert the API key |
| 77 | apiKey := &APIKey{} |
| 78 | var createdAt, lastUsedAt sql.NullTime |
| 79 | err = tx.QueryRowContext(ctx, ` |
| 80 | INSERT INTO api_keys (key_hash, name_id) |
| 81 | VALUES (?, ?) |
| 82 | RETURNING id, key_hash, created_at, last_used_at |
| 83 | `, keyHash, ids[0]).Scan( |
| 84 | &apiKey.ID, |
| 85 | &apiKey.KeyHash, |
| 86 | &createdAt, |
| 87 | &lastUsedAt, |
| 88 | ) |
| 89 | |
| 90 | if err != nil { |
| 91 | return "", nil, err |
| 92 | } |
| 93 | |
| 94 | if err = tx.Commit(); err != nil { |
| 95 | return "", nil, fmt.Errorf("failed to commit transaction: %w", err) |
| 96 | } |
| 97 | |
| 98 | apiKey.Name = name |
| 99 | apiKey.CreatedAt = createdAt.Time |
| 100 | if lastUsedAt.Valid { |
| 101 | apiKey.LastUsedAt = &lastUsedAt.Time |
| 102 | } |
| 103 | |
| 104 | // Return both the raw key (to show user once) and the model |
| 105 | return rawKey, apiKey, nil |
| 106 | } |
| 107 | |
| 108 | func (s *APIKeyStore) GetByHash(ctx context.Context, keyHash string) (*APIKey, error) { |
| 109 | query := ` |
nothing calls this directly
no test coverage detected