usernames: operator reserved-username blocklist
A plain blocklist for names like @support - separate from the collectible
system, so a reservation has no owner, no price and no "bought on Fragment"
badge.
- reserved_usernames table + migration.
- Enforced in replacePeerUsernameTx (the single editable-username write point:
account.updateUsername, channels.updateUsername, @BotFather /setusername) and
in the collectible mint path; a reserved name returns USERNAME_OCCUPIED.
- admin.Service: ReserveUsername / UnreserveUsername (journalled commands) and
the ReservedUsernames listing.
- adminapi: /v1/reserved-usernames{,/reserve,/unreserve}.
- telesrv-admin panel + a "Reserved Usernames" page in the web UI (dist rebuilt).
- Postgres and in-memory store implementations; the memory registry gains an
optional reserved-name check so tests exercise the same rule.
This commit is contained in:
parent
515038aace
commit
65aaa263b1
23 changed files with 874 additions and 39 deletions
|
|
@ -55,6 +55,24 @@ type CollectibleUsernameStore struct {
|
|||
transfers map[int64][]domain.CollectibleUsernameTransfer
|
||||
// commands maps a provenance command key onto the asset it touched.
|
||||
commands map[string]int64
|
||||
// reserved, when set, is the operator blocklist consulted before a name is
|
||||
// assigned to an editable slot or minted, mirroring the PostgreSQL checks.
|
||||
reserved *ReservedUsernameStore
|
||||
}
|
||||
|
||||
// WithReservedUsernames wires the operator blocklist into the registry so a
|
||||
// reserved name is refused, matching PostgreSQL.
|
||||
func (s *CollectibleUsernameStore) WithReservedUsernames(reserved *ReservedUsernameStore) *CollectibleUsernameStore {
|
||||
s.reserved = reserved
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *CollectibleUsernameStore) nameReservedLocked(usernameLower string) bool {
|
||||
if s.reserved == nil {
|
||||
return false
|
||||
}
|
||||
r, _ := s.reserved.IsReserved(context.Background(), usernameLower)
|
||||
return r
|
||||
}
|
||||
|
||||
// collectibleRegistryRow is one peer_usernames row: the owning peer plus the
|
||||
|
|
@ -101,6 +119,9 @@ func (s *CollectibleUsernameStore) SetEditableUsername(_ context.Context, peer d
|
|||
return false, domain.ErrUsernameInvalid
|
||||
}
|
||||
key := strings.ToLower(username)
|
||||
if s.nameReservedLocked(key) {
|
||||
return false, domain.ErrUsernameOccupied
|
||||
}
|
||||
if existing, ok := s.registry[key]; ok {
|
||||
if existing.peer == peer && existing.row.Editable {
|
||||
if existing.row.Username == username {
|
||||
|
|
@ -313,6 +334,9 @@ func (s *CollectibleUsernameStore) MintCollectibleUsername(_ context.Context, re
|
|||
if _, ok := s.registry[key]; ok {
|
||||
return domain.CollectibleUsername{}, false, domain.ErrUsernameOccupied
|
||||
}
|
||||
if s.nameReservedLocked(key) {
|
||||
return domain.CollectibleUsername{}, false, domain.ErrUsernameOccupied
|
||||
}
|
||||
now := time.Now().UTC()
|
||||
purchaseDate := req.PurchaseDate
|
||||
if purchaseDate.IsZero() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue