Files
initpage/main.go

158 lines
4.1 KiB
Go

package main
import (
"embed"
"io"
"io/fs"
"log"
"net/http"
"os"
"path/filepath"
"strings"
"time"
"github.com/alexedwards/scs/v2"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"github.com/joho/godotenv"
"golang.org/x/time/rate"
"github.com/weltonmoura/initpage/internal/config"
"github.com/weltonmoura/initpage/internal/db"
"github.com/weltonmoura/initpage/internal/handlers"
mw "github.com/weltonmoura/initpage/internal/middleware"
"github.com/weltonmoura/initpage/internal/sync"
)
//go:embed web/*
var embeddedWeb embed.FS
func main() {
_ = godotenv.Load()
cfg := config.Load()
dbConn, err := db.Init(cfg.DBPath)
if err != nil {
log.Fatalf("[InitPage] Falha ao inicializar banco de dados: %v", err)
}
defer dbConn.Close()
sessionManager := scs.New()
sessionManager.Lifetime = 24 * time.Hour
sessionManager.Cookie.Name = "initpage_session"
sessionManager.Cookie.HttpOnly = true
sessionManager.Cookie.SameSite = http.SameSiteLaxMode
sessionManager.Cookie.Secure = strings.HasPrefix(cfg.AppOrigin, "https://")
authH := &handlers.Auth{DB: dbConn, SM: sessionManager, Cfg: cfg}
syncH := &sync.Sync{DB: dbConn}
rl := mw.NewIPRateLimiter(rate.Every(time.Minute/10), 10)
rl.Cleanup()
r := chi.NewRouter()
r.Use(middleware.Logger)
r.Use(middleware.Recoverer)
r.Use(middleware.RealIP)
r.Use(sessionManager.LoadAndSave)
// Base path normalizado (ex: "" ou "/initpage")
base := strings.TrimRight(cfg.BasePath, "/")
if base != "" && !strings.HasPrefix(base, "/") {
base = "/" + base
}
// 1. Rotas de API
r.Route(base+"/api", func(api chi.Router) {
api.Group(func(g chi.Router) {
g.Use(mw.RateLimit(rl))
g.Post("/auth/register", authH.Register)
g.Post("/auth/login", authH.Login)
})
api.Post("/auth/logout", authH.Logout)
api.Get("/auth/me", authH.Me)
api.Group(func(g chi.Router) {
g.Use(mw.AuthRequired(sessionManager))
g.Use(mw.LoadUserID(sessionManager))
g.Get("/sync", syncH.Get)
g.Put("/sync", syncH.Put)
})
})
// 2. Sistema de arquivos para o Frontend:
// Prioridade 1: Se existir pasta ./web no disco, usa ela (permite edição sem recompilar).
// Prioridade 2: Se não existir, usa os arquivos embutidos no binário (standalone no Mini PaaS).
var staticFS fs.FS
if fi, err := os.Stat("web"); err == nil && fi.IsDir() {
log.Println("[InitPage] Servindo frontend a partir da pasta local 'web/' no disco.")
staticFS = os.DirFS("web")
} else {
log.Println("[InitPage] Servindo frontend a partir dos arquivos embutidos no binário (embed).")
sub, err := fs.Sub(embeddedWeb, "web")
if err != nil {
log.Fatalf("[InitPage] Erro ao carregar arquivos estáticos embutidos: %v", err)
}
staticFS = sub
}
fileServer := http.FileServer(http.FS(staticFS))
serveFileFromFS := func(w http.ResponseWriter, r *http.Request, name string) bool {
f, err := staticFS.Open(name)
if err != nil {
return false
}
defer f.Close()
st, err := f.Stat()
if err != nil || st.IsDir() {
return false
}
if seeker, ok := f.(io.ReadSeeker); ok {
http.ServeContent(w, r, name, st.ModTime(), seeker)
return true
}
return false
}
// Handlers de Frontend
handleIndex := func(w http.ResponseWriter, r *http.Request) {
if !serveFileFromFS(w, r, "index.html") {
http.NotFound(w, r)
}
}
// Rota raiz (ou base)
if base != "" {
r.Get(base, handleIndex)
}
r.Get(base+"/", handleIndex)
// Rotas estáticas
r.Get(base+"/*", func(w http.ResponseWriter, r *http.Request) {
p := strings.TrimPrefix(r.URL.Path, base)
rel := strings.TrimPrefix(p, "/")
if rel == "" || rel == "." {
handleIndex(w, r)
return
}
// Tenta servir o arquivo solicitado diretamente
if serveFileFromFS(w, r, rel) {
return
}
// Se for diretório (ex: politica-privacidade/), tenta o index.html dentro dele
indexPath := filepath.ToSlash(filepath.Join(rel, "index.html"))
if serveFileFromFS(w, r, indexPath) {
return
}
// Tenta fallback com http.FileServer
http.StripPrefix(base, fileServer).ServeHTTP(w, r)
})
log.Printf("[InitPage] Servidor rodando em :%s (base: '%s', db: '%s')", cfg.Port, base, cfg.DBPath)
log.Fatal(http.ListenAndServe(":"+cfg.Port, r))
}