mirror of
https://github.com/marcusDenslow/terminalShop.git
synced 2026-10-25 18:08:45 +01:00
* 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>
56 lines
2.4 KiB
Go
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,
|
|
}
|
|
}
|