174 lines
4.5 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|