package main import ( "embed" "fmt" "io/fs" "net/http" "time" "git.readonly.ch/bouzoure/pop-camarades/controllers" "git.readonly.ch/bouzoure/pop-camarades/helpers" "git.readonly.ch/bouzoure/pop-camarades/jobs" "git.readonly.ch/bouzoure/pop-camarades/middlewares" "github.com/flosch/pongo2/v6" "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/filesystem" loggerMiddleware "github.com/gofiber/fiber/v2/middleware/logger" "github.com/gofiber/helmet/v2" "github.com/gofiber/template/django/v3" ) //go:embed static/* var embedStatic embed.FS //go:embed views var embedViews embed.FS func main() { log := helpers.GetLogger() // Add embedded filesystems to shared state helpers.AddEmbeddedFS("static", &embedStatic) helpers.AddEmbeddedFS("views", &embedViews) // Fetch app config config, err := helpers.GetConfig() if err != nil { log.Fatal(err) } // Check if at least one account exists // If not, create the default one: admin@invalid.tld / password // Note: since this is the fist call to the DB, this also applies the migrations accountCheck, err := helpers.FirstAccountCheck() if err != nil { log.Fatal(err) } if !accountCheck { err = helpers.FirstAccountCreate() if err != nil { log.Fatal(err) } } // Register background jobs // Note: jobs will be executed at launch and after every interval go helpers.RegisterJob(60*time.Minute, "clean saved sessions", jobs.CleanSavedSessions) // Initialize the Pongo2 templating engine // If we are in dev mode, load templates from directory // Otherwise, use the templates embedded in the binary var engine *django.Engine if config.DevMode { engine = django.New("./views", ".html") engine.ShouldReload = true } else { embedViews2, err := fs.Sub(embedViews, "views") if err != nil { log.Fatal(err) } engine = django.NewFileSystem(http.FS(embedViews2), ".html") } // Register any custom functions or filters in the template engine pongo2.RegisterFilter("time_diff", helpers.TemplTimeDiff) // Base config for the Fiber web app // We pass our newly created template engine // as well as our custom error handler fiberConfig := fiber.Config{ Views: engine, ErrorHandler: helpers.FiberErrorHandler, } // If web app is behind a reverse proxy, // use X-Forwarded-For header as the IP header if config.App.BehindProxy { fiberConfig.ProxyHeader = fiber.HeaderXForwardedFor } // Create the Fiber web app app := fiber.New(fiberConfig) // If we are in dev mode, serve assets from directory // Otherwise, serve assets embedded in binary if config.DevMode { app.Static("/static", "./static") } else { app.Use("/static", filesystem.New(filesystem.Config{ Root: http.FS(embedStatic), PathPrefix: "static", Browse: false, })) } // Base middlewares app.Use(loggerMiddleware.New()) app.Use(helmet.New()) // Misc endpoints without auth app.Post("/set-color-mode", controllers.SetColorMode) // Licences page app.Use("/licenses", middlewares.TemplatesMiddleware) app.Get("/licenses", controllers.Licenses) // Security middlewares app.Use(middlewares.SavedSessionMiddleware) app.Use(middlewares.AuthMiddleware) app.Use(middlewares.WelcomeMiddleware) app.Use(middlewares.MfaEnrollMiddleware) app.Use(middlewares.MfaVerifyMiddleware) app.Use(middlewares.TemplatesMiddleware) app.Use("/admin", middlewares.AuthzAdmin) app.Use("/debug", middlewares.AuthzAdmin) // Main pages app.Get("/", controllers.Homepage) app.Get("/admin", controllers.Admin) // Login & logout app.Get("/login", controllers.LoginForm) app.Post("/login", controllers.LoginForm) app.Get("/logout", controllers.LogoutProcess) // Welcome page on first login app.Get("/welcome", controllers.WelcomePage) app.Post("/welcome", controllers.WelcomePage) // TOTP enroll & verify app.Get("/totp/enroll", controllers.TotpEnrollPage) app.Post("/totp/enroll", controllers.TotpEnrollPage) app.Get("/totp/verify", controllers.TotpVerifyPage) app.Post("/totp/verify", controllers.TotpVerifyPage) // Members app.Get("/members", controllers.Members) app.Get("/members/export", controllers.MembersExport) app.Get("/members/:id", controllers.MemberShow) app.Get("/members/add", controllers.MemberAdd) app.Post("/members/add", controllers.MemberAdd) app.Get("/members/:id/edit", controllers.MemberEdit) app.Post("/members/:id/edit", controllers.MemberEdit) app.Post("/members/:id/convert", controllers.MemberConvert) app.Post("/members/:id/archive", controllers.MemberArchive) app.Post("/members/:id/restore", controllers.MemberRestore) app.Post("/members/:id/purge", controllers.MemberPurge) // Contacts app.Get("/contacts", controllers.Contacts) app.Get("/contacts/export", controllers.ContactsExport) app.Get("/contacts/:id", controllers.ContactShow) app.Get("/contacts/add", controllers.ContactAdd) app.Post("/contacts/add", controllers.ContactAdd) app.Get("/contacts/:id/edit", controllers.ContactEdit) app.Post("/contacts/:id/edit", controllers.ContactEdit) app.Post("/contacts/:id/convert", controllers.ContactConvert) app.Post("/contacts/:id/archive", controllers.ContactArchive) app.Post("/contacts/:id/restore", controllers.ContactRestore) app.Post("/contacts/:id/purge", controllers.ContactPurge) // Fields app.Get("/fields/:id", controllers.FieldJSON) // Account manage app.Get("/account/manage", controllers.AccountManage) app.Post("/account/manage", controllers.AccountManage) app.Get("/account/totp", controllers.AccountTotp) app.Post("/account/totp", controllers.AccountTotp) // Admin: Sections app.Get("/admin/sections", controllers.Sections) app.Get("/admin/sections/:id", controllers.SectionShow) app.Get("/admin/sections/add", controllers.SectionAdd) app.Post("/admin/sections/add", controllers.SectionAdd) app.Get("/admin/sections/:id/edit", controllers.SectionEdit) app.Post("/admin/sections/:id/edit", controllers.SectionEdit) app.Post("/admin/sections/:id/delete", controllers.SectionDelete) // Admin: Lists app.Get("/admin/lists", controllers.Lists) app.Get("/admin/lists/:id", controllers.ListShow) app.Get("/admin/lists/add", controllers.ListAdd) app.Post("/admin/lists/add", controllers.ListAdd) app.Get("/admin/lists/:id/edit", controllers.ListEdit) app.Post("/admin/lists/:id/edit", controllers.ListEdit) app.Post("/admin/lists/:id/delete", controllers.ListDelete) app.Get("/admin/lists/:id/items/add", controllers.ListItemAdd) app.Post("/admin/lists/:id/items/add", controllers.ListItemAdd) app.Get("/admin/lists/:id/items/:itemid", controllers.ListItemEdit) app.Post("/admin/lists/:id/items/:itemid", controllers.ListItemEdit) app.Post("/admin/lists/:id/items/:itemid/delete", controllers.ListItemDelete) // Admin: Fields app.Get("/admin/fields", controllers.Fields) app.Get("/admin/fields/:id", controllers.FieldShow) app.Get("/admin/fields/add", controllers.FieldAdd) app.Post("/admin/fields/add", controllers.FieldAdd) app.Get("/admin/fields/:id/edit", controllers.FieldEdit) app.Post("/admin/fields/:id/edit", controllers.FieldEdit) app.Post("/admin/fields/:id/delete", controllers.FieldDelete) app.Get("/admin/fields/:id/move-up", controllers.FieldMoveUp) app.Get("/admin/fields/:id/move-down", controllers.FieldMoveDown) // Admin: Users app.Get("/admin/users", controllers.Users) app.Get("/admin/users/:id", controllers.UserShow) app.Get("/admin/users/add", controllers.UserAdd) app.Post("/admin/users/add", controllers.UserAdd) app.Get("/admin/users/:id/edit", controllers.UserEdit) app.Post("/admin/users/:id/edit", controllers.UserEdit) app.Get("/admin/users/:id/permissions", controllers.UserPermissions) app.Post("/admin/users/:id/permissions", controllers.UserPermissions) app.Post("/admin/users/:id/delete", controllers.UserDelete) // Admin: Roles app.Get("/admin/roles", controllers.Roles) app.Get("/admin/roles/:id", controllers.RoleShow) app.Get("/admin/roles/add", controllers.RoleAdd) app.Post("/admin/roles/add", controllers.RoleAdd) app.Get("/admin/roles/:id/edit", controllers.RoleEdit) app.Post("/admin/roles/:id/edit", controllers.RoleEdit) app.Post("/admin/roles/:id/delete", controllers.RoleDelete) // Endpoints only enabled when debug mode is enabled if config.Debug { app.Get("/debug/fake-members/:number", controllers.DebugFakeMembers) app.Get("/debug/fake-contacts/:number", controllers.DebugFakeContacts) app.Get("/debug/fix-fields-order", controllers.DebugFixFieldsOrder) } log.Info( "starting web server", "address", config.App.ListenAddress, "port", config.App.ListenPort, ) listenAddr := fmt.Sprintf( "%s:%d", config.App.ListenAddress, config.App.ListenPort, ) // Start the fiber web app err = app.Listen(listenAddr) if err != nil { log.Fatal(err) } }