diff --git a/app/app.go b/app/app.go new file mode 100644 index 0000000..c7d0125 --- /dev/null +++ b/app/app.go @@ -0,0 +1,19 @@ +package app + +import ( + "git.markbailey.dev/cerbervs/ptpp/app/routing" + "git.markbailey.dev/cerbervs/ptpp/app/server" +) + +type IApp interface { + Serve() +} + +type App struct { + Router routing.IRouter + Server server.IServer +} + +func (a App) Serve() { + a.Server.ListenAndServe() +} diff --git a/app/controller/controller.go b/app/controller/controller.go new file mode 100644 index 0000000..d82b83b --- /dev/null +++ b/app/controller/controller.go @@ -0,0 +1,97 @@ +package controller + +import ( + "git.markbailey.dev/cerbervs/ptpp/lib/logger" + "net/http" + + "git.markbailey.dev/cerbervs/ptpp/lib/database" + werror "git.markbailey.dev/cerbervs/ptpp/lib/error" + "git.markbailey.dev/cerbervs/ptpp/lib/session" +) + +type IController interface { + Get(w http.ResponseWriter, r *http.Request) error + Head(w http.ResponseWriter, r *http.Request) error + Options(w http.ResponseWriter, r *http.Request) error + Trace(w http.ResponseWriter, r *http.Request) error + Put(w http.ResponseWriter, r *http.Request) error + Delete(w http.ResponseWriter, r *http.Request) error + Post(w http.ResponseWriter, r *http.Request) error + Patch(w http.ResponseWriter, r *http.Request) error + Connect(w http.ResponseWriter, r *http.Request) error + Init(session.IManager, database.IDB, logger.ILogger) error +} + +type Controller struct { + Session session.IManager + Db database.IDB + Logger logger.ILogger +} + +func (c Controller) Init(s session.IManager, d database.IDB, l logger.ILogger) error { + return werror.Wrap(nil, "You must implement the init method in your extended controller") +} + +func (c Controller) Get(w http.ResponseWriter, _ *http.Request) (_ error) { + if _, err := w.Write([]byte("not implemented")); err != nil { + c.Logger.Error(c.Logger.Wrap(err, "Error writing response")) + return err + } + return nil +} +func (c Controller) Head(w http.ResponseWriter, _ *http.Request) (_ error) { + if _, err := w.Write([]byte("not implemented")); err != nil { + c.Logger.Error(c.Logger.Wrap(err, "Error writing response")) + return err + } + return nil +} +func (c Controller) Options(w http.ResponseWriter, _ *http.Request) (_ error) { + if _, err := w.Write([]byte("not implemented")); err != nil { + c.Logger.Error(c.Logger.Wrap(err, "Error writing response")) + return err + } + return nil +} +func (c Controller) Trace(w http.ResponseWriter, _ *http.Request) (_ error) { + if _, err := w.Write([]byte("not implemented")); err != nil { + c.Logger.Error(c.Logger.Wrap(err, "Error writing response")) + return err + } + return nil +} +func (c Controller) Put(w http.ResponseWriter, _ *http.Request) (_ error) { + if _, err := w.Write([]byte("not implemented")); err != nil { + c.Logger.Error(c.Logger.Wrap(err, "Error writing response")) + return err + } + return nil +} +func (c Controller) Delete(w http.ResponseWriter, _ *http.Request) (_ error) { + if _, err := w.Write([]byte("not implemented")); err != nil { + c.Logger.Error(c.Logger.Wrap(err, "Error writing response")) + return err + } + return nil +} +func (c Controller) Post(w http.ResponseWriter, _ *http.Request) (_ error) { + if _, err := w.Write([]byte("not implemented")); err != nil { + c.Logger.Error(c.Logger.Wrap(err, "Error writing response")) + return err + } + return nil +} +func (c Controller) Patch(w http.ResponseWriter, _ *http.Request) (_ error) { + if _, err := w.Write([]byte("not implemented")); err != nil { + c.Logger.Error(c.Logger.Wrap(err, "Error writing response")) + return err + } + return nil +} +func (c Controller) Connect(w http.ResponseWriter, _ *http.Request) (_ error) { + if _, err := w.Write([]byte("not implemented")); err != nil { + c.Logger.Error(c.Logger.Wrap(err, "Error writing response")) + return err + } + return nil +} diff --git a/app/handler/handler.go b/app/handler/handler.go new file mode 100644 index 0000000..61a1f96 --- /dev/null +++ b/app/handler/handler.go @@ -0,0 +1,13 @@ +package handler + +import "net/http" + +type Handler func(http.ResponseWriter, *http.Request) error + +func (fn Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if err := fn(w, r); err != nil { + w.Header().Set("HX-Retarget", "#layout_content") + w.Header().Set("HX-Reswap", "innerHTML") + http.Error(w, err.Error(), http.StatusInternalServerError) + } +} diff --git a/app/routing/router.go b/app/routing/router.go new file mode 100644 index 0000000..fd50bc0 --- /dev/null +++ b/app/routing/router.go @@ -0,0 +1,121 @@ +package routing + +import ( + "git.markbailey.dev/cerbervs/ptpp/app/controller" + "git.markbailey.dev/cerbervs/ptpp/app/handler" + "git.markbailey.dev/cerbervs/ptpp/lib/database" + werror "git.markbailey.dev/cerbervs/ptpp/lib/error" + "git.markbailey.dev/cerbervs/ptpp/lib/logger" + "git.markbailey.dev/cerbervs/ptpp/lib/middleware" + "git.markbailey.dev/cerbervs/ptpp/lib/session" + "git.markbailey.dev/cerbervs/ptpp/util/shared" + "log" + "net/http" +) + +var sess session.IManager + +type IRouter interface { + RegisterRoutes() http.Handler +} + +type Route struct { + Controller controller.IController + Path string + Name string +} + +type Router struct { + Mux *http.ServeMux + BasePath string + Routes []Route + SubRouters *[]Router + Middleware *[]middleware.Func +} + +func (r Router) HandleAllRequestMethods(route Route) { + r.Mux.Handle("GET "+r.BasePath+route.Path, handler.Handler(route.Controller.Get)) + r.Mux.Handle("OPTIONS "+r.BasePath+route.Path, handler.Handler(route.Controller.Options)) + r.Mux.Handle("TRACE "+r.BasePath+route.Path, handler.Handler(route.Controller.Trace)) + r.Mux.Handle("PUT "+r.BasePath+route.Path, handler.Handler(route.Controller.Put)) + r.Mux.Handle("DELETE "+r.BasePath+route.Path, handler.Handler(route.Controller.Delete)) + r.Mux.Handle("POST "+r.BasePath+route.Path, handler.Handler(route.Controller.Post)) + r.Mux.Handle("PATCH "+r.BasePath+route.Path, handler.Handler(route.Controller.Patch)) + r.Mux.Handle("CONNECT "+r.BasePath+route.Path, handler.Handler(route.Controller.Connect)) +} + +func (r Router) RegisterRoutes() http.Handler { + if r.Mux == nil { + r.Mux = http.NewServeMux() + } + + for _, route := range r.Routes { + if err := route.Controller.Init(sess, database.ChooseDB(), logger.NewCompositeLogger()); err != nil { + panic(err) + } + + r.HandleAllRequestMethods(route) + } + + if r.SubRouters != nil { + for _, subRouter := range *r.SubRouters { + sr := subRouter.RegisterRoutes() + r.Mux.Handle("GET "+r.BasePath+subRouter.BasePath, sr) + } + } + + if r.Middleware != nil { + mw := middleware.Compose(*r.Middleware) + return mw(r.Mux) + } + + return r.Mux +} + +func (r Router) RegisterFs() { + if r.Mux == nil { + r.Mux = http.NewServeMux() + } + + fs := http.FileServer(http.Dir(shared.GetFullyQualifiedPath("/public"))) + log.Println("Serving static files from: " + shared.GetFullyQualifiedPath("/public")) + r.Mux.Handle("GET "+r.BasePath+"public/", http.StripPrefix("/public/", fs)) +} + +type RouteMapping struct { + Path string + Name string +} + +func GetFlatRouteList(r Router) []RouteMapping { + var routes []RouteMapping + + for _, route := range r.Routes { + routes = append(routes, RouteMapping{Path: route.Path, Name: route.Name}) + } + + for _, subRouter := range *r.SubRouters { + routes = append(routes, GetFlatRouteList(subRouter)...) + } + + return routes +} + +func GetRouteByName(name string) (string, error) { + r := AppRouter + for _, route := range GetFlatRouteList(r) { + if route.Name == name { + return route.Path, nil + } + } + + return "", werror.Wrap(nil, "Route not found") +} +func init() { + var err error + + sess, err = session.NewManager("memory", "ptpp", 3600) + if err != nil { + panic(werror.Wrap(err, "Error creating session manager")) + } +} diff --git a/app/routing/routes.go b/app/routing/routes.go new file mode 100644 index 0000000..0f7a2a8 --- /dev/null +++ b/app/routing/routes.go @@ -0,0 +1,32 @@ +package routing + +import ( + "git.markbailey.dev/cerbervs/ptpp/handlers/admin" + "git.markbailey.dev/cerbervs/ptpp/handlers/shared" + "git.markbailey.dev/cerbervs/ptpp/lib/middleware" + "net/http" +) + +var AppRouter = Router{ + Mux: http.NewServeMux(), + BasePath: "/", + Routes: []Route{ + {Controller: &shared.HomePageHandler{}, Path: "", Name: "app.index"}, + {Controller: &shared.SignUpHandler{}, Path: "sign-up", Name: "app.user.sign_up"}, + {Controller: &shared.SignInHandler{}, Path: "sign-in", Name: "app.user.sign_in"}, + {Controller: &shared.SignOutHandler{}, Path: "sign-out", Name: "app.user.sign_out"}, + {Controller: &shared.PopulateHandler{}, Path: "populate", Name: "app.populate"}, + }, + SubRouters: &[]Router{ + { + Mux: nil, + BasePath: "admin/", + Routes: []Route{ + {Controller: &admin.IndexHandler{}, Path: "", Name: "app.admin.index"}, + {Controller: &admin.IndexHandler{}, Path: "butt", Name: "app.admin.butt"}, + }, + SubRouters: nil, + Middleware: &[]middleware.Func{middleware.WithAuth}, + }, + }, +} diff --git a/app/server/server.go b/app/server/server.go new file mode 100644 index 0000000..a7bc776 --- /dev/null +++ b/app/server/server.go @@ -0,0 +1,25 @@ +package server + +import ( + "log" + "net/http" + "strconv" +) + +type IServer interface { + ListenAndServe() +} + +type Server struct { + Addr string + Server http.Server + Port int +} + +func (s *Server) ListenAndServe() { + s.Addr = s.Addr + ":" + strconv.Itoa(s.Port) + serverError := s.Server.ListenAndServe() + if serverError != nil { + log.Fatal(serverError) + } +} diff --git a/cmd/main.go b/cmd/main.go index f56ac03..d6fb618 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -1,10 +1,61 @@ package main import ( - inf "git.markbailey.dev/cervers/ptpp/infrastructure" - _ "git.markbailey.dev/cervers/ptpp/lib/session/memory" + "git.markbailey.dev/cerbervs/ptpp/app" + "git.markbailey.dev/cerbervs/ptpp/app/routing" + "git.markbailey.dev/cerbervs/ptpp/app/server" + "net/http" + "os" + "strconv" + "time" + + _ "git.markbailey.dev/cerbervs/ptpp/lib/session/memory" ) func main() { - inf.NewServer().Serve() + const ( + addr = "0.0.0.0" + prodPort = 8080 + devPort = 8080 + ) + + r := routing.AppRouter + + rh := r.RegisterRoutes() + r.RegisterFs() + + var port int + if os.Getenv("HTMX_APP_ENV") == "production" { + port = prodPort + } else { + port = devPort + } + + s := server.Server{ + Addr: addr, + Server: http.Server{ + Addr: addr + ":" + strconv.Itoa(port), + Handler: rh, + DisableGeneralOptionsHandler: false, + TLSConfig: nil, + ReadTimeout: 5 * time.Second, + ReadHeaderTimeout: 0, + WriteTimeout: 5 * time.Second, + IdleTimeout: 0, + MaxHeaderBytes: 0, + TLSNextProto: nil, + ConnState: nil, + ErrorLog: nil, + BaseContext: nil, + ConnContext: nil, + }, + Port: port, + } + + a := app.App{ + Router: r, + Server: &s, + } + + a.Serve() } diff --git a/go.mod b/go.mod index f8197c0..eef1f19 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module git.markbailey.dev/cervers/ptpp +module git.markbailey.dev/cerbervs/ptpp go 1.23.2 diff --git a/handlers/admin/index.go b/handlers/admin/index.go index 3efde87..6d6c4c9 100644 --- a/handlers/admin/index.go +++ b/handlers/admin/index.go @@ -3,32 +3,34 @@ package admin import ( "context" "errors" - "git.markbailey.dev/cervers/ptpp/view/layout" + "git.markbailey.dev/cerbervs/ptpp/app/controller" + "git.markbailey.dev/cerbervs/ptpp/lib/database" + "git.markbailey.dev/cerbervs/ptpp/lib/logger" + "git.markbailey.dev/cerbervs/ptpp/lib/session" + "git.markbailey.dev/cerbervs/ptpp/view/layout" "net/http" - "git.markbailey.dev/cervers/ptpp/lib/logger" - "git.markbailey.dev/cervers/ptpp/lib/session" - "git.markbailey.dev/cervers/ptpp/view/admin" + "git.markbailey.dev/cerbervs/ptpp/view/admin" ) type IndexHandler struct { - logger logger.ILogger - session session.IManager + controller.Controller } -func NewAdminIndexHandler(l logger.ILogger, s session.IManager) *IndexHandler { - return &IndexHandler{ - logger: l, - session: s, - } +func (h IndexHandler) Init(s session.IManager, d database.IDB, l logger.ILogger) error { + h.Logger = l + h.Db = d + h.Session = s + + return nil } -func (h IndexHandler) Index(w http.ResponseWriter, r *http.Request) error { +func (h IndexHandler) Get(w http.ResponseWriter, r *http.Request) error { if r.URL.Path != "/" { w.WriteHeader(http.StatusNotFound) err := layout.NotFound().Render(context.Background(), w) if err != nil { - h.logger.Error(h.logger.Wrap(err, "Error rendering 404 page")) + h.Logger.Error(h.Logger.Wrap(err, "Error rendering 404 page")) return err } return nil @@ -37,12 +39,12 @@ func (h IndexHandler) Index(w http.ResponseWriter, r *http.Request) error { username, ok := r.Context().Value("username").(string) if !ok { err := errors.New("cannot decode request context: for key \"username\"") - h.logger.Error(h.logger.Wrap(err, "Error decoding request context")) + h.Logger.Error(h.Logger.Wrap(err, "Error decoding request context")) return err } if err := admin.Index(username).Render(context.Background(), w); err != nil { - h.logger.Error(h.logger.Wrap(err, "Error rendering admin index page")) + h.Logger.Error(h.Logger.Wrap(err, "Error rendering admin index page")) return err } diff --git a/handlers/homepage.go b/handlers/homepage.go deleted file mode 100644 index 8eed3f7..0000000 --- a/handlers/homepage.go +++ /dev/null @@ -1,40 +0,0 @@ -package handlers - -import ( - "context" - "git.markbailey.dev/cervers/ptpp/view/layout" - "net/http" - "os" - - "git.markbailey.dev/cervers/ptpp/lib/logger" - "git.markbailey.dev/cervers/ptpp/view/homepage" -) - -type HomePageHandler struct { - logger logger.ILogger -} - -func NewHomePageHandler(l logger.ILogger) *HomePageHandler { - return &HomePageHandler{ - logger: l, - } -} - -func (h HomePageHandler) Homepage(w http.ResponseWriter, r *http.Request) error { - if r.URL.Path != "/" { - w.WriteHeader(http.StatusNotFound) - err := layout.NotFound().Render(context.Background(), w) - if err != nil { - h.logger.Error(h.logger.Wrap(err, "Error rendering 404 page")) - return err - } - return nil - } - - if err := homepage.Homepage(os.Getenv("$HTMX_APP_ENV")).Render(context.Background(), w); err != nil { - h.logger.Error(h.logger.Wrap(err, "Error rendering homepage")) - return err - } - - return nil -} diff --git a/handlers/shared/homepage.go b/handlers/shared/homepage.go new file mode 100644 index 0000000..0ca0556 --- /dev/null +++ b/handlers/shared/homepage.go @@ -0,0 +1,45 @@ +package shared + +import ( + "context" + "git.markbailey.dev/cerbervs/ptpp/app/controller" + "git.markbailey.dev/cerbervs/ptpp/lib/database" + "git.markbailey.dev/cerbervs/ptpp/lib/logger" + "git.markbailey.dev/cerbervs/ptpp/lib/session" + "git.markbailey.dev/cerbervs/ptpp/view/layout" + "net/http" + "os" + + "git.markbailey.dev/cerbervs/ptpp/view/homepage" +) + +type HomePageHandler struct { + controller.Controller +} + +func (h HomePageHandler) Init(s session.IManager, d database.IDB, l logger.ILogger) error { + h.Logger = l + h.Db = d + h.Session = s + + return nil +} + +func (h HomePageHandler) Get(w http.ResponseWriter, r *http.Request) error { + if r.URL.Path != "/" { + w.WriteHeader(http.StatusNotFound) + err := layout.NotFound().Render(context.Background(), w) + if err != nil { + h.Logger.Error(h.Logger.Wrap(err, "Error rendering 404 page")) + return err + } + return nil + } + + if err := homepage.Homepage(os.Getenv("$HTMX_APP_ENV")).Render(context.Background(), w); err != nil { + h.Logger.Error(h.Logger.Wrap(err, "Error rendering homepage")) + return err + } + + return nil +} diff --git a/handlers/shared/user.go b/handlers/shared/user.go new file mode 100644 index 0000000..1fe61df --- /dev/null +++ b/handlers/shared/user.go @@ -0,0 +1,353 @@ +package shared + +import ( + "context" + "encoding/json" + "git.markbailey.dev/cerbervs/ptpp/app/controller" + "git.markbailey.dev/cerbervs/ptpp/lib/database" + "git.markbailey.dev/cerbervs/ptpp/lib/database/dto" + "git.markbailey.dev/cerbervs/ptpp/lib/logger" + "git.markbailey.dev/cerbervs/ptpp/lib/session" + "git.markbailey.dev/cerbervs/ptpp/util/auth" + "git.markbailey.dev/cerbervs/ptpp/util/shared" + "net/http" + "time" + + "git.markbailey.dev/cerbervs/ptpp/view/user" +) + +type SignUpHandler struct { + controller.Controller +} + +func (c SignUpHandler) Init(s session.IManager, d database.IDB, l logger.ILogger) error { + c.Logger = l + c.Db = d + c.Session = s + + return nil +} + +type SignInHandler struct { + controller.Controller +} + +func (c SignInHandler) Init(s session.IManager, d database.IDB, l logger.ILogger) error { + c.Logger = l + c.Db = d + c.Session = s + + return nil +} + +type PopulateHandler struct { + controller.Controller +} + +func (c PopulateHandler) Init(s session.IManager, d database.IDB, l logger.ILogger) error { + c.Logger = l + c.Db = d + c.Session = s + + return nil +} + +type SignOutHandler struct { + controller.Controller +} + +func (c SignOutHandler) Init(s session.IManager, d database.IDB, l logger.ILogger) error { + c.Logger = l + c.Db = d + c.Session = s + + return nil +} + +func (c PopulateHandler) Get(w http.ResponseWriter, r *http.Request) error { + existingOrg, err := c.Db.Repo().FindOrganizationByName("CerbervsSoft") + if existingOrg != nil && err == nil { + return shared.Redirect(w, r, "/signup", http.StatusSeeOther, false) + } + + authToken, err := auth.CreateTokenForUser("CerbervsSoft") + if err != nil { + return err + } + + organization := dto.Organization{ + Name: "CerbervsSoft", + OwnerName: "Mark Bailey", + OwnerEmail: "email@provider.com", + OwnerPhone: "+11111111111", + Authorized: 1, + AuthToken: authToken, + CreatedAt: time.Now(), + } + _, err = c.Db.Repo().CreateOrganization(organization) + if err != nil { + c.Db.Error(err) + return err + } + + return shared.Redirect(w, r, "/signup", http.StatusSeeOther, false) +} + +type UserSignInForm struct { + Username string `json:"username"` + Password string `json:"password"` +} + +func (c SignInHandler) Get(w http.ResponseWriter, r *http.Request) error { + sess := c.Session.SessionStart(w, r) + + username, ok := r.Context().Value("username").(string) + if ok { + if username == "" { + return failWithFormError(w, r, "Invalid Username or Password.", sess) + } else { + foundUser, err := c.Db.Repo().FindUserByUsername(username) + if foundUser == nil || err != nil { + return failWithFormError(w, r, "Invalid Username or Password.", sess) + } + + if foundUser.Admin == 1 { + return shared.Redirect(w, r, "/admin/", http.StatusSeeOther, false) + } else { + return shared.Redirect(w, r, "/", http.StatusSeeOther, false) + } + } + } + + formError, ok := sess.Get("formError").(string) + if !ok { + formError = "" + } + + if err := user.SignIn(formError).Render(context.Background(), w); err != nil { + c.Logger.Error(c.Logger.Wrap(err, "Error rendering sign in form")) + return err + } + + return nil +} + +func (c SignInHandler) Post(w http.ResponseWriter, r *http.Request) error { + sess := c.Session.SessionStart(w, r) + + fd := &UserSignInForm{} + + jsonDecoder := json.NewDecoder(r.Body) + if err := jsonDecoder.Decode(&fd); err != nil { + c.Logger.Error(c.Logger.Wrap(err, "Error decoding JSON")) + return err + } + + foundUser, err := c.Db.Repo().FindUserByUsername(fd.Username) + if foundUser == nil || err != nil { + return shared.Redirect(w, r, "/sign-in", http.StatusSeeOther, false) + } + + authenticated, err := auth.CheckPassword(fd.Password, foundUser.Password) + if err != nil || !authenticated { + return failWithFormError(w, r, "Invalid Username or Password.", sess) + } + + err = sess.Set("username", foundUser.Username) + if err != nil { + return err + } + + token, err := auth.CreateTokenForUser(foundUser.Username) + if err != nil { + c.Logger.Error(c.Logger.Wrap(err, "Error creating token")) + return err + } + + err = sess.Set("token", token) + if err != nil { + return err + } + + authToken, err := auth.CreateTokenForUser(foundUser.Username) + if err != nil { + c.Logger.Error(err) + return err + } + + _, err = c.Db.Repo().CreateHeartbeat(&dto.Heartbeat{ + User: foundUser.Identifier, + CreatedAt: time.Now(), + IpAddr: r.RemoteAddr, + AuthToken: authToken, + }) + if err != nil { + c.Db.Error(err) + return err + } + + jwtToken, err := auth.CreateTokenForUser(fd.Username) + if err != nil { + return err + } + + http.SetCookie(w, &http.Cookie{ + Name: "token", + Value: jwtToken, + Expires: time.Now().Add(time.Hour * 1), + Path: "/", + }) + + //err = sess.Delete("formError") + //if err != nil { + // return err + //} + + c.Logger.Info(foundUser.Username + " logged in") + if foundUser.Admin == 1 { + return shared.Redirect(w, r, "/admin/", http.StatusSeeOther, false) + } else { + return shared.Redirect(w, r, "/", http.StatusSeeOther, false) + } +} + +type UserSignUpForm struct { + Name string `json:"name"` + Email string `json:"email"` + Username string `json:"username"` + Password string `json:"password"` + PasswordConfirmation string `json:"password_confirmation"` +} + +func (c SignUpHandler) Get(w http.ResponseWriter, r *http.Request) error { + sess := c.Session.SessionStart(w, r) + + uname, ok := sess.Get("username").(string) + if !ok { + uname = "" + } + if uname != "" { + return shared.Redirect(w, r, "/", http.StatusSeeOther, false) + } + + if err := user.SignUpForm().Render(context.Background(), w); err != nil { + c.Logger.Error(c.Logger.Wrap(err, "Error rendering sign up form")) + return err + } + return nil +} + +func (c SignUpHandler) Post(w http.ResponseWriter, r *http.Request) error { + fd := &UserSignUpForm{} + + jsonDecoder := json.NewDecoder(r.Body) + if err := jsonDecoder.Decode(&fd); err == nil { + if fd.Password != fd.PasswordConfirmation { + if _, err := w.Write([]byte("Passwords don't match")); err != nil { + c.Logger.Error(c.Logger.Wrap(err, "Error writing response")) + return err + } + return nil + } + + foundUser, err := c.Db.Repo().FindUserByUsername(fd.Username) + + if foundUser != nil { + if _, err := w.Write([]byte("Invalid username. Please try another")); err != nil { + c.Logger.Error(c.Logger.Wrap(err, "Error writing response")) + } + return nil + } + + token, err := auth.CreateTokenForUser(fd.Username) + if err != nil { + c.Logger.Error(c.Logger.Wrap(err, "Error creating token")) + return err + } + + password, err := auth.HashPassword(fd.PasswordConfirmation) + if err != nil { + c.Logger.Error(c.Logger.Wrap(err, "Error hashing password")) + return err + } + + org, err := c.Db.Repo().FindOrganizationByName("CerbervsSoft") + if org == nil || err != nil { + c.Db.Error(err) + return err + } + + newUser, err := c.Db.Repo().CreateUser(dto.User{ + Username: fd.Username, + Password: password, + Name: fd.Name, + Email: fd.Email, + AuthToken: token, + Authorized: 1, + Admin: 1, + Organization: org.Identifier, + }) + if err != nil { + c.Db.Error(err) + return err + } + + _, err = c.Db.Repo().CreateHeartbeat(&dto.Heartbeat{ + User: newUser.Identifier, + CreatedAt: time.Now(), + IpAddr: r.RemoteAddr, + AuthToken: token, + }) + if err != nil { + c.Db.Error(err) + return err + } + + jwtToken, err := auth.CreateTokenForUser(fd.Username) + if err != nil { + return err + } + + http.SetCookie(w, &http.Cookie{ + Name: "token", + Value: jwtToken, + Expires: time.Now().Add(time.Hour * 1), + Path: "/", + }) + + return shared.Redirect(w, r, "/sign-in", http.StatusSeeOther, false) + } + + return nil +} + +func (c SignOutHandler) Get(w http.ResponseWriter, r *http.Request) error { + sess := c.Session.SessionStart(w, r) + + err := sess.Delete("username") + if err != nil { + return err + } + + err = sess.Delete("token") + if err != nil { + return err + } + + http.SetCookie(w, &http.Cookie{ + Name: "token", + Value: "", + Expires: time.Now().Add(-time.Hour), + Path: "/", + }) + + return shared.Redirect(w, r, "/", http.StatusSeeOther, true) +} + +func failWithFormError(w http.ResponseWriter, r *http.Request, formError string, sess session.ISession) error { + err := sess.Set("formError", formError) + if err != nil { + return err + } + return shared.Redirect(w, r, r.URL.Path, http.StatusSeeOther, false) +} diff --git a/handlers/user.go b/handlers/user.go deleted file mode 100644 index 9d6b507..0000000 --- a/handlers/user.go +++ /dev/null @@ -1,315 +0,0 @@ -package handlers - -import ( - "context" - "encoding/json" - "git.markbailey.dev/cervers/ptpp/lib/database" - "git.markbailey.dev/cervers/ptpp/lib/database/dto" - "net/http" - "time" - - "git.markbailey.dev/cervers/ptpp/lib/logger" - "git.markbailey.dev/cervers/ptpp/lib/session" - "git.markbailey.dev/cervers/ptpp/util" - "git.markbailey.dev/cervers/ptpp/view/user" -) - -type UserHandler struct { - logger logger.ILogger - session session.IManager - db database.IDB -} - -func NewUserHandler(l logger.ILogger, s session.IManager, db database.IDB) *UserHandler { - return &UserHandler{ - logger: l, - session: s, - db: db, - } -} - -func (u *UserHandler) Populate(w http.ResponseWriter, r *http.Request) error { - existingOrg, err := u.db.Repo().FindOrganizationByName("CerbervsSoft") - if existingOrg != nil && err == nil { - return util.Redirect(w, r, "/signup", http.StatusSeeOther, false) - } - - authToken, err := util.CreateTokenForUser("CerbervsSoft") - if err != nil { - return err - } - - organization := dto.Organization{ - Name: "CerbervsSoft", - OwnerName: "Mark Bailey", - OwnerEmail: "email@provider.com", - OwnerPhone: "+11111111111", - Authorized: 1, - AuthToken: authToken, - CreatedAt: time.Now(), - } - _, err = u.db.Repo().CreateOrganization(organization) - if err != nil { - return u.logDBError(err) - } - - return util.Redirect(w, r, "/signup", http.StatusSeeOther, false) -} - -type UserSignInForm struct { - Username string `json:"username"` - Password string `json:"password"` -} - -func (u *UserHandler) SignIn(w http.ResponseWriter, r *http.Request) error { - sess := u.session.SessionStart(w, r) - - if r.Method == http.MethodGet { - username, ok := r.Context().Value("username").(string) - if ok { - if username == "" { - return u.failWithFormError(w, r, "Invalid Username or Password.") - } else { - foundUser, err := u.db.Repo().FindUserByUsername(username) - if foundUser == nil || err != nil { - return u.failWithFormError(w, r, "Invalid Username or Password.") - } - - if foundUser.Admin == 1 { - return util.Redirect(w, r, "/admin/", http.StatusSeeOther, false) - } else { - return util.Redirect(w, r, "/", http.StatusSeeOther, false) - } - } - } - - formError, ok := sess.Get("formError").(string) - if !ok { - formError = "" - } - - if err := user.SignIn(formError).Render(context.Background(), w); err != nil { - u.logger.Error(u.logger.Wrap(err, "Error rendering sign in form")) - return err - } - - return nil - } - - fd := &UserSignInForm{} - - jsonDecoder := json.NewDecoder(r.Body) - if err := jsonDecoder.Decode(&fd); err != nil { - u.logger.Error(u.logger.Wrap(err, "Error decoding JSON")) - return err - } - - foundUser, err := u.db.Repo().FindUserByUsername(fd.Username) - if foundUser == nil || err != nil { - return util.Redirect(w, r, "/signin", http.StatusSeeOther, false) - } - - authenticated, err := util.CheckPassword(fd.Password, foundUser.Password) - if err != nil || !authenticated { - return u.failWithFormError(w, r, "Invalid Username or Password.") - } - - err = sess.Set("username", foundUser.Username) - if err != nil { - return err - } - - token, err := util.CreateTokenForUser(foundUser.Username) - if err != nil { - u.logger.Error(u.logger.Wrap(err, "Error creating token")) - return err - } - - err = sess.Set("token", token) - if err != nil { - return err - } - - authToken, err := util.CreateTokenForUser(foundUser.Username) - if err != nil { - u.logger.Error(err) - return err - } - - _, err = u.db.Repo().CreateHeartbeat(&dto.Heartbeat{ - User: foundUser.Identifier, - CreatedAt: time.Now(), - IpAddr: r.RemoteAddr, - AuthToken: authToken, - }) - if err != nil { - return u.logDBError(err) - } - - jwtToken, err := util.CreateTokenForUser(fd.Username) - if err != nil { - return err - } - - http.SetCookie(w, &http.Cookie{ - Name: "token", - Value: jwtToken, - Expires: time.Now().Add(time.Hour * 1), - Path: "/", - }) - - err = sess.Delete("formError") - if err != nil { - return err - } - - u.logger.Info(foundUser.Username + " logged in") - if foundUser.Admin == 1 { - return util.Redirect(w, r, "/admin/", http.StatusSeeOther, false) - } else { - return util.Redirect(w, r, "/", http.StatusSeeOther, false) - } -} - -type UserSignUpForm struct { - Name string `json:"name"` - Email string `json:"email"` - Username string `json:"username"` - Password string `json:"password"` - PasswordConfirmation string `json:"password_confirmation"` -} - -func (u *UserHandler) SignUp(w http.ResponseWriter, r *http.Request) error { - handlerSess := u.session.SessionStart(w, r) - uname, ok := handlerSess.Get("username").(string) - if !ok { - uname = "" - } - if uname != "" { - return util.Redirect(w, r, "/", http.StatusSeeOther, false) - } - - if r.Method == http.MethodGet { - if err := user.SignUpForm().Render(context.Background(), w); err != nil { - u.logger.Error(u.logger.Wrap(err, "Error rendering sign up form")) - return err - } - return nil - } - - fd := &UserSignUpForm{} - - jsonDecoder := json.NewDecoder(r.Body) - if err := jsonDecoder.Decode(&fd); err == nil { - if fd.Password != fd.PasswordConfirmation { - if _, err := w.Write([]byte("Passwords don't match")); err != nil { - u.logger.Error(u.logger.Wrap(err, "Error writing response")) - return err - } - return nil - } - - foundUser, err := u.db.Repo().FindUserByUsername(fd.Username) - - if foundUser != nil { - if _, err := w.Write([]byte("Invalid username. Please try another")); err != nil { - u.logger.Error(u.logger.Wrap(err, "Error writing response")) - } - return nil - } - - token, err := util.CreateTokenForUser(fd.Username) - if err != nil { - u.logger.Error(u.logger.Wrap(err, "Error creating token")) - return err - } - - password, err := util.HashPassword(fd.PasswordConfirmation) - if err != nil { - u.logger.Error(u.logger.Wrap(err, "Error hashing password")) - return err - } - - org, err := u.db.Repo().FindOrganizationByName("CerbervsSoft") - if org == nil || err != nil { - return u.logDBError(err) - } - - newUser, err := u.db.Repo().CreateUser(dto.User{ - Username: fd.Username, - Password: password, - Name: fd.Name, - Email: fd.Email, - AuthToken: token, - Authorized: 1, - Admin: 1, - Organization: org.Identifier, - }) - if err != nil { - return u.logDBError(err) - } - - _, err = u.db.Repo().CreateHeartbeat(&dto.Heartbeat{ - User: newUser.Identifier, - CreatedAt: time.Now(), - IpAddr: r.RemoteAddr, - AuthToken: token, - }) - if err != nil { - return u.logDBError(err) - } - - jwtToken, err := util.CreateTokenForUser(fd.Username) - if err != nil { - return err - } - - http.SetCookie(w, &http.Cookie{ - Name: "token", - Value: jwtToken, - Expires: time.Now().Add(time.Hour * 1), - Path: "/", - }) - - return util.Redirect(w, r, "/signin", http.StatusSeeOther, false) - } - - return nil -} - -func (u *UserHandler) SignOut(w http.ResponseWriter, r *http.Request) error { - sess := u.session.SessionStart(w, r) - - err := sess.Delete("username") - if err != nil { - return err - } - - err = sess.Delete("token") - if err != nil { - return err - } - - http.SetCookie(w, &http.Cookie{ - Name: "token", - Value: "", - Expires: time.Now().Add(-time.Hour), - Path: "/", - }) - - return util.Redirect(w, r, "/", http.StatusSeeOther, true) -} - -func (u *UserHandler) logDBError(err error) error { - u.db.Error(err) - return err -} - -func (u *UserHandler) failWithFormError(w http.ResponseWriter, r *http.Request, formError string) error { - sess := u.session.SessionStart(w, r) - err := sess.Set("formError", formError) - if err != nil { - return err - } - return util.Redirect(w, r, r.URL.Path, http.StatusSeeOther, false) -} diff --git a/infrastructure/router.go b/infrastructure/router.go deleted file mode 100644 index 8b30579..0000000 --- a/infrastructure/router.go +++ /dev/null @@ -1,79 +0,0 @@ -package infrastructure - -import ( - "git.markbailey.dev/cervers/ptpp/lib/database" - "net/http" - "sync" - - "git.markbailey.dev/cervers/ptpp/handlers" - "git.markbailey.dev/cervers/ptpp/handlers/admin" - "git.markbailey.dev/cervers/ptpp/lib/logger" - mw "git.markbailey.dev/cervers/ptpp/lib/middleware" - "git.markbailey.dev/cervers/ptpp/lib/session" -) - -var ( - globalSessions *session.Manager - commonRouter *http.ServeMux - adminStack mw.Func - commonStack mw.Func - lock = &sync.Mutex{} - il logger.ILogger -) - -func GetRouter() http.Handler { - commonStack = mw.Compose( - mw.WithLogger, - mw.WithUsername, - ) - - if commonRouter == nil { - lock.Lock() - defer lock.Unlock() - - adminStack = mw.Compose( - mw.WithAuth, - ) - - commonRouter = http.NewServeMux() - adminRouter := http.NewServeMux() - - // Serve static files - fs := http.FileServer(http.Dir("./public/")) - commonRouter.Handle("GET /public/", http.StripPrefix("/public", fs)) - commonRouter.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) { - http.ServeFile(w, r, "./public/assets/favicon/favicon.ico") - }) - - // Homepage routes - homepageHandler := handlers.NewHomePageHandler(il) - commonRouter.Handle("/", mw.ErrHandler(homepageHandler.Homepage)) - - if env == "production" { - return commonStack(commonRouter) - } - - // User routes - userHandler := handlers.NewUserHandler(il, globalSessions, database.ChooseDB()) - commonRouter.Handle("GET /signup", mw.ErrHandler(userHandler.SignUp)) - commonRouter.Handle("POST /signup", mw.ErrHandler(userHandler.SignUp)) - commonRouter.Handle("GET /signin", mw.ErrHandler(userHandler.SignIn)) - commonRouter.Handle("POST /signin", mw.ErrHandler(userHandler.SignIn)) - commonRouter.Handle("GET /signout", mw.ErrHandler(userHandler.SignOut)) - commonRouter.Handle("GET /populate", mw.ErrHandler(userHandler.Populate)) - - // Admin routes - adminIndexHandler := admin.NewAdminIndexHandler(il, globalSessions) - adminRouter.Handle("GET /", mw.ErrHandler(adminIndexHandler.Index)) - commonRouter.Handle("/admin/", http.StripPrefix("/admin", adminStack(adminRouter))) - } - - return commonStack(commonRouter) -} - -func init() { - globalSessions, _ = session.NewManager("memory", "ptpp", 3600) - go globalSessions.GC() - - il = logger.NewCompositeLogger() -} diff --git a/infrastructure/server.go b/infrastructure/server.go deleted file mode 100644 index 118382b..0000000 --- a/infrastructure/server.go +++ /dev/null @@ -1,64 +0,0 @@ -package infrastructure - -import ( - "fmt" - "log" - "net/http" - "os" - "strconv" - "time" -) - -type Server struct { - addr string - http.Server - port int -} - -var ( - env = os.Getenv("HTMX_APP_ENV") -) - -func NewServer() *Server { - const ( - addr = "0.0.0.0" - ) - - var port int - - if env == "production" { - port = 8080 - } else { - port = 8080 - } - - return &Server{ - addr, - http.Server{ - Addr: addr + ":" + strconv.Itoa(port), - Handler: GetRouter(), - DisableGeneralOptionsHandler: false, - TLSConfig: nil, - ReadTimeout: 5 * time.Second, - ReadHeaderTimeout: 0, - WriteTimeout: 5 * time.Second, - IdleTimeout: 0, - MaxHeaderBytes: 0, - TLSNextProto: nil, - ConnState: nil, - ErrorLog: nil, - BaseContext: nil, - ConnContext: nil, - }, - port, - } -} - -func (s *Server) Serve() { - fmt.Printf("Starting.\nListening at %s on port %s\n", s.addr, strconv.Itoa(s.port)) - serverError := s.ListenAndServe() - - if serverError != nil { - log.Fatal(serverError) - } -} diff --git a/lib/database/db.go b/lib/database/db.go index c14dca4..3c7fabe 100644 --- a/lib/database/db.go +++ b/lib/database/db.go @@ -1,12 +1,12 @@ package database import ( - "git.markbailey.dev/cervers/ptpp/lib/database/development" - "git.markbailey.dev/cervers/ptpp/lib/database/production" - "git.markbailey.dev/cervers/ptpp/lib/repository" + "git.markbailey.dev/cerbervs/ptpp/lib/database/development" + "git.markbailey.dev/cerbervs/ptpp/lib/database/production" + "git.markbailey.dev/cerbervs/ptpp/lib/repository" "os" - "git.markbailey.dev/cervers/ptpp/lib/logger" + "git.markbailey.dev/cerbervs/ptpp/lib/logger" ) type IDB interface { diff --git a/lib/database/development/db.go b/lib/database/development/db.go index 55194a7..cf4f3e3 100644 --- a/lib/database/development/db.go +++ b/lib/database/development/db.go @@ -2,9 +2,9 @@ package development import ( "database/sql" - "git.markbailey.dev/cervers/ptpp/lib/logger" - "git.markbailey.dev/cervers/ptpp/lib/repository" - "git.markbailey.dev/cervers/ptpp/models/sqlite" + "git.markbailey.dev/cerbervs/ptpp/lib/logger" + "git.markbailey.dev/cerbervs/ptpp/lib/repository" + "git.markbailey.dev/cerbervs/ptpp/models/sqlite" _ "github.com/mattn/go-sqlite3" "sync" ) diff --git a/lib/database/development/heartbeatrepo.go b/lib/database/development/heartbeatrepo.go index be276c1..eb8dba8 100644 --- a/lib/database/development/heartbeatrepo.go +++ b/lib/database/development/heartbeatrepo.go @@ -2,9 +2,9 @@ package development import ( "context" - "git.markbailey.dev/cervers/ptpp/lib/database/dto" - pterror "git.markbailey.dev/cervers/ptpp/lib/error" - "git.markbailey.dev/cervers/ptpp/models/sqlite" + "git.markbailey.dev/cerbervs/ptpp/lib/database/dto" + pterror "git.markbailey.dev/cerbervs/ptpp/lib/error" + "git.markbailey.dev/cerbervs/ptpp/models/sqlite" ) type HeartbeatRepository struct{} diff --git a/lib/database/development/organizationrepo.go b/lib/database/development/organizationrepo.go index 1916f3c..eb37fac 100644 --- a/lib/database/development/organizationrepo.go +++ b/lib/database/development/organizationrepo.go @@ -2,9 +2,9 @@ package development import ( "context" - "git.markbailey.dev/cervers/ptpp/lib/database/dto" - pterror "git.markbailey.dev/cervers/ptpp/lib/error" - "git.markbailey.dev/cervers/ptpp/models/sqlite" + "git.markbailey.dev/cerbervs/ptpp/lib/database/dto" + pterror "git.markbailey.dev/cerbervs/ptpp/lib/error" + "git.markbailey.dev/cerbervs/ptpp/models/sqlite" ) type OrganizationRepository struct{} diff --git a/lib/database/development/userrepo.go b/lib/database/development/userrepo.go index 67e6859..295c245 100644 --- a/lib/database/development/userrepo.go +++ b/lib/database/development/userrepo.go @@ -2,9 +2,9 @@ package development import ( "context" - "git.markbailey.dev/cervers/ptpp/lib/database/dto" - pterror "git.markbailey.dev/cervers/ptpp/lib/error" - "git.markbailey.dev/cervers/ptpp/models/sqlite" + "git.markbailey.dev/cerbervs/ptpp/lib/database/dto" + pterror "git.markbailey.dev/cerbervs/ptpp/lib/error" + "git.markbailey.dev/cerbervs/ptpp/models/sqlite" ) type UserRepository struct{} diff --git a/lib/database/production/db.go b/lib/database/production/db.go index e86a041..6005b5a 100644 --- a/lib/database/production/db.go +++ b/lib/database/production/db.go @@ -2,9 +2,9 @@ package production import ( "database/sql" - "git.markbailey.dev/cervers/ptpp/lib/logger" - "git.markbailey.dev/cervers/ptpp/lib/repository" - "git.markbailey.dev/cervers/ptpp/models/postgres" + "git.markbailey.dev/cerbervs/ptpp/lib/logger" + "git.markbailey.dev/cerbervs/ptpp/lib/repository" + "git.markbailey.dev/cerbervs/ptpp/models/postgres" _ "github.com/lib/pq" "os" "sync" diff --git a/lib/database/production/heartbeatrepo.go b/lib/database/production/heartbeatrepo.go index 1a20cb1..4f027cc 100644 --- a/lib/database/production/heartbeatrepo.go +++ b/lib/database/production/heartbeatrepo.go @@ -2,9 +2,9 @@ package production import ( "context" - "git.markbailey.dev/cervers/ptpp/lib/database/dto" - pterror "git.markbailey.dev/cervers/ptpp/lib/error" - "git.markbailey.dev/cervers/ptpp/models/postgres" + "git.markbailey.dev/cerbervs/ptpp/lib/database/dto" + pterror "git.markbailey.dev/cerbervs/ptpp/lib/error" + "git.markbailey.dev/cerbervs/ptpp/models/postgres" ) type HeartbeatRepository struct{} diff --git a/lib/database/production/organizationrepo.go b/lib/database/production/organizationrepo.go index 20651b0..2d79c46 100644 --- a/lib/database/production/organizationrepo.go +++ b/lib/database/production/organizationrepo.go @@ -2,9 +2,9 @@ package production import ( "context" - "git.markbailey.dev/cervers/ptpp/lib/database/dto" - pterror "git.markbailey.dev/cervers/ptpp/lib/error" - "git.markbailey.dev/cervers/ptpp/models/postgres" + "git.markbailey.dev/cerbervs/ptpp/lib/database/dto" + pterror "git.markbailey.dev/cerbervs/ptpp/lib/error" + "git.markbailey.dev/cerbervs/ptpp/models/postgres" ) type OrganizationRepository struct{} diff --git a/lib/database/production/userrepo.go b/lib/database/production/userrepo.go index 7d7bdc0..8e08d70 100644 --- a/lib/database/production/userrepo.go +++ b/lib/database/production/userrepo.go @@ -2,9 +2,9 @@ package production import ( "context" - "git.markbailey.dev/cervers/ptpp/lib/database/dto" - pterror "git.markbailey.dev/cervers/ptpp/lib/error" - "git.markbailey.dev/cervers/ptpp/models/postgres" + "git.markbailey.dev/cerbervs/ptpp/lib/database/dto" + pterror "git.markbailey.dev/cerbervs/ptpp/lib/error" + "git.markbailey.dev/cerbervs/ptpp/models/postgres" ) type UserRepository struct{} diff --git a/lib/locator/locator.go b/lib/locator/locator.go index f3178b9..f5a0db6 100644 --- a/lib/locator/locator.go +++ b/lib/locator/locator.go @@ -1,8 +1,8 @@ package locator import ( - perr "git.markbailey.dev/cervers/ptpp/lib/error" - "git.markbailey.dev/cervers/ptpp/lib/locator/service" + perr "git.markbailey.dev/cerbervs/ptpp/lib/error" + "git.markbailey.dev/cerbervs/ptpp/lib/locator/service" "sync" ) diff --git a/lib/locator/service/database.go b/lib/locator/service/database.go index 0d4bdee..beb03b6 100644 --- a/lib/locator/service/database.go +++ b/lib/locator/service/database.go @@ -1,7 +1,7 @@ package service import ( - "git.markbailey.dev/cervers/ptpp/lib/database" + "git.markbailey.dev/cerbervs/ptpp/lib/database" "sync" ) diff --git a/lib/locator/service/logger.go b/lib/locator/service/logger.go index 61ec7d0..6d057f1 100644 --- a/lib/locator/service/logger.go +++ b/lib/locator/service/logger.go @@ -1,7 +1,7 @@ package service import ( - "git.markbailey.dev/cervers/ptpp/lib/logger" + "git.markbailey.dev/cerbervs/ptpp/lib/logger" "sync" ) diff --git a/lib/logger/composite.go b/lib/logger/composite.go index 5c07c80..24a9fb2 100644 --- a/lib/logger/composite.go +++ b/lib/logger/composite.go @@ -2,8 +2,8 @@ package logger import ( "fmt" - pterror "git.markbailey.dev/cervers/ptpp/lib/error" - "git.markbailey.dev/cervers/ptpp/util" + pterror "git.markbailey.dev/cerbervs/ptpp/lib/error" + "git.markbailey.dev/cerbervs/ptpp/util/shared" "log" "os" "sync" @@ -44,7 +44,7 @@ func (l CompositeLogger) getLogFile() *os.File { compositeLogFileLock.Lock() defer compositeLogFileLock.Unlock() - absPath := util.GetFullyQualifiedPath("/log") + absPath := shared.GetFullyQualifiedPath("/log") err := os.MkdirAll(absPath, os.ModePerm) if err != nil { diff --git a/lib/logger/database.go b/lib/logger/database.go index 9347df9..5c4de69 100644 --- a/lib/logger/database.go +++ b/lib/logger/database.go @@ -2,8 +2,8 @@ package logger import ( "fmt" - pterror "git.markbailey.dev/cervers/ptpp/lib/error" - "git.markbailey.dev/cervers/ptpp/util" + pterror "git.markbailey.dev/cerbervs/ptpp/lib/error" + "git.markbailey.dev/cerbervs/ptpp/util/shared" "log" "os" "sync" @@ -44,7 +44,7 @@ func (l DBLogger) getLogFile() *os.File { dbLogFileLock.Lock() defer dbLogFileLock.Unlock() - absPath := util.GetFullyQualifiedPath("/log") + absPath := shared.GetFullyQualifiedPath("/log") generalLog, err := os.OpenFile(absPath+"/db-log.log", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) if err != nil { diff --git a/lib/middleware/middleware.go b/lib/middleware/middleware.go index c3ab1e1..7165a5b 100644 --- a/lib/middleware/middleware.go +++ b/lib/middleware/middleware.go @@ -2,38 +2,20 @@ package middleware import ( "fmt" - "git.markbailey.dev/cervers/ptpp/lib/database" + "git.markbailey.dev/cerbervs/ptpp/util/auth" + "git.markbailey.dev/cerbervs/ptpp/util/shared" "net/http" "os" - "git.markbailey.dev/cervers/ptpp/lib/logger" - "git.markbailey.dev/cervers/ptpp/lib/session" - "git.markbailey.dev/cervers/ptpp/util" + "git.markbailey.dev/cerbervs/ptpp/lib/logger" + "git.markbailey.dev/cerbervs/ptpp/lib/session" ) -type Middleware struct { - l logger.ILogger - db database.IDB -} +type Func func(http.Handler) http.Handler -type ( - ErrHandler func(http.ResponseWriter, *http.Request) error - Func func(http.Handler) http.Handler -) +var sess session.IManager -var ( - sess session.IManager -) - -func (fn ErrHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - if err := fn(w, r); err != nil { - w.Header().Set("HX-Retarget", "#layout_content") - w.Header().Set("HX-Reswap", "innerHTML") - http.Error(w, err.Error(), http.StatusInternalServerError) - } -} - -func Compose(xs ...Func) Func { +func Compose(xs []Func) Func { return func(next http.Handler) http.Handler { for i := len(xs) - 1; i >= 0; i-- { x := xs[i] @@ -78,7 +60,7 @@ func WithLogger(next http.Handler) http.Handler { func WithAuth(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var ( - claims *util.CustomClaims + claims *auth.CustomClaims cookie *http.Cookie err error token string @@ -86,7 +68,7 @@ func WithAuth(next http.Handler) http.Handler { ) if handlerSess.Get("username") != nil { - req := util.AddValuesToRequestContext(r, map[any]any{ + req := shared.AddValuesToRequestContext(r, map[any]any{ "username": handlerSess.Get("username"), }) next.ServeHTTP(w, req) @@ -94,19 +76,19 @@ func WithAuth(next http.Handler) http.Handler { } if cookie, err = r.Cookie("token"); err != nil { - _ = util.Redirect(w, r, "/signin", http.StatusSeeOther, true) + _ = shared.Redirect(w, r, "/signin", http.StatusSeeOther, true) return } if token = cookie.Value; token == "" { - _ = util.Redirect(w, r, "/signin", http.StatusSeeOther, true) + _ = shared.Redirect(w, r, "/signin", http.StatusSeeOther, true) return } - if claims, err = util.ParseToken(token, os.Getenv("TOKEN_SECRET")); err != nil { - _ = util.Redirect(w, r, "/signin", http.StatusSeeOther, true) + if claims, err = auth.ParseToken(token, os.Getenv("TOKEN_SECRET")); err != nil { + _ = shared.Redirect(w, r, "/signin", http.StatusSeeOther, true) return } - req := util.AddValuesToRequestContext(r, map[any]any{ + req := shared.AddValuesToRequestContext(r, map[any]any{ "username": claims.Username, }) next.ServeHTTP(w, req) @@ -116,7 +98,7 @@ func WithAuth(next http.Handler) http.Handler { func WithUsername(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var ( - claims *util.CustomClaims + claims *auth.CustomClaims cookie *http.Cookie err error token string @@ -124,7 +106,7 @@ func WithUsername(next http.Handler) http.Handler { ) if handlerSess.Get("username") != nil { - req := util.AddValuesToRequestContext(r, map[any]any{ + req := shared.AddValuesToRequestContext(r, map[any]any{ "username": handlerSess.Get("username"), }) next.ServeHTTP(w, req) @@ -136,14 +118,14 @@ func WithUsername(next http.Handler) http.Handler { if token = cookie.Value; token == "" { uname = nil } - if claims, err = util.ParseToken(token, os.Getenv("TOKEN_SECRET")); err != nil { + if claims, err = auth.ParseToken(token, os.Getenv("TOKEN_SECRET")); err != nil { uname = nil } uname = &claims.Username } if uname != nil { - req := util.AddValuesToRequestContext(r, map[any]any{ + req := shared.AddValuesToRequestContext(r, map[any]any{ "username": uname, }) next.ServeHTTP(w, req) diff --git a/lib/repository/repository.go b/lib/repository/repository.go index 4a03aa4..f36b944 100644 --- a/lib/repository/repository.go +++ b/lib/repository/repository.go @@ -1,6 +1,6 @@ package repository -import "git.markbailey.dev/cervers/ptpp/lib/database/dto" +import "git.markbailey.dev/cerbervs/ptpp/lib/database/dto" type IHeartbeat interface { CreateHeartbeat(h *dto.Heartbeat) (*dto.Heartbeat, error) diff --git a/lib/session/memory/memprovider.go b/lib/session/memory/memprovider.go index 266db8e..5ead1b8 100644 --- a/lib/session/memory/memprovider.go +++ b/lib/session/memory/memprovider.go @@ -5,7 +5,7 @@ import ( "sync" "time" - "git.markbailey.dev/cervers/ptpp/lib/session" + "git.markbailey.dev/cerbervs/ptpp/lib/session" ) var prov = &Provider{list: list.New()} diff --git a/package-lock.json b/package-lock.json index 50a6353..3b428f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,16 +1,15 @@ { - "name": "ptpp", + "name": "go-full-stack", "lockfileVersion": 3, "requires": true, "packages": { "": { "dependencies": { - "htmx.org": "^1.9.10", - "preline": "^2.0.3" + "htmx.org": "^1.9.10" }, "devDependencies": { "@tailwindcss/forms": "^0.5.9", - "tailwindcss": "^3.4.1" + "tailwindcss": "^3.4.15" } }, "node_modules/@alloc/quick-lru": { @@ -135,15 +134,6 @@ "node": ">=14" } }, - "node_modules/@popperjs/core": { - "version": "2.11.8", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", - "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" - } - }, "node_modules/@tailwindcss/forms": { "version": "0.5.9", "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.9.tgz", @@ -231,12 +221,13 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, + "license": "MIT", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -333,6 +324,7 @@ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "dev": true, + "license": "MIT", "bin": { "cssesc": "bin/cssesc" }, @@ -402,10 +394,11 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, + "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -562,6 +555,7 @@ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.12.0" } @@ -591,10 +585,11 @@ } }, "node_modules/jiti": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", - "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", + "version": "1.21.6", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", + "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", "dev": true, + "license": "MIT", "bin": { "jiti": "bin/jiti.js" } @@ -633,12 +628,13 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, + "license": "MIT", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -766,10 +762,11 @@ } }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", @@ -802,9 +799,9 @@ } }, "node_modules/postcss": { - "version": "8.4.35", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz", - "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==", + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", "dev": true, "funding": [ { @@ -820,10 +817,11 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" @@ -910,29 +908,37 @@ } }, "node_modules/postcss-nested": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", - "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", "dependencies": { - "postcss-selector-parser": "^6.0.11" + "postcss-selector-parser": "^6.1.1" }, "engines": { "node": ">=12.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, "peerDependencies": { "postcss": "^8.2.14" } }, "node_modules/postcss-selector-parser": { - "version": "6.0.15", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz", - "integrity": "sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", "dev": true, + "license": "MIT", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -947,14 +953,6 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "dev": true }, - "node_modules/preline": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/preline/-/preline-2.0.3.tgz", - "integrity": "sha512-V/sLmRIHd23UCdvJNRKKszntgUqA0381erVzRpQ48NjjMOgI7DyFW4mr+lg387V0oeBc5Dx9Jxa5voppVoH9GA==", - "dependencies": { - "@popperjs/core": "^2.11.2" - } - }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -1080,10 +1078,11 @@ } }, "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -1219,33 +1218,34 @@ } }, "node_modules/tailwindcss": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.1.tgz", - "integrity": "sha512-qAYmXRfk3ENzuPBakNK0SRrUDipP8NQnEY6772uDhflcQz5EhRdD7JNZxyrFHVQNCwULPBn6FNPp9brpO7ctcA==", + "version": "3.4.15", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.15.tgz", + "integrity": "sha512-r4MeXnfBmSOuKUWmXe6h2CcyfzJCEk4F0pptO5jlnYSIViUkVmsawj80N5h2lO3gwcmSb4n3PuN+e+GC1Guylw==", "dev": true, + "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", - "chokidar": "^3.5.3", + "chokidar": "^3.6.0", "didyoumean": "^1.2.2", "dlv": "^1.1.3", - "fast-glob": "^3.3.0", + "fast-glob": "^3.3.2", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", - "jiti": "^1.19.1", + "jiti": "^1.21.6", "lilconfig": "^2.1.0", - "micromatch": "^4.0.5", + "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", - "picocolors": "^1.0.0", - "postcss": "^8.4.23", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", - "postcss-load-config": "^4.0.1", - "postcss-nested": "^6.0.1", - "postcss-selector-parser": "^6.0.11", - "resolve": "^1.22.2", - "sucrase": "^3.32.0" + "postcss-load-config": "^4.0.2", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" }, "bin": { "tailwind": "lib/cli.js", @@ -1281,6 +1281,7 @@ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, + "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -1298,7 +1299,8 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/which": { "version": "2.0.2", diff --git a/package.json b/package.json index 30b2ebc..7aa41a5 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,9 @@ { "devDependencies": { "@tailwindcss/forms": "^0.5.9", - "tailwindcss": "^3.4.1" + "tailwindcss": "^3.4.15" }, "dependencies": { - "htmx.org": "^1.9.10", - "preline": "^2.0.3" + "htmx.org": "^1.9.10" } } diff --git a/util/auth.go b/util/auth/auth.go similarity index 99% rename from util/auth.go rename to util/auth/auth.go index 1610b5b..ede9cda 100644 --- a/util/auth.go +++ b/util/auth/auth.go @@ -1,4 +1,4 @@ -package util +package auth import ( "errors" diff --git a/util/shared.go b/util/shared/shared.go similarity index 98% rename from util/shared.go rename to util/shared/shared.go index 0b28df1..e370f98 100644 --- a/util/shared.go +++ b/util/shared/shared.go @@ -1,4 +1,4 @@ -package util +package shared import ( "context" diff --git a/view/admin/index.templ b/view/admin/index.templ index 38fdb62..f10614a 100644 --- a/view/admin/index.templ +++ b/view/admin/index.templ @@ -1,6 +1,6 @@ package admin -import "git.markbailey.dev/cervers/ptpp/view/layout" +import "git.markbailey.dev/cerbervs/ptpp/view/layout" templ Index(name string) { @layout.Layout() { diff --git a/view/admin/index_templ.go b/view/admin/index_templ.go index f3a84a5..3ac3db5 100644 --- a/view/admin/index_templ.go +++ b/view/admin/index_templ.go @@ -8,7 +8,7 @@ package admin import "github.com/a-h/templ" import templruntime "github.com/a-h/templ/runtime" -import "git.markbailey.dev/cervers/ptpp/view/layout" +import "git.markbailey.dev/cerbervs/ptpp/view/layout" func Index(name string) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { diff --git a/view/homepage/homepage.templ b/view/homepage/homepage.templ index f194882..6f95c38 100644 --- a/view/homepage/homepage.templ +++ b/view/homepage/homepage.templ @@ -1,6 +1,9 @@ package homepage -import "git.markbailey.dev/cervers/ptpp/view/layout" +import ( +"git.markbailey.dev/cerbervs/ptpp/view/layout" +"git.markbailey.dev/cerbervs/ptpp/app/routing" +) templ Homepage(env string) { @layout.Layout() { @@ -8,20 +11,18 @@ templ Homepage(env string) {