diff --git a/db/db.go b/db/db.go new file mode 100644 index 0000000..cde66a7 --- /dev/null +++ b/db/db.go @@ -0,0 +1,27 @@ +package db + +import ( + "os" + + "github.com/glebarez/sqlite" + "gorm.io/gorm" + + "log101-blog-services/models" +) + +var db *gorm.DB +var err error + +func InitDB() { + dbPath := os.Getenv("DB_PATH") + db, err = gorm.Open(sqlite.Open(dbPath), &gorm.Config{}) + if err != nil { + panic("failed to connect database") + } + + db.AutoMigrate(&models.EmojiReaction{}) +} + +func GetDB() *gorm.DB { + return db +} diff --git a/handlers/emojiForm.go b/handlers/emojiForm.go new file mode 100644 index 0000000..a54b5fd --- /dev/null +++ b/handlers/emojiForm.go @@ -0,0 +1,79 @@ +package handlers + +import ( + "errors" + "net/http" + + "github.com/gin-gonic/gin" + "gorm.io/gorm/clause" + + DB "log101-blog-services/db" + "log101-blog-services/models" +) + +func GetEmojiForm(c *gin.Context) { + postId := c.Query("postId") + + // get emoji counts for each emoji + emojiCounter, err := CountEmojis(postId) + if err != nil { + c.HTML(http.StatusOK, "emoji_form.tmpl", gin.H{"error": "error getting the emoji counts"}) + return + } + + // Return the html template + c.HTML(http.StatusOK, "emoji_form.tmpl", gin.H{ + "results": emojiCounter, + }) +} + +func PostEmojiForm(c *gin.Context) { + db := DB.GetDB() + reactedPostId := c.PostForm("postId") + reaction := c.PostForm("emojiInput") + + // Check if parameters are missing + if reactedPostId == "" || reaction == "" { + c.HTML(http.StatusOK, "emoji_form.tmpl", gin.H{"error": "missing parameters"}) + return + } + + // Add the new emoji reaction to the database + result := db.Clauses(clause.OnConflict{ + DoUpdates: clause.AssignmentColumns([]string{"emoji"}), + }).Create(&models.EmojiReaction{UserAnonIp: c.Request.RemoteAddr, Emoji: reaction, PostId: reactedPostId}) + if result.Error != nil { + c.HTML(http.StatusOK, "emoji_form.tmpl", gin.H{"error": "error writing to database"}) + return + } + + // get emoji counts for each emoji + emojiCounter, err := CountEmojis(reactedPostId) + if err != nil { + c.HTML(http.StatusOK, "emoji_form.tmpl", gin.H{"error": "error getting the emoji counts"}) + return + } + + // Return the html with the updated emoji counter + c.HTML(http.StatusOK, "emoji_form.tmpl", gin.H{"results": emojiCounter}) +} + +// Get the emoji counts foe a given post id +func CountEmojis(postId string) (map[string]int, error) { + postReactions := []models.PostReaction{} + db := DB.GetDB() + + // Get the emoji counts for each emoji + rows := db.Model(&models.EmojiReaction{}).Where("post_id = ?", postId).Select([]string{"emoji", "COUNT(*) as total_count"}).Group("emoji").Find(&postReactions) + if rows.Error != nil { + return nil, errors.New("couldn't get emoji counts") + } + + // Transform rows to map + result := make(map[string]int) + for _, reaction := range postReactions { + result[reaction.Emoji] = reaction.TotalCount + } + + return result, nil +} diff --git a/main.go b/main.go index b49dd5f..03e5be1 100644 --- a/main.go +++ b/main.go @@ -1,55 +1,18 @@ package main import ( - "errors" "log" - "net/http" - "os" "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" - "github.com/glebarez/sqlite" - "github.com/joho/godotenv" - "gorm.io/gorm" - "gorm.io/gorm/clause" + "github.com/joho/godotenv" + + DB "log101-blog-services/db" + "log101-blog-services/handlers" "log101-blog-services/middleware" ) -var db *gorm.DB - -// Emoji counts for each post are retrieved in this format -type PostReaction struct { - Emoji string - TotalCount int -} - -// Gorm model -type EmojiReaction struct { - UserAnonIp string `gorm:"index:idx_anon,unqiue"` - PostId string `gorm:"index:idx_anon,unique"` - Emoji string -} - -// Get the emoji counts foe a given post id -func countEmojis(postId string) (map[string]int, error) { - postReactions := []PostReaction{} - - // Get the emoji counts for each emoji - rows := db.Model(&EmojiReaction{}).Where("post_id = ?", postId).Select([]string{"emoji", "COUNT(*) as total_count"}).Group("emoji").Find(&postReactions) - if rows.Error != nil { - return nil, errors.New("couldn't get emoji counts") - } - - // Transform rows to map - result := make(map[string]int) - for _, reaction := range postReactions { - result[reaction.Emoji] = reaction.TotalCount - } - - return result, nil -} - func main() { // Load environment variables err := godotenv.Load() @@ -57,13 +20,8 @@ func main() { log.Println("Error loading .env file") } - dbPath := os.Getenv("DB_PATH") - db, err = gorm.Open(sqlite.Open(dbPath), &gorm.Config{}) - if err != nil { - panic("failed to connect database") - } - - db.AutoMigrate(&EmojiReaction{}) + // initialize db + DB.InitDB() r := gin.Default() r.LoadHTMLGlob("templates/*") @@ -77,6 +35,7 @@ func main() { corsConfig.AllowOrigins = append(corsConfig.AllowOrigins, "http://localhost:4321") } + // these are required for htmx to work corsConfig.AllowHeaders = []string{"hx-current-url", "hx-request", "hx-target", "hx-trigger"} // Middlewares @@ -86,54 +45,12 @@ func main() { blogForm := r.Group("/blog/api/") { // Get the emoji form, you must provide postId query parameter - blogForm.GET("/forms/emoji", func(c *gin.Context) { - postId := c.Query("postId") - - // get emoji counts for each emoji - emojiCounter, err := countEmojis(postId) - if err != nil { - c.HTML(http.StatusOK, "emoji_form.tmpl", gin.H{"error": "error getting the emoji counts"}) - return - } - - // Return the html template - c.HTML(http.StatusOK, "emoji_form.tmpl", gin.H{ - "results": emojiCounter, - }) - }) + blogForm.GET("/forms/emoji", handlers.GetEmojiForm) // Update the user's reaction to post, this handler will // add a new entry to the database with anonymized ip // updates if user reacted before - blogForm.POST("/forms/emoji/post", func(c *gin.Context) { - reactedPostId := c.PostForm("postId") - reaction := c.PostForm("emojiInput") - - // Check if parameters are missing - if reactedPostId == "" || reaction == "" { - c.HTML(http.StatusOK, "emoji_form.tmpl", gin.H{"error": "missing parameters"}) - return - } - - // Add the new emoji reaction to the database - result := db.Clauses(clause.OnConflict{ - DoUpdates: clause.AssignmentColumns([]string{"emoji"}), - }).Create(&EmojiReaction{UserAnonIp: c.Request.RemoteAddr, Emoji: reaction, PostId: reactedPostId}) - if result.Error != nil { - c.HTML(http.StatusOK, "emoji_form.tmpl", gin.H{"error": "error writing to database"}) - return - } - - // get emoji counts for each emoji - emojiCounter, err := countEmojis(reactedPostId) - if err != nil { - c.HTML(http.StatusOK, "emoji_form.tmpl", gin.H{"error": "error getting the emoji counts"}) - return - } - - // Return the html with the updated emoji counter - c.HTML(http.StatusOK, "emoji_form.tmpl", gin.H{"results": emojiCounter}) - }) + blogForm.POST("/forms/emoji/post", handlers.PostEmojiForm) } r.Run(":8000") diff --git a/models/Emoji.go b/models/Emoji.go new file mode 100644 index 0000000..2ff6c2d --- /dev/null +++ b/models/Emoji.go @@ -0,0 +1,14 @@ +package models + +// Emoji counts for each post are retrieved in this format +type PostReaction struct { + Emoji string + TotalCount int +} + +// Gorm model +type EmojiReaction struct { + UserAnonIp string `gorm:"index:idx_anon,unqiue"` + PostId string `gorm:"index:idx_anon,unique"` + Emoji string +}