diff options
Diffstat (limited to 'cmd/web/controller/htmx')
-rw-r--r-- | cmd/web/controller/htmx/article-action.go | 105 | ||||
-rw-r--r-- | cmd/web/controller/htmx/article.go | 64 | ||||
-rw-r--r-- | cmd/web/controller/htmx/comment.go | 96 | ||||
-rw-r--r-- | cmd/web/controller/htmx/editor.go | 218 | ||||
-rw-r--r-- | cmd/web/controller/htmx/home-action.go | 53 | ||||
-rw-r--r-- | cmd/web/controller/htmx/home.go | 322 | ||||
-rw-r--r-- | cmd/web/controller/htmx/setting.go | 81 | ||||
-rw-r--r-- | cmd/web/controller/htmx/sign-in.go | 80 | ||||
-rw-r--r-- | cmd/web/controller/htmx/sign-up.go | 46 | ||||
-rw-r--r-- | cmd/web/controller/htmx/user-action.go | 96 | ||||
-rw-r--r-- | cmd/web/controller/htmx/user.go | 195 |
11 files changed, 1356 insertions, 0 deletions
diff --git a/cmd/web/controller/htmx/article-action.go b/cmd/web/controller/htmx/article-action.go new file mode 100644 index 0000000..a7b1f23 --- /dev/null +++ b/cmd/web/controller/htmx/article-action.go @@ -0,0 +1,105 @@ +package HTMXController + +import ( + "errors" + "projecty/cmd/web/model" + "projecty/internal/authentication" + "projecty/internal/database" + "projecty/internal/helper" + + "github.com/gofiber/fiber/v2" + "gorm.io/gorm" +) + +func ArticleFavoriteAction(c *fiber.Ctx) error { + + var article model.Article + var authenticatedUser model.User + + isArticleFavorited := false + + isAuthenticated, userID := authentication.AuthGet(c) + if !isAuthenticated { + return helper.HTMXRedirectTo("/sign-in", "/htmx/sign-in", c) + + } + + db := database.Get() + + err := db.Model(&article). + Where("slug = ?", c.Params("slug")). + Preload("Favorites"). + Find(&article).Error + + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return helper.HTMXRedirectTo("/sign-in", "/htmx/sign-in", c) + } + } + + authenticatedUser.ID = userID + + if article.FavoritedBy(userID) { + db.Model(&article).Association("Favorites").Delete(&authenticatedUser) + } else { + db.Model(&article).Association("Favorites").Append(&authenticatedUser) + isArticleFavorited = true + } + + return c.Render("articles/partials/favorite-button", fiber.Map{ + "Article": article, + "Slug": article.Slug, + "IsArticleFavorited": isArticleFavorited, + "IsOob": true, + }, "layouts/app-htmx") +} + +func ArticleFollowAction(c *fiber.Ctx) error { + + var article model.Article + var authenticatedUser model.User + + isFollowed := false + + isAuthenticated, userID := authentication.AuthGet(c) + if !isAuthenticated { + return helper.HTMXRedirectTo("/sign-in", "/htmx/sign-in", c) + } + + db := database.Get() + + err := db.Model(&article). + Where("slug = ?", c.Params("slug")). + Preload("Favorites"). + Preload("User.Followers"). + Find(&article).Error + + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return helper.HTMXRedirectTo("/sign-in", "/htmx/sign-in", c) + } + } + + authenticatedUser.ID = userID + + if article.User.FollowedBy(userID) { + + f := model.Follow{ + FollowerID: article.UserID, + FollowingID: userID, + } + + db.Model(&article.User).Association("Followers").Find(&f) + db.Delete(&f) + + } else { + db.Model(&article.User).Association("Followers").Append(&model.Follow{FollowerID: article.UserID, FollowingID: userID}) + isFollowed = true + } + + return c.Render("articles/partials/follow-button", fiber.Map{ + "Article": article, + "IsFollowed": isFollowed, + "IsOob": true, + }, "layouts/app-htmx") +} diff --git a/cmd/web/controller/htmx/article.go b/cmd/web/controller/htmx/article.go new file mode 100644 index 0000000..c8dd8f4 --- /dev/null +++ b/cmd/web/controller/htmx/article.go @@ -0,0 +1,64 @@ +package HTMXController + +import ( + "errors" + "projecty/cmd/web/model" + "projecty/internal/authentication" + "projecty/internal/database" + + "github.com/gofiber/fiber/v2" + "gorm.io/gorm" +) + +func ArticleDetailPage(c *fiber.Ctx) error { + + var article model.Article + isSelf := false + isFollowed := false + var authenticatedUser model.User + + isAuthenticated, userID := authentication.AuthGet(c) + + db := database.Get() + + if isAuthenticated { + db.Model(&authenticatedUser). + Where("id = ?", userID). + First(&authenticatedUser) + } + + err := db.Model(&article). + Where("slug = ?", c.Params("slug")). + Preload("Favorites"). + Preload("Tags", func(db *gorm.DB) *gorm.DB { + return db.Order("tags.name asc") + }). + Preload("User"). + Find(&article).Error + + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return c.Redirect("/") + } + } + + if isAuthenticated && article.User.FollowedBy(userID) { + isFollowed = true + } + + if isAuthenticated && article.User.ID == userID { + isSelf = true + } + + return c.Render("articles/htmx-article-page", fiber.Map{ + "PageTitle": article.Title, + "NavBarActive": "none", + "Article": article, + "IsOob": false, + "IsSelf": isSelf, + "IsFollowed": isFollowed, + "IsArticleFavorited": article.FavoritedBy(userID), + "AuthenticatedUser": authenticatedUser, + "FiberCtx": c, + }, "layouts/app-htmx") +} diff --git a/cmd/web/controller/htmx/comment.go b/cmd/web/controller/htmx/comment.go new file mode 100644 index 0000000..3831c0e --- /dev/null +++ b/cmd/web/controller/htmx/comment.go @@ -0,0 +1,96 @@ +package HTMXController + +import ( + "errors" + "projecty/cmd/web/model" + "projecty/internal" + "projecty/internal/authentication" + "projecty/internal/database" + "projecty/internal/helper" + + "github.com/go-playground/validator/v10" + "github.com/gofiber/fiber/v2" + "gorm.io/gorm" +) + +func ArticleDetailCommentList(c *fiber.Ctx) error { + + var article model.Article + + isAuthenticated, _ := authentication.AuthGet(c) + + db := database.Get() + + db.Model(&article). + Where("slug = ?", c.Params("slug")). + Preload("User"). + Preload("Comments", func(db *gorm.DB) *gorm.DB { + return db.Order("id DESC").Preload("User") + }). + Find(&article) + + return c.Render("articles/partials/comments-wrapper", fiber.Map{ + "Article": article, + "IsAuthenticated": isAuthenticated, + }, "layouts/app-htmx") +} + +func ArticleComment(c *fiber.Ctx) error { + + var ( + errorBag []string + article model.Article + comment model.Comment + authenticatedUser model.User + ) + validate := internal.NewValidator() + + isAuthenticated, userID := authentication.AuthGet(c) + if !isAuthenticated { + return helper.HTMXRedirectTo("/sign-in", "/htmx/sign-in", c) + } + + db := database.Get() + + db.Model(&authenticatedUser). + Where("id = ?", userID). + First(&authenticatedUser) + + err := db.Model(&article). + Where("slug = ?", c.Params("slug")). + Preload("User"). + Find(&article).Error + + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return helper.HTMXRedirectTo("/", "/htmx/home", c) + } + } + + comment.UserID = userID + comment.ArticleID = article.ID + comment.Body = c.FormValue("comment") + + err = validate.Validate(comment) + if err != nil { + + for _, err := range err.(validator.ValidationErrors) { + errorBag = append(errorBag, internal.ErrorMessage(err.Field(), err.Tag())) + } + + return c.Render("components/error-message", fiber.Map{ + "Errors": errorBag, + }, "layouts/app-htmx") + } + + comment.User = authenticatedUser + + db.Create(&comment) + + return c.Render("articles/htmx-post-comments", fiber.Map{ + "IsOob": true, + "Article": article, + "Comment": comment, + "User": authenticatedUser, + }, "layouts/app-htmx") +} diff --git a/cmd/web/controller/htmx/editor.go b/cmd/web/controller/htmx/editor.go new file mode 100644 index 0000000..fda61a4 --- /dev/null +++ b/cmd/web/controller/htmx/editor.go @@ -0,0 +1,218 @@ +package HTMXController + +import ( + "encoding/json" + "errors" + "projecty/cmd/web/model" + "projecty/internal" + "projecty/internal/authentication" + "projecty/internal/database" + "projecty/internal/helper" + + "github.com/go-playground/validator/v10" + "github.com/gofiber/fiber/v2" + "github.com/gosimple/slug" + "gorm.io/gorm" +) + +func EditorPage(c *fiber.Ctx) error { + + var authenticatedUser model.User + var article model.Article + hasArticle := false + NavBarActive := "editor" + + isAuthenticated, userID := authentication.AuthGet(c) + + db := database.Get() + + if isAuthenticated { + db.Model(&authenticatedUser). + Where("id = ?", userID). + First(&authenticatedUser) + } + + if c.Params("slug") != "" { + + err := db.Model(&article). + Where("slug = ?", c.Params("slug")). + Preload("Tags", func(db *gorm.DB) *gorm.DB { + return db.Order("tags.name asc") + }). + Find(&article).Error + + if err == nil { + hasArticle = true + NavBarActive = "none" + } + } + + return c.Render("editor/htmx-editor-page", fiber.Map{ + "PageTitle": "Editor", + "FiberCtx": c, + "NavBarActive": NavBarActive, + "AuthenticatedUser": authenticatedUser, + "HasArticle": hasArticle, + "Article": article, + }, "layouts/app-htmx") +} + +func StoreArticle(c *fiber.Ctx) error { + + type TagItem struct { + Value string + } + + var ( + errorBag []string + tagItems []TagItem + authenticatedUser model.User + ) + validate := internal.NewValidator() + + isAuthenticated, userID := authentication.AuthGet(c) + if !isAuthenticated { + return c.Redirect("/") + } + + db := database.Get() + + db.Model(&authenticatedUser). + Where("id = ?", userID). + First(&authenticatedUser) + + article := &model.Article{ + Title: c.FormValue("title"), + Slug: slug.Make(c.FormValue("title")), + Description: c.FormValue("description"), + Body: c.FormValue("content"), + UserID: userID, + } + + err := validate.Validate(article) + if err != nil { + + for _, err := range err.(validator.ValidationErrors) { + errorBag = append(errorBag, internal.ErrorMessage(err.Field(), err.Tag())) + } + + return c.Render("editor/htmx-editor-page", fiber.Map{ + "IsOob": true, + "FiberCtx": c, + "NavBarActive": "editor", + "Errors": errorBag, + "AuthenticatedUser": authenticatedUser, + }, "layouts/app-htmx") + } + + db.Create(article) + + if c.FormValue("tags") != "" { + json.Unmarshal([]byte(c.FormValue("tags")), &tagItems) + + for i := 0; i < len(tagItems); i++ { + tagItem := tagItems[i] + tag := model.Tag{Name: tagItem.Value} + + err := db.Model(&tag).Where("name = ?", tagItem.Value).First(&tag).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + db.Create(&tag) + } + + if err := db.Model(&article).Association("Tags").Append(&tag); err != nil { + return err + } + } + } + + return helper.HTMXRedirectTo("/articles/"+article.Slug, "/htmx/articles/"+article.Slug, c) +} + +func UpdateArticle(c *fiber.Ctx) error { + + type TagItem struct { + Value string + } + + var ( + errorBag []string + tagItems []TagItem + authenticatedUser model.User + article model.Article + tags []model.Tag + ) + + validate := internal.NewValidator() + + isAuthenticated, userID := authentication.AuthGet(c) + if !isAuthenticated { + return c.Redirect("/") + } + + db := database.Get() + + db.Model(&authenticatedUser). + Where("id = ?", userID). + First(&authenticatedUser) + + err := db.Model(&article). + Where("slug = ?", c.Params("slug")). + Preload("Tags", func(db *gorm.DB) *gorm.DB { + return db.Order("tags.name asc") + }). + Find(&article).Error + + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return c.Redirect("/") + } + } + + article.Title = c.FormValue("title") + article.Description = c.FormValue("description") + article.Body = c.FormValue("content") + + err = validate.Validate(article) + if err != nil { + + for _, err := range err.(validator.ValidationErrors) { + errorBag = append(errorBag, internal.ErrorMessage(err.Field(), err.Tag())) + } + + return c.Render("editor/htmx-editor-page", fiber.Map{ + "IsOob": true, + "FiberCtx": c, + "NavBarActive": "editor", + "Errors": errorBag, + "AuthenticatedUser": authenticatedUser, + "HasArticle": true, + "Article": article, + }, "layouts/app-htmx") + } + + article.Slug = slug.Make(c.FormValue("title")) + + db.Updates(article) + + if c.FormValue("tags") != "" { + json.Unmarshal([]byte(c.FormValue("tags")), &tagItems) + + for i := 0; i < len(tagItems); i++ { + tagItem := tagItems[i] + tag := model.Tag{Name: tagItem.Value} + + err := db.Model(&tag).Where("name = ?", tagItem.Value).First(&tag).Error + if err != nil && errors.Is(err, gorm.ErrRecordNotFound) { + db.Create(&tag) + } + + tags = append(tags, tag) + } + + if err := db.Model(&article).Association("Tags").Replace(&tags); err != nil { + return err + } + } + + return helper.HTMXRedirectTo("/articles/"+article.Slug, "/htmx/articles/"+article.Slug, c) +} diff --git a/cmd/web/controller/htmx/home-action.go b/cmd/web/controller/htmx/home-action.go new file mode 100644 index 0000000..5c4b872 --- /dev/null +++ b/cmd/web/controller/htmx/home-action.go @@ -0,0 +1,53 @@ +package HTMXController + +import ( + "errors" + "projecty/cmd/web/model" + "projecty/internal/authentication" + "projecty/internal/database" + "projecty/internal/helper" + + "github.com/gofiber/fiber/v2" + "gorm.io/gorm" +) + +func HomeFavoriteAction(c *fiber.Ctx) error { + + var article model.Article + var authenticatedUser model.User + + isArticleFavorited := false + + isAuthenticated, userID := authentication.AuthGet(c) + if !isAuthenticated { + return helper.HTMXRedirectTo("/sign-in", "/htmx/sign-in", c) + } + + db := database.Get() + + err := db.Model(&article). + Where("slug = ?", c.Params("slug")). + Preload("Favorites"). + Find(&article).Error + + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return helper.HTMXRedirectTo("/sign-in", "/htmx/sign-in", c) + } + } + + authenticatedUser.ID = userID + + if article.FavoritedBy(userID) { + db.Model(&article).Association("Favorites").Delete(&authenticatedUser) + } else { + db.Model(&article).Association("Favorites").Append(&authenticatedUser) + isArticleFavorited = true + } + + return c.Render("home/partials/article-favorite-button", fiber.Map{ + "GetFavoriteCount": article.GetFavoriteCount(), + "Slug": article.Slug, + "IsFavorited": isArticleFavorited, + }, "layouts/app-htmx") +} diff --git a/cmd/web/controller/htmx/home.go b/cmd/web/controller/htmx/home.go new file mode 100644 index 0000000..07378d4 --- /dev/null +++ b/cmd/web/controller/htmx/home.go @@ -0,0 +1,322 @@ +package HTMXController + +import ( + "math" + "projecty/cmd/web/model" + "projecty/internal/authentication" + "projecty/internal/database" + + "github.com/gofiber/fiber/v2" + "gorm.io/gorm" +) + +func HomePage(c *fiber.Ctx) error { + + var authenticatedUser model.User + + isAuthenticated, userID := authentication.AuthGet(c) + + if isAuthenticated { + db := database.Get() + db.Model(&authenticatedUser). + Where("id = ?", userID). + First(&authenticatedUser) + } + + return c.Render("home/htmx-home-page", fiber.Map{ + "PageTitle": "Home", + "NavBarActive": "home", + "FiberCtx": c, + "AuthenticatedUser": authenticatedUser, + }, "layouts/app-htmx") +} + +func HomeYourFeed(c *fiber.Ctx) error { + var ( + articles []model.Article + hasArticles bool + user model.User + followings []model.Follow + hasPagination bool + totalPagination int + count int64 + ) + + page := 0 + if c.QueryInt("page") > 1 { + page = c.QueryInt("page") - 1 + } + + isAuthenticated, userID := authentication.AuthGet(c) + if !isAuthenticated { + return c.Redirect("/") + } + + db := database.Get() + db.Model(&user).Where("id = ?", userID).First(&user) + + db.Model(&user).Preload("Followings").Association("Followings").Find(&followings) + if len(followings) == 0 { + hasArticles = false + } + + ids := make([]uint, len(followings)) + for i, f := range followings { + ids[i] = f.FollowerID + } + + db.Where("user_id in (?)", ids). + Preload("Favorites"). + Preload("Tags", func(db *gorm.DB) *gorm.DB { + return db.Order("tags.name asc") + }). + Preload("User"). + Limit(5). + Offset(page * 5). + Order("created_at desc"). + Find(&articles) + + db.Model(&articles).Where("user_id in (?)", ids).Count(&count) + + if count > 0 && (count/5 > 0) { + pageDivision := float64(count) / float64(5) + totalPagination = int(math.Ceil(pageDivision)) + hasPagination = true + } + + feedNavbarItems := []fiber.Map{ + { + "Title": "Your Feed", + "IsActive": true, + "HXPushURL": "/your-feed", + "HXGetURL": "/htmx/home/your-feed", + }, + { + "Title": "Global Feed", + "IsActive": false, + "HXPushURL": "/", + "HXGetURL": "/htmx/home/global-feed", + }, + } + + if len(articles) > 0 { + hasArticles = true + } + + c.Render("home/htmx-home-feed", fiber.Map{ + "HasArticles": hasArticles, + "Articles": articles, + "FeedNavbarItems": feedNavbarItems, + "Personal": isAuthenticated, + "TotalPagination": totalPagination, + "HasPagination": hasPagination, + "CurrentPagination": page + 1, + "PushPathPagination": "your-feed", + "PathPagination": "your-feed", + }, "layouts/app-htmx") + + return nil +} + +func HomeGlobalFeed(c *fiber.Ctx) error { + + var ( + articles []model.Article + hasArticles bool + hasPagination bool + totalPagination int + count int64 + ) + + page := 0 + if c.QueryInt("page") > 1 { + page = c.QueryInt("page") - 1 + } + + isAuthenticated, userID := authentication.AuthGet(c) + + db := database.Get() + db.Model(&articles). + Preload("Favorites"). + Preload("Tags", func(db *gorm.DB) *gorm.DB { + return db.Order("tags.name asc") + }). + Preload("User"). + Limit(5). + Offset(page * 5). + Order("created_at desc"). + Find(&articles) + + db.Model(&articles).Count(&count) + + feedNavbarItems := []fiber.Map{ + { + "Title": "Global Feed", + "IsActive": true, + "HXPushURL": "/", + "HXGetURL": "/htmx/home/global-feed", + }, + } + + if count > 0 && (count/5 > 0) { + pageDivision := float64(count) / float64(5) + totalPagination = int(math.Ceil(pageDivision)) + hasPagination = true + } + + if isAuthenticated { + + feedNavbarItems = append([]fiber.Map{ + { + "Title": "Your Feed", + "IsActive": false, + "HXPushURL": "/your-feed", + "HXGetURL": "/htmx/home/your-feed", + }, + }, feedNavbarItems...) + } + + if len(articles) > 0 { + hasArticles = true + + for i := 0; i < len(articles); i++ { + articles[i].IsFavorited = articles[i].FavoritedBy(userID) + } + } + + c.Render("home/htmx-home-feed", fiber.Map{ + "HasArticles": hasArticles, + "Articles": articles, + "FeedNavbarItems": feedNavbarItems, + "AuthenticatedUserID": userID, + "TotalPagination": totalPagination, + "HasPagination": hasPagination, + "CurrentPagination": page + 1, + "PathPagination": "global-feed", + }, "layouts/app-htmx") + + return nil +} + +func HomeTagFeed(c *fiber.Ctx) error { + + var ( + tag model.Tag + articles []model.Article + hasArticles bool + hasPagination bool + totalPagination int + count int64 + ) + + page := 0 + if c.QueryInt("page") > 1 { + page = c.QueryInt("page") - 1 + } + + isAuthenticated, _ := authentication.AuthGet(c) + + tagText := c.Params("tag") + + db := database.Get() + + db.Where(&model.Tag{Name: tagText}).First(&tag) + + db.Model(&tag). + Preload("Favorites"). + Preload("Tags", func(db *gorm.DB) *gorm.DB { + return db.Order("tags.name asc") + }). + Preload("User"). + Limit(5). + Offset(page * 5). + Order("created_at desc"). + Association("Articles"). + Find(&articles) + + count = db.Model(&tag). + Association("Articles"). + Count() + + if len(articles) > 0 { + hasArticles = true + } + + if count > 0 && (count/5 > 0) { + pageDivision := float64(count) / float64(5) + totalPagination = int(math.Ceil(pageDivision)) + hasPagination = true + } + + feedNavbarItems := []fiber.Map{ + { + "Title": "Global Feed", + "IsActive": false, + "HXPushURL": "/", + "HXGetURL": "/htmx/home/global-feed", + }, + } + + if isAuthenticated { + + feedNavbarItems = append([]fiber.Map{ + { + "Title": "Your Feed", + "IsActive": false, + "HXPushURL": "/your-feed", + "HXGetURL": "/htmx/home/your-feed", + }, + }, feedNavbarItems...) + } + + feedNavbarItems = append(feedNavbarItems, + fiber.Map{ + "Title": tagText, + "IsActive": true, + "HXPushURL": "/", + "HXGetURL": "/htmx/home/global-feed", + }, + ) + + c.Render("home/htmx-home-feed", fiber.Map{ + "HasArticles": hasArticles, + "Articles": articles, + "FeedNavbarItems": feedNavbarItems, + "TotalPagination": totalPagination, + "HasPagination": hasPagination, + "CurrentPagination": page + 1, + "PushPathPagination": "tag-feed/" + tag.Name, + "PathPagination": "tag-feed/" + tag.Name, + }, "layouts/app-htmx") + + return nil +} + +func HomeTagList(c *fiber.Ctx) error { + + var ( + tag model.Tag + tags []model.Tag + hasTags bool + ) + + db := database.Get() + db.Model(&tag). + Select("*, COUNT(id) as favorite_count"). + Preload("Articles"). + Limit(5). + Order("favorite_count DESC"). + Group("id"). + Find(&tags) + + if len(tags) > 0 { + hasTags = true + } + + c.Render("home/partials/tag-item-list", fiber.Map{ + "Tags": tags, + "HasTags": hasTags, + }, "layouts/app-htmx") + + return nil +} diff --git a/cmd/web/controller/htmx/setting.go b/cmd/web/controller/htmx/setting.go new file mode 100644 index 0000000..28c138c --- /dev/null +++ b/cmd/web/controller/htmx/setting.go @@ -0,0 +1,81 @@ +package HTMXController + +import ( + "projecty/cmd/web/model" + "projecty/internal" + "projecty/internal/authentication" + "projecty/internal/database" + "projecty/internal/helper" + + "github.com/go-playground/validator/v10" + "github.com/gofiber/fiber/v2" +) + +func SettingPage(c *fiber.Ctx) error { + + var authenticatedUser model.User + + isAuthenticated, userID := authentication.AuthGet(c) + + if isAuthenticated { + db := database.Get() + db.Model(&authenticatedUser). + Where("id = ?", userID). + First(&authenticatedUser) + } + + return c.Render("settings/htmx-setting-page", fiber.Map{ + "PageTitle": "Settings", + "NavBarActive": "settings", + "FiberCtx": c, + "AuthenticatedUser": authenticatedUser, + }, "layouts/app-htmx") + +} + +func SettingAction(c *fiber.Ctx) error { + + var errorBag []string + validate := internal.NewValidator() + + isAuthenticated, userID := authentication.AuthGet(c) + if !isAuthenticated { + return helper.HTMXRedirectTo("/sign-in", "/htmx/sign-in", c) + } + + user := &model.User{ + ID: userID, + Image: c.FormValue("image"), + Name: c.FormValue("name"), + Bio: c.FormValue("bio"), + Email: c.FormValue("email"), + Password: c.FormValue("password"), + } + + err := validate.Validate(user) + if err != nil { + for _, err := range err.(validator.ValidationErrors) { + errorBag = append(errorBag, internal.ErrorMessage(err.Field(), err.Tag())) + } + + return c.Render("settings/partials/form-message", fiber.Map{ + "IsOob": true, + "Errors": errorBag, + }, "layouts/app-htmx") + } + + if user.Password != "" { + user.HashPassword() + } + + db := database.Get() + db.Model(user).Updates(user) + + return c.Render("settings/partials/htmx-form-message", fiber.Map{ + "IsOob": true, + "SuccessMessages": []string{"Data successfully saved."}, + "NavBarActive": "settings", + "FiberCtx": c, + "AuthenticatedUser": user, + }, "layouts/app-htmx") +} diff --git a/cmd/web/controller/htmx/sign-in.go b/cmd/web/controller/htmx/sign-in.go new file mode 100644 index 0000000..df60027 --- /dev/null +++ b/cmd/web/controller/htmx/sign-in.go @@ -0,0 +1,80 @@ +package HTMXController + +import ( + "errors" + "projecty/cmd/web/model" + "projecty/internal/authentication" + "projecty/internal/database" + "projecty/internal/helper" + + "github.com/gofiber/fiber/v2" + "gorm.io/gorm" +) + +func SignInPage(c *fiber.Ctx) error { + + return c.Render("sign-in/htmx-sign-in-page", fiber.Map{ + "PageTitle": "Sign In", + "NavBarActive": "sign-in", + "FiberCtx": c, + }, "layouts/app-htmx") + +} + +func SignInAction(c *fiber.Ctx) error { + + var user model.User + email := c.FormValue("email") + password := c.FormValue("password") + + if email == "" || password == "" { + + return c.Render("sign-in/partials/sign-in-form", fiber.Map{ + "Errors": []string{ + "Email or password cannot be null.", + }, + "IsOob": true, + }, "layouts/app-htmx") + } + + db := database.Get() + + db.Model(&user) + err := db.Where(&model.User{Email: email}). + First(&user).Error + + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return c.Render("sign-in/partials/sign-in-form", fiber.Map{ + "Errors": []string{ + "Email and password did not match.", + }, + }, "layouts/app-htmx") + } + } + + if !user.CheckPassword(password) { + + return c.Render("sign-in/partials/sign-in-form", fiber.Map{ + "Errors": []string{ + "Email and password did not match.", + }, + }, "layouts/app-htmx") + } + + authentication.AuthStore(c, user.ID) + + return helper.HTMXRedirectTo("/", "/htmx/home", c) +} + +func SignOut(c *fiber.Ctx) error { + + isAuthenticated, _ := authentication.AuthGet(c) + if !isAuthenticated { + return c.Redirect("/") + } + + authentication.AuthDestroy(c) + + return helper.HTMXRedirectTo("/", "/htmx/home", c) +} diff --git a/cmd/web/controller/htmx/sign-up.go b/cmd/web/controller/htmx/sign-up.go new file mode 100644 index 0000000..6bf9a3f --- /dev/null +++ b/cmd/web/controller/htmx/sign-up.go @@ -0,0 +1,46 @@ +package HTMXController + +import ( + "projecty/cmd/web/model" + "projecty/internal/authentication" + "projecty/internal/database" + "projecty/internal/helper" + + "github.com/gofiber/fiber/v2" +) + +func SignUpPage(c *fiber.Ctx) error { + + return c.Render("sign-up/htmx-sign-up-page", fiber.Map{ + "PageTitle": "Sign Up", + "NavBarActive": "sign-up", + "FiberCtx": c, + }, "layouts/app-htmx") +} + +func SignUpAction(c *fiber.Ctx) error { + + username := c.FormValue("username") + email := c.FormValue("email") + password := c.FormValue("password") + + if email == "" || username == "" || password == "" { + + return c.Render("sign-up/partials/sign-up-form", fiber.Map{ + "Errors": []string{ + "Username, email, and password cannot be null.", + }, + "IsOob": true, + }, "layouts/app-htmx") + } + + user := model.User{Username: username, Email: email, Password: password, Name: username} + user.HashPassword() + + db := database.Get() + db.Create(&user) + + authentication.AuthStore(c, user.ID) + + return helper.HTMXRedirectTo("/", "/htmx/home", c) +} diff --git a/cmd/web/controller/htmx/user-action.go b/cmd/web/controller/htmx/user-action.go new file mode 100644 index 0000000..507bfec --- /dev/null +++ b/cmd/web/controller/htmx/user-action.go @@ -0,0 +1,96 @@ +package HTMXController + +import ( + "errors" + "projecty/cmd/web/model" + "projecty/internal/authentication" + "projecty/internal/database" + "projecty/internal/helper" + + "github.com/gofiber/fiber/v2" + "gorm.io/gorm" +) + +func UserArticleFavoriteAction(c *fiber.Ctx) error { + + var article model.Article + var authenticatedUser model.User + + isAuthenticated, userID := authentication.AuthGet(c) + if !isAuthenticated { + return helper.HTMXRedirectTo("/sign-in", "/htmx/sign-in", c) + } + + db := database.Get() + + err := db.Model(&article). + Where("slug = ?", c.Params("slug")). + Preload("Favorites"). + Find(&article).Error + + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return helper.HTMXRedirectTo("/sign-in", "/htmx/sign-in", c) + } + } + + authenticatedUser.ID = userID + + if article.FavoritedBy(userID) { + db.Model(&article).Association("Favorites").Delete(&authenticatedUser) + article.IsFavorited = false + } else { + db.Model(&article).Association("Favorites").Append(&authenticatedUser) + article.IsFavorited = true + } + + return c.Render("users/partials/article-favorite-button", fiber.Map{ + "Article": article, + }, "layouts/app-htmx") +} + +func UserFollowAction(c *fiber.Ctx) error { + + var authenticatedUser model.User + var user model.User + isFollowed := false + + isAuthenticated, userID := authentication.AuthGet(c) + + db := database.Get() + + err := db.Model(&user). + Where("username = ?", c.Params("username")). + Preload("Followers"). + Find(&user).Error + + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return helper.HTMXRedirectTo("/", "/htmx/home", c) + } + } + + if isAuthenticated { + db.Model(&authenticatedUser). + Where("id = ?", userID). + First(&authenticatedUser) + } + + f := model.Follow{ + FollowerID: user.ID, + FollowingID: userID, + } + + if user.FollowedBy(userID) { + db.Model(&user).Association("Followers").Find(&f) + db.Delete(&f) + } else { + db.Model(&user).Association("Followers").Append(&f) + isFollowed = true + } + + return c.Render("users/partials/follow-button", fiber.Map{ + "User": user, + "IsFollowed": isFollowed, + }, "layouts/app-htmx") +} diff --git a/cmd/web/controller/htmx/user.go b/cmd/web/controller/htmx/user.go new file mode 100644 index 0000000..b0a2605 --- /dev/null +++ b/cmd/web/controller/htmx/user.go @@ -0,0 +1,195 @@ +package HTMXController + +import ( + "errors" + "projecty/cmd/web/model" + "projecty/internal/authentication" + "projecty/internal/database" + "projecty/internal/helper" + + "github.com/gofiber/fiber/v2" + "gorm.io/gorm" +) + +func UserDetailPage(c *fiber.Ctx) error { + + var authenticatedUser model.User + var user model.User + isSelf := false + isFollowed := false + navbarActive := "none" + + isAuthenticated, userID := authentication.AuthGet(c) + + db := database.Get() + + err := db.Model(&user). + Where("username = ?", c.Params("username")). + Preload("Followers"). + Find(&user).Error + + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return helper.HTMXRedirectTo("/", "/htmx/home", c) + } + } + + if isAuthenticated { + db.Model(&authenticatedUser). + Where("id = ?", userID). + First(&authenticatedUser) + } + + if isAuthenticated && user.ID == userID { + isSelf = true + navbarActive = "profile" + } + + if isAuthenticated && !isSelf && user.FollowedBy(userID) { + isFollowed = true + } + + return c.Render("users/htmx-users-page", fiber.Map{ + "PageTitle": user.Name, + "IsSelf": isSelf, + "IsFollowed": isFollowed, + "AuthenticatedUser": authenticatedUser, + "User": user, + "NavBarActive": navbarActive, + "FiberCtx": c, + }, "layouts/app-htmx") +} + +func UserArticles(c *fiber.Ctx) error { + + var articles []model.Article + var user model.User + hasArticles := false + + _, userID := authentication.AuthGet(c) + + db := database.Get() + + err := db.Where(&user). + Where("username = ?", c.Params("username")). + First(&user).Error + + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return helper.HTMXRedirectTo("/", "/htmx/home", c) + } + } + + db.Where(&model.Article{UserID: user.ID}). + Preload("Favorites"). + Preload("Tags", func(db *gorm.DB) *gorm.DB { + return db.Order("tags.name asc") + }). + Preload("User"). + Order("created_at desc"). + Find(&articles) + + if len(articles) > 0 { + hasArticles = true + + for i := 0; i < len(articles); i++ { + articles[i].IsFavorited = articles[i].FavoritedBy(userID) + } + } + + feedNavbarItems := []fiber.Map{ + { + "Title": "Articles", + "IsActive": true, + "HXPushURL": "/users/" + user.Username, + "HXGetURL": "/htmx/users/" + user.Username, + }, + { + "Title": "Favorited Articles", + "IsActive": false, + "HXPushURL": "/users/" + user.Username + "/favorites", + "HXGetURL": "/htmx/users/" + user.Username + "/favorites", + }, + } + + return c.Render("users/htmx-users-articles", fiber.Map{ + "HasArticles": hasArticles, + "Articles": articles, + "User": user, + "FeedNavbarItems": feedNavbarItems, + }, "layouts/app-htmx") +} + +func UserArticlesFavorite(c *fiber.Ctx) error { + + var articles []model.Article + var user model.User + isSelf := false + isFollowed := false + hasArticles := false + + isAuthenticated, userID := authentication.AuthGet(c) + + db := database.Get() + + err := db.Model(&user). + Where("username = ?", c.Params("username")). + First(&user).Error + + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return helper.HTMXRedirectTo("/", "/htmx/home", c) + } + } + + db.Model(&user). + Preload("Favorites"). + Preload("Tags", func(db *gorm.DB) *gorm.DB { + return db.Order("tags.name asc") + }). + Preload("User"). + Order("created_at desc"). + Association("Favorites"). + Find(&articles) + + if len(articles) > 0 { + hasArticles = true + + for i := 0; i < len(articles); i++ { + articles[i].IsFavorited = articles[i].FavoritedBy(userID) + } + } + + if isAuthenticated && user.ID == userID { + isSelf = true + } + + if isAuthenticated && !isSelf && user.FollowedBy(userID) { + isFollowed = true + } + + feedNavbarItems := []fiber.Map{ + { + "Title": "Articles", + "IsActive": false, + "HXPushURL": "/users/" + user.Username + "/articles", + "HXGetURL": "/htmx/users/" + user.Username + "/articles", + }, + { + "Title": "Favorited Articles", + "IsActive": true, + "HXPushURL": "/users/" + user.Username + "/favorites", + "HXGetURL": "/htmx/users/" + user.Username + "/favorites", + }, + } + + return c.Render("users/htmx-users-articles", fiber.Map{ + "IsSelf": isSelf, + "IsFollowed": isFollowed, + "HasArticles": hasArticles, + "Articles": articles, + "User": user, + "FeedNavbarItems": feedNavbarItems, + "IsLoadFavorites": true, + }, "layouts/app-htmx") +} |