diff --git a/.gitignore b/.gitignore index 7c8dc08..e31fe65 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,9 @@ # If you prefer the allow list template instead of the deny list, see community template: # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore # + +*.env + # Binaries for programs and plugins *.exe *.exe~ diff --git a/Justfile b/Justfile index c80431f..9a06eb0 100644 --- a/Justfile +++ b/Justfile @@ -1,4 +1,8 @@ # 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 + templ generate --watch --proxy="http://localhost:3000" --cmd="go run cmd/main.go" + +# Generates templ files +gen: + templ generate diff --git a/client/apiclient.go b/client/apiclient.go index 79b2910..7fbfccd 100644 --- a/client/apiclient.go +++ b/client/apiclient.go @@ -7,6 +7,7 @@ const ( type ApiClient struct { Auth Auth + Demo DemoApiClient ServerAddress string } @@ -14,5 +15,6 @@ type ApiClient struct { func New(serverAddress string) ApiClient { return ApiClient{ Auth: newAuthClient(serverAddress), + Demo: newDemoClient(serverAddress), } } diff --git a/client/auth.go b/client/auth.go index 492e94d..91ce39b 100644 --- a/client/auth.go +++ b/client/auth.go @@ -5,13 +5,14 @@ import ( "fmt" "io" "net/http" + "net/url" "git.jamestombleson.com/jtom38/go-cook/api/domain" ) type Auth interface { Register(username, password string) error - Login(username, password string) error + Login(username, password string) (LoginResponse, error) } type AuthClient struct { @@ -42,7 +43,7 @@ func (a AuthClient) Register(username, password string) error { return err } - defer resp.Body.Close() + //defer resp.Body.Close() content, err := io.ReadAll(resp.Body) if err != nil { @@ -58,31 +59,25 @@ func (a AuthClient) Register(username, password string) error { 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) +type LoginResponse struct { + Success bool `json:"success"` + Token string `json:"token"` + Type string `json:"type"` + RefreshToken string `json:"refreshToken"` +} + +func (a AuthClient) Login(username, password string) (LoginResponse, error) { + endpoint := fmt.Sprintf("%s/api/v1/auth/login", a.serverAddress) + + param := url.Values{} + param.Set("username", username) + param.Set("password", password) + + var bind = LoginResponse{} + err := PostUrlForm(a.client, endpoint, param, &bind) if err != nil { - return err + return LoginResponse{}, 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 + return bind, nil +} diff --git a/client/demo.go b/client/demo.go new file mode 100644 index 0000000..625070c --- /dev/null +++ b/client/demo.go @@ -0,0 +1,47 @@ +package client + +import ( + "fmt" + "io" + "log" + "net/http" +) + +type DemoApiClient struct { + serverAddress string + client http.Client +} + +func newDemoClient(serverAddress string) DemoApiClient { + return DemoApiClient{ + serverAddress: serverAddress, + client: http.Client{}, + } +} + +type HelloBodyParam struct { + Name string `json:"name"` +} + +// This is an example route to demo passing a body in +func (d DemoApiClient) Hello() error { + endpoint := fmt.Sprintf("%s/api/v1/demo/hello", d.serverAddress) + req, err := http.NewRequest(http.MethodGet, endpoint, nil) + if err != nil { + return err + } + + resp, err := d.client.Do(req) + if err != nil { + return err + } + //defer resp.Body.Close() + + content, err := io.ReadAll(resp.Body) + if err != nil { + return err + } + log.Println(string(content)) + + return nil +} diff --git a/client/util.go b/client/util.go new file mode 100644 index 0000000..d9d3ca5 --- /dev/null +++ b/client/util.go @@ -0,0 +1,31 @@ +package client + +import ( + "bytes" + "encoding/json" + "net/http" + "net/url" +) + +func PostUrlForm(client http.Client, endpoint string, param url.Values, t any) error { + payload := bytes.NewBufferString(param.Encode()) + req, err := http.NewRequest(http.MethodPost, endpoint, payload) + if err != nil { + return err + } + + req.Header.Set(HeaderContentType, MIMEApplicationForm) + resp, err := client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + decoder := json.NewDecoder(resp.Body) + err = decoder.Decode(&t) + if err != nil { + return err + } + + return nil +} diff --git a/cmd/main.go b/cmd/main.go new file mode 100644 index 0000000..4bbbb35 --- /dev/null +++ b/cmd/main.go @@ -0,0 +1,31 @@ +package main + +import ( + "fmt" + "templ-test/client" + "templ-test/handlers" + "templ-test/services" + + "github.com/gorilla/sessions" + "github.com/labstack/echo-contrib/session" + "github.com/labstack/echo/v4" + "github.com/labstack/echo/v4/middleware" +) + +func main() { + cfg := services.NewEnvConfig() + + // connect to api server + apiClient := client.New(cfg.ApiServerUri) + + e := echo.New() + e.Use(session.Middleware(sessions.NewCookieStore([]byte(cfg.CookieSecret)))) + e.Pre(middleware.Logger()) + + handler := e.Group("") + portalClient := handlers.NewHandlerClient(apiClient, cfg) + portalClient.Register(*handler) + + fmt.Println("Listening on :1324") + e.Logger.Fatal(e.Start(":1324")) +} diff --git a/handlers/auth.go b/handlers/auth.go index 879889b..29d4c73 100644 --- a/handlers/auth.go +++ b/handlers/auth.go @@ -3,22 +3,46 @@ package handlers import ( "net/http" "templ-test/views" + "templ-test/views/auth" "github.com/labstack/echo/v4" ) func (h *Handlers) AuthLogin(c echo.Context) error { - return Render(c, http.StatusOK, views.AuthLogin()) + return Render(c, http.StatusOK, auth.AuthLogin()) } func (h *Handlers) AuthLoginPost(c echo.Context) error { // check the form data - //user := c.FormValue("email") - //password := c.FormValue("password") + user := c.FormValue("username") + password := c.FormValue("password") // send request to the API - //h.api. + resp, err := h.api.Auth.Login(user, password) + if err != nil { + return err + } + + cookie := new(http.Cookie) + cookie.Name = CookieToken + cookie.Value = resp.Token + c.SetCookie(cookie) + + cookie = new(http.Cookie) + cookie.Name = CookieRefreshToken + cookie.Value = resp.RefreshToken + c.SetCookie(cookie) + + cookie = new(http.Cookie) + cookie.Name = CookieUser + cookie.Value = user + c.SetCookie(cookie) // render - return nil + return Render(c, http.StatusOK, views.Home()) +} + +func (h *Handlers) AuthShowCookies(c echo.Context) error { + cookies := GetCookieValues(c) + return Render(c, http.StatusOK, auth.ShowCookie(cookies)) } diff --git a/handlers/handler.go b/handlers/handler.go index 3a390d4..7e66d2c 100644 --- a/handlers/handler.go +++ b/handlers/handler.go @@ -2,35 +2,42 @@ package handlers import ( "templ-test/client" + "templ-test/models" "templ-test/services" "github.com/a-h/templ" - "github.com/gorilla/sessions" - "github.com/labstack/echo-contrib/session" + "github.com/labstack/echo/v4" ) +const ( + CookieToken = "token" + CookieRefreshToken = "refresh" + CookieUser = "user" +) + type Handlers struct { - Server *echo.Echo - api client.ApiClient + api client.ApiClient + cfg services.EnvConfig } func NewHandlerClient(api client.ApiClient, cfg services.EnvConfig) *Handlers { h := Handlers{ - api: api, + api: api, + cfg: cfg, } - 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") + return &h +} + +func (h *Handlers) Register(group echo.Group) { + group.GET("/", h.HomeHandler) + group.GET("/list", h.ListHandler) + + auth := group.Group("/auth") auth.GET("/login", h.AuthLogin) auth.POST("/login", h.AuthLoginPost) - - h.Server = e - return &h + auth.GET("/cookie", h.AuthShowCookies) } func Render(ctx echo.Context, statusCode int, t templ.Component) error { @@ -38,3 +45,24 @@ func Render(ctx echo.Context, statusCode int, t templ.Component) error { ctx.Response().Header().Set(echo.HeaderContentType, echo.MIMETextHTML) return t.Render(ctx.Request().Context(), ctx.Response().Writer) } + +func GetCookieValues(ctx echo.Context) models.AllCookies { + m := models.AllCookies{} + + token, err := ctx.Cookie(CookieToken) + if err == nil { + m.Token = token.Value + } + + user, err := ctx.Cookie(CookieUser) + if err == nil { + m.Username = user.Value + } + + refresh, err := ctx.Cookie(CookieRefreshToken) + if err == nil { + m.RefreshToken = refresh.Value + } + + return m +} diff --git a/main.go b/main.go deleted file mode 100644 index ef45cb1..0000000 --- a/main.go +++ /dev/null @@ -1,19 +0,0 @@ -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/models/auth.go b/models/auth.go new file mode 100644 index 0000000..af18023 --- /dev/null +++ b/models/auth.go @@ -0,0 +1,11 @@ +package models + +type AllCookies struct { + Username string + Token string + RefreshToken string +} + +type ShowCookie struct { + AllCookies +} diff --git a/services/config.go b/services/config.go index ebfec2a..94a0baf 100644 --- a/services/config.go +++ b/services/config.go @@ -13,9 +13,12 @@ type EnvConfig struct { } func NewEnvConfig() EnvConfig { - err := godotenv.Load() - if err != nil { - log.Println(err) + _, err := os.Stat(".env") + if err == nil { + err = godotenv.Load() + if err != nil { + log.Println(err) + } } return EnvConfig{ diff --git a/views/auth.templ b/views/auth.templ deleted file mode 100644 index b930484..0000000 --- a/views/auth.templ +++ /dev/null @@ -1,22 +0,0 @@ -package views - -templ AuthLogin() { - @WithLayout("Login", true) { -
-
- - -
We'll never share your email with anyone else.
-
-
- - -
-
- - -
- -
- } -} diff --git a/views/auth/cookie.templ b/views/auth/cookie.templ new file mode 100644 index 0000000..ff7bf00 --- /dev/null +++ b/views/auth/cookie.templ @@ -0,0 +1,13 @@ +package auth + +import "templ-test/views" +import "templ-test/models" + +templ ShowCookie(m models.AllCookies) { + @views.WithLayout("Cookie Explorer", true) { +

These are stored as cookies

+

Username: { m.Username }

+

JWT Token: { m.Token }

+

RefreshToken: { m.RefreshToken }

+ } +} diff --git a/views/auth/login.templ b/views/auth/login.templ new file mode 100644 index 0000000..a6e6728 --- /dev/null +++ b/views/auth/login.templ @@ -0,0 +1,23 @@ +package auth + +import "templ-test/views" + +templ AuthLogin() { + @views.WithLayout("Login", true) { +
+
+ + +
+
+ + +
+
+ + +
+ +
+ } +} \ No newline at end of file diff --git a/views/core.templ b/views/layout.templ similarity index 100% rename from views/core.templ rename to views/layout.templ