Also add qr code generation route The table management in the database does currently not work
203 lines
4.3 KiB
Go
203 lines
4.3 KiB
Go
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)
|
|
}
|