124 lines
2.7 KiB
Go
124 lines
2.7 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"errors"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
func mustRandToken(n int) string {
|
|
b := make([]byte, n)
|
|
if _, err := rand.Read(b); err != nil {
|
|
panic(err)
|
|
}
|
|
return hex.EncodeToString(b)
|
|
}
|
|
|
|
func now() time.Time { return time.Now().UTC() }
|
|
|
|
func defaultUsername() string {
|
|
// io-game style: adjective-animal-xxxx
|
|
adjectives := []string{"swift", "brave", "mighty", "cheeky", "sneaky", "zippy", "bouncy", "crispy", "fuzzy", "spicy", "snappy", "jazzy", "spry", "bold", "witty"}
|
|
animals := []string{"otter", "panda", "falcon", "lynx", "badger", "tiger", "koala", "yak", "gecko", "eagle", "mamba", "fox", "yak", "whale", "rhino"}
|
|
a := adjectives[int(time.Now().UnixNano())%len(adjectives)]
|
|
an := animals[int(time.Now().UnixNano()/17)%len(animals)]
|
|
return a + "-" + an + "-" + strings.ToLower(mustRandToken(2))
|
|
}
|
|
|
|
func slugify(s string) string {
|
|
s = strings.ToLower(s)
|
|
s = strings.TrimSpace(s)
|
|
s = strings.ReplaceAll(s, " ", "-")
|
|
// keep alnum and - only
|
|
var b strings.Builder
|
|
for _, r := range s {
|
|
if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '-' {
|
|
b.WriteRune(r)
|
|
}
|
|
}
|
|
out := b.String()
|
|
if out == "" {
|
|
out = "user-" + mustRandToken(2)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func ensureUniqueUsernameAndSlug(u *User) error {
|
|
// try to keep username/slug unique by appending short token if needed
|
|
baseU := u.Username
|
|
baseS := slugify(u.Username)
|
|
for i := 0; i < 5; i++ {
|
|
candU := baseU
|
|
candS := baseS
|
|
if i > 0 {
|
|
suffix := "-" + mustRandToken(1)
|
|
candU = baseU + suffix
|
|
candS = baseS + suffix
|
|
}
|
|
var cnt int64
|
|
db.Model(&User{}).Where("username = ?", candU).Count(&cnt)
|
|
if cnt == 0 {
|
|
db.Model(&User{}).Where("slug = ?", candS).Count(&cnt)
|
|
if cnt == 0 {
|
|
u.Username = candU
|
|
u.Slug = candS
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
return errors.New("cannot create unique username/slug")
|
|
}
|
|
|
|
func userByEmail(email string) (*User, error) {
|
|
var u User
|
|
tx := db.Where("email = ?", strings.ToLower(email)).First(&u)
|
|
if tx.Error != nil {
|
|
return nil, tx.Error
|
|
}
|
|
return &u, nil
|
|
}
|
|
|
|
func userBySlug(slug string) (*User, error) {
|
|
var u User
|
|
tx := db.Where("slug = ?", slug).First(&u)
|
|
if tx.Error != nil {
|
|
return nil, tx.Error
|
|
}
|
|
return &u, nil
|
|
}
|
|
|
|
func atoiSafe(s string) int {
|
|
var n int
|
|
for _, r := range s {
|
|
if r < '0' || r > '9' {
|
|
return 0
|
|
}
|
|
}
|
|
_, _ = fmtSscanf(s, "%d", &n)
|
|
return n
|
|
}
|
|
|
|
// minimal sscanf to avoid fmt import just for one call
|
|
func fmtSscanf(s, _ string, a *int) (int, error) {
|
|
// Only supports "%d"
|
|
n := 0
|
|
for i := 0; i < len(s); i++ {
|
|
c := s[i]
|
|
if c < '0' || c > '9' {
|
|
break
|
|
}
|
|
n = n*10 + int(c-'0')
|
|
}
|
|
*a = n
|
|
return 1, nil
|
|
}
|
|
|
|
func stripAfterDot(s string) string {
|
|
if idx := strings.Index(s, "."); idx != -1 {
|
|
return s[:idx]
|
|
}
|
|
return s
|
|
}
|