package main import ( "database/sql" "fmt" "io/fs" "net/http" "os" "strconv" userctx "git.kealoha.me/lks/lenslocked/context" ctrlrs "git.kealoha.me/lks/lenslocked/controllers" "git.kealoha.me/lks/lenslocked/migrations" "git.kealoha.me/lks/lenslocked/models" "github.com/go-chi/chi/v5" "github.com/gorilla/csrf" "github.com/joho/godotenv" "github.com/pressly/goose/v3" "github.com/go-chi/chi/v5/middleware" _ "github.com/jackc/pgx/v4/stdlib" ) const DEBUG bool = true type config struct { Postgres string Email struct { Host string Port int Username, Pass, Sender string } Csrf struct { Key []byte Secure bool } Server struct { Address string } } func loadConfig() (config, error) { var cfg config cfg.Csrf.Secure = !DEBUG err := godotenv.Load() if err != nil { fmt.Println("Warning: Could not load a .env file") } cfg.Csrf.Key = []byte(os.Getenv("LENSLOCKED_CSRF_KEY")) if len(cfg.Csrf.Key) < 32 { return cfg, fmt.Errorf("Error: no or bad csrf protection key\nPlease set the LENSLOCKED_CSRF_KEY env var to a key at least 32 characters long.") } cfg.Postgres = os.Getenv("LENSLOCKED_DB_STRING") cfg.Email.Host = os.Getenv("LENSLOCKED_EMAIL_HOST") cfg.Email.Username = os.Getenv("LENSLOCKED_EMAIL_USERNAME") cfg.Email.Pass = os.Getenv("LENSLOCKED_EMAIL_PASSWORD") cfg.Email.Sender = os.Getenv("LENSLOCKED_EMAIL_FROM") cfg.Email.Port, err = strconv.Atoi(os.Getenv("LENSLOCKED_EMAIL_PORT")) if err != nil { fmt.Println("Warning: Invalid STMP port set in LENSLOCKED_EMAIL_PORT. Using port 587") cfg.Email.Port = 587 } cfg.Server.Address = os.Getenv("LENSLOCKED_ADDRESS") if cfg.Server.Address == "" { if DEBUG { cfg.Server.Address = ":3000" } else { return cfg, fmt.Errorf("No server address set\nPlease set the LENSLOCKED_ADDRESS env var to the servers address") } } return cfg, nil } func notFoundHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html; charset=utf8") w.WriteHeader(http.StatusNotFound) fmt.Fprint(w, "404 page not found") } func ConnectDB(dbstr string) *sql.DB { db, err := sql.Open("pgx", dbstr) if err != nil { panic(fmt.Sprint("Error connecting to database: %w", err)) } err = db.Ping() if err != nil { panic(fmt.Sprint("Error connecting to database: %w", err)) } return db } func MigrateDB(db *sql.DB, subfs fs.FS) error { goose.SetBaseFS(subfs) defer func() { goose.SetBaseFS(nil) }() err := goose.SetDialect("postgres") if err != nil { return fmt.Errorf("Migrate: %w", err) } err = goose.Up(db, ".") if err != nil { return fmt.Errorf("Migrate: %w", err) } return nil } func main() { cfg, err := loadConfig() if err != nil { panic(err) } db := ConnectDB(cfg.Postgres) defer db.Close() err = MigrateDB(db, migrations.FS) if err != nil { panic(err) } userService := models.UserService{DB: db} sessionService := models.SessionService{DB: db} emailService := models.NewEmailService(cfg.Email.Host, cfg.Email.Port, cfg.Email.Username, cfg.Email.Pass, cfg.Email.Sender) var usersCtrlr ctrlrs.Users = ctrlrs.Default(&userService, &sessionService, emailService) umw := userctx.UserMiddleware{SS: &sessionService} r := chi.NewRouter() r.Use(middleware.Logger) r.Use(csrf.Protect(cfg.Csrf.Key, csrf.Secure(cfg.Csrf.Secure))) r.Use(umw.SetUser) r.Get("/", ctrlrs.StaticController("home.gohtml", "tailwind.gohtml")) r.Get("/contact", ctrlrs.StaticController("contact.gohtml", "tailwind.gohtml")) r.Get("/faq", ctrlrs.FAQ("faq.gohtml", "tailwind.gohtml")) r.Get("/signup", usersCtrlr.GetSignup) r.Post("/signup", usersCtrlr.PostSignup) r.Get("/signin", usersCtrlr.GetSignin) r.Post("/signin", usersCtrlr.PostSignin) r.Post("/signout", usersCtrlr.GetSignout) r.Get("/forgot-pw", usersCtrlr.GetForgotPassword) r.Post("/forgot-pw", usersCtrlr.PostForgotPassword) r.Get("/reset-pw", usersCtrlr.GetResetPass) r.Post("/reset-pw", usersCtrlr.PostResetPass) r.Get("/user", umw.RequireUserfn(usersCtrlr.CurrentUser)) r.NotFound(notFoundHandler) fmt.Printf("Starting the server on %s...\n", cfg.Server.Address) http.ListenAndServe(cfg.Server.Address, r) }