first push #1

Merged
jtom38 merged 1 commits from features/first-push into main 2024-04-09 07:30:13 -07:00
18 changed files with 512 additions and 1 deletions

3
.gitignore vendored
View File

@ -8,7 +8,8 @@
*.dll *.dll
*.so *.so
*.dylib *.dylib
*_templ.txt
*_templ.go
# Test binary, built with `go test -c` # Test binary, built with `go test -c`
*.test *.test

6
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,6 @@
{
"files.exclude": {
"**/*_templ.go": true,
"**/*_templ.txt": true
}
}

4
Justfile Normal file
View File

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

18
client/apiclient.go Normal file
View File

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

88
client/auth.go Normal file
View File

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

31
go.mod Normal file
View File

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

53
go.sum Normal file
View File

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

24
handlers/auth.go Normal file
View File

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

40
handlers/handler.go Normal file
View File

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

16
handlers/home.go Normal file
View File

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

19
main.go Normal file
View File

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

25
services/config.go Normal file
View File

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

22
views/auth.templ Normal file
View File

@ -0,0 +1,22 @@
package views
templ AuthLogin() {
@WithLayout("Login", true) {
<form>
<div class="mb-3">
<label for="exampleInputEmail1" class="form-label">Email address</label>
<input type="email" class="form-control" id="exampleInputEmail1" aria-describedby="emailHelp"/>
<div id="emailHelp" class="form-text">We'll never share your email with anyone else.</div>
</div>
<div class="mb-3">
<label for="exampleInputPassword1" class="form-label">Password</label>
<input type="password" class="form-control" id="exampleInputPassword1"/>
</div>
<div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="exampleCheck1"/>
<label class="form-check-label" for="exampleCheck1">Check me out</label>
</div>
<button type="submit" class="btn btn-primary" hx-post="/auth/" >Submit</button>
</form>
}
}

View File

@ -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) {
<div class={ getAlertVariant(variant) } role="alert">
{ message }
</div>
}
templ BootstrapButton(message, variant string) {
<button type="button" class={ getButtonVariant(variant) }>{ message }</button>
}

View File

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

View File

@ -0,0 +1,16 @@
package bootstrap
templ Table(header []string) {
<table class="table">
<thead>
<tr>
for _, item := range header{
<th scope="col">{ item }</th>
}
</tr>
</thead>
<tbody>
</tbody>
</table>
}

84
views/core.templ Normal file
View File

@ -0,0 +1,84 @@
package views
templ WithLayout(pageName string, useDarkMode bool) {
<html>
@getHtmlHead()
<body>
@bootstrapNavBar()
@getBodyHeader(pageName)
<div class="container-fluid">
{ children... }
</div>
</body>
</html>
}
templ Testing(pageName string, useDarkMode bool) {
<html>
@getHtmlHead()
<body>
@bootstrapNavBar()
@getBodyHeader(pageName)
<div class="container-fluid">
{ children... }
</div>
</body>
</html>
}
templ getHtmlHead() {
<head>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous"/>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz" crossorigin="anonymous"></script>
<script src="https://unpkg.com/htmx.org@1.9.11" integrity="sha384-0gxUXCCR8yv9FM2b+U3FDbsKthCI66oH5IA9fHppQq9DDMHuMauqq1ZHBpJxQ0J0" crossorigin="anonymous"></script>
<meta charset="utf-8"/>
<meta name="twitter:card" content="fill in later"/>
<meta name="twitter:image" content=""/>
<meta name="og:image" content=""/>
</head>
}
templ getBodyHeader(pageName string) {
<header>
<h1>{ pageName }</h1>
</header>
}
templ bootstrapNavBar() {
<nav class="navbar navbar-expand-lg bg-body-tertiary" data-bs-theme="dark">
<div class="container-fluid">
<a class="navbar-brand" href="#">Navbar</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link active" aria-current="page" href="#">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#">Link</a>
</li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
Dropdown
</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="#">Action</a></li>
<li><a class="dropdown-item" href="#">Another action</a></li>
<li><hr class="dropdown-divider"/></li>
<li><a class="dropdown-item" href="#">Something else here</a></li>
</ul>
</li>
<li class="nav-item">
<a class="nav-link disabled" aria-disabled="true">Disabled</a>
</li>
</ul>
<form class="d-flex" role="search">
<input class="form-control me-2" type="search" placeholder="Search" aria-label="Search"/>
<button class="btn btn-outline-success" type="submit">Search</button>
</form>
</div>
</div>
</nav>
}

22
views/home.templ Normal file
View File

@ -0,0 +1,22 @@
package views
import "templ-test/views/components/bootstrap"
templ Home() {
@Testing("Home", true) {
<p>
this should be above the alert
</p>
@bootstrap.BootstrapAlert("Testing!", bootstrap.VariantDark)
<p>you should now see this under the Alert </p>
@bootstrap.BootstrapButton("I am in danger", bootstrap.VariantDanger)
@bootstrap.BootstrapButton("I am the darkness", bootstrap.VariantDark)
}
}
templ List() {
@Testing("Lists", true) {
}
}