Merge pull request #15 from Floriansylvain/feature/database_ops_overhaul

Feature/database_ops_overhaul
This commit is contained in:
Florian Sylvain
2023-01-19 00:29:10 +01:00
committed by GitHub
13 changed files with 296 additions and 179 deletions
+7 -1
View File
@@ -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
+5 -5
View File
@@ -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() {
+3 -3
View File
@@ -7,13 +7,13 @@ import (
)
func SendBadRequest(c *gin.Context, message string) {
c.JSON(http.StatusBadRequest, gin.H{"message": message})
c.JSON(http.StatusBadRequest, gin.H{"message": message, "code": 400})
}
func SendOk(c *gin.Context, message string) {
c.JSON(http.StatusOK, gin.H{"message": message})
c.JSON(http.StatusOK, gin.H{"message": message, "code": 200})
}
func SendForbidden(c *gin.Context, message string) {
c.JSON(http.StatusForbidden, gin.H{"message": message})
c.JSON(http.StatusForbidden, gin.H{"message": message, "code": 403})
}
+39
View File
@@ -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!")
}
+25
View File
@@ -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.")
}
}
+29
View File
@@ -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.")
}
}
+90
View File
@@ -0,0 +1,90 @@
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("%varticles?skip=%v&take=%v", getApiFullUrl(), skip+take, take)
}
func getBuiltGetResponse(articles []Article, skip uint64, take uint64) map[string]any {
articlesCap := uint64(len(articles))
var normTake uint64
if skip+take > articlesCap {
normTake = articlesCap
} else {
normTake = skip + take
}
slicedArticles := articles[skip:normTake]
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, defaultValue uint64) (uint64, error) {
if c.Query(param) == "" {
return defaultValue, nil
}
value, err := strconv.ParseUint(c.Query(param), 10, 0)
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", 0)
take, takeErr := parseUintQueryParam(c, "take", 10)
if skipErr != nil || takeErr != nil {
api.SendBadRequest(c, "Take and Skip query parameters must be positive numbers.")
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))
}
-93
View File
@@ -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.")
}
}
-77
View File
@@ -2,9 +2,7 @@ package database
import (
"context"
"errors"
"github.com/gin-gonic/gin"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
@@ -14,11 +12,6 @@ type Location struct {
Collection string
}
type DocumentUpdate struct {
Filter gin.H `json:"filter"`
Update gin.H `json:"update"`
}
func GetNewClient() *mongo.Client {
client, err := mongo.Connect(
context.TODO(),
@@ -28,73 +21,3 @@ func GetNewClient() *mongo.Client {
}
return client
}
func PushDocument(location Location, document interface{}) error {
client := GetNewClient()
collection := client.Database(location.Database).Collection(location.Collection)
defer client.Disconnect(context.TODO())
_, err := collection.InsertOne(context.TODO(), document)
if err != nil {
return err
}
return nil
}
func GetDocuments(location Location, filter interface{}) ([][]byte, error) {
client := GetNewClient()
collection := client.Database(location.Database).Collection(location.Collection)
defer client.Disconnect(context.TODO())
var results [][]byte
cursor, err := collection.Find(context.TODO(), filter)
if err != nil {
return results, errors.New("something is wrong with filter to find the document.")
}
for cursor.TryNext(context.TODO()) {
results = append(results, cursor.Current)
}
return results, nil
}
func GetUniqueDocument(location Location, filter interface{}) ([]byte, error) {
client := GetNewClient()
collection := client.Database(location.Database).Collection(location.Collection)
defer client.Disconnect(context.TODO())
singleResult := collection.FindOne(context.TODO(), filter)
if singleResult.Err() != nil {
return nil, errors.New("no document was found, check the filter or the location.")
}
return singleResult.DecodeBytes()
}
func DeleteDocument(location Location, filter interface{}) (int64, error) {
client := GetNewClient()
collection := client.Database(location.Database).Collection(location.Collection)
defer client.Disconnect(context.TODO())
result, err := collection.DeleteOne(context.TODO(), filter)
if err != nil {
return 0, errors.New("something is wrong with filter to delete the document.")
}
return result.DeletedCount, nil
}
func EditDocument(location Location, jsons DocumentUpdate) (int64, error) {
client := GetNewClient()
collection := client.Database(location.Database).Collection(location.Collection)
defer client.Disconnect(context.TODO())
result, err := collection.UpdateOne(context.TODO(), jsons.Filter, gin.H{"$set": jsons.Update})
if err != nil {
return 0, err
}
if result.MatchedCount == 0 {
return 0, errors.New("cannot find document matching filter.")
}
return result.ModifiedCount, nil
}
+19
View File
@@ -0,0 +1,19 @@
package database
import (
"context"
"errors"
)
func DeleteDocument(location Location, filter interface{}) (int64, error) {
client := GetNewClient()
collection := client.Database(location.Database).Collection(location.Collection)
defer client.Disconnect(context.TODO())
result, err := collection.DeleteOne(context.TODO(), filter)
if err != nil {
return 0, errors.New("something is wrong with filter to delete the document.")
}
return result.DeletedCount, nil
}
+29
View File
@@ -0,0 +1,29 @@
package database
import (
"context"
"errors"
"github.com/gin-gonic/gin"
)
type DocumentUpdate struct {
Filter gin.H `json:"filter"`
Update gin.H `json:"update"`
}
func EditDocument(location Location, jsons DocumentUpdate) (int64, error) {
client := GetNewClient()
collection := client.Database(location.Database).Collection(location.Collection)
defer client.Disconnect(context.TODO())
result, err := collection.UpdateOne(context.TODO(), jsons.Filter, gin.H{"$set": jsons.Update})
if err != nil {
return 0, err
}
if result.MatchedCount == 0 {
return 0, errors.New("cannot find document matching filter.")
}
return result.ModifiedCount, nil
}
+35
View File
@@ -0,0 +1,35 @@
package database
import (
"context"
"errors"
)
func GetDocuments(location Location, filter interface{}) ([][]byte, error) {
client := GetNewClient()
collection := client.Database(location.Database).Collection(location.Collection)
defer client.Disconnect(context.TODO())
var results [][]byte
cursor, err := collection.Find(context.TODO(), filter)
if err != nil {
return results, errors.New("something is wrong with filter to find the document.")
}
for cursor.TryNext(context.TODO()) {
results = append(results, cursor.Current)
}
return results, nil
}
func GetUniqueDocument(location Location, filter interface{}) ([]byte, error) {
client := GetNewClient()
collection := client.Database(location.Database).Collection(location.Collection)
defer client.Disconnect(context.TODO())
singleResult := collection.FindOne(context.TODO(), filter)
if singleResult.Err() != nil {
return nil, errors.New("no document was found, check the filter or the location.")
}
return singleResult.DecodeBytes()
}
+15
View File
@@ -0,0 +1,15 @@
package database
import "context"
func PushDocument(location Location, document interface{}) error {
client := GetNewClient()
collection := client.Database(location.Database).Collection(location.Collection)
defer client.Disconnect(context.TODO())
_, err := collection.InsertOne(context.TODO(), document)
if err != nil {
return err
}
return nil
}