Compare commits

...

12 Commits

53 changed files with 1101 additions and 717 deletions

View File

@ -4,8 +4,8 @@ tmp_dir = "tmp"
[build]
args_bin = []
bin = "./tmp/main"
cmd = "go build -o ./tmp/main ./cmd/"
bin = "./air-bin"
cmd = "go build -o ./air-bin ./cmd/"
delay = 1000
exclude_dir = ["public", "data", "tmp"]
exclude_file = []

1
.gitignore vendored
View File

@ -10,3 +10,4 @@ tmp/*
data/*
data/database.db
.idea
air-bin

19
app/app.go Normal file
View File

@ -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()
}

View File

@ -0,0 +1,106 @@
package controller
import (
"net/http"
"git.markbailey.dev/cerbervs/ptpp/app/session"
"git.markbailey.dev/cerbervs/ptpp/lib/logger"
"git.markbailey.dev/cerbervs/ptpp/lib/database"
)
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, IControllerCtx) IController
}
type Controller struct {
Session session.IManager
Db database.IDB
Logger logger.ILogger
Ctx IControllerCtx
}
func (c *Controller) Init(s session.IManager, d database.IDB, l logger.ILogger, ctx IControllerCtx) IController {
return nil
}
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
}

22
app/controller/ctx.go Normal file
View File

@ -0,0 +1,22 @@
package controller
type IRouterCtx interface {
GetRouteByName(string) (string, error)
}
type IControllerCtx interface {
GetRouteByName(string) string
}
type ControllerCtx struct {
RouterCtx IRouterCtx
}
func (c ControllerCtx) GetRouteByName(name string) string {
path, err := c.RouterCtx.GetRouteByName(name)
if err != nil {
panic(err)
}
return path
}

15
app/handler/handler.go Normal file
View File

@ -0,0 +1,15 @@
package handler
import (
"net/http"
)
type HandlerFunc func(http.ResponseWriter, *http.Request) error
func (fn HandlerFunc) 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)
}
}

125
app/routing/router.go Normal file
View File

@ -0,0 +1,125 @@
package routing
import (
"errors"
"net/http"
"sync"
"git.markbailey.dev/cerbervs/ptpp/app/controller"
"git.markbailey.dev/cerbervs/ptpp/app/handler"
"git.markbailey.dev/cerbervs/ptpp/app/session"
"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/util"
)
type IRouter interface {
RegisterRoutes() http.Handler
}
type Route struct {
Controller controller.IController
Path string
Name string
}
type Router struct {
Mux *http.ServeMux
Middleware *[]middleware.Func
BasePath string
SubRouters []*Router
Routes []Route
}
var (
rtr *Router
rtrOnce sync.Once
mux *http.ServeMux
muxOnce sync.Once
fsOnce sync.Once
sess session.IManager
)
func (r *Router) HandleAllRequestMethods(route Route, fullPath string) {
ctx := controller.ControllerCtx{RouterCtx: rtr}
c := route.Controller.Init(sess, database.ChooseDB(), logger.NewCompositeLogger(), ctx)
r.Mux.Handle("GET "+fullPath, handler.HandlerFunc(c.Get))
r.Mux.Handle("OPTIONS "+fullPath, handler.HandlerFunc(c.Options))
r.Mux.Handle("TRACE "+fullPath, handler.HandlerFunc(c.Trace))
r.Mux.Handle("PUT "+fullPath, handler.HandlerFunc(c.Put))
r.Mux.Handle("DELETE "+fullPath, handler.HandlerFunc(c.Delete))
r.Mux.Handle("POST "+fullPath, handler.HandlerFunc(c.Post))
r.Mux.Handle("PATCH "+fullPath, handler.HandlerFunc(c.Patch))
r.Mux.Handle("CONNECT "+fullPath, handler.HandlerFunc(c.Connect))
}
func (r *Router) RegisterRoutes() http.Handler {
rtrOnce.Do(func() { rtr = r })
muxOnce.Do(func() { mux = http.NewServeMux(); r.Mux = mux })
fsOnce.Do(func() { r.RegisterFs() })
if r.Mux == nil {
r.Mux = http.NewServeMux()
}
for _, route := range r.Routes {
r.HandleAllRequestMethods(route, r.BasePath+route.Path)
}
for _, subRouter := range r.SubRouters {
r.Mux.Handle(
"GET "+r.BasePath+subRouter.BasePath,
http.StripPrefix(subRouter.BasePath, subRouter.RegisterRoutes()),
)
}
if r.Middleware != nil {
mw := middleware.Compose(*r.Middleware)
mctx := middleware.MiddlewareCtx{RouterCtx: rtr}
return mw(mux, mctx)
}
return mux
}
func (r *Router) RegisterFs() {
fs := http.FileServer(http.Dir(util.GetFullyQualifiedPath("/public")))
r.Mux.Handle("GET "+r.BasePath+"public/", http.StripPrefix("/public/", fs))
}
type RouteMapping struct {
Path string
Name string
}
func (r *Router) GetFlatRouteList() []RouteMapping {
routes := []RouteMapping{}
for _, route := range r.Routes {
routes = append(routes, RouteMapping{Path: r.BasePath + route.Path, Name: route.Name})
}
for _, subRouter := range r.SubRouters {
routes = append(routes, subRouter.GetFlatRouteList()...)
}
return routes
}
func (r *Router) GetRouteByName(name string) (string, error) {
for _, route := range r.GetFlatRouteList() {
if route.Name == name {
return route.Path, nil
}
}
return "", werror.Wrap(errors.New(name+" does not exist"), "Route not found")
}
func init() {
var err error
sess, err = session.NewManager("memory", "ptpp", 3600)
go sess.GC()
if err != nil {
panic(werror.Wrap(err, "Error creating session manager"))
}
}

45
app/routing/routes.go Normal file
View File

@ -0,0 +1,45 @@
package routing
import (
"sync"
"git.markbailey.dev/cerbervs/ptpp/handlers/admin"
"git.markbailey.dev/cerbervs/ptpp/handlers/shared"
"git.markbailey.dev/cerbervs/ptpp/lib/middleware"
)
var (
rtrinst *Router
rtrinstOnce sync.Once
)
func NewRouter() *Router {
rtrinstOnce.Do(func() {
rtrinst = &Router{
Mux: nil,
BasePath: "/",
Routes: []Route{
{Controller: shared.HomePageController{}, 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},
},
},
Middleware: &[]middleware.Func{middleware.DontPanic, middleware.WithLogger, middleware.WithUsername},
}
})
return rtrinst
}

25
app/server/server.go Normal file
View File

@ -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)
}
}

View File

@ -0,0 +1 @@
package session

View File

@ -1,11 +1,9 @@
package memory
package session
import (
"container/list"
"sync"
"time"
"git.markbailey.dev/cervers/ptpp/lib/session"
)
var prov = &Provider{list: list.New()}
@ -58,7 +56,7 @@ type Provider struct {
lock sync.Mutex
}
func (p *Provider) SessionInit(sid string) (session.ISession, error) {
func (p *Provider) SessionInit(sid string) (ISession, error) {
prov.lock.Lock()
defer prov.lock.Unlock()
v := make(map[interface{}]interface{}, 0)
@ -68,7 +66,7 @@ func (p *Provider) SessionInit(sid string) (session.ISession, error) {
return newSess, nil
}
func (p *Provider) SessionRead(sid string) (session.ISession, error) {
func (p *Provider) SessionRead(sid string) (ISession, error) {
if element, ok := prov.sessions[sid]; ok {
return element.Value.(*MemSessionStore), nil
}
@ -119,5 +117,5 @@ func (p *Provider) SessionUpdate(sid string) error {
func init() {
prov.sessions = make(map[string]*list.Element, 0)
session.Register("memory", prov)
Register("memory", prov)
}

View File

@ -290,6 +290,7 @@ __sqlc() {
echo ================================================================================
echo = Generating SQLC ==============================================================
echo ================================================================================
rm -rf ./models/*
sqlc generate
echo -e "\n"
}
@ -298,6 +299,7 @@ __templ() {
echo ================================================================================
echo = Generating templates =========================================================
echo ================================================================================
find . -name '*_templ.go' -delete
templ generate
echo -e "\n"
}

View File

@ -1,10 +1,75 @@
package main
import (
inf "git.markbailey.dev/cervers/ptpp/infrastructure"
_ "git.markbailey.dev/cervers/ptpp/lib/session/memory"
"fmt"
"net/http"
"os"
"strconv"
"time"
"git.markbailey.dev/cerbervs/ptpp/app"
"git.markbailey.dev/cerbervs/ptpp/app/routing"
"git.markbailey.dev/cerbervs/ptpp/app/server"
"git.markbailey.dev/cerbervs/ptpp/lib/logger"
_ "git.markbailey.dev/cerbervs/ptpp/app/session"
)
func main() {
inf.NewServer().Serve()
const (
addr = "0.0.0.0"
prodPort = 8080
devPort = 8080
)
logger := logger.NewCompositeLogger()
r := routing.NewRouter()
mux := r.RegisterRoutes()
rl := r.GetFlatRouteList()
for _, route := range rl {
path, err := r.GetRouteByName(route.Name)
if err != nil {
logger.Error(fmt.Errorf(fmt.Sprintf("Error getting route by name: %s", route.Name)))
}
logger.Info(fmt.Sprintf("Name: %s, Path: %s", route.Name, path))
}
logger.Info(fmt.Sprintf("%+v", mux))
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: mux,
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()
}

4
go.mod
View File

@ -1,6 +1,6 @@
module git.markbailey.dev/cervers/ptpp
module git.markbailey.dev/cerbervs/ptpp
go 1.23.2
go 1.23
require (
github.com/a-h/templ v0.2.793

View File

@ -3,32 +3,35 @@ package admin
import (
"context"
"errors"
"git.markbailey.dev/cervers/ptpp/view/layout"
"git.markbailey.dev/cerbervs/ptpp/app/controller"
"git.markbailey.dev/cerbervs/ptpp/app/session"
"git.markbailey.dev/cerbervs/ptpp/lib/database"
"git.markbailey.dev/cerbervs/ptpp/lib/logger"
"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, ctx controller.IControllerCtx) controller.IController {
h.Logger = l
h.Db = d
h.Session = s
h.Ctx = ctx
return h
}
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 +40,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
}

View File

@ -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
}

View File

@ -0,0 +1,46 @@
package shared
import (
"context"
"git.markbailey.dev/cerbervs/ptpp/app/controller"
"git.markbailey.dev/cerbervs/ptpp/app/session"
"git.markbailey.dev/cerbervs/ptpp/lib/database"
"git.markbailey.dev/cerbervs/ptpp/lib/logger"
"git.markbailey.dev/cerbervs/ptpp/view/layout"
"net/http"
"os"
"git.markbailey.dev/cerbervs/ptpp/view/homepage"
)
type HomePageController struct {
controller.Controller
}
func (c *HomePageController) Init(s session.IManager, d database.IDB, l logger.ILogger, ctx controller.IControllerCtx) controller.IController {
c.Logger = l
c.Db = d
c.Session = s
c.Ctx = ctx
return c
}
func (h HomePageController) 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"), h.Ctx).Render(context.Background(), w); err != nil {
h.Logger.Error(h.Logger.Wrap(err, "Error rendering homepage"))
return err
}
return nil
}

361
handlers/shared/user.go Normal file
View File

@ -0,0 +1,361 @@
package shared
import (
"context"
"encoding/json"
"net/http"
"time"
"git.markbailey.dev/cerbervs/ptpp/app/controller"
"git.markbailey.dev/cerbervs/ptpp/app/session"
"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/util"
"git.markbailey.dev/cerbervs/ptpp/view/user"
)
type PopulateHandler struct {
controller.Controller
}
func (c PopulateHandler) Init(s session.IManager, d database.IDB, l logger.ILogger, ctx controller.IControllerCtx) controller.IController {
c.Logger = l
c.Db = d
c.Session = s
c.Ctx = ctx
return c
}
func (c PopulateHandler) Get(w http.ResponseWriter, r *http.Request) error {
existingOrg, err := c.Db.Repo().FindOrganizationByName("CerbervsSoft")
if existingOrg != nil && err == nil {
return util.Redirect(w, r, c.Ctx.GetRouteByName("app.user.sign_up"), 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 = c.Db.Repo().CreateOrganization(organization)
if err != nil {
c.Db.Error(err)
return err
}
return util.Redirect(w, r, c.Ctx.GetRouteByName("app.user.sign_up"), http.StatusSeeOther, false)
}
type SignInHandler struct {
controller.Controller
}
func (c SignInHandler) Init(s session.IManager, d database.IDB, l logger.ILogger, ctx controller.IControllerCtx) controller.IController {
c.Logger = l
c.Db = d
c.Session = s
c.Ctx = ctx
return c
}
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 util.Redirect(w, r, c.Ctx.GetRouteByName("app.admin.index"), http.StatusSeeOther, false)
} else {
return util.Redirect(w, r, c.Ctx.GetRouteByName("app.index"), http.StatusSeeOther, false)
}
}
}
formError, ok := sess.Get("formError").(string)
if !ok {
formError = ""
}
if err := user.SignIn(formError, c.Ctx).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 util.Redirect(w, r, c.Ctx.GetRouteByName("app.user.sign_in"), http.StatusPermanentRedirect, false)
}
authenticated, err := util.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 := util.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 := util.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 := 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
}
c.Logger.Info(foundUser.Username + " logged in")
if foundUser.Admin == 1 {
return util.Redirect(w, r, c.Ctx.GetRouteByName("app.admin.index"), http.StatusSeeOther, false)
} else {
return util.Redirect(w, r, c.Ctx.GetRouteByName("app.index"), http.StatusSeeOther, false)
}
}
type SignUpHandler struct {
controller.Controller
}
func (c SignUpHandler) Init(s session.IManager, d database.IDB, l logger.ILogger, ctx controller.IControllerCtx) controller.IController {
c.Logger = l
c.Db = d
c.Session = s
c.Ctx = ctx
return c
}
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 util.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 err != nil {
c.Db.Error(err)
return err
}
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 := util.CreateTokenForUser(fd.Username)
if err != nil {
c.Logger.Error(c.Logger.Wrap(err, "Error creating token"))
return err
}
password, err := util.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 := 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, "/sign-in", http.StatusSeeOther, false)
}
return nil
}
type SignOutHandler struct {
controller.Controller
}
func (c SignOutHandler) Init(s session.IManager, d database.IDB, l logger.ILogger, ctx controller.IControllerCtx) controller.IController {
c.Logger = l
c.Db = d
c.Session = s
c.Ctx = ctx
return c
}
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 util.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 util.Redirect(w, r, r.URL.Path, http.StatusSeeOther, false)
}

View File

@ -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)
}

View File

@ -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()
}

View File

@ -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)
}
}

View File

@ -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 {

View File

@ -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"
)

View File

@ -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{}

View File

@ -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{}

View File

@ -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{}

View File

@ -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"

View File

@ -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{}

View File

@ -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{}

View File

@ -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{}

View File

@ -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"
)

View File

@ -1,7 +1,7 @@
package service
import (
"git.markbailey.dev/cervers/ptpp/lib/database"
"git.markbailey.dev/cerbervs/ptpp/lib/database"
"sync"
)

View File

@ -1,7 +1,7 @@
package service
import (
"git.markbailey.dev/cervers/ptpp/lib/logger"
"git.markbailey.dev/cerbervs/ptpp/lib/logger"
"sync"
)

View File

@ -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"
"log"
"os"
"sync"

View File

@ -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"
"log"
"os"
"sync"

View File

@ -2,56 +2,71 @@ package middleware
import (
"fmt"
"git.markbailey.dev/cervers/ptpp/lib/database"
"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/app/session"
"git.markbailey.dev/cerbervs/ptpp/util"
"git.markbailey.dev/cerbervs/ptpp/lib/logger"
)
type Middleware struct {
l logger.ILogger
db database.IDB
type IRouterCtx interface {
GetRouteByName(string) (string, error)
}
type (
ErrHandler func(http.ResponseWriter, *http.Request) error
Func func(http.Handler) http.Handler
)
type IMiddlewareCtx interface {
GetRouteByName(string) string
}
var (
sess session.IManager
)
type MiddlewareCtx struct {
RouterCtx IRouterCtx
}
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 (m MiddlewareCtx) GetRouteByName(name string) string {
path, err := m.RouterCtx.GetRouteByName(name)
if err != nil {
panic(err)
}
return path
}
func Compose(xs ...Func) Func {
return func(next http.Handler) http.Handler {
type Func func(http.Handler, IMiddlewareCtx) http.Handler
var sess session.IManager
func Compose(xs []Func) Func {
return func(next http.Handler, m IMiddlewareCtx) http.Handler {
for i := len(xs) - 1; i >= 0; i-- {
x := xs[i]
next = x(next)
next = x(next, m)
}
return next
}
}
func WithLogger(next http.Handler) http.Handler {
func DontPanic(next http.Handler, m IMiddlewareCtx) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if r := recover(); r != nil {
http.Error(w, fmt.Sprintf("%v", r), http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}
func WithLogger(next http.Handler, m IMiddlewareCtx) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
handlerSess := sess.SessionStart(w, r)
username, ok := handlerSess.Get("username").(string)
if !ok {
username = "<Not Found>"
username = ""
}
username = "(username " + username + ")"
ipAddr := r.Header.Get("X-Real-IP")
if ipAddr == "" {
@ -63,7 +78,7 @@ func WithLogger(next http.Handler) http.Handler {
handlerLogger := logger.NewCompositeLogger()
output := fmt.Sprintf(
"%s Request sent from %s to %s (username? %s)",
"%s Request sent from %s to %s %s",
r.Method,
ipAddr,
r.URL.Path,
@ -75,7 +90,7 @@ func WithLogger(next http.Handler) http.Handler {
})
}
func WithAuth(next http.Handler) http.Handler {
func WithAuth(next http.Handler, m IMiddlewareCtx) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var (
claims *util.CustomClaims
@ -94,15 +109,15 @@ func WithAuth(next http.Handler) http.Handler {
}
if cookie, err = r.Cookie("token"); err != nil {
_ = util.Redirect(w, r, "/signin", http.StatusSeeOther, true)
_ = util.Redirect(w, r, m.GetRouteByName("app.user.sign_in"), http.StatusPermanentRedirect, true)
return
}
if token = cookie.Value; token == "" {
_ = util.Redirect(w, r, "/signin", http.StatusSeeOther, true)
_ = util.Redirect(w, r, m.GetRouteByName("app.user.sign_in"), http.StatusPermanentRedirect, true)
return
}
if claims, err = util.ParseToken(token, os.Getenv("TOKEN_SECRET")); err != nil {
_ = util.Redirect(w, r, "/signin", http.StatusSeeOther, true)
_ = util.Redirect(w, r, m.GetRouteByName("app.user.sign_in"), http.StatusPermanentRedirect, true)
return
}
@ -113,7 +128,7 @@ func WithAuth(next http.Handler) http.Handler {
})
}
func WithUsername(next http.Handler) http.Handler {
func WithUsername(next http.Handler, m IMiddlewareCtx) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var (
claims *util.CustomClaims
@ -155,5 +170,9 @@ func WithUsername(next http.Handler) http.Handler {
}
func init() {
sess, _ = session.NewManager("memory", "ptpp", 3600)
var err error
sess, err = session.NewManager("memory", "ptpp", 3600)
if err != nil {
panic(err)
}
}

View File

@ -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)

View File

@ -1 +0,0 @@
package memory

148
package-lock.json generated
View File

@ -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",

View File

@ -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"
}
}

View File

@ -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() {

View File

@ -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) {

View File

@ -1,27 +1,28 @@
package homepage
import "git.markbailey.dev/cervers/ptpp/view/layout"
import (
"git.markbailey.dev/cerbervs/ptpp/view/layout"
"git.markbailey.dev/cerbervs/ptpp/app/controller"
)
templ Homepage(env string) {
templ Homepage(env string, context controller.IControllerCtx) {
@layout.Layout() {
<div class="h-screen flex flex-col items-center justify-evenly text-blue-400 text-2xl">
<div>
Welcome to the homepage
</div>
if env == "development" {
<div>
<a href="/signup">
<button class="text-gray-400 text-md border-black rounded-lg bg-gray-300 p-2">
Sign Up
</button>
</a>
<a href="/signin">
<button class="text-gray-400 text-md border-black rounded-lg bg-gray-300 p-2">
Sign In
</button>
</a>
</div>
}
<div>
<a href={ templ.SafeURL(context.GetRouteByName("app.user.sign_up")) }>
<button class="text-gray-400 text-md border-black rounded-lg bg-gray-300 p-2">
Sign Up
</button>
</a>
<a href={ templ.SafeURL(context.GetRouteByName("app.user.sign_in")) }>
<button class="text-gray-400 text-md border-black rounded-lg bg-gray-300 p-2">
Sign In
</button>
</a>
</div>
</div>
}
}

View File

@ -8,9 +8,12 @@ package homepage
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/app/controller"
"git.markbailey.dev/cerbervs/ptpp/view/layout"
)
func Homepage(env string) templ.Component {
func Homepage(env string, context controller.IControllerCtx) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
@ -43,17 +46,25 @@ func Homepage(env string) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"h-screen flex flex-col items-center justify-evenly text-blue-400 text-2xl\"><div>Welcome to the homepage</div>")
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"h-screen flex flex-col items-center justify-evenly text-blue-400 text-2xl\"><div>Welcome to the homepage</div><div><a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
if env == "development" {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div><a href=\"/signup\"><button class=\"text-gray-400 text-md border-black rounded-lg bg-gray-300 p-2\" disabled>Sign Up</button></a> <a href=\"/signin\"><button class=\"text-gray-400 text-md border-black rounded-lg bg-gray-300 p-2\">Sign In</button></a></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 templ.SafeURL = templ.SafeURL(context.GetRouteByName("app.user.sign_up"))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var3)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>")
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><button class=\"text-gray-400 text-md border-black rounded-lg bg-gray-300 p-2\">Sign Up</button></a> <a href=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var4 templ.SafeURL = templ.SafeURL(context.GetRouteByName("app.user.sign_in"))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(string(templ_7745c5c3_Var4)))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\"><button class=\"text-gray-400 text-md border-black rounded-lg bg-gray-300 p-2\">Sign In</button></a></div></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}

View File

@ -15,7 +15,7 @@ templ Layout() {
</head>
<body>
<div id="#layout_content" hx-ext="response-targets" hx-target-5*="this">
<div id="#layout_content" hx-ext="response-targets" hx-target-error="this">
{ children... }
</div>
<script type="text/javascript" src="/public/assets/js/index.js"></script>

View File

@ -29,7 +29,7 @@ func Layout() templ.Component {
templ_7745c5c3_Var1 = templ.NopComponent
}
ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<!doctype html><html lang=\"en\"><head><title></title><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"device-width, initial-scale=1.0\"><script src=\"https://unpkg.com/htmx.org@1.9.10\"></script><script src=\"https://unpkg.com/htmx.org@1.9.10/dist/ext/json-enc.js\"></script><script src=\"https://unpkg.com/htmx.org/dist/ext/response-targets.js\"></script><link href=\"/public/assets/css/output.css\" rel=\"stylesheet\"></head><body><div id=\"#layout_content\" hx-ext=\"response-targets\" hx-target-5*=\"this\">")
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<!doctype html><html lang=\"en\"><head><title></title><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"device-width, initial-scale=1.0\"><script src=\"https://unpkg.com/htmx.org@1.9.10\"></script><script src=\"https://unpkg.com/htmx.org@1.9.10/dist/ext/json-enc.js\"></script><script src=\"https://unpkg.com/htmx.org/dist/ext/response-targets.js\"></script><link href=\"/public/assets/css/output.css\" rel=\"stylesheet\"></head><body><div id=\"#layout_content\" hx-ext=\"response-targets\" hx-target-error=\"this\">")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}

View File

@ -1,8 +1,11 @@
package user
import "git.markbailey.dev/cervers/ptpp/view/layout"
import (
"git.markbailey.dev/cerbervs/ptpp/view/layout"
"git.markbailey.dev/cerbervs/ptpp/app/controller"
)
templ SignIn(err string) {
templ SignIn(err string, context controller.IControllerCtx) {
@layout.Layout() {
<section class="bg-gray-50 dark:bg-gray-900" id="section">
<div class="flex flex-col items-center justify-center px-6 py-8 mx-auto md:h-screen lg:py-0">
@ -15,9 +18,7 @@ templ SignIn(err string) {
</h1>
<form
class="space-y-4 md:space-y-6"
action="/signin"
method="post"
hx-post="/signin"
hx-post={ context.GetRouteByName("app.user.sign_in") }
hx-swap="outerHTML"
hx-target="#section"
>

View File

@ -8,9 +8,12 @@ package user
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/app/controller"
"git.markbailey.dev/cerbervs/ptpp/view/layout"
)
func SignIn(err string) templ.Component {
func SignIn(err string, context controller.IControllerCtx) templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {
templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context
if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil {
@ -43,7 +46,20 @@ func SignIn(err string) templ.Component {
}()
}
ctx = templ.InitializeContext(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<section class=\"bg-gray-50 dark:bg-gray-900\" id=\"section\"><div class=\"flex flex-col items-center justify-center px-6 py-8 mx-auto md:h-screen lg:py-0\"><div class=\"w-full bg-white rounded-lg shadow dark:border md:mt-0 sm:max-w-md xl:p-0 dark:bg-gray-800 dark:border-gray-700\"><div class=\"p-6 space-y-4 md:space-y-6 sm:p-8\"><h1 class=\"text-xl font-bold leading-tight tracking-tight text-gray-900 md:text-2xl dark:text-white\">Create an account</h1><form class=\"space-y-4 md:space-y-6\" action=\"/signin\" method=\"post\" hx-post=\"/signin\" hx-swap=\"outerHTML\" hx-target=\"#section\"><div><label for=\"username\" class=\"block mb-2 text-sm font-medium text-gray-900 dark:text-white\">Username</label> <input type=\"text\" name=\"username\" id=\"username\" placeholder=\"Username\" class=\"bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500\" required=\"true\"></div><div><label for=\"password\" class=\"block mb-2 text-sm font-medium text-gray-900 dark:text-white\">Password</label> <input type=\"password\" name=\"password\" id=\"password\" placeholder=\"••••••••\" class=\"bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500\" required=\"true\"></div>")
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<section class=\"bg-gray-50 dark:bg-gray-900\" id=\"section\"><div class=\"flex flex-col items-center justify-center px-6 py-8 mx-auto md:h-screen lg:py-0\"><div class=\"w-full bg-white rounded-lg shadow dark:border md:mt-0 sm:max-w-md xl:p-0 dark:bg-gray-800 dark:border-gray-700\"><div class=\"p-6 space-y-4 md:space-y-6 sm:p-8\"><h1 class=\"text-xl font-bold leading-tight tracking-tight text-gray-900 md:text-2xl dark:text-white\">Create an account</h1><form class=\"space-y-4 md:space-y-6\" hx-post=\"")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(context.GetRouteByName("app.user.sign_in"))
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/user/signin.templ`, Line: 21, Col: 59}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\" hx-swap=\"outerHTML\" hx-target=\"#section\"><div><label for=\"username\" class=\"block mb-2 text-sm font-medium text-gray-900 dark:text-white\">Username</label> <input type=\"text\" name=\"username\" id=\"username\" placeholder=\"Username\" class=\"bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500\" required=\"true\"></div><div><label for=\"password\" class=\"block mb-2 text-sm font-medium text-gray-900 dark:text-white\">Password</label> <input type=\"password\" name=\"password\" id=\"password\" placeholder=\"••••••••\" class=\"bg-gray-50 border border-gray-300 text-gray-900 sm:text-sm rounded-lg focus:ring-primary-600 focus:border-primary-600 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500\" required=\"true\"></div>")
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
@ -52,12 +68,12 @@ func SignIn(err string) templ.Component {
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
var templ_7745c5c3_Var3 string
templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(err)
var templ_7745c5c3_Var4 string
templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(err)
if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/user/signin.templ`, Line: 54, Col: 14}
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view/user/signin.templ`, Line: 55, Col: 14}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3))
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4))
if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}

View File

@ -1,6 +1,6 @@
package user
import "git.markbailey.dev/cervers/ptpp/view/layout"
import "git.markbailey.dev/cerbervs/ptpp/view/layout"
templ SignUpForm() {
@layout.Layout() {

View File

@ -8,7 +8,7 @@ package user
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 SignUpForm() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {

View File

@ -1,6 +1,6 @@
package user
import "git.markbailey.dev/cervers/ptpp/view/layout"
import "git.markbailey.dev/cerbervs/ptpp/view/layout"
templ SignUpSuccess() {
@layout.Layout() {

View File

@ -8,7 +8,7 @@ package user
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 SignUpSuccess() templ.Component {
return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) {