Skip to content

Commit

Permalink
feat: api endpoint to serve readable content (#885)
Browse files Browse the repository at this point in the history
* add endpoint to serve readable content

* add id and modified time in response

* revert id and modified time

* remove public check status

* chnage 401 to 403

* add basic unittest for readable content

* check response in unittest

* update strcut name and move that above handler
  • Loading branch information
Monirzadeh authored Apr 20, 2024
1 parent b0ca981 commit b8a3578
Show file tree
Hide file tree
Showing 5 changed files with 195 additions and 0 deletions.
33 changes: 33 additions & 0 deletions docs/swagger/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,28 @@ const docTemplate = `{
}
}
},
"/api/v1/bookmarks/id/readable": {
"get": {
"produces": [
"application/json"
],
"tags": [
"Auth"
],
"summary": "Get readable version of bookmark.",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api_v1.contentResponseMessage"
}
},
"403": {
"description": "Token not provided/invalid"
}
}
}
},
"/api/v1/tags": {
"get": {
"produces": [
Expand Down Expand Up @@ -206,6 +228,17 @@ const docTemplate = `{
}
},
"definitions": {
"api_v1.contentResponseMessage": {
"type": "object",
"properties": {
"content": {
"type": "string"
},
"html": {
"type": "string"
}
}
},
"api_v1.loginRequestPayload": {
"type": "object",
"required": [
Expand Down
33 changes: 33 additions & 0 deletions docs/swagger/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,28 @@
}
}
},
"/api/v1/bookmarks/id/readable": {
"get": {
"produces": [
"application/json"
],
"tags": [
"Auth"
],
"summary": "Get readable version of bookmark.",
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/api_v1.contentResponseMessage"
}
},
"403": {
"description": "Token not provided/invalid"
}
}
}
},
"/api/v1/tags": {
"get": {
"produces": [
Expand Down Expand Up @@ -195,6 +217,17 @@
}
},
"definitions": {
"api_v1.contentResponseMessage": {
"type": "object",
"properties": {
"content": {
"type": "string"
},
"html": {
"type": "string"
}
}
},
"api_v1.loginRequestPayload": {
"type": "object",
"required": [
Expand Down
21 changes: 21 additions & 0 deletions docs/swagger/swagger.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
definitions:
api_v1.contentResponseMessage:
properties:
content:
type: string
html:
type: string
type: object
api_v1.loginRequestPayload:
properties:
password:
Expand Down Expand Up @@ -218,6 +225,20 @@ paths:
summary: Update Cache and Ebook on server.
tags:
- Auth
/api/v1/bookmarks/id/readable:
get:
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/api_v1.contentResponseMessage'
"403":
description: Token not provided/invalid
summary: Get readable version of bookmark.
tags:
- Auth
/api/v1/tags:
get:
produces:
Expand Down
58 changes: 58 additions & 0 deletions internal/http/routes/api/v1/bookmarks.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type BookmarksAPIRoutes struct {
func (r *BookmarksAPIRoutes) Setup(g *gin.RouterGroup) model.Routes {
g.Use(middleware.AuthenticationRequired())
g.PUT("/cache", r.updateCache)
g.GET("/:id/readable", r.bookmarkReadable)
return r
}

Expand Down Expand Up @@ -57,6 +58,63 @@ func (p *updateCachePayload) IsValid() error {
return nil
}

func (r *BookmarksAPIRoutes) getBookmark(c *context.Context) (*model.BookmarkDTO, error) {
bookmarkIDParam, present := c.Params.Get("id")
if !present {
response.SendError(c.Context, http.StatusBadRequest, "Invalid bookmark ID")
return nil, model.ErrBookmarkInvalidID
}

bookmarkID, err := strconv.Atoi(bookmarkIDParam)
if err != nil {
r.logger.WithError(err).Error("error parsing bookmark ID parameter")
response.SendInternalServerError(c.Context)
return nil, err
}

if bookmarkID == 0 {
response.SendError(c.Context, http.StatusNotFound, nil)
return nil, model.ErrBookmarkNotFound
}

bookmark, err := r.deps.Domains.Bookmarks.GetBookmark(c.Context, model.DBID(bookmarkID))
if err != nil {
response.SendError(c.Context, http.StatusNotFound, nil)
return nil, model.ErrBookmarkNotFound
}

return bookmark, nil
}

type readableResponseMessage struct {
Content string `json:"content"`
Html string `json:"html"`
}

// Bookmark Readable godoc
//
// @Summary Get readable version of bookmark.
// @Tags Auth
// @securityDefinitions.apikey ApiKeyAuth
// @Produce json
// @Success 200 {object} contentResponseMessage
// @Failure 403 {object} nil "Token not provided/invalid"
// @Router /api/v1/bookmarks/id/readable [get]
func (r *BookmarksAPIRoutes) bookmarkReadable(c *gin.Context) {
ctx := context.NewContextFromGin(c)

bookmark, err := r.getBookmark(ctx)
if err != nil {
return
}
responseMessage := readableResponseMessage{
Content: bookmark.Content,
Html: bookmark.HTML,
}

response.Send(c, 200, responseMessage)
}

// updateCache godoc
//
// @Summary Update Cache and Ebook on server.
Expand Down
50 changes: 50 additions & 0 deletions internal/http/routes/api/v1/bookmarks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,53 @@ func TestUpdateBookmarkCache(t *testing.T) {
require.Equal(t, http.StatusForbidden, w.Code)
})
}

func TestReadableeBookmarkContent(t *testing.T) {
logger := logrus.New()
ctx := context.TODO()

g := gin.New()

_, deps := testutil.GetTestConfigurationAndDependencies(t, ctx, logger)
g.Use(middleware.AuthMiddleware(deps))

router := NewBookmarksAPIRoutes(logger, deps)
router.Setup(g.Group("/"))

account := model.Account{
Username: "test",
Password: "test",
Owner: false,
}
require.NoError(t, deps.Database.SaveAccount(ctx, account))
token, err := deps.Domains.Auth.CreateTokenForAccount(&account, time.Now().Add(time.Minute))
require.NoError(t, err)

bookmark := testutil.GetValidBookmark()
_, err = deps.Database.SaveBookmarks(ctx, true, *bookmark)
require.NoError(t, err)
response := `{"ok":true,"message":{"content":"","html":""}}`

t.Run("require authentication", func(t *testing.T) {
w := testutil.PerformRequest(g, "GET", "/1/readable")
require.Equal(t, http.StatusUnauthorized, w.Code)
})
t.Run("get content but invalid id", func(t *testing.T) {
w := testutil.PerformRequest(g, "GET", "/invalidId/readable", testutil.WithHeader(model.AuthorizationHeader, model.AuthorizationTokenType+" "+token))
require.Equal(t, http.StatusInternalServerError, w.Code)
})
t.Run("get content but 0 id", func(t *testing.T) {
w := testutil.PerformRequest(g, "GET", "/0/readable", testutil.WithHeader(model.AuthorizationHeader, model.AuthorizationTokenType+" "+token))
require.Equal(t, http.StatusNotFound, w.Code)
})
t.Run("get content but not exist", func(t *testing.T) {
w := testutil.PerformRequest(g, "GET", "/2/readable", testutil.WithHeader(model.AuthorizationHeader, model.AuthorizationTokenType+" "+token))
require.Equal(t, http.StatusNotFound, w.Code)
})
t.Run("get content", func(t *testing.T) {
w := testutil.PerformRequest(g, "GET", "/1/readable", testutil.WithHeader(model.AuthorizationHeader, model.AuthorizationTokenType+" "+token))
require.Equal(t, response, w.Body.String())
require.Equal(t, http.StatusOK, w.Code)
})

}

0 comments on commit b8a3578

Please sign in to comment.