Files
BagExchange/main.go
2026-02-19 20:14:14 +01:00

174 lines
4.5 KiB
Go

package main
import (
"bufio"
"crypto/sha256"
"encoding/hex"
"html/template"
"log"
"os"
"path/filepath"
"strings"
"github.com/gofiber/fiber/v3"
"gorm.io/driver/sqlite"
"gorm.io/gorm"
"prada.ch/controllers"
)
type landingData struct {
Brand string
InitialTitle string
FooterText string
AssetVersion string
}
type resetPasswordPageData struct {
Token string
}
func main() {
loadDotEnv(".env")
db, err := initDB("data/subscribers.db")
if err != nil {
log.Fatal(err)
}
sqlDB, err := db.DB()
if err != nil {
log.Fatal(err)
}
defer sqlDB.Close()
h := &controller{
db: db,
mailConfig: getSMTPConfigFromEnv(),
landingTemplate: template.Must(template.ParseFiles("templates/public/index.html")),
howToWorkTemplate: template.Must(template.ParseFiles("templates/public/howtowork.html")),
loginTemplate: template.Must(template.ParseFiles("templates/public/login.html")),
signupTemplate: template.Must(template.ParseFiles("templates/public/signup.html")),
forgotPasswordTemplate: template.Must(template.ParseFiles("templates/public/forgot_password.html")),
resetPasswordTemplate: template.Must(template.ParseFiles("templates/public/reset_password.html")),
welcomeTemplate: template.Must(template.ParseFiles("templates/private/welcome.html")),
assetVersion: buildAssetVersion("static/css/main.css", "static/js/i18n.js"),
}
h.subscribeController = &controllers.SubscribeController{
DB: h.db,
IsUniqueConstraint: isUniqueConstraint,
}
h.authController = &controllers.AuthController{
DB: h.db,
IsUniqueConstraint: isUniqueConstraint,
AppBaseURL: getAppBaseURL,
MailConfigured: h.mailConfig.isConfigured,
SendPasswordResetEmail: func(recipientEmail, resetURL string) error {
return sendPasswordResetEmail(h.mailConfig, recipientEmail, resetURL)
},
SendEmailVerificationMail: func(recipientEmail, verifyURL string) error {
return sendEmailVerificationEmail(h.mailConfig, recipientEmail, verifyURL)
},
}
app := fiber.New()
app.Get("/static/*", h.staticFile)
app.Post("/api/subscribe", h.subscribeController.Subscribe)
app.Post("/api/auth/register", h.authController.Register)
app.Post("/api/auth/login", h.authController.Login)
app.Post("/api/auth/logout", h.authController.Logout)
app.Get("/api/auth/me", h.authController.Me)
app.Post("/api/auth/forgot-password", h.authController.ForgotPassword)
app.Post("/api/auth/resend-verification", h.authController.ResendVerification)
app.Post("/api/auth/reset-password", h.authController.ResetPassword)
app.Get("/auth/verify-email", h.authController.VerifyEmail)
app.Get("/", h.home)
app.Get("/reset-password", h.resetPasswordPage)
app.Get("/howtowork", h.howToWorkPage)
app.Get("/login", h.loginPage)
app.Get("/signup", h.signupPage)
app.Get("/forgot-password", h.forgotPasswordPage)
app.Get("/welcome", h.welcomePage)
log.Fatal(app.Listen(":6082"))
}
func buildAssetVersion(paths ...string) string {
hasher := sha256.New()
for _, path := range paths {
content, err := os.ReadFile(path)
if err != nil {
log.Printf("asset version fallback, unable to read %s: %v", path, err)
return "dev"
}
hasher.Write(content)
}
return hex.EncodeToString(hasher.Sum(nil))[:12]
}
func initDB(path string) (*gorm.DB, error) {
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
return nil, err
}
db, err := gorm.Open(sqlite.Open(path), &gorm.Config{})
if err != nil {
return nil, err
}
if err := db.Exec("PRAGMA foreign_keys = ON;").Error; err != nil {
return nil, err
}
models := []any{
&Subscriber{},
&User{},
&Session{},
&PasswordResetToken{},
&EmailVerificationToken{},
}
for _, model := range models {
if db.Migrator().HasTable(model) {
continue
}
if err := db.AutoMigrate(model); err != nil {
return nil, err
}
}
return db, nil
}
func isUniqueConstraint(err error) bool {
if err == nil {
return false
}
return strings.Contains(strings.ToLower(err.Error()), "unique")
}
func loadDotEnv(path string) {
file, err := os.Open(path)
if err != nil {
return
}
defer file.Close()
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" || strings.HasPrefix(line, "#") {
continue
}
parts := strings.SplitN(line, "=", 2)
if len(parts) != 2 {
continue
}
key := strings.TrimSpace(parts[0])
value := strings.TrimSpace(parts[1])
if key == "" {
continue
}
if _, exists := os.LookupEnv(key); !exists {
os.Setenv(key, value)
}
}
}