diff --git a/adapters/secondary/gateways/pageRepository.go b/adapters/secondary/gateways/pageRepository.go new file mode 100644 index 0000000..691eb21 --- /dev/null +++ b/adapters/secondary/gateways/pageRepository.go @@ -0,0 +1,32 @@ +package gateways + +import ( + . "GohCMS2/domain/gateways" + "bytes" + "embed" + "html/template" +) + +type PageRepository struct{} + +//go:embed web/templates/* +var templateFiles embed.FS + +func NewPageRepository() *PageRepository { + return &PageRepository{} +} + +func (p *PageRepository) Get(name string, data interface{}) ([]byte, error) { + var processedHTML bytes.Buffer + tmpl, err := template.ParseFS(templateFiles, "web/templates/"+name+".html") + if err != nil { + return nil, err + } + err = tmpl.Execute(&processedHTML, data) + if err != nil { + return nil, err + } + return processedHTML.Bytes(), nil +} + +var _ IPageRepository = &PageRepository{} diff --git a/api/web/templates/componentNavbar.html b/adapters/secondary/gateways/web/templates/componentNavbar.html similarity index 100% rename from api/web/templates/componentNavbar.html rename to adapters/secondary/gateways/web/templates/componentNavbar.html diff --git a/api/web/templates/home.html b/adapters/secondary/gateways/web/templates/home.html similarity index 100% rename from api/web/templates/home.html rename to adapters/secondary/gateways/web/templates/home.html diff --git a/api/web/templates/login.html b/adapters/secondary/gateways/web/templates/login.html similarity index 100% rename from api/web/templates/login.html rename to adapters/secondary/gateways/web/templates/login.html diff --git a/api/article.go b/api/article.go index c868f6c..d64683b 100644 --- a/api/article.go +++ b/api/article.go @@ -20,7 +20,7 @@ func getArticle(w http.ResponseWriter, r *http.Request) { return } - article, err := container.GetArticleUseCase.GetArticle(uint32(id)) + article, err := Container.GetArticleUseCase.GetArticle(uint32(id)) if err != nil { http.Error(w, "The requested resource, identified by its unique ID, could not be found on the server.", http.StatusNotFound) return @@ -44,7 +44,7 @@ func postArticle(w http.ResponseWriter, r *http.Request) { return } - createdArticle, err := container.CreateArticleUseCase.CreateArticle(CreateArticleCommand{ + createdArticle, err := Container.CreateArticleUseCase.CreateArticle(CreateArticleCommand{ Title: article.Title, Body: article.Body, }) @@ -58,7 +58,7 @@ func postArticle(w http.ResponseWriter, r *http.Request) { } func listArticles(w http.ResponseWriter, _ *http.Request) { - articles := container.ListArticlesUseCase.ListArticles() + articles := Container.ListArticlesUseCase.ListArticles() articlesJson, _ := json.Marshal(articles) _, _ = w.Write(articlesJson) diff --git a/api/auth.go b/api/auth.go index ebb8aff..ec05bdc 100644 --- a/api/auth.go +++ b/api/auth.go @@ -45,8 +45,8 @@ func SetJwtCookie(w *http.ResponseWriter, userId uint32) error { return nil } -func isUserTableEmpty() bool { - users := container.ListUsersUseCase.ListUsers() +func IsUserTableEmpty() bool { + users := Container.ListUsersUseCase.ListUsers() return len(users) == 0 } @@ -75,7 +75,7 @@ func login(w http.ResponseWriter, r *http.Request) { return } - dbUser, err := container.GetUserUseCase.GetUserByUsername(user.Username) + dbUser, err := Container.GetUserUseCase.GetUserByUsername(user.Username) if err != nil { http.Error(w, logsErrorMessage, http.StatusForbidden) return @@ -94,7 +94,7 @@ func login(w http.ResponseWriter, r *http.Request) { } func register(w http.ResponseWriter, r *http.Request) { - if !IsLoggedIn(r) && !isUserTableEmpty() { + if !IsLoggedIn(r) && !IsUserTableEmpty() { http.Error(w, "You are not allowed to create a user. Log in or reset database.", http.StatusForbidden) return } @@ -112,7 +112,7 @@ func register(w http.ResponseWriter, r *http.Request) { return } - createdUser, err := container.CreateUserUseCase.CreateUser(useCases.CreateUserCommand{ + createdUser, err := Container.CreateUserUseCase.CreateUser(useCases.CreateUserCommand{ Username: user.Username, Password: user.Password, Email: user.Email, @@ -128,7 +128,7 @@ func register(w http.ResponseWriter, r *http.Request) { _, _ = w.Write(message) } -func removeJwtCookie(w http.ResponseWriter) { +func RemoveJwtCookie(w http.ResponseWriter) { http.SetCookie(w, &http.Cookie{ Name: "jwt", Value: "", @@ -141,7 +141,7 @@ func removeJwtCookie(w http.ResponseWriter) { } func logout(w http.ResponseWriter, _ *http.Request) { - removeJwtCookie(w) + RemoveJwtCookie(w) message, _ := json.Marshal(map[string]interface{}{"message": "User logged out! HTTPonly jwt cookie deleted"}) _, _ = w.Write(message) } diff --git a/api/dependecyInjection.go b/api/dependecyInjection.go index 33fcc21..5dbef1b 100644 --- a/api/dependecyInjection.go +++ b/api/dependecyInjection.go @@ -8,16 +8,17 @@ import ( "gorm.io/gorm" ) -type Container struct { +type LocalContainer struct { CreateArticleUseCase *CreateArticleUseCase GetArticleUseCase *GetArticleUseCase ListArticlesUseCase *ListArticlesUseCase GetUserUseCase *GetUserUseCase CreateUserUseCase *CreateUserUseCase ListUsersUseCase *ListUsersUseCase + GetPageUseCase *GetPageUseCase } -var container *Container +var Container *LocalContainer func setContainer( createArticle *CreateArticleUseCase, @@ -26,20 +27,22 @@ func setContainer( getUser *GetUserUseCase, createUser *CreateUserUseCase, listUsers *ListUsersUseCase, -) *Container { - container = &Container{ + getPage *GetPageUseCase, +) *LocalContainer { + Container = &LocalContainer{ CreateArticleUseCase: createArticle, GetArticleUseCase: getArticle, ListArticlesUseCase: listArticle, GetUserUseCase: getUser, CreateUserUseCase: createUser, ListUsersUseCase: listUsers, + GetPageUseCase: getPage, } - return container + return Container } func InitContainer() { - if container != nil { + if Container != nil { return } @@ -59,6 +62,7 @@ func InitContainer() { _ = digContainer.Provide(func(db *gorm.DB) *GetUserUseCase { return NewGetUserUseCase(db) }) _ = digContainer.Provide(func(db *gorm.DB) *CreateUserUseCase { return NewCreateUserUseCase(db) }) _ = digContainer.Provide(func(db *gorm.DB) *ListUsersUseCase { return NewListUsersUseCase(db) }) + _ = digContainer.Provide(NewGetPageUseCase) _ = digContainer.Invoke(setContainer) } diff --git a/api/page.go b/api/page.go index 36a7ff6..a99690f 100644 --- a/api/page.go +++ b/api/page.go @@ -1,22 +1,14 @@ package api import ( - "bytes" "embed" - "encoding/json" "github.com/go-chi/chi/v5" - "html/template" "io/fs" "net/http" - "os" "path/filepath" - "strings" ) -//go:embed web/templates/* -var templateFiles embed.FS - -//go:embed web/static +//go:embed static var staticFolder embed.FS var contentTypes = map[string]string{ @@ -29,110 +21,7 @@ var contentTypes = map[string]string{ ".ico": "image/x-icon", } -const loginRoute = "/login" - -type LoginPage struct { - IsError bool `json:"isError"` - Error string `json:"error"` - Username string `json:"username"` -} - -func NewLoginPage(error string, username string) *LoginPage { - return &LoginPage{ - IsError: strings.Compare(error, "") != 0, - Error: error, - Username: username, - } -} - -func getPage(page string, data interface{}) ([]byte, error) { - var processedHTML bytes.Buffer - tmpl, err := template.ParseFS(templateFiles, "web/templates/"+page+".html") - if err != nil { - return nil, err - } - err = tmpl.Execute(&processedHTML, data) - if err != nil { - return nil, err - } - return processedHTML.Bytes(), nil -} - -func getLoginPageHandler(loginPage *LoginPage) http.HandlerFunc { - return func(w http.ResponseWriter, r *http.Request) { - if IsLoggedIn(r) { - http.Redirect(w, r, "/home", http.StatusSeeOther) - return - } - if r.Method == http.MethodPost { - postLoginPage(w, r) - return - } - bs, err := getPage("login", &loginPage) - if err != nil { - _, _ = w.Write([]byte(err.Error())) - } - _, _ = w.Write(bs) - } -} - -func postLoginPage(w http.ResponseWriter, r *http.Request) { - _ = r.ParseForm() - - credentials, err := json.Marshal(&UserLogin{ - Username: r.FormValue("username"), - Password: r.FormValue("password"), - }) - if err != nil { - r.Method = http.MethodGet - getLoginPageHandler(NewLoginPage("Invalid username or password.", r.FormValue("username")))(w, r) - return - } - - response, err := http.Post( - "http://localhost:"+os.Getenv("PORT")+"/v1/auth/login", - "application/json", - bytes.NewBuffer(credentials)) - - if err != nil || response.StatusCode != http.StatusOK { - r.Method = http.MethodGet - getLoginPageHandler(NewLoginPage("Invalid username or password.", r.FormValue("username")))(w, r) - return - } - - w.Header().Set("Set-Cookie", response.Header.Get("Set-Cookie")) - - http.Redirect(w, r, "/home", http.StatusSeeOther) -} - -func getHomePage(w http.ResponseWriter, _ *http.Request) { - navbarTmpl, _ := getPage("componentNavbar", nil) - homeTmpl, _ := getPage("home", map[string]interface{}{ - "Navbar": template.HTML(navbarTmpl), - }) - _, _ = w.Write(homeTmpl) -} - -func getLogin(w http.ResponseWriter, r *http.Request) { - http.Redirect(w, r, loginRoute, http.StatusPermanentRedirect) -} - -func getLogout(w http.ResponseWriter, r *http.Request) { - removeJwtCookie(w) - http.Redirect(w, r, loginRoute, http.StatusSeeOther) -} - -func IsLoggedInMiddleware(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if !IsLoggedIn(r) { - http.Redirect(w, r, loginRoute, http.StatusSeeOther) - return - } - next.ServeHTTP(w, r) - }) -} - -func staticFileServerWithContentType(fsys http.FileSystem) http.Handler { +func StaticFileServerWithContentType(fsys http.FileSystem) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { path := r.URL.Path if ext := filepath.Ext(path); ext != "" { @@ -147,9 +36,9 @@ func staticFileServerWithContentType(fsys http.FileSystem) http.Handler { func NewPageRouter() http.Handler { r := chi.NewRouter() - contentStatic, _ := fs.Sub(fs.FS(staticFolder), "web/static") + contentStatic := fs.FS(staticFolder) - r.Handle("/static/*", staticFileServerWithContentType(http.FS(contentStatic))) + r.Handle("/static/*", StaticFileServerWithContentType(http.FS(contentStatic))) r.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) { _, err := http.FS(staticFolder).Open("favicon.ico") if err != nil { @@ -157,14 +46,14 @@ func NewPageRouter() http.Handler { } }) - r.Get("/", getLogin) - r.Get(loginRoute, getLoginPageHandler(NewLoginPage("", ""))) - r.Post(loginRoute, getLoginPageHandler(NewLoginPage("", ""))) - r.Get("/logout", getLogout) + r.Get("/", GetLogin) + r.Get(LoginRoute, GetLoginPageHandler(NewLoginPage("", ""))) + r.Post(LoginRoute, GetLoginPageHandler(NewLoginPage("", ""))) + r.Get("/logout", GetLogout) r.Group(func(r chi.Router) { r.Use(IsLoggedInMiddleware) - r.Get("/home", getHomePage) + r.Get("/home", GetHomePage) }) return r diff --git a/api/pageAuth.go b/api/pageAuth.go new file mode 100644 index 0000000..9b2520f --- /dev/null +++ b/api/pageAuth.go @@ -0,0 +1,91 @@ +package api + +import ( + "bytes" + "encoding/json" + "net/http" + "os" + "strings" +) + +const LoginRoute = "/login" + +type LoginPage struct { + IsError bool `json:"isError"` + Error string `json:"error"` + Username string `json:"username"` +} + +func NewLoginPage(error string, username string) *LoginPage { + return &LoginPage{ + IsError: strings.Compare(error, "") != 0, + Error: error, + Username: username, + } +} + +func GetLoginPageHandler(loginPage *LoginPage) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if IsLoggedIn(r) { + http.Redirect(w, r, "/home", http.StatusSeeOther) + return + } + if r.Method == http.MethodPost { + PostLoginPage(w, r) + return + } + bs, err := Container.GetPageUseCase.GetPage("login", &loginPage) + if err != nil { + _, _ = w.Write([]byte(err.Error())) + } + _, _ = w.Write(bs) + } +} + +func PostLoginPage(w http.ResponseWriter, r *http.Request) { + _ = r.ParseForm() + + credentials, err := json.Marshal(&UserLogin{ + Username: r.FormValue("username"), + Password: r.FormValue("password"), + }) + if err != nil { + r.Method = http.MethodGet + GetLoginPageHandler(NewLoginPage("Invalid username or password.", r.FormValue("username")))(w, r) + return + } + + response, err := http.Post( + "http://localhost:"+os.Getenv("PORT")+"/v1/auth/login", + "application/json", + bytes.NewBuffer(credentials)) + + if err != nil || response.StatusCode != http.StatusOK { + r.Method = http.MethodGet + GetLoginPageHandler(NewLoginPage("Invalid username or password.", r.FormValue("username")))(w, r) + return + } + + w.Header().Set("Set-Cookie", response.Header.Get("Set-Cookie")) + + http.Redirect(w, r, "/home", http.StatusSeeOther) +} + +func GetLogin(w http.ResponseWriter, r *http.Request) { + http.Redirect(w, r, LoginRoute, http.StatusPermanentRedirect) +} + +func GetLogout(w http.ResponseWriter, r *http.Request) { + RemoveJwtCookie(w) + http.Redirect(w, r, LoginRoute, http.StatusSeeOther) +} + +func IsLoggedInMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if !IsLoggedIn(r) { + http.Redirect(w, r, LoginRoute, http.StatusSeeOther) + return + } + next.ServeHTTP(w, r) + }) +} diff --git a/api/pageHome.go b/api/pageHome.go new file mode 100644 index 0000000..34ccd44 --- /dev/null +++ b/api/pageHome.go @@ -0,0 +1,14 @@ +package api + +import ( + "html/template" + "net/http" +) + +func GetHomePage(w http.ResponseWriter, _ *http.Request) { + navbarTmpl, _ := Container.GetPageUseCase.GetPage("componentNavbar", nil) + homeTmpl, _ := Container.GetPageUseCase.GetPage("home", map[string]interface{}{ + "Navbar": template.HTML(navbarTmpl), + }) + _, _ = w.Write(homeTmpl) +} diff --git a/api/web/static/bootstrap.min.css b/api/static/bootstrap.min.css similarity index 100% rename from api/web/static/bootstrap.min.css rename to api/static/bootstrap.min.css diff --git a/api/web/static/bootstrap.min.css.map b/api/static/bootstrap.min.css.map similarity index 100% rename from api/web/static/bootstrap.min.css.map rename to api/static/bootstrap.min.css.map diff --git a/api/web/static/bootstrap.min.js b/api/static/bootstrap.min.js similarity index 100% rename from api/web/static/bootstrap.min.js rename to api/static/bootstrap.min.js diff --git a/api/web/static/bootstrap.min.js.map b/api/static/bootstrap.min.js.map similarity index 100% rename from api/web/static/bootstrap.min.js.map rename to api/static/bootstrap.min.js.map diff --git a/api/web/static/favicon.ico b/api/static/favicon.ico similarity index 100% rename from api/web/static/favicon.ico rename to api/static/favicon.ico diff --git a/api/web/static/gohcms-favicon-128.png b/api/static/gohcms-favicon-128.png similarity index 100% rename from api/web/static/gohcms-favicon-128.png rename to api/static/gohcms-favicon-128.png diff --git a/domain/gateways/IPageRepository.go b/domain/gateways/IPageRepository.go new file mode 100644 index 0000000..37e1bfd --- /dev/null +++ b/domain/gateways/IPageRepository.go @@ -0,0 +1,5 @@ +package gateways + +type IPageRepository interface { + Get(name string, data interface{}) ([]byte, error) +} diff --git a/useCases/GetPageUseCase.go b/useCases/GetPageUseCase.go new file mode 100644 index 0000000..42ee55f --- /dev/null +++ b/useCases/GetPageUseCase.go @@ -0,0 +1,19 @@ +package useCases + +import ( + . "GohCMS2/adapters/secondary/gateways" +) + +type GetPageUseCase struct { + pageRepository PageRepository +} + +func NewGetPageUseCase() *GetPageUseCase { + return &GetPageUseCase{ + pageRepository: *NewPageRepository(), + } +} + +func (g *GetPageUseCase) GetPage(name string, data interface{}) ([]byte, error) { + return g.pageRepository.Get(name, data) +}