Files
terminalShop-old/pkg/models/user.go
marcus06 aa446cdfa5 feat(sca): add per-user spend cap override (#53)
* feat(sca): add per-user spend cap override

* feat(sca): add admin endpoint to set per-user order cap

- PATCH /api/v1/users/{id}/order-cap (RequireAdmin / X-Admin-Key) sets or
  clears User.MaxOrderCents; null clears (revert to global), >=0 sets,
  negative rejected 400 INVALID_CAP at the write boundary
- harden ConvertCart: a negative override falls back to the global cap with
  a slog.Warn (mirrors loadMaxOrderCents) so a bad DB write cannot silently
  void the fraud control
- emit user_order_cap_set audit event (set/cleared)
- tests: admin handler table + RequireAdmin gate router test + negative
  read-path fallback case

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* docs(api): drop orphan comment referencing nonexistent request struct

The paragraph above SetUserOrderCap documented a setUserOrderCapRequest
struct that does not exist; the handler decodes into a raw map instead.
The raw-map rationale already lives inline at the decode site.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 14:56:38 +02:00

56 lines
2.4 KiB
Go

package models
import (
"time"
"gorm.io/gorm"
)
// User represents a user authenticated via SSH public key
// No passwords - SSH key is the ONLY authentication method (matches terminal.shop exactly)
type User struct {
ID uint `gorm:"primaryKey" json:"id"`
SSHKeyFingerprint string `gorm:"uniqueIndex;size:100;not null" json:"ssh_key_fingerprint"` // SHA256 fingerprint - PRIMARY identifier
SSHPublicKey string `gorm:"uniqueIndex;type:text;not null" json:"-"` // Full SSH public key
Name string `gorm:"size:255" json:"name,omitempty"` // Real name (for shipping labels)
Email string `gorm:"size:255" json:"email,omitempty"` // Email (for receipts)
Anonymous bool `gorm:"default:false" json:"anonymous"` // True if connected without SSH key
StripeCustomerID string `gorm:"size:255" json:"-"`
// MaxOrderCents is an optional per-user override of the global MAX_ORDER_CENTS
// spend cap. It may raise OR lower the global cap — both directions are
// intentional (e.g. a vetted corporate buyer gets a higher ceiling; a flagged
// account gets a lower one). States: nil = inherit the global; explicit 0 =
// per-user off-switch (same semantics as the global); >0 = custom ceiling. A
// negative value is treated as "no override" (falls back to global) with a
// warning — see ConvertCart. Operator-set out-of-band; kept out of API payloads.
MaxOrderCents *int `json:"-"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
}
// TableName specifies the table name for the User model
func (User) TableName() string {
return "users"
}
// PublicUser returns a user object safe for public consumption
type PublicUser struct {
ID uint `json:"id"`
SSHKeyFingerprint string `json:"ssh_key_fingerprint"`
Name string `json:"name,omitempty"`
Email string `json:"email,omitempty"`
CreatedAt time.Time `json:"created_at"`
}
// ToPublic converts a User to a PublicUser
func (u *User) ToPublic() PublicUser {
return PublicUser{
ID: u.ID,
SSHKeyFingerprint: u.SSHKeyFingerprint,
Name: u.Name,
Email: u.Email,
CreatedAt: u.CreatedAt,
}
}