package main import ( "bytes" "crypto/rand" "encoding/hex" "errors" "html/template" "log" "net/http" "os" "strings" "time" "github.com/gin-gonic/gin" "gopkg.in/gomail.v2" "github.com/skip2/go-qrcode" ) 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 nameFromEmail(email string) string { parts := strings.SplitN(email, "@", 2) if len(parts) > 0 { return parts[0] } return "" } func sendEmail(email string, token string) error { // Parse the email body template tmpl, err := template.ParseFiles("other/email.txt") if err != nil { panic(err) } // Execute template with provided data var body bytes.Buffer if err := tmpl.Execute(&body, gin.H{"Token": token, "Name": nameFromEmail(email)}); err != nil { panic(err) } // Compose email m := gomail.NewMessage() m.SetHeader("From", os.Getenv("SMTP_MAIL")) m.SetHeader("To", email) m.SetHeader("Subject", "Registration/Login to QRank") m.SetBody("text/plain", body.String()) // Dial and send d := gomail.NewDialer(os.Getenv("SMTP_HOST"), 587, os.Getenv("SMTP_MAIL"), os.Getenv("SMTP_PASS")) if err := d.DialAndSend(m); err != nil { log.Fatalln(err) return err } return nil } func getQr(c *gin.Context) { slug := c.Param("qrSlug") var url string if slug == "" { url = "http://localhost:18765" } else { url = "http://localhost:18765/table/" + slug } png, err := qrcode.Encode(url, qrcode.Medium, 256) if err != nil { c.String(http.StatusInternalServerError, "could not generate QR") return } c.Data(http.StatusOK, "image/png", png) }