diff --git a/.gitignore b/.gitignore index 655e4ed..3d9342a 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ dsn api_key.pem admin_hash.pem create_admin_form.html +error.log diff --git a/LICENSE b/LICENSE index 46444c8..4a8ff16 100644 --- a/LICENSE +++ b/LICENSE @@ -631,7 +631,7 @@ to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. - site-take-two + rawley.xyz Copyright (C) 2022 Rawley This program is free software: you can redistribute it and/or modify @@ -652,7 +652,7 @@ Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: - Copyright (C) 2022 Rawley + rawley.xyz Copyright (C) 2022 Rawley This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. diff --git a/controllers/admin.go b/controllers/admin.go index 08ab30a..b57bdc2 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.StatusUnauthorized - } - 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.StatusUnauthorized, "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.StatusUnauthorized, "forbidden.tmpl", models.Page{Title: " | forbidden"}) + 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..50f87e9 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").Order("date desc").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..0a90be7 100644 --- a/static/index.css +++ b/static/index.css @@ -14,18 +14,18 @@ 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 */ - +/* Color Scheme from here: https://draculatheme.com */ html { max-width: 100vw; min-height: 100vh; } body { - color: #242124; + color: #282a36; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; -webkit-font-smoothing: antialias; -moz-osx-font-smoothing: grayscale; - background-color: #fafafa; + background-color: #f8f8f2; font-size: calc(12px + 0.33vw); margin: auto; padding: 1.3em; @@ -70,18 +70,11 @@ li { padding-bottom: 6px; } -span { - background-color: rgba(25, 25, 25, 0.6); - color: #50f77b; - padding-left: 2px; - padding-right: 2px; -} - a { text-decoration: none; - color: #242124; position: relative; transition: 100ms linear; + color: inherit; } a::after { @@ -93,7 +86,7 @@ a::after { right: 0; bottom: 0; transition: 102ms cubic-bezier(0, 0.86, 0.1, 1); - background-color: rgba(255, 222, 0); + background-color: #fff42b; } a:hover:after { @@ -164,13 +157,35 @@ a:hover:after { content: "' "; } -.comment { - margin-top: 3px; - margin-bottom: 3px; - border: 2px solid #454545; +/* For code snippets */ +code { + background-color: #44475a; + color: #50fa7b; + padding: 3px; + font-family: monospace; } -.comment-body { - background-color: rgba(222, 222, 222, 0.5); - word-wrap: break-word; +code::before { + content: "\A"; + white-space: pre; +} + +code::after { + content: "\A"; +} + +@media (prefers-color-scheme: dark) { + body { + color: #f8f8f2; + background-color: #282a36; + } + body * { + color: #f8f8f2; + } + body > * { + color: #f8f8f2; + } + a::after { + background-color: #bd93f9; + } } 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..3a06c0a 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/resume.tmpl b/templates/resume.tmpl index fe7998d..c6dd250 100644 --- a/templates/resume.tmpl +++ b/templates/resume.tmpl @@ -4,30 +4,33 @@

View my resume (pdf).

-

Skills

+

Skill keywords

GNU/Linux Debian + C/C++ OpenBSD Go - Gin + Bash Clojure Git Gentoo Java - NGiNX + NGiNX + TypeScript Vim - C/C++ NetBSD JavaScript HTML CSS Unix + X11 Vue MariaDB Korn Shell - Bash Lua + React + NodeJS

Education

@@ -51,4 +54,8 @@

parser

A simple bash utility designed to save time when parsing files against patterns.

+

+

nfs-util (wip)

+ Bash utility for easily creating and validating nfs shares. Including dry runs for connectivity tests. +

{{ template "footer_no_banners.tmpl" . }} 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) + } + } +}