feat: backend service
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// ActiveLimit is the read-side projection of a row in `limit_active`
|
||||
// joined with the audit columns from the underlying `limit_records`
|
||||
// row. It mirrors the OpenAPI `ActiveLimit` schema.
|
||||
type ActiveLimit struct {
|
||||
LimitCode string
|
||||
Value int32
|
||||
ReasonCode string
|
||||
Actor ActorRef
|
||||
AppliedAt time.Time
|
||||
ExpiresAt *time.Time
|
||||
}
|
||||
|
||||
// ApplyLimitInput carries the admin-supplied parameters of
|
||||
// `POST /api/v1/admin/users/{user_id}/limits`.
|
||||
type ApplyLimitInput struct {
|
||||
UserID uuid.UUID
|
||||
LimitCode string
|
||||
Value int32
|
||||
ReasonCode string
|
||||
Actor ActorRef
|
||||
ExpiresAt *time.Time
|
||||
}
|
||||
|
||||
// ApplyLimit persists a fresh `limit_records` row and upserts
|
||||
// `limit_active` in one transaction. The implementation keeps `limit_code` as an
|
||||
// open string; The implementation may add a CHECK constraint once the closed
|
||||
// set is locked in.
|
||||
func (s *Service) ApplyLimit(ctx context.Context, input ApplyLimitInput) (Account, error) {
|
||||
if input.UserID == uuid.Nil {
|
||||
return Account{}, ErrAccountNotFound
|
||||
}
|
||||
if strings.TrimSpace(input.LimitCode) == "" {
|
||||
return Account{}, fmt.Errorf("%w: limit_code must be non-empty", ErrInvalidInput)
|
||||
}
|
||||
if err := input.Actor.Validate(); err != nil {
|
||||
return Account{}, err
|
||||
}
|
||||
if strings.TrimSpace(input.ReasonCode) == "" {
|
||||
return Account{}, fmt.Errorf("%w: reason_code must be non-empty", ErrInvalidInput)
|
||||
}
|
||||
|
||||
now := s.deps.Now().UTC()
|
||||
expiresAt := input.ExpiresAt
|
||||
if expiresAt != nil {
|
||||
t := expiresAt.UTC()
|
||||
expiresAt = &t
|
||||
}
|
||||
|
||||
if err := s.deps.Store.ApplyLimitTx(ctx, limitInsert{
|
||||
UserID: input.UserID,
|
||||
LimitCode: input.LimitCode,
|
||||
Value: input.Value,
|
||||
ReasonCode: input.ReasonCode,
|
||||
ActorType: input.Actor.Type,
|
||||
ActorID: input.Actor.ID,
|
||||
AppliedAt: now,
|
||||
ExpiresAt: expiresAt,
|
||||
}); err != nil {
|
||||
if errors.Is(err, ErrAccountNotFound) {
|
||||
return Account{}, err
|
||||
}
|
||||
return Account{}, fmt.Errorf("user apply limit: %w", err)
|
||||
}
|
||||
return s.GetAccount(ctx, input.UserID)
|
||||
}
|
||||
Reference in New Issue
Block a user