diff --git a/README.md b/README.md index 620fb36..3d22f18 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ The features are not complete, not tested and subject to many changes. ## Setup +Don't forget to setup the [Environment variables](#environment-variables)! + ### Use with Docker To **run** the app, run the command @@ -18,7 +20,7 @@ docker-compose up TODO -## Environment variables +### Environment variables - ./env - APP_API_ADDRESS: `http://example.com` @@ -29,6 +31,10 @@ TODO - APP_FRONT_PORT: `1234` - APP_JWT_SECRET: `secret` +## API Usage + +TODO + ## Demo TODO diff --git a/cmd/main.go b/cmd/main.go index eb38be8..7719433 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -63,11 +63,11 @@ func initArticlesRoutes(r *gin.RouterGroup) { articlesRouter := r.Group("/articles") articlesRouter.Use(corsMiddleware, api.AuthMiddleware.MiddlewareFunc()) - articlesRouter.GET("/", articles.GetArticleHandler) - articlesRouter.GET("/:id", articles.GetArticleHandler) - articlesRouter.POST("/:id", articles.AddArticleHandler) - articlesRouter.PUT("/:id", articles.EditArticleHandler) - articlesRouter.DELETE("/:id", articles.DeleteArticleHandler) + articlesRouter.GET("/", articles.Get) + articlesRouter.GET("/:id", articles.Get) + articlesRouter.POST("/:id", articles.Add) + articlesRouter.PUT("/:id", articles.Edit) + articlesRouter.DELETE("/:id", articles.Delete) } func initGin() { diff --git a/internal/articles/add.go b/internal/articles/add.go new file mode 100644 index 0000000..a564fb8 --- /dev/null +++ b/internal/articles/add.go @@ -0,0 +1,39 @@ +package articles + +import ( + "fmt" + + "github.com/Floriansylvain/GohCMS/internal/api" + "github.com/Floriansylvain/GohCMS/internal/database" + "github.com/gin-gonic/gin" + "go.mongodb.org/mongo-driver/bson" +) + +func Add(c *gin.Context) { + var article Article + article.TitleID = c.Params.ByName("id") + if c.BindJSON(&article) != nil { + api.SendBadRequest(c, "Could not correctly parse the article.") + return + } + + document, err := bson.Marshal(article) + if err != nil { + api.SendBadRequest(c, "Could not correctly marshal the article.") + return + } + + documents, _ := database.GetDocuments(articlesLocation, map[string]any{}) + if IsArticleIdAlreadyUsed(article.TitleID, documents) { + api.SendBadRequest(c, fmt.Sprintf("Article ID '%v' already used.", article.TitleID)) + return + } + + err = database.PushDocument(articlesLocation, document) + if err != nil { + api.SendBadRequest(c, fmt.Sprintf(`Could not insert document(s) into DB: %v`, err.Error())) + return + } + + api.SendOk(c, "Article successfully added!") +} diff --git a/internal/articles/delete.go b/internal/articles/delete.go new file mode 100644 index 0000000..835e209 --- /dev/null +++ b/internal/articles/delete.go @@ -0,0 +1,25 @@ +package articles + +import ( + "fmt" + + "github.com/Floriansylvain/GohCMS/internal/api" + "github.com/Floriansylvain/GohCMS/internal/database" + "github.com/gin-gonic/gin" +) + +func Delete(c *gin.Context) { + id := c.Params.ByName("id") + + deleteCount, err := database.DeleteDocument(articlesLocation, map[string]any{"titleID": id}) + if err != nil { + api.SendBadRequest(c, fmt.Sprintf(`Could not delete document(s) from DB: %v`, err.Error())) + return + } + + if deleteCount != 0 { + api.SendOk(c, fmt.Sprintf("%d article(s) was/were successfully deleted!", deleteCount)) + } else { + api.SendOk(c, "No articles were deleted.") + } +} diff --git a/internal/articles/edit.go b/internal/articles/edit.go new file mode 100644 index 0000000..f08138a --- /dev/null +++ b/internal/articles/edit.go @@ -0,0 +1,29 @@ +package articles + +import ( + "fmt" + + "github.com/Floriansylvain/GohCMS/internal/api" + "github.com/Floriansylvain/GohCMS/internal/database" + "github.com/gin-gonic/gin" +) + +func Edit(c *gin.Context) { + id := c.Params.ByName("id") + + var articleUpdate database.DocumentUpdate + articleUpdate.Filter = map[string]any{"titleID": id} + c.BindJSON(&articleUpdate.Update) + + editCount, err := database.EditDocument(articlesLocation, articleUpdate) + if err != nil { + api.SendBadRequest(c, fmt.Sprintf(`Could not edit document(s) from DB: %v`, err.Error())) + return + } + + if editCount != 0 { + api.SendOk(c, fmt.Sprintf("%d article(s) was/were successfully edited!", editCount)) + } else { + api.SendOk(c, "No articles were edited.") + } +} diff --git a/internal/articles/get.go b/internal/articles/get.go new file mode 100644 index 0000000..1f6f531 --- /dev/null +++ b/internal/articles/get.go @@ -0,0 +1,80 @@ +package articles + +import ( + "fmt" + "net/http" + "os" + "strconv" + + "github.com/Floriansylvain/GohCMS/internal/api" + "github.com/Floriansylvain/GohCMS/internal/database" + "github.com/gin-gonic/gin" +) + +func getApiFullUrl() string { + return fmt.Sprintf("%v%v", os.Getenv("APP_API_ADDRESS"), os.Getenv("APP_BASE_API_PATH")) +} + +func getArticleSkipTakeFullUrl(skip uint64, take uint64) string { + return fmt.Sprintf("%v/articles?skip=%v&take=%v", getApiFullUrl(), skip+take, take) +} + +func getBuiltGetResponse(articles []Article, skip uint64, take uint64) map[string]any { + slicedArticles := articles[skip : skip+take] + return map[string]any{ + "content": slicedArticles, + "total": len(slicedArticles), + "pagination": map[string]any{ + "skip": skip, + "take": take, + "links": map[string]any{ + "next": getArticleSkipTakeFullUrl(skip+take, take), + "previous": getArticleSkipTakeFullUrl(skip-take, take), + }, + }, + } +} + +func parseUintQueryParam(c *gin.Context, param string) (uint64, error) { + value, err := strconv.ParseUint(c.Query(param), 10, 0) + if err != nil { + c.JSON(http.StatusBadRequest, fmt.Sprintf("%s query parameter must be a positive number.", param)) + } + return value, err +} + +func getArticleIdFilter(titleID string) map[string]any { + filter := map[string]any{} + if titleID != "" { + filter["titleID"] = titleID + } + return filter +} + +func Get(c *gin.Context) { + titleID := c.Params.ByName("id") + + skip, skipErr := parseUintQueryParam(c, "skip") + take, takeErr := parseUintQueryParam(c, "take") + if skipErr != nil || takeErr != nil { + return + } + + articles, err := database.GetDocuments(articlesLocation, getArticleIdFilter(titleID)) + if err != nil { + api.SendBadRequest(c, fmt.Sprintf("The ID '%v' doesn't match any article.", titleID)) + return + } + + articlesArray := ParseArticlesFromBytesToArray(articles) + articlesArrayLength := uint64(len(articlesArray)) + if skip >= articlesArrayLength || take == 0 { + c.JSON(http.StatusOK, map[string]any{ + "content": []string{}, + "total": "0", + }) + return + } + + c.JSON(http.StatusOK, getBuiltGetResponse(articlesArray, skip, take)) +} diff --git a/internal/articles/handlers.go b/internal/articles/handlers.go deleted file mode 100644 index 55856e4..0000000 --- a/internal/articles/handlers.go +++ /dev/null @@ -1,93 +0,0 @@ -package articles - -import ( - "fmt" - "net/http" - - "github.com/Floriansylvain/GohCMS/internal/api" - "github.com/Floriansylvain/GohCMS/internal/database" - "github.com/gin-gonic/gin" - "go.mongodb.org/mongo-driver/bson" -) - -func GetArticleHandler(c *gin.Context) { - articleID := c.Params.ByName("id") - - filter := gin.H{} - if articleID != "" { - filter["titleID"] = articleID - } - - articles, err := database.GetDocuments(articlesLocation, filter) - if err != nil { - api.SendBadRequest(c, fmt.Sprintf("The ID '%v' doesn't match any article.", articleID)) - return - } - - c.JSON(http.StatusOK, ParseArticlesFromBytesToArray(articles)) -} - -func AddArticleHandler(c *gin.Context) { - var article Article - article.TitleID = c.Params.ByName("id") - if c.BindJSON(&article) != nil { - api.SendBadRequest(c, "Could not correctly parse the article.") - return - } - - document, err := bson.Marshal(article) - if err != nil { - api.SendBadRequest(c, "Could not correctly marshal the article.") - return - } - - documents, _ := database.GetDocuments(articlesLocation, gin.H{}) - if IsArticleIdAlreadyUsed(article.TitleID, documents) { - api.SendBadRequest(c, fmt.Sprintf("Article ID '%v' already used.", article.TitleID)) - return - } - - err = database.PushDocument(articlesLocation, document) - if err != nil { - api.SendBadRequest(c, fmt.Sprintf(`Could not insert document(s) into DB: %v`, err.Error())) - return - } - - api.SendOk(c, "Article successfully added!") -} - -func DeleteArticleHandler(c *gin.Context) { - id := c.Params.ByName("id") - - deleteCount, err := database.DeleteDocument(articlesLocation, gin.H{"titleID": id}) - if err != nil { - api.SendBadRequest(c, fmt.Sprintf(`Could not delete document(s) from DB: %v`, err.Error())) - return - } - - if deleteCount != 0 { - api.SendOk(c, fmt.Sprintf("%d article(s) was/were successfully deleted!", deleteCount)) - } else { - api.SendOk(c, "No articles were deleted.") - } -} - -func EditArticleHandler(c *gin.Context) { - id := c.Params.ByName("id") - - var articleUpdate database.DocumentUpdate - articleUpdate.Filter = gin.H{"titleID": id} - c.BindJSON(&articleUpdate.Update) - - editCount, err := database.EditDocument(articlesLocation, articleUpdate) - if err != nil { - api.SendBadRequest(c, fmt.Sprintf(`Could not edit document(s) from DB: %v`, err.Error())) - return - } - - if editCount != 0 { - api.SendOk(c, fmt.Sprintf("%d article(s) was/were successfully edited!", editCount)) - } else { - api.SendOk(c, "No articles were edited.") - } -}