v0
This commit is contained in:
273
internal/repository/interfaces.go
Normal file
273
internal/repository/interfaces.go
Normal file
@ -0,0 +1,273 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/kms/api-key-service/internal/domain"
|
||||
)
|
||||
|
||||
// ApplicationRepository defines the interface for application data operations
|
||||
type ApplicationRepository interface {
|
||||
// Create creates a new application
|
||||
Create(ctx context.Context, app *domain.Application) error
|
||||
|
||||
// GetByID retrieves an application by its ID
|
||||
GetByID(ctx context.Context, appID string) (*domain.Application, error)
|
||||
|
||||
// List retrieves applications with pagination
|
||||
List(ctx context.Context, limit, offset int) ([]*domain.Application, error)
|
||||
|
||||
// Update updates an existing application
|
||||
Update(ctx context.Context, appID string, updates *domain.UpdateApplicationRequest) (*domain.Application, error)
|
||||
|
||||
// Delete deletes an application
|
||||
Delete(ctx context.Context, appID string) error
|
||||
|
||||
// Exists checks if an application exists
|
||||
Exists(ctx context.Context, appID string) (bool, error)
|
||||
}
|
||||
|
||||
// StaticTokenRepository defines the interface for static token data operations
|
||||
type StaticTokenRepository interface {
|
||||
// Create creates a new static token
|
||||
Create(ctx context.Context, token *domain.StaticToken) error
|
||||
|
||||
// GetByID retrieves a static token by its ID
|
||||
GetByID(ctx context.Context, tokenID uuid.UUID) (*domain.StaticToken, error)
|
||||
|
||||
// GetByKeyHash retrieves a static token by its key hash
|
||||
GetByKeyHash(ctx context.Context, keyHash string) (*domain.StaticToken, error)
|
||||
|
||||
// GetByAppID retrieves all static tokens for an application
|
||||
GetByAppID(ctx context.Context, appID string) ([]*domain.StaticToken, error)
|
||||
|
||||
// List retrieves static tokens with pagination
|
||||
List(ctx context.Context, limit, offset int) ([]*domain.StaticToken, error)
|
||||
|
||||
// Delete deletes a static token
|
||||
Delete(ctx context.Context, tokenID uuid.UUID) error
|
||||
|
||||
// Exists checks if a static token exists
|
||||
Exists(ctx context.Context, tokenID uuid.UUID) (bool, error)
|
||||
}
|
||||
|
||||
// PermissionRepository defines the interface for permission data operations
|
||||
type PermissionRepository interface {
|
||||
// CreateAvailablePermission creates a new available permission
|
||||
CreateAvailablePermission(ctx context.Context, permission *domain.AvailablePermission) error
|
||||
|
||||
// GetAvailablePermission retrieves an available permission by ID
|
||||
GetAvailablePermission(ctx context.Context, permissionID uuid.UUID) (*domain.AvailablePermission, error)
|
||||
|
||||
// GetAvailablePermissionByScope retrieves an available permission by scope
|
||||
GetAvailablePermissionByScope(ctx context.Context, scope string) (*domain.AvailablePermission, error)
|
||||
|
||||
// ListAvailablePermissions retrieves available permissions with pagination and filtering
|
||||
ListAvailablePermissions(ctx context.Context, category string, includeSystem bool, limit, offset int) ([]*domain.AvailablePermission, error)
|
||||
|
||||
// UpdateAvailablePermission updates an available permission
|
||||
UpdateAvailablePermission(ctx context.Context, permissionID uuid.UUID, permission *domain.AvailablePermission) error
|
||||
|
||||
// DeleteAvailablePermission deletes an available permission
|
||||
DeleteAvailablePermission(ctx context.Context, permissionID uuid.UUID) error
|
||||
|
||||
// ValidatePermissionScopes checks if all given scopes exist and are valid
|
||||
ValidatePermissionScopes(ctx context.Context, scopes []string) ([]string, error) // returns invalid scopes
|
||||
|
||||
// GetPermissionHierarchy returns all parent and child permissions for given scopes
|
||||
GetPermissionHierarchy(ctx context.Context, scopes []string) ([]*domain.AvailablePermission, error)
|
||||
}
|
||||
|
||||
// GrantedPermissionRepository defines the interface for granted permission operations
|
||||
type GrantedPermissionRepository interface {
|
||||
// GrantPermissions grants multiple permissions to a token
|
||||
GrantPermissions(ctx context.Context, grants []*domain.GrantedPermission) error
|
||||
|
||||
// GetGrantedPermissions retrieves all granted permissions for a token
|
||||
GetGrantedPermissions(ctx context.Context, tokenType domain.TokenType, tokenID uuid.UUID) ([]*domain.GrantedPermission, error)
|
||||
|
||||
// GetGrantedPermissionScopes retrieves only the scopes for a token (more efficient)
|
||||
GetGrantedPermissionScopes(ctx context.Context, tokenType domain.TokenType, tokenID uuid.UUID) ([]string, error)
|
||||
|
||||
// RevokePermission revokes a specific permission from a token
|
||||
RevokePermission(ctx context.Context, grantID uuid.UUID, revokedBy string) error
|
||||
|
||||
// RevokeAllPermissions revokes all permissions from a token
|
||||
RevokeAllPermissions(ctx context.Context, tokenType domain.TokenType, tokenID uuid.UUID, revokedBy string) error
|
||||
|
||||
// HasPermission checks if a token has a specific permission
|
||||
HasPermission(ctx context.Context, tokenType domain.TokenType, tokenID uuid.UUID, scope string) (bool, error)
|
||||
|
||||
// HasAnyPermission checks if a token has any of the specified permissions
|
||||
HasAnyPermission(ctx context.Context, tokenType domain.TokenType, tokenID uuid.UUID, scopes []string) (map[string]bool, error)
|
||||
}
|
||||
|
||||
// DatabaseProvider defines the interface for database operations
|
||||
type DatabaseProvider interface {
|
||||
// GetDB returns the underlying database connection
|
||||
GetDB() interface{}
|
||||
|
||||
// Ping checks the database connection
|
||||
Ping(ctx context.Context) error
|
||||
|
||||
// Close closes all database connections
|
||||
Close() error
|
||||
|
||||
// BeginTx starts a database transaction
|
||||
BeginTx(ctx context.Context) (TransactionProvider, error)
|
||||
|
||||
// Migrate runs database migrations
|
||||
Migrate(ctx context.Context, migrationPath string) error
|
||||
}
|
||||
|
||||
// TransactionProvider defines the interface for database transaction operations
|
||||
type TransactionProvider interface {
|
||||
// Commit commits the transaction
|
||||
Commit() error
|
||||
|
||||
// Rollback rolls back the transaction
|
||||
Rollback() error
|
||||
|
||||
// GetTx returns the underlying transaction
|
||||
GetTx() interface{}
|
||||
}
|
||||
|
||||
// CacheProvider defines the interface for caching operations
|
||||
type CacheProvider interface {
|
||||
// Get retrieves a value from cache
|
||||
Get(ctx context.Context, key string) ([]byte, error)
|
||||
|
||||
// Set stores a value in cache with expiration
|
||||
Set(ctx context.Context, key string, value []byte, expiration time.Duration) error
|
||||
|
||||
// Delete removes a value from cache
|
||||
Delete(ctx context.Context, key string) error
|
||||
|
||||
// Exists checks if a key exists in cache
|
||||
Exists(ctx context.Context, key string) (bool, error)
|
||||
|
||||
// Flush clears all cache entries
|
||||
Flush(ctx context.Context) error
|
||||
|
||||
// Close closes the cache connection
|
||||
Close() error
|
||||
}
|
||||
|
||||
// TokenProvider defines the interface for token operations
|
||||
type TokenProvider interface {
|
||||
// GenerateUserToken generates a JWT token for user authentication
|
||||
GenerateUserToken(ctx context.Context, userToken *domain.UserToken, hmacKey string) (string, error)
|
||||
|
||||
// ValidateUserToken validates and parses a JWT token
|
||||
ValidateUserToken(ctx context.Context, token string, hmacKey string) (*domain.UserToken, error)
|
||||
|
||||
// GenerateStaticToken generates a static API key
|
||||
GenerateStaticToken(ctx context.Context) (string, error)
|
||||
|
||||
// HashStaticToken creates a secure hash of a static token
|
||||
HashStaticToken(ctx context.Context, token string) (string, error)
|
||||
|
||||
// ValidateStaticToken validates a static token against its hash
|
||||
ValidateStaticToken(ctx context.Context, token, hash string) (bool, error)
|
||||
|
||||
// RenewUserToken renews a user token while preserving max validity
|
||||
RenewUserToken(ctx context.Context, currentToken *domain.UserToken, renewalDuration time.Duration, hmacKey string) (string, error)
|
||||
}
|
||||
|
||||
// HashProvider defines the interface for cryptographic hashing operations
|
||||
type HashProvider interface {
|
||||
// Hash creates a secure hash of the input
|
||||
Hash(ctx context.Context, input string) (string, error)
|
||||
|
||||
// Compare compares an input against a hash
|
||||
Compare(ctx context.Context, input, hash string) (bool, error)
|
||||
|
||||
// GenerateKey generates a secure random key
|
||||
GenerateKey(ctx context.Context, length int) (string, error)
|
||||
}
|
||||
|
||||
// LoggerProvider defines the interface for logging operations
|
||||
type LoggerProvider interface {
|
||||
// Info logs an info level message
|
||||
Info(ctx context.Context, msg string, fields ...interface{})
|
||||
|
||||
// Warn logs a warning level message
|
||||
Warn(ctx context.Context, msg string, fields ...interface{})
|
||||
|
||||
// Error logs an error level message
|
||||
Error(ctx context.Context, msg string, err error, fields ...interface{})
|
||||
|
||||
// Debug logs a debug level message
|
||||
Debug(ctx context.Context, msg string, fields ...interface{})
|
||||
|
||||
// With returns a logger with additional fields
|
||||
With(fields ...interface{}) LoggerProvider
|
||||
}
|
||||
|
||||
// ConfigProvider defines the interface for configuration operations
|
||||
type ConfigProvider interface {
|
||||
// GetString retrieves a string configuration value
|
||||
GetString(key string) string
|
||||
|
||||
// GetInt retrieves an integer configuration value
|
||||
GetInt(key string) int
|
||||
|
||||
// GetBool retrieves a boolean configuration value
|
||||
GetBool(key string) bool
|
||||
|
||||
// GetDuration retrieves a duration configuration value
|
||||
GetDuration(key string) time.Duration
|
||||
|
||||
// GetStringSlice retrieves a string slice configuration value
|
||||
GetStringSlice(key string) []string
|
||||
|
||||
// IsSet checks if a configuration key is set
|
||||
IsSet(key string) bool
|
||||
|
||||
// Validate validates all required configuration values
|
||||
Validate() error
|
||||
}
|
||||
|
||||
// AuthenticationProvider defines the interface for user authentication
|
||||
type AuthenticationProvider interface {
|
||||
// GetUserID extracts the user ID from the request context/headers
|
||||
GetUserID(ctx context.Context) (string, error)
|
||||
|
||||
// ValidateUser validates if the user is authentic
|
||||
ValidateUser(ctx context.Context, userID string) error
|
||||
|
||||
// GetUserClaims retrieves additional user information/claims
|
||||
GetUserClaims(ctx context.Context, userID string) (map[string]string, error)
|
||||
|
||||
// Name returns the provider name for identification
|
||||
Name() string
|
||||
}
|
||||
|
||||
// RateLimitProvider defines the interface for rate limiting operations
|
||||
type RateLimitProvider interface {
|
||||
// Allow checks if a request should be allowed for the given identifier
|
||||
Allow(ctx context.Context, identifier string) (bool, error)
|
||||
|
||||
// Remaining returns the number of remaining requests for the identifier
|
||||
Remaining(ctx context.Context, identifier string) (int, error)
|
||||
|
||||
// Reset returns when the rate limit will reset for the identifier
|
||||
Reset(ctx context.Context, identifier string) (time.Time, error)
|
||||
}
|
||||
|
||||
// MetricsProvider defines the interface for metrics collection
|
||||
type MetricsProvider interface {
|
||||
// IncrementCounter increments a counter metric
|
||||
IncrementCounter(ctx context.Context, name string, labels map[string]string)
|
||||
|
||||
// RecordHistogram records a value in a histogram
|
||||
RecordHistogram(ctx context.Context, name string, value float64, labels map[string]string)
|
||||
|
||||
// SetGauge sets a gauge metric value
|
||||
SetGauge(ctx context.Context, name string, value float64, labels map[string]string)
|
||||
|
||||
// RecordDuration records the duration of an operation
|
||||
RecordDuration(ctx context.Context, name string, duration time.Duration, labels map[string]string)
|
||||
}
|
||||
343
internal/repository/postgres/application_repository.go
Normal file
343
internal/repository/postgres/application_repository.go
Normal file
@ -0,0 +1,343 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/lib/pq"
|
||||
"github.com/kms/api-key-service/internal/domain"
|
||||
"github.com/kms/api-key-service/internal/repository"
|
||||
)
|
||||
|
||||
// ApplicationRepository implements the ApplicationRepository interface for PostgreSQL
|
||||
type ApplicationRepository struct {
|
||||
db repository.DatabaseProvider
|
||||
}
|
||||
|
||||
// NewApplicationRepository creates a new PostgreSQL application repository
|
||||
func NewApplicationRepository(db repository.DatabaseProvider) repository.ApplicationRepository {
|
||||
return &ApplicationRepository{db: db}
|
||||
}
|
||||
|
||||
// Create creates a new application
|
||||
func (r *ApplicationRepository) Create(ctx context.Context, app *domain.Application) error {
|
||||
query := `
|
||||
INSERT INTO applications (
|
||||
app_id, app_link, type, callback_url, hmac_key,
|
||||
token_renewal_duration, max_token_duration,
|
||||
owner_type, owner_name, owner_owner,
|
||||
created_at, updated_at
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
||||
`
|
||||
|
||||
db := r.db.GetDB().(*sql.DB)
|
||||
now := time.Now()
|
||||
|
||||
// Convert application types to string array
|
||||
typeStrings := make([]string, len(app.Type))
|
||||
for i, t := range app.Type {
|
||||
typeStrings[i] = string(t)
|
||||
}
|
||||
|
||||
_, err := db.ExecContext(ctx, query,
|
||||
app.AppID,
|
||||
app.AppLink,
|
||||
pq.Array(typeStrings),
|
||||
app.CallbackURL,
|
||||
app.HMACKey,
|
||||
app.TokenRenewalDuration.Nanoseconds(),
|
||||
app.MaxTokenDuration.Nanoseconds(),
|
||||
string(app.Owner.Type),
|
||||
app.Owner.Name,
|
||||
app.Owner.Owner,
|
||||
now,
|
||||
now,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
if isUniqueViolation(err) {
|
||||
return fmt.Errorf("application with ID '%s' already exists", app.AppID)
|
||||
}
|
||||
return fmt.Errorf("failed to create application: %w", err)
|
||||
}
|
||||
|
||||
app.CreatedAt = now
|
||||
app.UpdatedAt = now
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetByID retrieves an application by its ID
|
||||
func (r *ApplicationRepository) GetByID(ctx context.Context, appID string) (*domain.Application, error) {
|
||||
query := `
|
||||
SELECT app_id, app_link, type, callback_url, hmac_key,
|
||||
token_renewal_duration, max_token_duration,
|
||||
owner_type, owner_name, owner_owner,
|
||||
created_at, updated_at
|
||||
FROM applications
|
||||
WHERE app_id = $1
|
||||
`
|
||||
|
||||
db := r.db.GetDB().(*sql.DB)
|
||||
row := db.QueryRowContext(ctx, query, appID)
|
||||
|
||||
app := &domain.Application{}
|
||||
var typeStrings pq.StringArray
|
||||
var tokenRenewalNanos, maxTokenNanos int64
|
||||
var ownerType string
|
||||
|
||||
err := row.Scan(
|
||||
&app.AppID,
|
||||
&app.AppLink,
|
||||
&typeStrings,
|
||||
&app.CallbackURL,
|
||||
&app.HMACKey,
|
||||
&tokenRenewalNanos,
|
||||
&maxTokenNanos,
|
||||
&ownerType,
|
||||
&app.Owner.Name,
|
||||
&app.Owner.Owner,
|
||||
&app.CreatedAt,
|
||||
&app.UpdatedAt,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, fmt.Errorf("application with ID '%s' not found", appID)
|
||||
}
|
||||
return nil, fmt.Errorf("failed to get application: %w", err)
|
||||
}
|
||||
|
||||
// Convert string array to application types
|
||||
app.Type = make([]domain.ApplicationType, len(typeStrings))
|
||||
for i, t := range typeStrings {
|
||||
app.Type[i] = domain.ApplicationType(t)
|
||||
}
|
||||
|
||||
// Convert nanoseconds to duration
|
||||
app.TokenRenewalDuration = time.Duration(tokenRenewalNanos)
|
||||
app.MaxTokenDuration = time.Duration(maxTokenNanos)
|
||||
|
||||
// Convert owner type
|
||||
app.Owner.Type = domain.OwnerType(ownerType)
|
||||
|
||||
return app, nil
|
||||
}
|
||||
|
||||
// List retrieves applications with pagination
|
||||
func (r *ApplicationRepository) List(ctx context.Context, limit, offset int) ([]*domain.Application, error) {
|
||||
query := `
|
||||
SELECT app_id, app_link, type, callback_url, hmac_key,
|
||||
token_renewal_duration, max_token_duration,
|
||||
owner_type, owner_name, owner_owner,
|
||||
created_at, updated_at
|
||||
FROM applications
|
||||
ORDER BY created_at DESC
|
||||
LIMIT $1 OFFSET $2
|
||||
`
|
||||
|
||||
db := r.db.GetDB().(*sql.DB)
|
||||
rows, err := db.QueryContext(ctx, query, limit, offset)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list applications: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var applications []*domain.Application
|
||||
|
||||
for rows.Next() {
|
||||
app := &domain.Application{}
|
||||
var typeStrings pq.StringArray
|
||||
var tokenRenewalNanos, maxTokenNanos int64
|
||||
var ownerType string
|
||||
|
||||
err := rows.Scan(
|
||||
&app.AppID,
|
||||
&app.AppLink,
|
||||
&typeStrings,
|
||||
&app.CallbackURL,
|
||||
&app.HMACKey,
|
||||
&tokenRenewalNanos,
|
||||
&maxTokenNanos,
|
||||
&ownerType,
|
||||
&app.Owner.Name,
|
||||
&app.Owner.Owner,
|
||||
&app.CreatedAt,
|
||||
&app.UpdatedAt,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to scan application: %w", err)
|
||||
}
|
||||
|
||||
// Convert string array to application types
|
||||
app.Type = make([]domain.ApplicationType, len(typeStrings))
|
||||
for i, t := range typeStrings {
|
||||
app.Type[i] = domain.ApplicationType(t)
|
||||
}
|
||||
|
||||
// Convert nanoseconds to duration
|
||||
app.TokenRenewalDuration = time.Duration(tokenRenewalNanos)
|
||||
app.MaxTokenDuration = time.Duration(maxTokenNanos)
|
||||
|
||||
// Convert owner type
|
||||
app.Owner.Type = domain.OwnerType(ownerType)
|
||||
|
||||
applications = append(applications, app)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, fmt.Errorf("failed to iterate applications: %w", err)
|
||||
}
|
||||
|
||||
return applications, nil
|
||||
}
|
||||
|
||||
// Update updates an existing application
|
||||
func (r *ApplicationRepository) Update(ctx context.Context, appID string, updates *domain.UpdateApplicationRequest) (*domain.Application, error) {
|
||||
// Build dynamic update query
|
||||
var setParts []string
|
||||
var args []interface{}
|
||||
argIndex := 1
|
||||
|
||||
if updates.AppLink != nil {
|
||||
setParts = append(setParts, fmt.Sprintf("app_link = $%d", argIndex))
|
||||
args = append(args, *updates.AppLink)
|
||||
argIndex++
|
||||
}
|
||||
|
||||
if updates.Type != nil {
|
||||
typeStrings := make([]string, len(*updates.Type))
|
||||
for i, t := range *updates.Type {
|
||||
typeStrings[i] = string(t)
|
||||
}
|
||||
setParts = append(setParts, fmt.Sprintf("type = $%d", argIndex))
|
||||
args = append(args, pq.Array(typeStrings))
|
||||
argIndex++
|
||||
}
|
||||
|
||||
if updates.CallbackURL != nil {
|
||||
setParts = append(setParts, fmt.Sprintf("callback_url = $%d", argIndex))
|
||||
args = append(args, *updates.CallbackURL)
|
||||
argIndex++
|
||||
}
|
||||
|
||||
if updates.HMACKey != nil {
|
||||
setParts = append(setParts, fmt.Sprintf("hmac_key = $%d", argIndex))
|
||||
args = append(args, *updates.HMACKey)
|
||||
argIndex++
|
||||
}
|
||||
|
||||
if updates.TokenRenewalDuration != nil {
|
||||
setParts = append(setParts, fmt.Sprintf("token_renewal_duration = $%d", argIndex))
|
||||
args = append(args, updates.TokenRenewalDuration.Nanoseconds())
|
||||
argIndex++
|
||||
}
|
||||
|
||||
if updates.MaxTokenDuration != nil {
|
||||
setParts = append(setParts, fmt.Sprintf("max_token_duration = $%d", argIndex))
|
||||
args = append(args, updates.MaxTokenDuration.Nanoseconds())
|
||||
argIndex++
|
||||
}
|
||||
|
||||
if updates.Owner != nil {
|
||||
setParts = append(setParts, fmt.Sprintf("owner_type = $%d", argIndex))
|
||||
args = append(args, string(updates.Owner.Type))
|
||||
argIndex++
|
||||
|
||||
setParts = append(setParts, fmt.Sprintf("owner_name = $%d", argIndex))
|
||||
args = append(args, updates.Owner.Name)
|
||||
argIndex++
|
||||
|
||||
setParts = append(setParts, fmt.Sprintf("owner_owner = $%d", argIndex))
|
||||
args = append(args, updates.Owner.Owner)
|
||||
argIndex++
|
||||
}
|
||||
|
||||
if len(setParts) == 0 {
|
||||
return r.GetByID(ctx, appID) // No updates, return current state
|
||||
}
|
||||
|
||||
// Always update the updated_at field
|
||||
setParts = append(setParts, fmt.Sprintf("updated_at = $%d", argIndex))
|
||||
args = append(args, time.Now())
|
||||
argIndex++
|
||||
|
||||
// Add WHERE clause
|
||||
args = append(args, appID)
|
||||
|
||||
query := fmt.Sprintf(`
|
||||
UPDATE applications
|
||||
SET %s
|
||||
WHERE app_id = $%d
|
||||
`, strings.Join(setParts, ", "), argIndex)
|
||||
|
||||
db := r.db.GetDB().(*sql.DB)
|
||||
result, err := db.ExecContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to update application: %w", err)
|
||||
}
|
||||
|
||||
rowsAffected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get rows affected: %w", err)
|
||||
}
|
||||
|
||||
if rowsAffected == 0 {
|
||||
return nil, fmt.Errorf("application with ID '%s' not found", appID)
|
||||
}
|
||||
|
||||
// Return updated application
|
||||
return r.GetByID(ctx, appID)
|
||||
}
|
||||
|
||||
// Delete deletes an application
|
||||
func (r *ApplicationRepository) Delete(ctx context.Context, appID string) error {
|
||||
query := `DELETE FROM applications WHERE app_id = $1`
|
||||
|
||||
db := r.db.GetDB().(*sql.DB)
|
||||
result, err := db.ExecContext(ctx, query, appID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete application: %w", err)
|
||||
}
|
||||
|
||||
rowsAffected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get rows affected: %w", err)
|
||||
}
|
||||
|
||||
if rowsAffected == 0 {
|
||||
return fmt.Errorf("application with ID '%s' not found", appID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Exists checks if an application exists
|
||||
func (r *ApplicationRepository) Exists(ctx context.Context, appID string) (bool, error) {
|
||||
query := `SELECT 1 FROM applications WHERE app_id = $1`
|
||||
|
||||
db := r.db.GetDB().(*sql.DB)
|
||||
var exists int
|
||||
err := db.QueryRowContext(ctx, query, appID).Scan(&exists)
|
||||
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return false, nil
|
||||
}
|
||||
return false, fmt.Errorf("failed to check application existence: %w", err)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// isUniqueViolation checks if the error is a unique constraint violation
|
||||
func isUniqueViolation(err error) bool {
|
||||
if pqErr, ok := err.(*pq.Error); ok {
|
||||
return pqErr.Code == "23505" // unique_violation
|
||||
}
|
||||
return false
|
||||
}
|
||||
124
internal/repository/postgres/permission_repository.go
Normal file
124
internal/repository/postgres/permission_repository.go
Normal file
@ -0,0 +1,124 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/kms/api-key-service/internal/domain"
|
||||
"github.com/kms/api-key-service/internal/repository"
|
||||
)
|
||||
|
||||
// PermissionRepository implements the PermissionRepository interface for PostgreSQL
|
||||
type PermissionRepository struct {
|
||||
db repository.DatabaseProvider
|
||||
}
|
||||
|
||||
// NewPermissionRepository creates a new PostgreSQL permission repository
|
||||
func NewPermissionRepository(db repository.DatabaseProvider) repository.PermissionRepository {
|
||||
return &PermissionRepository{db: db}
|
||||
}
|
||||
|
||||
// CreateAvailablePermission creates a new available permission
|
||||
func (r *PermissionRepository) CreateAvailablePermission(ctx context.Context, permission *domain.AvailablePermission) error {
|
||||
// TODO: Implement actual permission creation
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAvailablePermission retrieves an available permission by ID
|
||||
func (r *PermissionRepository) GetAvailablePermission(ctx context.Context, permissionID uuid.UUID) (*domain.AvailablePermission, error) {
|
||||
// TODO: Implement actual permission retrieval
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// GetAvailablePermissionByScope retrieves an available permission by scope
|
||||
func (r *PermissionRepository) GetAvailablePermissionByScope(ctx context.Context, scope string) (*domain.AvailablePermission, error) {
|
||||
// TODO: Implement actual permission retrieval by scope
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// ListAvailablePermissions retrieves available permissions with pagination and filtering
|
||||
func (r *PermissionRepository) ListAvailablePermissions(ctx context.Context, category string, includeSystem bool, limit, offset int) ([]*domain.AvailablePermission, error) {
|
||||
// TODO: Implement actual permission listing
|
||||
return []*domain.AvailablePermission{}, nil
|
||||
}
|
||||
|
||||
// UpdateAvailablePermission updates an available permission
|
||||
func (r *PermissionRepository) UpdateAvailablePermission(ctx context.Context, permissionID uuid.UUID, permission *domain.AvailablePermission) error {
|
||||
// TODO: Implement actual permission update
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteAvailablePermission deletes an available permission
|
||||
func (r *PermissionRepository) DeleteAvailablePermission(ctx context.Context, permissionID uuid.UUID) error {
|
||||
// TODO: Implement actual permission deletion
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidatePermissionScopes checks if all given scopes exist and are valid
|
||||
func (r *PermissionRepository) ValidatePermissionScopes(ctx context.Context, scopes []string) ([]string, error) {
|
||||
// TODO: Implement actual scope validation
|
||||
// For now, assume all scopes are valid
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
// GetPermissionHierarchy returns all parent and child permissions for given scopes
|
||||
func (r *PermissionRepository) GetPermissionHierarchy(ctx context.Context, scopes []string) ([]*domain.AvailablePermission, error) {
|
||||
// TODO: Implement actual permission hierarchy retrieval
|
||||
return []*domain.AvailablePermission{}, nil
|
||||
}
|
||||
|
||||
// GrantedPermissionRepository implements the GrantedPermissionRepository interface for PostgreSQL
|
||||
type GrantedPermissionRepository struct {
|
||||
db repository.DatabaseProvider
|
||||
}
|
||||
|
||||
// NewGrantedPermissionRepository creates a new PostgreSQL granted permission repository
|
||||
func NewGrantedPermissionRepository(db repository.DatabaseProvider) repository.GrantedPermissionRepository {
|
||||
return &GrantedPermissionRepository{db: db}
|
||||
}
|
||||
|
||||
// GrantPermissions grants multiple permissions to a token
|
||||
func (r *GrantedPermissionRepository) GrantPermissions(ctx context.Context, grants []*domain.GrantedPermission) error {
|
||||
// TODO: Implement actual permission granting
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetGrantedPermissions retrieves all granted permissions for a token
|
||||
func (r *GrantedPermissionRepository) GetGrantedPermissions(ctx context.Context, tokenType domain.TokenType, tokenID uuid.UUID) ([]*domain.GrantedPermission, error) {
|
||||
// TODO: Implement actual granted permissions retrieval
|
||||
return []*domain.GrantedPermission{}, nil
|
||||
}
|
||||
|
||||
// GetGrantedPermissionScopes retrieves only the scopes for a token (more efficient)
|
||||
func (r *GrantedPermissionRepository) GetGrantedPermissionScopes(ctx context.Context, tokenType domain.TokenType, tokenID uuid.UUID) ([]string, error) {
|
||||
// TODO: Implement actual scope retrieval
|
||||
return []string{}, nil
|
||||
}
|
||||
|
||||
// RevokePermission revokes a specific permission from a token
|
||||
func (r *GrantedPermissionRepository) RevokePermission(ctx context.Context, grantID uuid.UUID, revokedBy string) error {
|
||||
// TODO: Implement actual permission revocation
|
||||
return nil
|
||||
}
|
||||
|
||||
// RevokeAllPermissions revokes all permissions from a token
|
||||
func (r *GrantedPermissionRepository) RevokeAllPermissions(ctx context.Context, tokenType domain.TokenType, tokenID uuid.UUID, revokedBy string) error {
|
||||
// TODO: Implement actual permission revocation
|
||||
return nil
|
||||
}
|
||||
|
||||
// HasPermission checks if a token has a specific permission
|
||||
func (r *GrantedPermissionRepository) HasPermission(ctx context.Context, tokenType domain.TokenType, tokenID uuid.UUID, scope string) (bool, error) {
|
||||
// TODO: Implement actual permission checking
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// HasAnyPermission checks if a token has any of the specified permissions
|
||||
func (r *GrantedPermissionRepository) HasAnyPermission(ctx context.Context, tokenType domain.TokenType, tokenID uuid.UUID, scopes []string) (map[string]bool, error) {
|
||||
// TODO: Implement actual permission checking
|
||||
result := make(map[string]bool)
|
||||
for _, scope := range scopes {
|
||||
result[scope] = true
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
120
internal/repository/postgres/token_repository.go
Normal file
120
internal/repository/postgres/token_repository.go
Normal file
@ -0,0 +1,120 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/kms/api-key-service/internal/domain"
|
||||
"github.com/kms/api-key-service/internal/repository"
|
||||
)
|
||||
|
||||
// StaticTokenRepository implements the StaticTokenRepository interface for PostgreSQL
|
||||
type StaticTokenRepository struct {
|
||||
db repository.DatabaseProvider
|
||||
}
|
||||
|
||||
// NewStaticTokenRepository creates a new PostgreSQL static token repository
|
||||
func NewStaticTokenRepository(db repository.DatabaseProvider) repository.StaticTokenRepository {
|
||||
return &StaticTokenRepository{db: db}
|
||||
}
|
||||
|
||||
// Create creates a new static token
|
||||
func (r *StaticTokenRepository) Create(ctx context.Context, token *domain.StaticToken) error {
|
||||
query := `
|
||||
INSERT INTO static_tokens (
|
||||
id, app_id, owner_type, owner_name, owner_owner,
|
||||
key_hash, type, created_at, updated_at
|
||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
||||
`
|
||||
|
||||
db := r.db.GetDB().(*sql.DB)
|
||||
now := time.Now()
|
||||
|
||||
_, err := db.ExecContext(ctx, query,
|
||||
token.ID,
|
||||
token.AppID,
|
||||
string(token.Owner.Type),
|
||||
token.Owner.Name,
|
||||
token.Owner.Owner,
|
||||
token.KeyHash,
|
||||
string(token.Type),
|
||||
now,
|
||||
now,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create static token: %w", err)
|
||||
}
|
||||
|
||||
token.CreatedAt = now
|
||||
token.UpdatedAt = now
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetByID retrieves a static token by its ID
|
||||
func (r *StaticTokenRepository) GetByID(ctx context.Context, tokenID uuid.UUID) (*domain.StaticToken, error) {
|
||||
// TODO: Implement actual token retrieval
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// GetByKeyHash retrieves a static token by its key hash
|
||||
func (r *StaticTokenRepository) GetByKeyHash(ctx context.Context, keyHash string) (*domain.StaticToken, error) {
|
||||
// TODO: Implement actual token retrieval by hash
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// GetByAppID retrieves all static tokens for an application
|
||||
func (r *StaticTokenRepository) GetByAppID(ctx context.Context, appID string) ([]*domain.StaticToken, error) {
|
||||
// TODO: Implement actual token listing
|
||||
return []*domain.StaticToken{}, nil
|
||||
}
|
||||
|
||||
// List retrieves static tokens with pagination
|
||||
func (r *StaticTokenRepository) List(ctx context.Context, limit, offset int) ([]*domain.StaticToken, error) {
|
||||
// TODO: Implement actual token listing
|
||||
return []*domain.StaticToken{}, nil
|
||||
}
|
||||
|
||||
// Delete deletes a static token
|
||||
func (r *StaticTokenRepository) Delete(ctx context.Context, tokenID uuid.UUID) error {
|
||||
query := `DELETE FROM static_tokens WHERE id = $1`
|
||||
|
||||
db := r.db.GetDB().(*sql.DB)
|
||||
result, err := db.ExecContext(ctx, query, tokenID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete static token: %w", err)
|
||||
}
|
||||
|
||||
rowsAffected, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get rows affected: %w", err)
|
||||
}
|
||||
|
||||
if rowsAffected == 0 {
|
||||
return fmt.Errorf("static token with ID '%s' not found", tokenID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Exists checks if a static token exists
|
||||
func (r *StaticTokenRepository) Exists(ctx context.Context, tokenID uuid.UUID) (bool, error) {
|
||||
query := `SELECT 1 FROM static_tokens WHERE id = $1`
|
||||
|
||||
db := r.db.GetDB().(*sql.DB)
|
||||
var exists int
|
||||
err := db.QueryRowContext(ctx, query, tokenID).Scan(&exists)
|
||||
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return false, nil
|
||||
}
|
||||
return false, fmt.Errorf("failed to check static token existence: %w", err)
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
Reference in New Issue
Block a user