diff --git a/.air.toml b/.air.toml new file mode 100644 index 0000000..a770bbd --- /dev/null +++ b/.air.toml @@ -0,0 +1,51 @@ +root = "." +testdata_dir = "testdata" +tmp_dir = "tmp" + +[build] + args_bin = [] + bin = "tmp\\main.exe" + cmd = "go build -o ./tmp/main.exe ./main" + delay = 1000 + exclude_dir = ["assets", "tmp", "vendor", "testdata", "api\\static\\tinymce", "api\\static\\bootstrap-icons"] + exclude_file = [] + exclude_regex = ["_test.go"] + exclude_unchanged = false + follow_symlink = false + full_bin = "" + include_dir = [] + include_ext = ["go", "tpl", "tmpl", "html"] + include_file = [] + kill_delay = "0s" + log = "build-errors.log" + poll = false + poll_interval = 0 + post_cmd = [] + pre_cmd = [] + rerun = false + rerun_delay = 500 + send_interrupt = false + stop_on_error = false + +[color] + app = "" + build = "yellow" + main = "magenta" + runner = "green" + watcher = "cyan" + +[log] + main_only = false + time = false + +[misc] + clean_on_exit = false + +[proxy] + app_port = 0 + enabled = false + proxy_port = 0 + +[screen] + clear_on_rebuild = false + keep_scroll = true diff --git a/.gitignore b/.gitignore index 0653454..449aa10 100644 --- a/.gitignore +++ b/.gitignore @@ -9,4 +9,6 @@ bin # Environment -.env \ No newline at end of file +.env + +tmp/ diff --git a/adapters/secondary/gateways/postRepository.go b/adapters/secondary/gateways/postRepository.go index 533dbbb..9f098ac 100644 --- a/adapters/secondary/gateways/postRepository.go +++ b/adapters/secondary/gateways/postRepository.go @@ -29,6 +29,16 @@ func (a *PostRepository) Get(id uint32) (domain.Post, error) { return mapPostToDomain(post), nil } +func (a *PostRepository) GetByName(name string) (domain.Post, error) { + var post entity.Post + err := a.db.Model(&entity.Post{}).Where("title = ?", name).First(&post).Error + if err != nil { + return domain.Post{}, err + } + + return mapPostToDomain(post), nil +} + func (a *PostRepository) Create(post domain.Post) (domain.Post, error) { creationResult := a.db.Create(&entity.Post{ Title: post.Title, @@ -59,4 +69,25 @@ func (a *PostRepository) GetAll() []domain.Post { return domainPosts } +func (a *PostRepository) UpdateBody(id uint32, body string) error { + var localPost entity.Post + err := a.db.Model(&entity.Post{}).First(&localPost, id).Error + if err != nil { + return err + } + + localPost.Body = body + err = a.db.Save(&localPost).Error + if err != nil { + return err + } + + return nil +} + +func (a *PostRepository) Delete(id uint32) error { + //TODO implement me + panic("implement me") +} + var _ gateways.IPostRepository = &PostRepository{} diff --git a/adapters/secondary/gateways/web/templates/postCreate.html b/adapters/secondary/gateways/web/templates/postCreate.html new file mode 100644 index 0000000..6e35512 --- /dev/null +++ b/adapters/secondary/gateways/web/templates/postCreate.html @@ -0,0 +1,66 @@ + + + + GoCMS | Create post + {{.Head}} + + + +{{.Navbar}} +
+

Post - creation

+

Create a new post

+
+
+ + +
{{ .PageError.Message }}
+
+ +
+
+ + + + + diff --git a/adapters/secondary/gateways/web/templates/postEdit.html b/adapters/secondary/gateways/web/templates/postEdit.html index 285b031..164ec2b 100644 --- a/adapters/secondary/gateways/web/templates/postEdit.html +++ b/adapters/secondary/gateways/web/templates/postEdit.html @@ -6,28 +6,36 @@ {{.Navbar}} -
+

Post - edition

- +
+ {{ if .Alert.Message }} + + {{ end }}
-
- +
-
+ + diff --git a/api/dependecyInjection.go b/api/dependecyInjection.go index 871e969..14eddc7 100644 --- a/api/dependecyInjection.go +++ b/api/dependecyInjection.go @@ -14,6 +14,7 @@ type UseCases struct { CreatePostUseCase *useCases.CreatePostUseCase GetPostUseCase *useCases.GetPostUseCase ListPostsUseCase *useCases.ListPostsUseCase + UpdatePostUseCase *useCases.UpdatePostUseCase GetUserUseCase *useCases.GetUserUseCase CreateUserUseCase *useCases.CreateUserUseCase UpdateUserUseCase *useCases.UpdateUserUseCase @@ -49,6 +50,7 @@ func InitContainer() { CreatePostUseCase: useCases.NewCreatePostUseCase(db), GetPostUseCase: useCases.NewGetPostUseCase(db), ListPostsUseCase: useCases.NewListPostsUseCase(db), + UpdatePostUseCase: useCases.NewUpdatePostUseCase(db), GetUserUseCase: useCases.NewGetUserUseCase(db), CreateUserUseCase: useCases.NewCreateUserUseCase(db), UpdateUserUseCase: useCases.NewUpdateUserUseCase(db), diff --git a/api/page.go b/api/page.go index 0dfe53a..60a2b64 100644 --- a/api/page.go +++ b/api/page.go @@ -122,7 +122,10 @@ func NewPageRouter() http.Handler { r.Use(IsVerifiedMiddleware) r.Get("/home", GetHomePage) r.Get("/post", GetPostsPage) - r.Get("/post/edit", GetPostEditPage) + r.Get("/post/{name}/edit", GetPostEditPage) + r.Post("/post/{name}/edit", PostPostEditPage) + r.Get("/post/create", GetPostCreatePage) + r.Post("/post/create", PostPostCreatePage) }) return r diff --git a/api/pagePostCreate.go b/api/pagePostCreate.go new file mode 100644 index 0000000..d8d12d8 --- /dev/null +++ b/api/pagePostCreate.go @@ -0,0 +1,56 @@ +package api + +import ( + "GoCMS/useCases" + "html/template" + "net/http" + "regexp" +) + +type PostCreatePageError struct { + IsError bool + Message string +} + +func GetPostCreatePageTemplate(postName string, errorMessage string) ([]byte, error) { + navbarTmpl, _ := Container.GetPageUseCase.GetPage("componentNavbar", nil) + return Container.GetPageUseCase.GetPage("postCreate", map[string]interface{}{ + "Navbar": template.HTML(navbarTmpl), + "Head": headTmpl, + "PageError": PostCreatePageError{ + IsError: errorMessage != "", + Message: errorMessage, + }, + "Name": postName, + }) +} + +func PostPostCreatePage(w http.ResponseWriter, r *http.Request) { + _ = r.ParseForm() + + postName := r.FormValue("name") + pattern := regexp.MustCompile("^[a-zA-Z0-9À-ÖØ-öø-ÿĀ-ſḀ-ỿ ]{3,50}$") + + if !pattern.MatchString(postName) { + postsTmpl, _ := GetPostCreatePageTemplate(postName, "Name should be alphanumeric, and between 3 and 50 characters.") + _, _ = w.Write(postsTmpl) + return + } + + post, err := Container.CreatePostUseCase.CreatePost(useCases.CreatePostCommand{ + Title: postName, + Body: "", + }) + if err != nil { + postsTmpl, _ := GetPostCreatePageTemplate(postName, "Something went wrong when creating the post, please contact admin.") + _, _ = w.Write(postsTmpl) + return + } + + http.Redirect(w, r, "/post/"+post.Title+"/edit", http.StatusSeeOther) +} + +func GetPostCreatePage(w http.ResponseWriter, _ *http.Request) { + postsTmpl, _ := GetPostCreatePageTemplate("", "") + _, _ = w.Write(postsTmpl) +} diff --git a/api/pagePostEdit.go b/api/pagePostEdit.go index a39a682..4c885e3 100644 --- a/api/pagePostEdit.go +++ b/api/pagePostEdit.go @@ -1,15 +1,68 @@ package api import ( + "github.com/go-chi/chi/v5" "html/template" "net/http" + "net/url" ) -func GetPostEditPage(w http.ResponseWriter, _ *http.Request) { +type PostEditPageAlert struct { + IsError bool + Message string +} + +func getPostEditPageTemplate(body string, alert PostEditPageAlert) []byte { navbarTmpl, _ := Container.GetPageUseCase.GetPage("componentNavbar", nil) - postsTmpl, _ := Container.GetPageUseCase.GetPage("postEdit", map[string]interface{}{ + postTmpl, _ := Container.GetPageUseCase.GetPage("postEdit", map[string]interface{}{ "Navbar": template.HTML(navbarTmpl), "Head": headTmpl, + "Body": body, + "Alert": alert, }) - _, _ = w.Write(postsTmpl) + return postTmpl +} + +func PostPostEditPage(w http.ResponseWriter, r *http.Request) { + postName := chi.URLParam(r, "name") + if len(postName) == 0 { + _, _ = w.Write(getPostEditPageTemplate("", PostEditPageAlert{ + IsError: true, + Message: "Could not find the requested post.", + })) + return + } + + _ = r.ParseForm() + postBody := r.FormValue("postBody") + + parsedName, _ := url.PathUnescape(postName) + post, _ := Container.GetPostUseCase.GetPostByName(parsedName) + err := Container.UpdatePostUseCase.UpdateBody(post.ID, postBody) + if err != nil { + _, _ = w.Write(getPostEditPageTemplate(postBody, PostEditPageAlert{ + IsError: true, + Message: "Could not save the post: " + err.Error(), + })) + return + } + + _, _ = w.Write(getPostEditPageTemplate(postBody, PostEditPageAlert{ + IsError: false, + Message: "Post successfully edited!", + })) +} + +func GetPostEditPage(w http.ResponseWriter, r *http.Request) { + postName := chi.URLParam(r, "name") + if len(postName) == 0 { + w.WriteHeader(http.StatusBadRequest) + return + } + parsedName, _ := url.PathUnescape(postName) + post, _ := Container.GetPostUseCase.GetPostByName(parsedName) + _, _ = w.Write(getPostEditPageTemplate(post.Body, PostEditPageAlert{ + IsError: false, + Message: "", + })) } diff --git a/api/pagePosts.go b/api/pagePosts.go index 504e40e..514d637 100644 --- a/api/pagePosts.go +++ b/api/pagePosts.go @@ -6,10 +6,12 @@ import ( ) func GetPostsPage(w http.ResponseWriter, _ *http.Request) { + posts := Container.ListPostsUseCase.ListPosts() navbarTmpl, _ := Container.GetPageUseCase.GetPage("componentNavbar", nil) postsTmpl, _ := Container.GetPageUseCase.GetPage("posts", map[string]interface{}{ "Navbar": template.HTML(navbarTmpl), "Head": headTmpl, + "Posts": posts, }) _, _ = w.Write(postsTmpl) } diff --git a/api/static/bootstrap-icons.svg b/api/static/bootstrap-icons.svg new file mode 100644 index 0000000..b7d55a8 --- /dev/null +++ b/api/static/bootstrap-icons.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/domain/gateways/IPostRepository.go b/domain/gateways/IPostRepository.go index 4982e45..66fe876 100644 --- a/domain/gateways/IPostRepository.go +++ b/domain/gateways/IPostRepository.go @@ -6,6 +6,9 @@ import ( type IPostRepository interface { Get(id uint32) (post.Post, error) + GetByName(name string) (post.Post, error) GetAll() []post.Post Create(post post.Post) (post.Post, error) + UpdateBody(id uint32, body string) error + Delete(id uint32) error } diff --git a/useCases/GetPostUseCase.go b/useCases/GetPostUseCase.go index ec8e8e2..7945e48 100644 --- a/useCases/GetPostUseCase.go +++ b/useCases/GetPostUseCase.go @@ -19,3 +19,7 @@ func NewGetPostUseCase(db *gorm.DB) *GetPostUseCase { func (g *GetPostUseCase) GetPost(id uint32) (post.Post, error) { return g.postRepository.Get(id) } + +func (g *GetPostUseCase) GetPostByName(name string) (post.Post, error) { + return g.postRepository.GetByName(name) +} diff --git a/useCases/UpdatePostUseCase.go b/useCases/UpdatePostUseCase.go new file mode 100644 index 0000000..60b9b0f --- /dev/null +++ b/useCases/UpdatePostUseCase.go @@ -0,0 +1,20 @@ +package useCases + +import ( + "GoCMS/adapters/secondary/gateways" + "gorm.io/gorm" +) + +type UpdatePostUseCase struct { + postRepository gateways.PostRepository +} + +func NewUpdatePostUseCase(db *gorm.DB) *UpdatePostUseCase { + return &UpdatePostUseCase{ + postRepository: *gateways.NewPostRepository(db), + } +} + +func (g *UpdatePostUseCase) UpdateBody(id uint32, body string) error { + return g.postRepository.UpdateBody(id, body) +} diff --git a/useCases/UpdateUserUseCase.go b/useCases/UpdateUserUseCase.go index 9af4359..1005819 100644 --- a/useCases/UpdateUserUseCase.go +++ b/useCases/UpdateUserUseCase.go @@ -10,10 +10,6 @@ type UpdateUserUseCase struct { userRepository gateways.UserRepository } -type UpdateVerificationStatusCommand struct { - isVerified bool -} - func NewUpdateUserUseCase(db *gorm.DB) *UpdateUserUseCase { return &UpdateUserUseCase{ userRepository: *gateways.NewUserRepository(db),