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() } var adjectives = []string{"swift", "brave", "mighty", "cheeky", "sneaky", "zippy", "bouncy", "crispy", "fuzzy", "spicy", "snappy", "jazzy", "spry", "bold", "witty"} var animals = []string{"otter", "panda", "falcon", "lynx", "badger", "tiger", "koala", "yak", "gecko", "eagle", "mamba", "fox", "yak", "whale", "rhino"} func defaultUsername() string { // Inspired from IO games: adjective-animal-xx 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) { // Email must be lowercased and trimmed var u User tx := db.Where("email = ?", email).First(&u) if tx.Error != nil { return nil, tx.Error } return &u, nil } func userByName(name string) (*User, error) { var u User tx := db.Where("username = ?", name).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 } func getStatsFromUser(u *User) stats { var gps []GamePlayer var games, wins, losses int db.Where("user_id = ?", u.ID).Find(&gps) gameMap := map[uint]string{} for _, gp := range gps { gameMap[gp.GameID] = gp.Side } if len(gameMap) > 0 { var gamesL []Game ids := make([]uint, 0, len(gameMap)) for gid := range gameMap { ids = append(ids, gid) } db.Find(&gamesL, ids) for _, g := range gamesL { games++ if g.ScoreA == g.ScoreB { continue } if g.ScoreA > g.ScoreB && gameMap[g.ID] == "A" { wins++ } else if g.ScoreB > g.ScoreA && gameMap[g.ID] == "B" { wins++ } else { losses++ } } } return stats{Games: games, Wins: wins, Losses: losses} }