Files
Nip-05-api/internal/user/model.go
Michilis bbfc64733a Allow dot in usernames per NIP-05 local-part spec
Extend usernameRE to [a-z0-9_.-], preserve dots in SanitizeForUsername,
and add tests for validation, sanitization, and nip05 sync precedence.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-05 06:10:14 +00:00

76 lines
1.9 KiB
Go

package user
import (
"errors"
"regexp"
"strings"
"time"
)
type SubscriptionType string
const (
SubYearly SubscriptionType = "yearly"
SubLifetime SubscriptionType = "lifetime"
)
func (s SubscriptionType) Valid() bool {
return s == SubYearly || s == SubLifetime
}
type User struct {
ID int64
Pubkey string
Username string
SubscriptionType SubscriptionType
ExpiresAt *time.Time
IsActive bool
ManualUsername bool
LastSyncedAt *time.Time
ExpiringReminderSentAt *time.Time
DeactivatedAt *time.Time
CreatedAt time.Time
}
func (u *User) IsLifetime() bool { return u.SubscriptionType == SubLifetime }
func (u *User) InGrace() bool { return !u.IsActive && u.DeactivatedAt != nil }
var (
ErrInvalidUsername = errors.New("invalid username")
ErrUserNotFound = errors.New("user not found")
ErrUsernameTaken = errors.New("username taken")
)
// Username rules: 1-30 chars, [a-z0-9_.-], lowercase, must start with alnum.
// Dot is allowed per NIP-05 local-part spec.
var usernameRE = regexp.MustCompile(`^[a-z0-9][a-z0-9_.-]{0,29}$`)
func ValidateUsername(name string, reserved []string) error {
name = strings.ToLower(strings.TrimSpace(name))
if !usernameRE.MatchString(name) {
return ErrInvalidUsername
}
for _, r := range reserved {
if strings.EqualFold(name, strings.TrimSpace(r)) {
return ErrInvalidUsername
}
}
return nil
}
func NormalizeUsername(name string) string {
return strings.ToLower(strings.TrimSpace(name))
}
// ProvisionalUsername returns a deterministic placeholder handle for a pubkey
// when the caller did not supply one. The format `u_<first 16 hex>` keeps the
// result inside the 30-char username rule and matches usernameRE.
func ProvisionalUsername(pubkey string) string {
hex := strings.ToLower(strings.TrimSpace(pubkey))
if len(hex) > 16 {
hex = hex[:16]
}
return "u_" + hex
}