diff --git a/.gitignore b/.gitignore index adf8f72..7c8dc08 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,8 @@ *.dll *.so *.dylib - +*_templ.txt +*_templ.go # Test binary, built with `go test -c` *.test diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..1d914e3 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "files.exclude": { + "**/*_templ.go": true, + "**/*_templ.txt": true + } +} \ No newline at end of file diff --git a/Justfile b/Justfile new file mode 100644 index 0000000..c80431f --- /dev/null +++ b/Justfile @@ -0,0 +1,4 @@ + +# Runs the 'templ generate` every time a file is updated and reloads the debugger +debug: + templ generate --watch --proxy="http://localhost:3000" --cmd="go run ." \ No newline at end of file diff --git a/client/apiclient.go b/client/apiclient.go new file mode 100644 index 0000000..79b2910 --- /dev/null +++ b/client/apiclient.go @@ -0,0 +1,18 @@ +package client + +const ( + HeaderContentType = "Content-Type" + MIMEApplicationForm = "application/x-www-form-urlencoded" +) + +type ApiClient struct { + Auth Auth + + ServerAddress string +} + +func New(serverAddress string) ApiClient { + return ApiClient{ + Auth: newAuthClient(serverAddress), + } +} diff --git a/client/auth.go b/client/auth.go new file mode 100644 index 0000000..492e94d --- /dev/null +++ b/client/auth.go @@ -0,0 +1,88 @@ +package client + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + + "git.jamestombleson.com/jtom38/go-cook/api/domain" +) + +type Auth interface { + Register(username, password string) error + Login(username, password string) error +} + +type AuthClient struct { + serverAddress string + client http.Client +} + +func newAuthClient(serverAddress string) AuthClient { + return AuthClient{ + serverAddress: serverAddress, + client: http.Client{}, + } +} + +func (a AuthClient) Register(username, password string) error { + endpoint := fmt.Sprintf("%s/api/v1/auth/register", a.serverAddress) + + req, err := http.NewRequest(http.MethodPost, endpoint, nil) + if err != nil { + return err + } + + req.Header.Set(HeaderContentType, MIMEApplicationForm) + req.Form.Add("username", username) + req.Form.Add("password", password) + resp, err := a.client.Do(req) + if err != nil { + return err + } + + defer resp.Body.Close() + + content, err := io.ReadAll(resp.Body) + if err != nil { + return err + } + + var bind = domain.ErrorResponse{} + err = json.Unmarshal(content, &bind) + if err != nil { + return err + } + + return nil +} + +func (a AuthClient) Login(username, password string) error { + endpoint := fmt.Sprintf("%s/api/v1/auth/register", a.serverAddress) + req, err := http.NewRequest(http.MethodPost, endpoint, nil) + if err != nil { + return err + } + + req.Header.Set(HeaderContentType, MIMEApplicationForm) + req.Form.Add("username", username) + req.Form.Add("password", password) + resp, err := a.client.Do(req) + if err != nil { + return err + } + + defer resp.Body.Close() + content, err := io.ReadAll(req.Body) + if err != nil { + return err + } + + var bind = domain.ErrorResponse{} + err = json.Unmarshal(content, &bind) + if err != nil { + return err + } + return nil +} \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..b3614a9 --- /dev/null +++ b/go.mod @@ -0,0 +1,31 @@ +module templ-test + +go 1.22.1 + +require ( + git.jamestombleson.com/jtom38/go-cook v0.0.0-20240406005506-adf6c1e9ddb3 + github.com/joho/godotenv v1.5.1 +) + +require ( + github.com/a-h/templ v0.2.648 + github.com/gorilla/sessions v1.2.2 + github.com/labstack/echo-contrib v0.16.0 + github.com/labstack/echo/v4 v4.11.4 +) + +require ( + github.com/golang-jwt/jwt v3.2.2+incompatible // indirect + github.com/gorilla/context v1.1.2 // indirect + github.com/gorilla/securecookie v1.1.2 // indirect + github.com/labstack/gommon v0.4.2 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasttemplate v1.2.2 // indirect + golang.org/x/crypto v0.21.0 // indirect + golang.org/x/net v0.22.0 // indirect + golang.org/x/sys v0.18.0 // indirect + golang.org/x/text v0.14.0 // indirect + golang.org/x/time v0.5.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..0e993ac --- /dev/null +++ b/go.sum @@ -0,0 +1,53 @@ +git.jamestombleson.com/jtom38/go-cook v0.0.0-20240406005506-adf6c1e9ddb3 h1:XUnvhhRB5X8k5wg/kErQH0nCihbNR1mTxfCxWvWKIAM= +git.jamestombleson.com/jtom38/go-cook v0.0.0-20240406005506-adf6c1e9ddb3/go.mod h1:4l/tX7wJagBjEUf7a2hC4RhQhFEcn8OmI1D+FbiwYCs= +github.com/a-h/templ v0.2.648 h1:A1ggHGIE7AONOHrFaDTM8SrqgqHL6fWgWCijQ21Zy9I= +github.com/a-h/templ v0.2.648/go.mod h1:SA7mtYwVEajbIXFRh3vKdYm/4FYyLQAtPH1+KxzGPA8= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/gorilla/context v1.1.2 h1:WRkNAv2uoa03QNIc1A6u4O7DAGMUVoopZhkiXWA2V1o= +github.com/gorilla/context v1.1.2/go.mod h1:KDPwT9i/MeWHiLl90fuTgrt4/wPcv75vFAZLaOOcbxM= +github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= +github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= +github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY= +github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/labstack/echo-contrib v0.16.0 h1:vk5Kd+egpTOJxD3l+3IvZzQWPbrXiYxhkkgkJL99j/w= +github.com/labstack/echo-contrib v0.16.0/go.mod h1:mjX5VB3OqJcroIEycptBOY9Hr7rK+unq79W8QFKGNV0= +github.com/labstack/echo/v4 v4.11.4 h1:vDZmA+qNeh1pd/cCkEicDMrjtrnMGQ1QFI9gWN1zGq8= +github.com/labstack/echo/v4 v4.11.4/go.mod h1:noh7EvLwqDsmh/X/HWKPUl1AjzJrhyptRyEbQJfxen8= +github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= +github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= +github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/handlers/auth.go b/handlers/auth.go new file mode 100644 index 0000000..879889b --- /dev/null +++ b/handlers/auth.go @@ -0,0 +1,24 @@ +package handlers + +import ( + "net/http" + "templ-test/views" + + "github.com/labstack/echo/v4" +) + +func (h *Handlers) AuthLogin(c echo.Context) error { + return Render(c, http.StatusOK, views.AuthLogin()) +} + +func (h *Handlers) AuthLoginPost(c echo.Context) error { + // check the form data + //user := c.FormValue("email") + //password := c.FormValue("password") + + // send request to the API + //h.api. + + // render + return nil +} diff --git a/handlers/handler.go b/handlers/handler.go new file mode 100644 index 0000000..3a390d4 --- /dev/null +++ b/handlers/handler.go @@ -0,0 +1,40 @@ +package handlers + +import ( + "templ-test/client" + "templ-test/services" + + "github.com/a-h/templ" + "github.com/gorilla/sessions" + "github.com/labstack/echo-contrib/session" + "github.com/labstack/echo/v4" +) + +type Handlers struct { + Server *echo.Echo + api client.ApiClient +} + +func NewHandlerClient(api client.ApiClient, cfg services.EnvConfig) *Handlers { + h := Handlers{ + api: api, + } + + e := echo.New() + e.Use(session.Middleware(sessions.NewCookieStore([]byte(cfg.CookieSecret)))) + e.GET("/", h.HomeHandler) + e.GET("/list", h.ListHandler) + + auth := e.Group("/auth") + auth.GET("/login", h.AuthLogin) + auth.POST("/login", h.AuthLoginPost) + + h.Server = e + return &h +} + +func Render(ctx echo.Context, statusCode int, t templ.Component) error { + ctx.Response().Writer.WriteHeader(statusCode) + ctx.Response().Header().Set(echo.HeaderContentType, echo.MIMETextHTML) + return t.Render(ctx.Request().Context(), ctx.Response().Writer) +} diff --git a/handlers/home.go b/handlers/home.go new file mode 100644 index 0000000..aac02ef --- /dev/null +++ b/handlers/home.go @@ -0,0 +1,16 @@ +package handlers + +import ( + "net/http" + "templ-test/views" + + "github.com/labstack/echo/v4" +) + +func (h *Handlers) HomeHandler(c echo.Context) error { + return Render(c, http.StatusOK, views.Home()) +} + +func (h *Handlers) ListHandler(c echo.Context) error { + return Render(c, http.StatusOK, views.List()) +} \ No newline at end of file diff --git a/main.go b/main.go new file mode 100644 index 0000000..ef45cb1 --- /dev/null +++ b/main.go @@ -0,0 +1,19 @@ +package main + +import ( + "fmt" + "templ-test/client" + "templ-test/handlers" + "templ-test/services" +) + +func main() { + cfg := services.NewEnvConfig() + + // connect to api server + apiClient := client.New(cfg.ApiServerUri) + handler := handlers.NewHandlerClient(apiClient, cfg) + + fmt.Println("Listening on :3000") + handler.Server.Start(":3000") +} diff --git a/services/config.go b/services/config.go new file mode 100644 index 0000000..ebfec2a --- /dev/null +++ b/services/config.go @@ -0,0 +1,25 @@ +package services + +import ( + "log" + "os" + + "github.com/joho/godotenv" +) + +type EnvConfig struct { + ApiServerUri string + CookieSecret string +} + +func NewEnvConfig() EnvConfig { + err := godotenv.Load() + if err != nil { + log.Println(err) + } + + return EnvConfig{ + ApiServerUri: os.Getenv("ApiServerUri"), + CookieSecret: os.Getenv("CookieSecret"), + } +} diff --git a/views/auth.templ b/views/auth.templ new file mode 100644 index 0000000..b930484 --- /dev/null +++ b/views/auth.templ @@ -0,0 +1,22 @@ +package views + +templ AuthLogin() { + @WithLayout("Login", true) { +
+
+ + +
We'll never share your email with anyone else.
+
+
+ + +
+
+ + +
+ +
+ } +} diff --git a/views/components/bootstrap/bs_card.templ b/views/components/bootstrap/bs_card.templ new file mode 100644 index 0000000..6f4073b --- /dev/null +++ b/views/components/bootstrap/bs_card.templ @@ -0,0 +1,23 @@ +package bootstrap + +const ( + VariantPrimary = "primary" + VariantSecondary = "secondary" + VariantSuccess = "success" + VariantDanger = "danger" + VariantWarning = "warning" + VariantInfo = "info" + VariantLight = "light" + VariantDark = "dark" + VariantLink = "link" +) + +templ BootstrapAlert(message, variant string) { + +} + +templ BootstrapButton(message, variant string) { + +} diff --git a/views/components/bootstrap/helper.go b/views/components/bootstrap/helper.go new file mode 100644 index 0000000..68a890f --- /dev/null +++ b/views/components/bootstrap/helper.go @@ -0,0 +1,19 @@ +package bootstrap + +import "fmt" + +func getButtonVariant(variant string) string { + if variant == "" { + return "btn" + } else { + return fmt.Sprintf("btn btn-%s", variant) + } +} + +func getAlertVariant(variant string) string { + if variant == "" { + return "alert" + } else { + return fmt.Sprintf("alert alert-%s", variant) + } +} diff --git a/views/components/bootstrap/table.templ b/views/components/bootstrap/table.templ new file mode 100644 index 0000000..cd4cfea --- /dev/null +++ b/views/components/bootstrap/table.templ @@ -0,0 +1,16 @@ +package bootstrap + +templ Table(header []string) { + + + + for _, item := range header{ + + } + + + + + +
{ item }
+} \ No newline at end of file diff --git a/views/core.templ b/views/core.templ new file mode 100644 index 0000000..1557aae --- /dev/null +++ b/views/core.templ @@ -0,0 +1,84 @@ +package views + +templ WithLayout(pageName string, useDarkMode bool) { + + @getHtmlHead() + + @bootstrapNavBar() + @getBodyHeader(pageName) +
+ { children... } +
+ + +} + +templ Testing(pageName string, useDarkMode bool) { + + @getHtmlHead() + + @bootstrapNavBar() + @getBodyHeader(pageName) +
+ { children... } +
+ + +} + +templ getHtmlHead() { + + + + + + + + + +} + +templ getBodyHeader(pageName string) { +
+

{ pageName }

+
+} + +templ bootstrapNavBar() { + +} diff --git a/views/home.templ b/views/home.templ new file mode 100644 index 0000000..f02c7eb --- /dev/null +++ b/views/home.templ @@ -0,0 +1,22 @@ +package views + +import "templ-test/views/components/bootstrap" + +templ Home() { + @Testing("Home", true) { +

+ this should be above the alert +

+ @bootstrap.BootstrapAlert("Testing!", bootstrap.VariantDark) +

you should now see this under the Alert

+ @bootstrap.BootstrapButton("I am in danger", bootstrap.VariantDanger) + @bootstrap.BootstrapButton("I am the darkness", bootstrap.VariantDark) + + } +} + +templ List() { + @Testing("Lists", true) { + + } +}