internal/domain/collectible_phone.go
Rama main · réplica de lectura del repositorio original.
package domain
import (
"errors"
"strings"
"time"
)
const (
MinCollectiblePhoneLength = 7
MaxCollectiblePhoneLength = 15
)
var (
ErrCollectiblePhoneInvalid = errors.New("collectible phone invalid")
ErrCollectiblePhoneNotFound = errors.New("collectible phone not found")
ErrCollectiblePhoneNotOwned = errors.New("collectible phone not owned")
ErrCollectiblePhoneBurned = errors.New("collectible phone burned")
ErrCollectiblePhoneOwnerLimit = errors.New("user already owns a collectible phone")
)
// CollectiblePhoneTier controls visibility. Standard numbers follow the
// account phone privacy rule; exclusive numbers are a server extension that
// remain visible on every projection of their owner.
type CollectiblePhoneTier string
const (
CollectiblePhoneTierStandard CollectiblePhoneTier = "standard"
CollectiblePhoneTierExclusive CollectiblePhoneTier = "exclusive"
)
func (t CollectiblePhoneTier) Valid() bool {
return t == CollectiblePhoneTierStandard || t == CollectiblePhoneTierExclusive
}
type CollectiblePhone struct {
ID int64
Phone string
Tier CollectiblePhoneTier
Status CollectibleUsernameStatus
OwnerUserID int64
PurchaseDate time.Time
Currency string
Amount int64
CryptoCurrency string
CryptoAmount int64
URL string
OriginalOwnerUserID int64
TransferCount int
Version int64
CreatedAt time.Time
UpdatedAt time.Time
}
func (c CollectiblePhone) Owned() bool {
return c.Status == CollectibleUsernameStatusOwned && c.OwnerUserID > 0
}
func (c CollectiblePhone) AlwaysVisible() bool {
return c.Owned() && c.Tier == CollectiblePhoneTierExclusive
}
func (c CollectiblePhone) Info() CollectibleInfo {
date := 0
if !c.PurchaseDate.IsZero() {
date = int(c.PurchaseDate.Unix())
}
return CollectibleInfo{PurchaseDate: date, Currency: c.Currency, Amount: c.Amount,
CryptoCurrency: c.CryptoCurrency, CryptoAmount: c.CryptoAmount, URL: c.URL}
}
func (c CollectiblePhone) Validate() error {
if !ValidCollectiblePhone(c.Phone) || !c.Tier.Valid() || !c.Status.Valid() {
return ErrCollectiblePhoneInvalid
}
if (c.Status == CollectibleUsernameStatusOwned) != (c.OwnerUserID > 0) {
return ErrCollectiblePhoneInvalid
}
if err := ValidateCollectiblePhonePrice(c.Currency, c.Amount, c.CryptoCurrency, c.CryptoAmount); err != nil {
return err
}
if len(c.URL) > MaxCollectibleUsernameURLLength || c.OriginalOwnerUserID < 0 || c.TransferCount < 0 {
return ErrCollectiblePhoneInvalid
}
return nil
}
// ValidateCollectiblePhonePrice mirrors the shape Telegram Desktop expects in
// fragment.collectibleInfo: TON (nanotons) is drawn first and the USD purchase
// amount (cents) is drawn in parentheses. Leaving the crypto leg empty makes the
// client render the literal fallback "{:0}".
func ValidateCollectiblePhonePrice(currency string, amount int64, cryptoCurrency string, cryptoAmount int64) error {
if currency != CollectibleCurrencyUSD || amount <= 0 || cryptoCurrency != CollectibleCryptoCurrencyTON || cryptoAmount <= 0 {
return ErrCollectibleCurrencyInvalid
}
return nil
}
// NormalizeCollectiblePhone accepts the formatting clients commonly send and
// returns the canonical digits shared by virtual users.phone login identities
// and inputCollectiblePhone assets. Login identity and asset ownership remain
// independent facts.
func NormalizeCollectiblePhone(phone string) string {
phone = strings.TrimSpace(phone)
var b strings.Builder
for i, r := range phone {
switch {
case r >= '0' && r <= '9':
b.WriteRune(r)
case r == '+' && i == 0:
case r == ' ' || r == '-' || r == '(' || r == ')' || r == '\t':
default:
return ""
}
}
return b.String()
}
func ValidCollectiblePhone(phone string) bool {
if len(phone) < MinCollectiblePhoneLength || len(phone) > MaxCollectiblePhoneLength || !strings.HasPrefix(phone, "888") {
return false
}
for _, r := range phone {
if r < '0' || r > '9' {
return false
}
}
return true
}
type MintCollectiblePhoneRequest struct {
Phone string
Tier CollectiblePhoneTier
OwnerUserID int64
PurchaseDate time.Time
Currency string
Amount int64
CryptoCurrency string
CryptoAmount int64
URL string
Actor string
Reason string
CommandKey string
}
func (r MintCollectiblePhoneRequest) Validate() error {
if !ValidCollectiblePhone(r.Phone) || !r.Tier.Valid() || r.OwnerUserID < 0 {
return ErrCollectiblePhoneInvalid
}
if err := ValidateCollectiblePhonePrice(r.Currency, r.Amount, r.CryptoCurrency, r.CryptoAmount); err != nil {
return err
}
if len(r.URL) > MaxCollectibleUsernameURLLength || len(r.Actor) > MaxCollectibleUsernameActorLength ||
len(r.Reason) > MaxCollectibleUsernameReasonLength || len(r.CommandKey) > MaxCollectibleUsernameCommandKeyLength {
return ErrCollectiblePhoneInvalid
}
return nil
}
type UpdateCollectiblePhonePriceRequest struct {
Phone string
Currency string
Amount int64
CryptoCurrency string
CryptoAmount int64
Actor string
Reason string
}
func (r UpdateCollectiblePhonePriceRequest) Validate() error {
if !ValidCollectiblePhone(r.Phone) || len(r.Actor) > MaxCollectibleUsernameActorLength || len(r.Reason) > MaxCollectibleUsernameReasonLength {
return ErrCollectiblePhoneInvalid
}
return ValidateCollectiblePhonePrice(r.Currency, r.Amount, r.CryptoCurrency, r.CryptoAmount)
}
type TransferCollectiblePhoneRequest struct {
Phone string
ToUserID int64
Actor, Reason, CommandKey string
}
func (r TransferCollectiblePhoneRequest) Validate() error {
if !ValidCollectiblePhone(r.Phone) || r.ToUserID <= 0 || len(r.Actor) > MaxCollectibleUsernameActorLength || len(r.Reason) > MaxCollectibleUsernameReasonLength || len(r.CommandKey) > MaxCollectibleUsernameCommandKeyLength {
return ErrCollectiblePhoneInvalid
}
return nil
}
type RevokeCollectiblePhoneRequest struct {
Phone string
Burn bool
Actor, Reason, CommandKey string
}
func (r RevokeCollectiblePhoneRequest) Validate() error {
if !ValidCollectiblePhone(r.Phone) || len(r.Actor) > MaxCollectibleUsernameActorLength || len(r.Reason) > MaxCollectibleUsernameReasonLength || len(r.CommandKey) > MaxCollectibleUsernameCommandKeyLength {
return ErrCollectiblePhoneInvalid
}
return nil
}
type DeleteCollectiblePhoneRequest struct {
Phone string
Actor, Reason, CommandKey string
}
func (r DeleteCollectiblePhoneRequest) Validate() error {
if !ValidCollectiblePhone(r.Phone) || len(r.Actor) > MaxCollectibleUsernameActorLength || len(r.Reason) > MaxCollectibleUsernameReasonLength || len(r.CommandKey) > MaxCollectibleUsernameCommandKeyLength {
return ErrCollectiblePhoneInvalid
}
return nil
}
type CollectiblePhoneTransfer struct {
ID, CollectibleID int64
Kind CollectibleUsernameTransferKind
FromUserID, ToUserID int64
Currency string
Amount int64
Actor, Reason, CommandKey string
CreatedAt time.Time
}
type CollectiblePhoneFilter struct {
Status CollectibleUsernameStatus
Tier CollectiblePhoneTier
OwnerUserID int64
Query string
BeforeID int64
Limit int
}