aboutsummaryrefslogtreecommitdiff
path: root/cmd/web/controller/htmx
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/web/controller/htmx')
-rw-r--r--cmd/web/controller/htmx/article-action.go105
-rw-r--r--cmd/web/controller/htmx/article.go64
-rw-r--r--cmd/web/controller/htmx/comment.go96
-rw-r--r--cmd/web/controller/htmx/editor.go218
-rw-r--r--cmd/web/controller/htmx/home-action.go53
-rw-r--r--cmd/web/controller/htmx/home.go322
-rw-r--r--cmd/web/controller/htmx/setting.go81
-rw-r--r--cmd/web/controller/htmx/sign-in.go80
-rw-r--r--cmd/web/controller/htmx/sign-up.go46
-rw-r--r--cmd/web/controller/htmx/user-action.go96
-rw-r--r--cmd/web/controller/htmx/user.go195
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")
+}