diff --git a/admin-app/pages.go b/admin-app/pages.go index 120e92e..f150499 100644 --- a/admin-app/pages.go +++ b/admin-app/pages.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "net/http" - "regexp" "strings" "github.com/gin-gonic/gin" @@ -27,8 +26,14 @@ func postPageHandler(database database.Database) func(*gin.Context) { return } + err := checkRequiredPageData(add_page_request) + if err != nil { + c.JSON(http.StatusBadRequest, common.MsgErrorRes("invalid page data")) + return + } + decoder := json.NewDecoder(c.Request.Body) - err := decoder.Decode(&add_page_request) + err = decoder.Decode(&add_page_request) if err != nil { log.Warn().Msgf("invalid page request: %v", err) @@ -129,7 +134,7 @@ func checkRequiredPageData(add_page_request AddPageRequest) error { return fmt.Errorf("missing required data 'Content'") } - err := validateLinkRegex(add_page_request.Link) + err := validateLink(add_page_request.Link) if err != nil { return err } @@ -137,14 +142,16 @@ func checkRequiredPageData(add_page_request AddPageRequest) error { return nil } -func validateLinkRegex(link string) error { - match, err := regexp.MatchString("^[a-zA-Z0-9_\\-]+$", link) - if err != nil { - return fmt.Errorf("could not match the string: %v", err) - } +func validateLink(link string) error { + for char := range link { + char_val := int(char) + is_uppercase := (char_val >= 65) && (char_val <= 90) + is_lowercase := (char_val >= 97) && (char_val <= 122) + is_sign := (char == '-') || (char == '_') - if !match { - return fmt.Errorf("could not match the string") + if !(is_uppercase || is_lowercase || is_sign) { + return fmt.Errorf("invalid character in link %s", char) + } } return nil diff --git a/app/app.go b/app/app.go index 4efb4c6..7b0413b 100644 --- a/app/app.go +++ b/app/app.go @@ -44,6 +44,7 @@ func SetupRoutes(app_settings common.AppSettings, database database.Database) *g // Where all the static files (css, js, etc) are served from r.Static("/static", "./static") + return r } @@ -54,6 +55,7 @@ func addCachableHandler(e *gin.Engine, method string, endpoint string, generator if app_settings.CacheEnabled { cached_endpoint, err := (*cache).Get(c.Request.RequestURI) if err == nil { + log.Info().Msgf("cache hit for page: %s", c.Request.RequestURI) c.Data(http.StatusOK, "text/html; charset=utf-8", cached_endpoint.Contents) return } diff --git a/app/page.go b/app/page.go new file mode 100644 index 0000000..31931ff --- /dev/null +++ b/app/page.go @@ -0,0 +1,43 @@ +package app + +import ( + "bytes" + "net/http" + + "github.com/gin-gonic/gin" + "github.com/matheusgomes28/urchin/common" + "github.com/matheusgomes28/urchin/database" + "github.com/matheusgomes28/urchin/views" + "github.com/rs/zerolog/log" +) + +func pageHandler(c *gin.Context, app_settings common.AppSettings, database database.Database) ([]byte, error) { + var page_binding common.PageLinkBinding + err := c.ShouldBindUri(&page_binding) + + if err != nil || len(page_binding.Link) == 0 { + // TODO : we should be serving an error page + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid page uri"}) + + return nil, err + } + + // Get the page with the ID + page, err := database.GetPage(page_binding.Link) + + if err != nil || page.Content == "" { + // TODO : serve the error page instead + c.JSON(http.StatusNotFound, gin.H{"error": "Page Not Found"}) + return nil, err + } + + // Generate HTML page + page.Content = string(mdToHTML([]byte(page.Content))) + post_view := views.MakePage(page.Title, page.Content, app_settings.AppNavbar.Links) + html_buffer := bytes.NewBuffer(nil) + if err = post_view.Render(c, html_buffer); err != nil { + log.Error().Msgf("could not render: %v", err) + } + + return html_buffer.Bytes(), nil +} diff --git a/common/page.go b/common/page.go new file mode 100644 index 0000000..acc225b --- /dev/null +++ b/common/page.go @@ -0,0 +1,8 @@ +package common + +type Page struct { + Id int `json:"id"` + Title string `json:"title"` + Content string `json:"content"` + Link string `json:"link"` +} diff --git a/database/database.go b/database/database.go index 781c459..528bdbb 100644 --- a/database/database.go +++ b/database/database.go @@ -17,6 +17,7 @@ type Database interface { ChangePost(id int, title string, excerpt string, content string) error DeletePost(id int) (int, error) AddPage(title string, content string, link string) (int, error) + GetPage(link string) (common.Page, error) } type SqlDatabase struct { @@ -160,6 +161,24 @@ func (db SqlDatabase) AddPage(title string, content string, link string) (int, e return int(id), nil } +func (db SqlDatabase) GetPage(link string) (common.Page, error) { + rows, err := db.Connection.Query("SELECT id, title, content, link FROM pages WHERE link=?;", link) + if err != nil { + return common.Page{}, err + } + defer func() { + err = errors.Join(rows.Close()) + }() + + page := common.Page{} + rows.Next() + if err = rows.Scan(&page.Id, &page.Title, &page.Content, &page.Link); err != nil { + return common.Page{}, err + } + + return page, nil +} + func MakeSqlConnection(user string, password string, address string, port int, database string) (SqlDatabase, error) { /// TODO : let user specify the DB diff --git a/tests/mocks/mocks.go b/tests/mocks/mocks.go index dad196e..512dff6 100644 --- a/tests/mocks/mocks.go +++ b/tests/mocks/mocks.go @@ -10,6 +10,7 @@ type DatabaseMock struct { AddPostHandler func(string, string, string) (int, error) DeletePostHandler func(int) (int, error) AddPageHandler func(string, string, string) (int, error) + GetPageHandler func(string) (common.Page, error) } func (db DatabaseMock) GetPosts(limit int, offset int) ([]common.Post, error) { @@ -35,3 +36,7 @@ func (db DatabaseMock) DeletePost(id int) (int, error) { func (db DatabaseMock) AddPage(title string, content string, link string) (int, error) { return db.AddPageHandler(title, content, link) } + +func (db DatabaseMock) GetPage(link string) (common.Page, error) { + return db.GetPageHandler(link) +}