139 lines
3.6 KiB
Go
139 lines
3.6 KiB
Go
package main
|
|
|
|
import (
|
|
"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"
|
|
)
|
|
|
|
func main() {
|
|
_ = godotenv.Load()
|
|
cfg := config.Load()
|
|
|
|
dbConn, err := db.Init(cfg.DBPath)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer dbConn.Close()
|
|
|
|
sessionManager := scs.New()
|
|
sessionManager.Lifetime = 24 * time.Hour
|
|
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) // ~10 req/min
|
|
rl.Cleanup()
|
|
|
|
r := chi.NewRouter()
|
|
r.Use(middleware.Logger)
|
|
r.Use(middleware.Recoverer)
|
|
r.Use(middleware.RealIP)
|
|
r.Use(sessionManager.LoadAndSave)
|
|
|
|
base := strings.TrimRight(cfg.BasePath, "/")
|
|
|
|
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)
|
|
})
|
|
})
|
|
|
|
// servir frontend
|
|
workDir, _ := os.Getwd()
|
|
webDir := filepath.Join(workDir, "web")
|
|
fileServer := http.FileServer(http.Dir(webDir))
|
|
|
|
r.Get(base, func(w http.ResponseWriter, r *http.Request) {
|
|
http.ServeFile(w, r, filepath.Join(webDir, "index.html"))
|
|
})
|
|
r.Get(base+"/", func(w http.ResponseWriter, r *http.Request) {
|
|
http.ServeFile(w, r, filepath.Join(webDir, "index.html"))
|
|
})
|
|
r.Get(base+"/*", func(w http.ResponseWriter, r *http.Request) {
|
|
// O BASE_PATH é apenas o prefixo público: /initpage/apps/ aponta para
|
|
// /app/web/apps/, sem repetir "initpage" dentro da pasta web.
|
|
p := strings.TrimPrefix(r.URL.Path, base)
|
|
rel := filepath.Clean(filepath.FromSlash(strings.TrimPrefix(p, "/")))
|
|
serveIndex := func(indexPath string) bool {
|
|
st, err := os.Stat(indexPath)
|
|
if err != nil || st.IsDir() {
|
|
return false
|
|
}
|
|
http.ServeFile(w, r, indexPath)
|
|
return true
|
|
}
|
|
|
|
if rel == "." {
|
|
serveIndex(filepath.Join(webDir, "index.html"))
|
|
return
|
|
}
|
|
if !filepath.IsLocal(rel) {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
|
|
full := filepath.Join(webDir, rel)
|
|
st, err := os.Stat(full)
|
|
if err == nil && st.IsDir() {
|
|
if !strings.HasSuffix(r.URL.Path, "/") {
|
|
http.Redirect(w, r, r.URL.Path+"/", http.StatusMovedPermanently)
|
|
return
|
|
}
|
|
if serveIndex(filepath.Join(full, "index.html")) {
|
|
return
|
|
}
|
|
} else if err == nil {
|
|
http.StripPrefix(base, fileServer).ServeHTTP(w, r)
|
|
return
|
|
}
|
|
|
|
// Em rotas sem extensão, usa o index.html da pasta mais próxima.
|
|
// Isso permite recarregar rotas internas de SPAs estáticas em web/<site>/.
|
|
if filepath.Ext(rel) == "" {
|
|
dir := filepath.Dir(rel)
|
|
for {
|
|
if serveIndex(filepath.Join(webDir, dir, "index.html")) {
|
|
return
|
|
}
|
|
parent := filepath.Dir(dir)
|
|
if parent == dir || dir == "." {
|
|
break
|
|
}
|
|
dir = parent
|
|
}
|
|
}
|
|
http.NotFound(w, r)
|
|
})
|
|
|
|
log.Printf("Servidor rodando em :%s base=%s", cfg.Port, base)
|
|
log.Fatal(http.ListenAndServe(":"+cfg.Port, r))
|
|
}
|