From 860115cbcc41b94e4d8cc38658e49e5a2ad6cbb9 Mon Sep 17 00:00:00 2001 From: Rawley Date: Wed, 9 Mar 2022 04:20:27 +0000 Subject: [PATCH] WIP Branch completed: Overhaul of site logic, removed comments, repositories --- .gitignore | 2 + controllers/admin.go | 204 ++++++++++++------ controllers/admin_test.go | 18 ++ controllers/blog.go | 163 +++----------- controllers/blog_test.go | 17 ++ controllers/comment_test.go | 1 - go.mod | 1 + go.sum | 2 + models/administrator.go | 11 + models/blog_post.go | 10 +- repos/admin_repo.go | 93 ++++++++ repos/admin_repo_test.go | 67 ++++++ repos/blog_repo.go | 106 +++++++++ repos/blog_repo_test.go | 50 +++++ repos/comment_repo.go | 80 ------- repos/comment_repo_test.go | 76 ------- static/index.css | 14 +- templates/admin_success_redirect.tmpl | 16 +- templates/blog.tmpl | 14 +- templates/blog_post.tmpl | 32 +-- templates/comment_post.tmpl | 22 -- templates/comment_post_failed.tmpl | 25 --- templates/create_post.tmpl | 22 +- templates/forbidden.tmpl | 12 +- templates/index.tmpl | 2 +- templates/internal_server_error.tmpl | 15 +- templates/login.tmpl | 2 +- templates/not_found.tmpl | 16 +- templates/post_success.tmpl | 23 +- ...nt_post_spam.tmpl => session_revoked.tmpl} | 15 +- utils/admin_hash.go | 24 --- utils/api_key.go | 5 +- utils/captcha.go | 17 -- utils/{migrations.go => db_creator.go} | 15 +- utils/dsn.go | 1 + models/comment.go => utils/initialize_db.go | 17 +- utils/serve_page.go | 30 ++- utils/spam_checker.go | 6 - utils/timed_clear.go | 19 -- utils/timed_clear_map.go | 29 +++ 40 files changed, 730 insertions(+), 564 deletions(-) create mode 100644 controllers/admin_test.go delete mode 100644 controllers/comment_test.go create mode 100644 models/administrator.go create mode 100644 repos/admin_repo.go create mode 100644 repos/admin_repo_test.go delete mode 100644 repos/comment_repo.go delete mode 100644 repos/comment_repo_test.go delete mode 100644 templates/comment_post.tmpl delete mode 100644 templates/comment_post_failed.tmpl rename templates/{comment_post_spam.tmpl => session_revoked.tmpl} (59%) delete mode 100644 utils/admin_hash.go delete mode 100644 utils/captcha.go rename utils/{migrations.go => db_creator.go} (77%) rename models/comment.go => utils/initialize_db.go (66%) delete mode 100644 utils/spam_checker.go delete mode 100644 utils/timed_clear.go create mode 100644 utils/timed_clear_map.go diff --git a/.gitignore b/.gitignore index 21465f2..3d9342a 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ dsn *.pem api_key.pem admin_hash.pem +create_admin_form.html +error.log diff --git a/controllers/admin.go b/controllers/admin.go index fbabce7..9879084 100644 --- a/controllers/admin.go +++ b/controllers/admin.go @@ -1,101 +1,179 @@ package controllers +/* +Copyright (C) 2022 Rawley Fowler + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see .Rawley Fowler, 2022 +*/ + import ( - "crypto/sha256" - "fmt" "net/http" + "time" "github.com/gin-gonic/gin" "gitlab.com/rawleyifowler/site-rework/models" + "gitlab.com/rawleyifowler/site-rework/repos" "gitlab.com/rawleyifowler/site-rework/utils" ) var ( - key string = utils.LoadApiKey("api_key.pem") - admin_hash [32]byte = utils.LoadAdminHash("admin_hash.pem") - att map[string]uint32 = make(map[string]uint32) + api_key string = utils.LoadApiKey("api_key.pem") ) -// Ensure is logged in -func AdminOnly() gin.HandlerFunc { - return func(c *gin.Context) { - var status uint = http.StatusOK - k, err := c.Cookie("admin_key") - if err != nil { - status = http.StatusForbidden - } - fmt.Println(k) - if k == key && status == http.StatusOK { - c.Next() - } else { - c.HTML(int(status), "forbidden.tmpl", models.Page{Title: " | forbidden"}) - c.Abort() - return - } - } +type AdminController struct { + Repository *repos.AdminRepo + BlogRepository *repos.BlogRepo + ActiveLogins map[string]int64 + LoginAttemptsByIp map[string]uint +} + +func NewAdminController(r *repos.AdminRepo) *AdminController { + var a AdminController + a.Repository = r + a.ActiveLogins = map[string]int64{} + a.LoginAttemptsByIp = map[string]uint{} + go utils.TimeClearMap(a.ActiveLogins, 850) + return &a } func RegisterAdminGroup(r *gin.RouterGroup) { + a := NewAdminController(repos.NewAdminRepo("dsn")) + r.Use(a.UserBannedMiddleware) r.GET("/", utils.ServePage("login.tmpl")) - r.POST("/login", HandleLogin) - r.GET("/post", AdminOnly(), utils.ServePage("create_post.tmpl")) - r.POST("/post", AdminOnly(), CreatePost) + r.POST("/login", a.AdminLogin) + r.GET("/post", a.AuthMiddleware, utils.ServePage("create_post.tmpl")) + r.POST("/post", a.AuthMiddleware, a.CRUDPost) + r.POST("/user", a.CreateAdmin) } -func HandleLogin(c *gin.Context) { - if att[c.ClientIP()] > 5 { - c.HTML(http.StatusForbidden, "forbidden.tmpl", models.Page{Title: " | forbidden"}) +func (a *AdminController) AuthMiddleware(c *gin.Context) { + cookie, err := c.Request.Cookie("token") + if err != nil { + c.HTML(http.StatusForbidden, "forbidden.tmpl", &gin.H{}) + c.Abort() return } + if a.ActiveLogins[cookie.Value] == 0 { + c.HTML(http.StatusForbidden, "session_revoked.tmpl", &gin.H{}) + c.Abort() + return + } + c.Next() +} + +func (a *AdminController) UserBannedMiddleware(c *gin.Context) { + if a.IpIsBanned(c.ClientIP()) { + c.HTML(http.StatusForbidden, "forbidden.tmpl", &gin.H{}) + c.Abort() + return + } + c.Next() +} + +func (a *AdminController) IpIsBanned(ip string) bool { + return a.LoginAttemptsByIp[ip] >= 3 +} + +func (a *AdminController) AdminLogin(c *gin.Context) { err := c.Request.ParseForm() if err != nil { - c.HTML(http.StatusForbidden, "forbidden.tmpl", models.Page{Title: " | forbidden"}) + c.HTML(http.StatusForbidden, "forbidden.tmpl", &gin.H{}) + a.LoginAttemptsByIp[c.ClientIP()]++ return } f := c.Request.Form - for _, v := range []string{"username", "password"} { - if !f.Has(v) { - c.AbortWithStatus(http.StatusNotAcceptable) - return - } + if !f.Has("username") || + !f.Has("password") { + c.HTML(http.StatusNotAcceptable, "admin_success_redirect.tmpl", false) + a.LoginAttemptsByIp[c.ClientIP()]++ + return } - str := f.Get("username") + f.Get("password") - s := sha256.Sum256([]byte(str)) - var success bool - if s == admin_hash { - c.SetCookie("admin_key", key, 3600, "/", "rawley.xyz", false, true) - success = true - } else { - att[c.ClientIP()]++ - success = false + ad, err := a.Repository.GetAdminByCredentials(f.Get("username"), f.Get("password")) + if err != nil || + ad == nil { + c.HTML(http.StatusNotAcceptable, "admin_success_redirect.tmpl", false) + a.LoginAttemptsByIp[c.ClientIP()]++ + return } - c.HTML(http.StatusOK, "admin_success_redirect.tmpl", struct { - Success bool - Title string - }{Success: success, Title: "login"}) + a.ActiveLogins[ad.Token] = time.Now().UnixMilli() + (3600 * 1000) + c.SetCookie("token", ad.Token, 3600*1000, "/", "rawley.xyz", true, true) + c.HTML(http.StatusAccepted, "admin_success_redirect.tmpl", true) } -func CreatePost(c *gin.Context) { +func (a *AdminController) CRUDPost(c *gin.Context) { err := c.Request.ParseForm() if err != nil { - c.AbortWithStatus(http.StatusBadRequest) + c.HTML(http.StatusNotAcceptable, "post_success.tmpl", false) return } f := c.Request.Form - for _, v := range []string{"title", "content", "url"} { - if !f.Has(v) { - c.AbortWithStatus(http.StatusNotAcceptable) - return - } + if f.Get("url") == "" || + f.Get("op") == "" { + c.HTML(http.StatusNotAcceptable, "post_success.tmpl", false) + return } - b := AddBlogPost(&models.BlogPost{ - Title: f.Get("title"), - Content: f.Get("content"), + tempBlogRepo := repos.NewBlogRepo("dsn") + tempPost := &models.BlogPost{ Url: f.Get("url"), - }) - c.HTML(http.StatusOK, "post_success.tmpl", struct { - Url string - Success bool - Title string - }{Url: f.Get("url"), Success: b, Title: " | post attempt"}) + Content: f.Get("content"), + Title: f.Get("title"), + } + switch f.Get("op") { + case "create": + err = tempBlogRepo.CreateBlogPost(tempPost) + break + case "update": + err = tempBlogRepo.UpdateExistingPost(tempPost) + break + case "delete": + err = tempBlogRepo.DeleteExistingPost(tempPost) + break + default: + c.HTML(http.StatusNotAcceptable, "post_success.tmpl", false) + return + } + if err != nil { + c.HTML(http.StatusNotAcceptable, "post_success.tmpl", false) + return + } + c.HTML(http.StatusAccepted, "post_success.tmpl", true) +} + +func (a *AdminController) CreateAdmin(c *gin.Context) { + err := c.Request.ParseForm() + if err != nil { + c.HTML(http.StatusNotAcceptable, "forbidden.tmpl", &gin.H{}) + return + } + var at models.Administrator + f := c.Request.Form + if !f.Has("password") || + !f.Has("username") || + !f.Has("api_key") { + c.HTML(http.StatusBadRequest, "forbidden.tmpl", &gin.H{}) + return + } + at.Username = f.Get("username") + at.Password = f.Get("password") + if f.Get("api_key") != api_key { + c.HTML(http.StatusForbidden, "forbidden.tmpl", &gin.H{}) + return + } + err = a.Repository.CreateAdmin(&at) + if err != nil { + c.HTML(http.StatusForbidden, "forbidden.tmpl", &gin.H{}) + return + } + c.HTML(http.StatusAccepted, "post_success.tmpl", true) } diff --git a/controllers/admin_test.go b/controllers/admin_test.go new file mode 100644 index 0000000..b39cffe --- /dev/null +++ b/controllers/admin_test.go @@ -0,0 +1,18 @@ +package controllers + +/* +Copyright (C) 2022 Rawley Fowler + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see .Rawley Fowler, 2022 +*/ diff --git a/controllers/blog.go b/controllers/blog.go index ef87381..a5ed30f 100644 --- a/controllers/blog.go +++ b/controllers/blog.go @@ -17,158 +17,47 @@ along with this program. If not, see .Rawley Fow import ( "net/http" - "strconv" "github.com/gin-gonic/gin" - "gitlab.com/rawleyifowler/site-rework/models" - "gitlab.com/rawleyifowler/site-rework/utils" - "gorm.io/driver/mysql" - "gorm.io/gorm" + "gitlab.com/rawleyifowler/site-rework/repos" ) type CommentDto struct { Url string } -var ( - db *gorm.DB - recentPosters map[string]uint - captchaVals [2]int -) +type BlogController struct { + Repository *repos.BlogRepo +} + +func NewBlogController(r *repos.BlogRepo) *BlogController { + c := new(BlogController) + c.Repository = r + return c +} func RegisterBlogGroup(r *gin.RouterGroup) { - // Initialize recent posters cache - recentPosters = make(map[string]uint) - // Load dsn and initialize database - dsn := utils.LoadDSN("dsn") - var err error - db, err = gorm.Open(mysql.Open(dsn), &gorm.Config{}) + c := NewBlogController(repos.NewBlogRepo("dsn")) + r.GET("/", c.IndexBlogPage) + r.GET("/post/:url", c.IndividualBlogPage) +} + +func (bc *BlogController) IndexBlogPage(c *gin.Context) { + posts, err := bc.Repository.GetAllBlogPosts() if err != nil { - panic("Database connection failed...") - } - utils.PerformMigrations(db) - r.GET("/", RenderBlogPage) - r.GET("/post/:url", RenderIndividualBlogPost) - r.POST("/post") - r.POST("/post/comment", CreateComment) - // Clear the commenters cache every 15 minutes - go utils.TimedClearMap(&recentPosters, 320000*5) - go utils.UpdateCaptcha(&captchaVals) -} - -func RenderBlogPage(c *gin.Context) { - posts := GetAllBlogPosts() - if posts == nil { - c.HTML(http.StatusInternalServerError, "internal_server_error.tmpl", models.Page{Title: " | 500"}) - } else { - c.HTML(http.StatusOK, "blog.tmpl", struct { - Posts []models.BlogPost - Title string - }{Posts: *posts, Title: " | blog"}) - } -} - -func RenderIndividualBlogPost(c *gin.Context) { - bp := GetBlogPostById(c.Param("url")) - if bp == nil { - c.HTML(http.StatusNotFound, "not_found.tmpl", models.Page{Title: "404"}) - return - } else { - // Blog posts handle the title themselves - c.HTML(http.StatusOK, "blog_post.tmpl", struct { - Post *models.BlogPost - Captcha [2]int - }{Post: bp, Captcha: captchaVals}) - } -} - -func CreateComment(c *gin.Context) { - if c.Request.ParseForm() != nil { - c.AbortWithStatus(http.StatusNotAcceptable) + c.HTML(http.StatusInternalServerError, "internal_server_error.tmpl", &gin.H{}) return } - a := []string{c.Request.Form.Get("author"), - c.Request.Form.Get("content"), - c.Request.Form.Get("url"), - c.Request.Form.Get("captcha")} - // If the length of the comment is great enough, and the comment already exists we can safely assume it is spam. - if len(a[1]) > 20 && len(*GetCommentsByContent(a[1], a[2])) > 0 { - c.HTML(http.StatusNotAcceptable, "comment_post_failed.tmpl", CommentDto{Url: a[2]}) - return - } - i, err := strconv.ParseInt(a[3], 10, 32) - if err != nil || int(i) != (captchaVals[0]+captchaVals[1]) { - c.HTML(http.StatusNotAcceptable, "comment_post_failed.tmpl", CommentDto{Url: a[2]}) - return - } - if GetNumberOfRecentPosts(c) >= 2 { - c.HTML(http.StatusNotAcceptable, "comment_post_spam.tmpl", CommentDto{Url: a[2]}) - return - } - for _, v := range a { - if len(v) == 0 { - c.HTML(http.StatusNotAcceptable, "comment_post_failed.tmpl", CommentDto{Url: a[2]}) - return - } - } - comment := models.Comment{ - Author: a[0], - Content: a[1], - AssociatedPost: a[2], - } - db.Create(&comment) - // Something was wrong with c.ClientIP() with trusted?? Weird. - recentPosters[c.ClientIP()]++ - // Pass the associated post to the template to add to the href - c.HTML(http.StatusOK, "comment_post.tmpl", CommentDto{Url: comment.AssociatedPost}) + c.HTML(http.StatusOK, "blog.tmpl", *posts) } -func GetNumberOfRecentPosts(c *gin.Context) uint { - return recentPosters[c.ClientIP()] -} - -func AddBlogPost(bp *models.BlogPost) bool { - err := db.Create(bp).Error - return err == nil -} - -func DeleteBlogPostById(id string) bool { - err := db.Model(&models.BlogPost{}).Delete(&models.BlogPost{Url: id}).Error - return err == nil -} - -func GetAllBlogPosts() *[]models.BlogPost { - var posts []models.BlogPost - // Select title, date, and url fields from the blog post records an store them in posts. - // This is so we don't grab the entire blog post when we render them on the overview page. Saves a couple ms. - err := db.Model(&models.BlogPost{}).Select("title, date, url").Order("date DESC").Scan(&posts).Error +func (bc *BlogController) IndividualBlogPage(c *gin.Context) { + post, err := bc.Repository.GetBlogByUrl(c.Param("url")) if err != nil { - return nil + c.HTML(http.StatusNotFound, "not_found.tmpl", &gin.H{}) + return } - return &posts -} - -func GetBlogPostById(id string) *models.BlogPost { - var post models.BlogPost = models.BlogPost{} - err := db.Model(&post).Where(&models.BlogPost{Url: id}).Find(&post).Error - if err != nil || post.Equals(&models.BlogPost{}) { - return nil - } - // TODO: Figure out gorm joins! - // gorm joins are not working at all, just going to do another query to make it work for now. - err = db.Model(&models.Comment{}).Where(&models.Comment{AssociatedPost: id}).Find(&post.Comments).Error - if err != nil { - return nil - } - return &post -} - -func GetCommentsByContent(content string, url string) *[]models.Comment { - // This is used to make sure people don't paste the same thing over and over. - var comments []models.Comment - err := db.Model(&comments).Where("content like ? and associated_post like ?", "%"+content[1:]+"%", url).Scan(&comments).Error - if err != nil { - return &[]models.Comment{} - } - return &comments + // TODO: Re implement captcha here. It is already included on the blog post model, though not in the database. + // The idea is to generate a captcha for each post, and index them by title. The async service should then update each captcha every hour. + c.HTML(http.StatusOK, "blog_post.tmpl", post) } diff --git a/controllers/blog_test.go b/controllers/blog_test.go index 2d32936..b39cffe 100644 --- a/controllers/blog_test.go +++ b/controllers/blog_test.go @@ -1 +1,18 @@ package controllers + +/* +Copyright (C) 2022 Rawley Fowler + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see .Rawley Fowler, 2022 +*/ diff --git a/controllers/comment_test.go b/controllers/comment_test.go deleted file mode 100644 index 2d32936..0000000 --- a/controllers/comment_test.go +++ /dev/null @@ -1 +0,0 @@ -package controllers diff --git a/go.mod b/go.mod index aced78f..94b2dd1 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.15 require ( github.com/gin-gonic/gin v1.7.7 + github.com/google/uuid v1.3.0 // indirect gorm.io/driver/mysql v1.2.3 gorm.io/gorm v1.22.5 ) diff --git a/go.sum b/go.sum index 6946f07..ab7375a 100644 --- a/go.sum +++ b/go.sum @@ -16,6 +16,8 @@ github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.3/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= diff --git a/models/administrator.go b/models/administrator.go new file mode 100644 index 0000000..ceb555f --- /dev/null +++ b/models/administrator.go @@ -0,0 +1,11 @@ +package models + +import "gorm.io/gorm" + +type Administrator struct { + gorm.Model + Id uint `json:"id" gorm:"primaryKey AUTO_INCREMENT"` + Token string `json:"token" gorm:"unique"` + Username string `gorm:"unique"` + Password string +} diff --git a/models/blog_post.go b/models/blog_post.go index f270508..af44b6d 100644 --- a/models/blog_post.go +++ b/models/blog_post.go @@ -22,11 +22,11 @@ import ( type BlogPost struct { gorm.Model - Url string `json:"url" gorm:"primaryKey"` - Title string `json:"title" gorm:"unique"` - Date string `json:"date" gorm:"default:NOW()"` - Content string `json:"content"` - Comments []Comment `gorm:"foreignKey:AssociatedPost;references:Url;type:varchar(191)"` + Url string `json:"url" gorm:"primaryKey"` + Title string `json:"title" gorm:"unique"` + Date string `json:"date" gorm:"default:NOW()"` + Content string `json:"content"` + Captcha [2]int `gorm:"-"` } func (b BlogPost) Equals(m *BlogPost) bool { diff --git a/repos/admin_repo.go b/repos/admin_repo.go new file mode 100644 index 0000000..6b333c9 --- /dev/null +++ b/repos/admin_repo.go @@ -0,0 +1,93 @@ +package repos + +/* +Copyright (C) 2022 Rawley Fowler + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see .Rawley Fowler, 2022 +*/ + +import ( + "crypto/sha256" + "errors" + "fmt" + + "github.com/google/uuid" + "gitlab.com/rawleyifowler/site-rework/models" + "gitlab.com/rawleyifowler/site-rework/utils" + "gorm.io/gorm" +) + +type AdminRepo struct { + DB *gorm.DB +} + +func NewAdminRepo(dsnPath string) *AdminRepo { + c := AdminRepo{} + var err error + c.DB, err = utils.CreateDatabase(utils.LoadDSN(dsnPath)) + if err != nil { + panic(err) + } + c.DB.AutoMigrate(&models.Administrator{}) + return &c +} + +func (ar *AdminRepo) CreateAdmin(a *models.Administrator) error { + if a.Password == "" || + a.Username == "" { + return errors.New("Administrators must have a valid username and password") + } + if len(a.Password) < 8 { + return errors.New("Passwords must have a length of atleast 8") + } + a.Token = uuid.New().String() + a.Password = fmt.Sprintf("%x", string(sha256.New().Sum([]byte(a.Password)))) + err := ar.DB.Table("administrators").Create(a).Error + if err != nil { + return err + } + return nil +} + +func (ar *AdminRepo) GetAdminByToken(k string) (*models.Administrator, error) { + if k == "" { + return nil, errors.New("Session key cannot be blank") + } + if len(k) != 36 { + return nil, errors.New("Valid sessions keys are always 36 byte utf-8 strings") + } + var a *models.Administrator + err := ar.DB.Table("administrators").Where(&models.Administrator{Token: k}).Scan(a).Error + if err != nil { + return nil, err + } + return a, nil +} + +func (ar *AdminRepo) GetAdminByCredentials(uname, pword string) (*models.Administrator, error) { + if uname == "" || + pword == "" { + return nil, errors.New("Username or password mustn't be empty") + } + if len(pword) < 8 { + return nil, errors.New("Password length must be no less than 8") + } + pword = fmt.Sprintf("%x", string(sha256.New().Sum([]byte(pword)))) + var a models.Administrator + err := ar.DB.Table("administrators").Where(&models.Administrator{Username: uname, Password: pword}).First(&a).Error + if err != nil { + return nil, err + } + return &a, nil +} diff --git a/repos/admin_repo_test.go b/repos/admin_repo_test.go new file mode 100644 index 0000000..0d65e9a --- /dev/null +++ b/repos/admin_repo_test.go @@ -0,0 +1,67 @@ +package repos + +/* +Copyright (C) 2022 Rawley Fowler + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see .Rawley Fowler, 2022 +*/ + +import ( + "testing" + + "gitlab.com/rawleyifowler/site-rework/models" +) + +var test_admin_repo = NewAdminRepo("dsn") + +func TestCreateAdmin(t *testing.T) { + err := test_admin_repo.CreateAdmin(&models.Administrator{}) + if err == nil { + t.Fatalf("Expected: You cannot create an admin with empty credentials") + } + err = test_admin_repo.CreateAdmin(&models.Administrator{Username: "", Password: "12345678"}) + if err == nil { + t.Fatalf("Expected: You cannot create an admin with empty credentials") + } + err = test_admin_repo.CreateAdmin(&models.Administrator{Username: "12345678", Password: ""}) + if err == nil { + t.Fatalf("Expected: You cannot create an admin with empty credentials") + } + err = test_admin_repo.CreateAdmin(&models.Administrator{Username: "valid_username", Password: "invalid"}) + if err == nil { + t.Fatalf("Expected: You cannot create an admin with a password less than 8 characters") + } +} + +func TestGetAdminByToken(t *testing.T) { + _, err := test_admin_repo.GetAdminByToken("") + if err == nil { + t.Fatalf("Expected: Error should be thrown when an empty session key is provided") + } +} + +func TestGetAdminByCredentials(t *testing.T) { + _, err := test_admin_repo.GetAdminByCredentials("", "") + if err == nil { + t.Fatalf("Expected: Error should be thrown if empty credentials are given") + } + _, err = test_admin_repo.GetAdminByCredentials("1234", "") + if err == nil { + t.Fatalf("Expected: Error should be thrown if empty credentials are given") + } + _, err = test_admin_repo.GetAdminByCredentials("", "123456789") + if err == nil { + t.Fatalf("Expected: Error should be thrown if empty credentials are given") + } +} diff --git a/repos/blog_repo.go b/repos/blog_repo.go index 2b0d3a9..986c144 100644 --- a/repos/blog_repo.go +++ b/repos/blog_repo.go @@ -1 +1,107 @@ package repos + +/* +Copyright (C) 2022 Rawley Fowler + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see .Rawley Fowler, 2022 +*/ + +import ( + "errors" + + "gitlab.com/rawleyifowler/site-rework/models" + "gitlab.com/rawleyifowler/site-rework/utils" + "gorm.io/gorm" +) + +type BlogRepo struct { + DB *gorm.DB + initialized bool +} + +func NewBlogRepo(dsnPath string) *BlogRepo { + b := BlogRepo{} + var err error + b.DB, err = utils.CreateDatabase(utils.LoadDSN(dsnPath)) + if err != nil { + panic(err) + } + b.DB.AutoMigrate(&models.BlogPost{}) + return &b +} + +func (b *BlogRepo) CreateBlogPost(p *models.BlogPost) error { + if p == nil { + return errors.New("Blog post must not be nil") + } + if p.Title == "" || + p.Content == "" || + p.Url == "" { + return errors.New("Blog posts must be fully formed") + } + err := b.DB.Table("blog_posts").Create(p).Error + if err != nil { + return err + } + return nil +} + +func (b *BlogRepo) GetBlogByUrl(url string) (*models.BlogPost, error) { + if url == "" { + return nil, errors.New("Cannot query by empty value") + } + var post models.BlogPost + err := b.DB.Table("blog_posts").Where(&models.BlogPost{Url: url}).Scan(&post).Error + if err != nil { + return nil, err + } + return &post, nil +} + +func (b *BlogRepo) GetAllBlogPosts() (*[]models.BlogPost, error) { + posts := new([]models.BlogPost) + err := b.DB.Table("blog_posts").Scan(posts).Error + if err != nil { + return nil, errors.New("Could not load all posts from database") + } + return posts, nil +} + +func (b *BlogRepo) UpdateExistingPost(p *models.BlogPost) error { + if p == nil { + return errors.New("Cannot update with nil value for post") + } + if p.Url == "" { + return errors.New("Url must exist to update a blog post record") + } + err := b.DB.Table("blog_posts").Save(p).Error + if err != nil { + return err + } + return nil +} + +func (b *BlogRepo) DeleteExistingPost(p *models.BlogPost) error { + if p == nil { + return errors.New("Cannot delete with nil value for post") + } + if p.Url == "" { + return errors.New("Url must exist to delete a blog post record") + } + err := b.DB.Table("blog_posts").Delete(p).Error + if err != nil { + return err + } + return nil +} diff --git a/repos/blog_repo_test.go b/repos/blog_repo_test.go index e69de29..a71847b 100644 --- a/repos/blog_repo_test.go +++ b/repos/blog_repo_test.go @@ -0,0 +1,50 @@ +package repos + +/* +Copyright (C) 2022 Rawley Fowler + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see .Rawley Fowler, 2022 +*/ + +import ( + "testing" + + "gitlab.com/rawleyifowler/site-rework/models" +) + +var test_blog_repo = NewBlogRepo("dsn") + +func TestGetBlogByUrl(t *testing.T) { + _, err := test_blog_repo.GetBlogByUrl("") + if err == nil { + t.Fatalf("Expected: Empty values of url for get blog by url should return an error") + } +} + +func TestCreateBlogPost(t *testing.T) { + err := test_blog_repo.CreateBlogPost(&models.BlogPost{}) + if err == nil { + t.Fatalf("Expected: Blog posts must be fully formed") + } + + err = test_blog_repo.CreateBlogPost(&models.BlogPost{Title: "abc"}) + if err == nil { + t.Fatalf("Expected: Blog posts must be fully formed, title, content, url") + } + + err = test_blog_repo.CreateBlogPost(nil) + if err == nil { + t.Fatalf("Expected: Cannot create blog posts from nil values") + } +} diff --git a/repos/comment_repo.go b/repos/comment_repo.go deleted file mode 100644 index b493439..0000000 --- a/repos/comment_repo.go +++ /dev/null @@ -1,80 +0,0 @@ -package repos - -import ( - "errors" - - "gitlab.com/rawleyifowler/site-rework/models" - "gitlab.com/rawleyifowler/site-rework/utils" - "gorm.io/driver/mysql" - "gorm.io/gorm" -) - -type CommentRepo struct { - DB *gorm.DB - Initialized bool -} - -func NewCommentRepo(dsnPath string) (*CommentRepo, error) { - c := CommentRepo{} - err := c.Initialize(dsnPath) - if err != nil { - return &c, err - } - return &c, nil -} - -func (c *CommentRepo) Initialize(path string) error { - dsn := utils.LoadDSN(path) - var err error - c.DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{}) - if err != nil { - return err - } - c.Initialized = true - return nil -} - -func (c *CommentRepo) GetCommentById(id uint) (*models.Comment, error) { - if id == 0 { - return nil, nil - } - var comm models.Comment - c.DB.Where(&models.Comment{Id: id}).First(&comm) - return &comm, nil -} - -func (c *CommentRepo) CreateComment(comm *models.Comment) error { - if comm == nil { - return errors.New("nil comment") - } - err := c.DB.Create(comm).Error - if err != nil { - return err - } - // nil return means no errors, yay! - return nil -} - -func (c *CommentRepo) GetCommentsByAssociatedPost(title string) (*[]models.Comment, error) { - if len(title) < 1 { - return nil, errors.New("empty associated post title") - } - var comms []models.Comment - err := c.DB.Where(&models.Comment{AssociatedPost: title}).Scan(&comms).Error - if err != nil { - return nil, err - } - return &comms, nil -} - -func (c *CommentRepo) GetCommentsByAuthor(auth string) (*[]models.Comment, error) { - if len(auth) < 1 { - return nil, errors.New("empty author name") - } - var comms []models.Comment - err := c.DB.Where(&models.Comment{Author: auth}).Scan(&comms).Error - if err != nil { - return nil, err - } - return &comms, nil -} diff --git a/repos/comment_repo_test.go b/repos/comment_repo_test.go deleted file mode 100644 index 07752c1..0000000 --- a/repos/comment_repo_test.go +++ /dev/null @@ -1,76 +0,0 @@ -package repos - -import ( - "testing" - - "gitlab.com/rawleyifowler/site-rework/models" -) - -var cr *CommentRepo - -func TestInitialize(t *testing.T) { - var err error - cr, err = NewCommentRepo("dsn") - if err != nil { - - } -} - -func TestGetCommentById(t *testing.T) { - const ID uint = 1 - const AUTHOR string = "Whoops" - const CONTENT string = "Uhhhhhhhh that wasn't supposed to happen..." - // This comment will exist forever on the database, it's part of the test blog post. - x := models.Comment{Id: ID, Author: AUTHOR, Content: CONTENT} - y, err := cr.GetCommentById(ID) - if err != nil { - t.Fatalf("Expected: Comment with id: %d should be found without errors.", ID) - } - if x.Id != y.Id || - x.Author != y.Author || - x.Content != y.Content { - t.Fatalf("Expected(left expected, right result):\nid -> %d == %d\nauthor -> %s == %s\ncontent -> %s == %s", - ID, y.Id, AUTHOR, y.Author, CONTENT, y.Content) - } - _, err := cr.GetCommentById(0) - if err == nil { - t.Fatalf("Expected: Values of less than or equal to 0 are considered illegal values for comment id") - } -} - -func TestGetCommentsByAssociatedPost(t *testing.T) { - _, err := cr.GetCommentsByAssociatedPost("") - if err == nil { - t.Fatalf("Expected: Empty values are considered illegal for associated post") - } - // Wild associated post should return empty array - post, err := cr.GetCommentsByAssociatedPost("kjdoJDOWIJWKIJALSKAJSAKIJDOIAj") - if len(*post) > 0 { - t.Fatalf("Expected: Should return empty array") - } - if err != nil { - t.Fatalf("Expected: Non existant post should not throw error, just empty array") - } -} - -func TestGetCommentsByAuthor(t *testing.T) { - _, err := cr.GetCommentsByAuthor("") - if err == nil { - t.Fatalf("Expected: An empty author value should throw an error") - } -} - -func TestCreateComment(t *testing.T) { - err := cr.CreateComment(nil) - if err == nil { - t.Fatalf("Expected: Nil values should result in errors when trying to create comments") - } - err = cr.CreateComment(&models.Comment{}) - if err == nil { - t.Fatalf("Expected: Empty or incomplete models should result in errors.") - } - err = cr.CreateComment(&models.Comment{Author: "Chuck"}) - if err == nil { - t.Fatalf("Expected: Empty or incomplete models should result in errors.") - } -} diff --git a/static/index.css b/static/index.css index d8a7d16..157eb2c 100644 --- a/static/index.css +++ b/static/index.css @@ -164,13 +164,9 @@ a:hover:after { content: "' "; } -.comment { - margin-top: 3px; - margin-bottom: 3px; - border: 2px solid #454545; -} - -.comment-body { - background-color: rgba(222, 222, 222, 0.5); - word-wrap: break-word; +@media (prefers-color-scheme: dark) { + body { + color: #fafafa; + background-color: #242124; + } } diff --git a/templates/admin_success_redirect.tmpl b/templates/admin_success_redirect.tmpl index 8a0cc7f..1ab975a 100644 --- a/templates/admin_success_redirect.tmpl +++ b/templates/admin_success_redirect.tmpl @@ -1,9 +1,19 @@ -{{ template "header.tmpl" . }} -{{ if .Success }} + + + + + + + + admin login + + +{{ template "nav.tmpl" . }} +{{ if . }}

Login was successful!

Click here to go to the post creation area.

{{ else }} -

Login was not successful!

+

Login was not successful...

Click here to return to the login page to try again.

{{ end }} diff --git a/templates/blog.tmpl b/templates/blog.tmpl index 5e0175c..9b33227 100644 --- a/templates/blog.tmpl +++ b/templates/blog.tmpl @@ -1,6 +1,16 @@ -{{ template "header.tmpl" . }} + + + + + + + + rawley.xyz | blog + + + {{ template "nav.tmpl" . }}

Blog posts

-{{ range $val := .Posts }} +{{ range $val := . }}
{{ $val.Title }} diff --git a/templates/blog_post.tmpl b/templates/blog_post.tmpl index abb3363..64de20c 100644 --- a/templates/blog_post.tmpl +++ b/templates/blog_post.tmpl @@ -5,33 +5,15 @@ - {{ .Post.Title }} + {{ .Title }} {{ template "nav.tmpl" . }}
-

{{ .Post.Title }}

-
{{ .Post.Content | UnescapeHTML }}
-

Comments

-{{ range $comment := .Post.Comments }} -
-
- {{ $comment.Author }} at {{ $comment.Date }} -
-
- {{ $comment.Content }} -
-
-{{ else }} -

No comments...

-{{ end }} -

Leave a comment!

-
- - - - - -
-
+

{{ .Title }}

+
{{ .Content | UnescapeHTML }}
+
+

+This site used to have comments. Unfortunatley maintaining a comment system that can resist bots, spammers, and other hooligans is quite tiresome. Until I figure out a way to efficiently handle comments, they are disabled. Sorry for the inconvenience. +

{{ template "footer_no_banners.tmpl" . }} diff --git a/templates/comment_post.tmpl b/templates/comment_post.tmpl deleted file mode 100644 index 6e22141..0000000 --- a/templates/comment_post.tmpl +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - Comment success - - - {{ template "nav.tmpl" . }} -

Your comment is being posted...

-

You will be redirected or, click here to return to the blog post.

- - - - diff --git a/templates/comment_post_failed.tmpl b/templates/comment_post_failed.tmpl deleted file mode 100644 index f43f493..0000000 --- a/templates/comment_post_failed.tmpl +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - Comment failed - - - {{ template "nav.tmpl" . }} -

Your comment failed to be posted...

-

You will be redirected or, click here to return to the blog post.

-

-

Your comment failed for one of these reasons:

-

-
    -
  • You failed the captcha.
  • -
  • Your post had links in it.
  • -
  • The name on your comment was "rawley" or "admin".
  • -
  • You're spamming!
  • -
- - diff --git a/templates/create_post.tmpl b/templates/create_post.tmpl index bdb93d4..f6c24e9 100644 --- a/templates/create_post.tmpl +++ b/templates/create_post.tmpl @@ -1,10 +1,26 @@ -{{ template "header.tmpl" . }} + + + + + + + + crud post + + +{{ template "nav.tmpl" . }}

Create post

- + +
-{{ template "footer.tmpl" . }} + + diff --git a/templates/forbidden.tmpl b/templates/forbidden.tmpl index 24d0eca..b5f1f7c 100644 --- a/templates/forbidden.tmpl +++ b/templates/forbidden.tmpl @@ -1,4 +1,14 @@ -{{ template "header.tmpl" . }} + + + + + + + + forbidden + + +{{ template "nav.tmpl" . }}

403 You're not allowed here >:(

Click here to return home.

diff --git a/templates/index.tmpl b/templates/index.tmpl index f1d39c7..7790ea3 100644 --- a/templates/index.tmpl +++ b/templates/index.tmpl @@ -4,7 +4,7 @@ I am an independent software engineer, and self proclaimed systems administrator from Canada. Currently, I am pursuing a degree in Computer Science, with hopes of participating in a graduate program later down the road.

- I am a GNU/Linux and OpenBSD enthusiast. You'll find me active in the Debian Linux, Void Linux and OpenBSD communities. My favourite programming languages are Go and Clojure. Currently, I am working on my website, and an open-source community based blogging network called xrn. You can check out my work on GitLab. + I am a GNU/Linux and OpenBSD enthusiast. You'll find me active in the Debian Linux, Void Linux and OpenBSD communities. My favourite programming languages are Go and Clojure. Currently, I am working on my website, and an open-source community based blogging network called xrn. All of my work is open source, you can check it out on my GitLab.

When I'm not programming, I'm either reading, cooking, playing table-top games, or walking my dog. diff --git a/templates/internal_server_error.tmpl b/templates/internal_server_error.tmpl index c3ab57e..b876eb9 100644 --- a/templates/internal_server_error.tmpl +++ b/templates/internal_server_error.tmpl @@ -1,4 +1,15 @@ -{{ template "header.tmpl" . }} + + + + + + + + internal server error + + +{{ template "nav.tmpl" . }}

500 Internal server error :(

Click here to return home.

-{{ template "footer.tmpl" . }} + + diff --git a/templates/login.tmpl b/templates/login.tmpl index 63d24a7..d40cd1f 100644 --- a/templates/login.tmpl +++ b/templates/login.tmpl @@ -5,4 +5,4 @@ -{{ template "footer.tmpl" . }} +{{ template "footer_no_banners.tmpl" . }} diff --git a/templates/not_found.tmpl b/templates/not_found.tmpl index 1cccbaf..1d66f13 100644 --- a/templates/not_found.tmpl +++ b/templates/not_found.tmpl @@ -1,7 +1,19 @@ -{{ template "header.tmpl" . }} + + + + + + + + not found + + +{{ template "nav.tmpl" . }} +

404 Not found :(

Click here to return home.

-{{ template "footer.tmpl" . }} + + diff --git a/templates/post_success.tmpl b/templates/post_success.tmpl index 0f4c151..eaf3977 100644 --- a/templates/post_success.tmpl +++ b/templates/post_success.tmpl @@ -1,11 +1,20 @@ -{{ template "header.tmpl" . }} -{{ if .Success }} -

Post created successfully!

-

Click here to view the post.

-

Click here to return to the posting page.

+ + + + + + + + forbidden + + +{{ template "nav.tmpl" . }} +{{ if . }} +

Operation performced successfuly!

+

Click here to return to the management page.

{{ else }} -

Post failed to be created...

-

Click here to return to the posting page.

+

Operation failed to perform...

+

Click here to return to the management page.

{{ end }} diff --git a/templates/comment_post_spam.tmpl b/templates/session_revoked.tmpl similarity index 59% rename from templates/comment_post_spam.tmpl rename to templates/session_revoked.tmpl index cb5323d..eaefe05 100644 --- a/templates/comment_post_spam.tmpl +++ b/templates/session_revoked.tmpl @@ -1,16 +1,15 @@ - - + - Comment spam - - - {{ template "nav.tmpl" . }} -

You've commented too many times, too quickly. Your comment will not be posted.

-

Click here to return to the post.

+ session revoked + + +{{ template "nav.tmpl" . }} +

403 Your session no longer exists

+

Click here to return home.

diff --git a/utils/admin_hash.go b/utils/admin_hash.go deleted file mode 100644 index 4f39a2e..0000000 --- a/utils/admin_hash.go +++ /dev/null @@ -1,24 +0,0 @@ -package utils - -import ( - "bufio" - "os" -) - -func LoadAdminHash(path string) [32]byte { - file, err := os.Open(path) - if err != nil { - panic("Could not find admin hash...") - } - defer file.Close() - reader := bufio.NewScanner(file) - // The dsn should be the first line of the file - if reader.Scan() { - var buff [32]byte - for i, b := range reader.Bytes() { - buff[i] = b - } - return buff - } - panic(path + " is empty...") -} diff --git a/utils/api_key.go b/utils/api_key.go index 34b827e..eff08e4 100644 --- a/utils/api_key.go +++ b/utils/api_key.go @@ -16,6 +16,7 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see .Rawley Fowler, 2022 */ + import ( "bufio" "os" @@ -23,10 +24,10 @@ import ( func LoadApiKey(path string) string { file, err := os.Open(path) - defer file.Close() if err != nil { - panic("Could not find file: " + path) + panic("Could not find DSN for database...") } + defer file.Close() reader := bufio.NewScanner(file) if reader.Scan() { return reader.Text() diff --git a/utils/captcha.go b/utils/captcha.go deleted file mode 100644 index 54eb589..0000000 --- a/utils/captcha.go +++ /dev/null @@ -1,17 +0,0 @@ -package utils - -import ( - "math/rand" - "time" -) - -func GenerateCaptchaValues() (int, int) { - return rand.Intn(100), rand.Intn(100) -} - -func UpdateCaptcha(captcha *[2]int) { - for { - captcha[0], captcha[1] = GenerateCaptchaValues() - time.Sleep(30 * time.Minute) - } -} diff --git a/utils/migrations.go b/utils/db_creator.go similarity index 77% rename from utils/migrations.go rename to utils/db_creator.go index be17039..0df5459 100644 --- a/utils/migrations.go +++ b/utils/db_creator.go @@ -1,4 +1,5 @@ package utils + /* Copyright (C) 2022 Rawley Fowler @@ -15,13 +16,17 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see .Rawley Fowler, 2022 */ + import ( - "gitlab.com/rawleyifowler/site-rework/models" + "gorm.io/driver/mysql" "gorm.io/gorm" ) -// Performs migrations of all models -func PerformMigrations(db *gorm.DB) { - db.AutoMigrate(&models.BlogPost{}) - db.AutoMigrate(&models.Comment{}) +func CreateDatabase(dsn string) (*gorm.DB, error) { + var err error + db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) + if err != nil { + return nil, err + } + return db, nil } diff --git a/utils/dsn.go b/utils/dsn.go index 4771f4f..f340856 100644 --- a/utils/dsn.go +++ b/utils/dsn.go @@ -16,6 +16,7 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see .Rawley Fowler, 2022 */ + import ( "bufio" "os" diff --git a/models/comment.go b/utils/initialize_db.go similarity index 66% rename from models/comment.go rename to utils/initialize_db.go index 8a07bdc..dd9b440 100644 --- a/models/comment.go +++ b/utils/initialize_db.go @@ -1,4 +1,4 @@ -package models +package utils /* Copyright (C) 2022 Rawley Fowler @@ -16,15 +16,16 @@ GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see .Rawley Fowler, 2022 */ + import ( + "gorm.io/driver/mysql" "gorm.io/gorm" ) -type Comment struct { - gorm.Model - Id uint `json:"id" gorm:"primaryKey, AUTO_INCREMENT"` - Date string `json:"date" gorm:"default:NOW()"` - Author string `json:"author" form:"author" gorm:"type:varchar(50)"` - Content string `json:"content" form:"content" gorm:"type:varchar(500)"` - AssociatedPost string +func InitializeDatabase(dsn string) *gorm.DB { + db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) + if err != nil { + panic("Failed to open database with dsn -> " + dsn) + } + return db } diff --git a/utils/serve_page.go b/utils/serve_page.go index 2aacc61..aab78ec 100644 --- a/utils/serve_page.go +++ b/utils/serve_page.go @@ -1,5 +1,22 @@ package utils +/* +Copyright (C) 2022 Rawley Fowler + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see .Rawley Fowler, 2022 +*/ + import ( "net/http" "strings" @@ -7,21 +24,14 @@ import ( "github.com/gin-gonic/gin" ) -// Returns a handler function to serve a page based on the template that is inputted -func ServePage(temp string) gin.HandlerFunc { - title := strings.Split(temp, ".")[0] +func ServePage(t string) gin.HandlerFunc { + title := strings.Split(t, ".")[0] if title == "index" { - // Index is the home page so we don't care title = "" } else { - // Else title it accordingly title = " | " + title } return func(c *gin.Context) { - c.HTML( - http.StatusOK, - temp, - struct{ Title string }{Title: title}, - ) + c.HTML(http.StatusOK, t, struct{ Title string }{Title: title}) } } diff --git a/utils/spam_checker.go b/utils/spam_checker.go deleted file mode 100644 index 20358ed..0000000 --- a/utils/spam_checker.go +++ /dev/null @@ -1,6 +0,0 @@ -package utils - -// TODO: Implement a spam checking module -func IsSpam(t string) bool { - return false -} diff --git a/utils/timed_clear.go b/utils/timed_clear.go deleted file mode 100644 index 2ee4536..0000000 --- a/utils/timed_clear.go +++ /dev/null @@ -1,19 +0,0 @@ -package utils - -import ( - "strconv" - "time" -) - -func TimedClearMap(m *map[string]uint, timer uint) { - for { - t, err := time.ParseDuration(strconv.FormatUint(uint64(timer), 10) + "ms") - if err != nil { - panic("Something went wrong initializing time!!") - } - time.Sleep(t) - for key := range *m { - delete(*m, key) - } - } -} diff --git a/utils/timed_clear_map.go b/utils/timed_clear_map.go new file mode 100644 index 0000000..7ad9286 --- /dev/null +++ b/utils/timed_clear_map.go @@ -0,0 +1,29 @@ +package utils + +/* +Copyright (C) 2022 Rawley Fowler + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see .Rawley Fowler, 2022 +*/ + +import "time" + +func TimeClearMap(m map[string]int64, t int64) { + time.Sleep(time.Duration(t) * time.Millisecond) + for k := range m { + if m[k] < time.Now().UnixMilli()+t { + delete(m, k) + } + } +}