-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Arbitrary Page Creation Support (#92)
TODOS: - Need the get page endpoints - Some more unit tests for this
- Loading branch information
1 parent
ad4fa36
commit 8e125a9
Showing
14 changed files
with
370 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
package admin_app | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"net/http" | ||
"strings" | ||
|
||
"github.com/gin-gonic/gin" | ||
"github.com/matheusgomes28/urchin/common" | ||
"github.com/matheusgomes28/urchin/database" | ||
"github.com/rs/zerolog/log" | ||
) | ||
|
||
// func getPageHandler(database database.Database) func(*gin.Context) { | ||
// return func(c *gin.Context) { | ||
// } | ||
// } | ||
|
||
// / postPageHandler is the function handling the endpoint for adding new pages | ||
func postPageHandler(database database.Database) func(*gin.Context) { | ||
return func(c *gin.Context) { | ||
var add_page_request AddPageRequest | ||
if c.Request.Body == nil { | ||
c.JSON(http.StatusBadRequest, common.MsgErrorRes("no request body provided")) | ||
return | ||
} | ||
|
||
decoder := json.NewDecoder(c.Request.Body) | ||
err := decoder.Decode(&add_page_request) | ||
if err != nil { | ||
log.Warn().Msgf("invalid page request: %v", err) | ||
c.JSON(http.StatusBadRequest, common.ErrorRes("invalid request body", err)) | ||
return | ||
} | ||
|
||
err = checkRequiredPageData(add_page_request) | ||
if err != nil { | ||
c.JSON(http.StatusBadRequest, common.MsgErrorRes("invalid page data")) | ||
return | ||
} | ||
|
||
err = checkRequiredPageData(add_page_request) | ||
if err != nil { | ||
log.Error().Msgf("failed to add post required data is missing: %v", err) | ||
c.JSON(http.StatusBadRequest, common.ErrorRes("missing required data", err)) | ||
return | ||
} | ||
|
||
id, err := database.AddPage( | ||
add_page_request.Title, | ||
add_page_request.Content, | ||
add_page_request.Link, | ||
) | ||
if err != nil { | ||
log.Error().Msgf("failed to add post: %v", err) | ||
c.JSON(http.StatusBadRequest, common.ErrorRes("could not add post", err)) | ||
return | ||
} | ||
|
||
c.JSON(http.StatusOK, PageResponse{ | ||
Id: id, | ||
Link: add_page_request.Link, | ||
}) | ||
} | ||
} | ||
|
||
// func putPostHandler(database database.Database) func(*gin.Context) { | ||
// return func(c *gin.Context) { | ||
// var change_post_request ChangePostRequest | ||
// decoder := json.NewDecoder(c.Request.Body) | ||
// decoder.DisallowUnknownFields() | ||
|
||
// err := decoder.Decode(&change_post_request) | ||
// if err != nil { | ||
// log.Warn().Msgf("could not get post from DB: %v", err) | ||
// c.JSON(http.StatusBadRequest, common.ErrorRes("invalid request body", err)) | ||
// return | ||
// } | ||
|
||
// err = database.ChangePost( | ||
// change_post_request.Id, | ||
// change_post_request.Title, | ||
// change_post_request.Excerpt, | ||
// change_post_request.Content, | ||
// ) | ||
// if err != nil { | ||
// log.Error().Msgf("failed to change post: %v", err) | ||
// c.JSON(http.StatusBadRequest, common.ErrorRes("could not change post", err)) | ||
// return | ||
// } | ||
|
||
// c.JSON(http.StatusOK, PostIdResponse{ | ||
// change_post_request.Id, | ||
// }) | ||
// } | ||
// } | ||
|
||
// func deletePostHandler(database database.Database) func(*gin.Context) { | ||
// return func(c *gin.Context) { | ||
// var delete_post_binding DeletePostBinding | ||
// err := c.ShouldBindUri(&delete_post_binding) | ||
// if err != nil { | ||
// c.JSON(http.StatusBadRequest, common.ErrorRes("no id provided to delete post", err)) | ||
// return | ||
// } | ||
|
||
// rows_affected, err := database.DeletePost(delete_post_binding.Id) | ||
// if err != nil { | ||
// log.Error().Msgf("failed to delete post: %v", err) | ||
// c.JSON(http.StatusBadRequest, common.ErrorRes("could not delete post", err)) | ||
// return | ||
// } | ||
|
||
// if rows_affected == 0 { | ||
// log.Error().Msgf("no post found with id `%d`", delete_post_binding.Id) | ||
// c.JSON(http.StatusNotFound, common.MsgErrorRes("no post found")) | ||
// return | ||
// } | ||
|
||
// c.JSON(http.StatusOK, PostIdResponse{ | ||
// delete_post_binding.Id, | ||
// }) | ||
// } | ||
// } | ||
|
||
func checkRequiredPageData(add_page_request AddPageRequest) error { | ||
if strings.TrimSpace(add_page_request.Title) == "" { | ||
return fmt.Errorf("missing required data 'Title'") | ||
} | ||
|
||
if strings.TrimSpace(add_page_request.Content) == "" { | ||
return fmt.Errorf("missing required data 'Content'") | ||
} | ||
|
||
err := validateLink(add_page_request.Link) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
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 !(is_uppercase || is_lowercase || is_sign) { | ||
// TODO : what is this conversion?! | ||
return fmt.Errorf("invalid character in link %s", string(rune(char))) | ||
} | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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"` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
-- +goose Up | ||
-- +goose StatementBegin | ||
CREATE TABLE pages ( | ||
id INTEGER PRIMARY KEY AUTO_INCREMENT, | ||
content TEXT NOT NULL, | ||
link TEXT NOT NULL, | ||
title TEXT NOT NULL | ||
); | ||
-- +goose StatementEnd | ||
|
||
-- +goose Down | ||
-- +goose StatementBegin | ||
DROP TABLE pages; | ||
-- +goose StatementEnd |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package endpoint_tests | ||
|
||
import ( | ||
"bytes" | ||
"encoding/json" | ||
"net/http" | ||
"net/http/httptest" | ||
"testing" | ||
|
||
admin_app "github.com/matheusgomes28/urchin/admin-app" | ||
"github.com/matheusgomes28/urchin/tests/mocks" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestAddPageHappyPath(t *testing.T) { | ||
databaseMock := mocks.DatabaseMock{ | ||
AddPageHandler: func(string, string, string) (int, error) { | ||
return 0, nil | ||
}, | ||
} | ||
|
||
page_data := admin_app.AddPageRequest{ | ||
Title: "Title", | ||
Content: "Content", | ||
Link: "Link", | ||
} | ||
|
||
router := admin_app.SetupRoutes(app_settings, databaseMock) | ||
responseRecorder := httptest.NewRecorder() | ||
|
||
body, _ := json.Marshal(page_data) | ||
request, _ := http.NewRequest(http.MethodPost, "/pages", bytes.NewBuffer(body)) | ||
|
||
router.ServeHTTP(responseRecorder, request) | ||
|
||
assert.Equal(t, http.StatusOK, responseRecorder.Code) | ||
var response admin_app.PageResponse | ||
err := json.Unmarshal(responseRecorder.Body.Bytes(), &response) | ||
assert.Nil(t, err) | ||
|
||
assert.NotNil(t, response.Id) | ||
assert.NotEmpty(t, response.Link) | ||
assert.Equal(t, page_data.Link, response.Link) | ||
} |
Oops, something went wrong.