From 264381b340ed9a918dfdc395c85f9116342c5f15 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Mon, 24 Jul 2023 14:28:55 -0400 Subject: [PATCH] =?UTF-8?q?Ajouter=20handlers=20API=20=C3=A0=20serverhandl?= =?UTF-8?q?ers/?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 2 + serverhandlers/serverhandlers.go | 381 +++++++++++++++++++++++++++++++ 2 files changed, 383 insertions(+) create mode 100644 serverhandlers/serverhandlers.go diff --git a/Dockerfile b/Dockerfile index a062281..c5d663e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,6 +18,8 @@ ADD media/ media/ ADD templates/ templates/ +ADD serverhandlers/ serverhandlers/ + RUN CGO_ENABLED=0 go build -a -installsuffix cgo -o agecem-org . # Alpine diff --git a/serverhandlers/serverhandlers.go b/serverhandlers/serverhandlers.go new file mode 100644 index 0000000..eb688b5 --- /dev/null +++ b/serverhandlers/serverhandlers.go @@ -0,0 +1,381 @@ +package serverhandlers + +import ( + "context" + "net/http" + "sort" + + "git.agecem.com/agecem/agecem-org/config" + "git.agecem.com/agecem/agecem-org/media" + "github.com/labstack/echo/v4" + "github.com/minio/minio-go/v7" + "github.com/spf13/viper" +) + +// API Handlers + +// HandleV1 affiche les routes accessibles. +// Les routes sont triées selon .Path, pour les rendre plus facilement navigables. +func HandleV1(c echo.Context) error { + routes := c.Echo().Routes() + sort.Slice(routes, func(i, j int) bool { return routes[i].Path < routes[j].Path }) + return c.JSON(http.StatusOK, routes) +} + +// HandleV1Seed créé des buckets dans minio selon la liste de buckets dans server.documents.buckets +// Les buckets sont créés avec paramètres par défaut, et sont ensuite visible dans /v1/bucket. +func HandleV1Seed(c echo.Context) error { + mediaClient, err := media.NewMediaClientFromViper() + if err != nil { + return c.JSON(http.StatusInternalServerError, map[string]string{ + "message": "Error during media.NewMediaClientFromViper()", + "error": err.Error(), + }) + } + + new_buckets, err := mediaClient.Seed() + if err != nil { + return c.JSON(http.StatusInternalServerError, map[string]string{ + "message": "Error during mediaClient.Seed()", + "error": err.Error(), + }) + } + + var message string + if len(new_buckets) == 0 { + message = "All buckets already exist" + + } else { + message = "Buckets successfully created" + } + + return c.JSON(http.StatusOK, map[string]interface{}{ + "message": message, + "buckets": new_buckets, + }) +} + +// HandleV1BucketList affiche les buckets permis par server.documents.buckets, qui existent. +func HandleV1BucketList(c echo.Context) error { + var cfg config.Config + if err := viper.Unmarshal(&cfg); err != nil { + return c.JSON(http.StatusInternalServerError, map[string]string{ + "error": err.Error(), + }) + } + + mediaClient, err := media.NewMediaClientFromViper() + if err != nil { + return c.JSON(http.StatusInternalServerError, map[string]string{ + "message": "Error during media.NewMediaClientFromViper()", + "error": err.Error(), + }) + } + + var buckets []string + + for _, bucket_name := range cfg.Server.Documents.Buckets { + exists, err := mediaClient.MinioClient.BucketExists(context.Background(), bucket_name) + if err != nil { + return c.JSON(http.StatusInternalServerError, "Error during minio#BucketExists") + } + + if exists { + buckets = append(buckets, bucket_name) + } + } + + return c.JSON(http.StatusOK, buckets) +} + +func HandleV1BucketRead(c echo.Context) error { + var cfg config.Config + if err := viper.Unmarshal(&cfg); err != nil { + return c.JSON(http.StatusInternalServerError, map[string]string{ + "error": err.Error(), + }) + } + + bucket := c.Param("bucket") + + allowed := false + for _, bucket_allowed := range cfg.Server.Documents.Buckets { + if bucket == bucket_allowed { + allowed = true + } + } + + if !allowed { + /* + return c.JSON(http.StatusBadRequest, map[string]string{ + "message": "Bucket is not allowed in server.documents.buckets", + }) + */ + return c.JSON(http.StatusNotFound, map[string]string{"message": "Not Found"}) + } + + ctx, cancel := context.WithCancel(context.Background()) + + defer cancel() + + mediaClient, err := media.NewMediaClientFromViper() + if err != nil { + return c.JSON(http.StatusInternalServerError, map[string]string{ + "message": "Error during media.NewMediaClientFromViper()", + "error": err.Error(), + }) + } + + exists, err := mediaClient.MinioClient.BucketExists(ctx, bucket) + if err != nil { + return c.JSON(http.StatusInternalServerError, "Error during minio#BucketExists") + } + + if !exists { + return c.JSON(http.StatusNotFound, map[string]string{"message": "Not Found"}) + } + + var keys []string + + objectCh := mediaClient.MinioClient.ListObjects(ctx, bucket, minio.ListObjectsOptions{}) + for object := range objectCh { + if object.Err != nil { + return c.JSON(http.StatusInternalServerError, map[string]string{ + "message": "Error during minio#ListObjects", + }) + } + + keys = append(keys, object.Key) + } + + return c.JSON(http.StatusOK, keys) +} + +// HandleV1DocumentCreate permet d'ajouter un object dans un bucket, par multipart/form-data +func HandleV1DocumentCreate(c echo.Context) error { + var cfg config.Config + if err := viper.Unmarshal(&cfg); err != nil { + return c.JSON(http.StatusInternalServerError, map[string]string{ + "error": err.Error(), + }) + } + + bucket := c.Param("bucket") + + form_file, err := c.FormFile("document") + if err != nil { + return c.JSON(http.StatusBadRequest, map[string]interface{}{ + "message": "Error during HandleV1DocumentCreate's echo#Context.FormFile", + "error": err, + }) + } + + allowed := false + for _, bucket_allowed := range cfg.Server.Documents.Buckets { + if bucket == bucket_allowed { + allowed = true + } + } + + if !allowed { + return c.JSON(http.StatusNotFound, map[string]string{"message": "Not Found"}) + } + + ctx, cancel := context.WithCancel(context.Background()) + + defer cancel() + + mediaClient, err := media.NewMediaClientFromViper() + if err != nil { + return c.JSON(http.StatusInternalServerError, map[string]string{ + "message": "Error during media.NewMediaClientFromViper()", + "error": err.Error(), + }) + } + + src, err := form_file.Open() + if err != nil { + return err + } + defer src.Close() + + /* + reg, err := regexp.Compile("[^.a-zA-Z0-9_-]+") + if err != nil { + return c.Render(http.StatusInternalServerError, "documentation-html", nil) + } + + filename_processed := reg.ReplaceAllString(form_file.Filename, "") + */ + + info, err := mediaClient.MinioClient.PutObject(ctx, bucket, form_file.Filename, src, form_file.Size, minio.PutObjectOptions{ + ContentType: form_file.Header.Get("Content-Type"), + }) + if err != nil { + return c.JSON(http.StatusInternalServerError, map[string]string{ + "message": "Error during minio#PutObject", + }) + } + + return c.JSON(http.StatusOK, map[string]interface{}{ + "message": "ok", + "info": map[string]interface{}{ + "bucket": info.Bucket, + "key": info.Key, + "size": info.Size, + }, + }) +} + +// HandleV1DocumentRead permet de lire le contenu d'un fichier et protentiellement de le télécharger +func HandleV1DocumentRead(c echo.Context) error { + var cfg config.Config + if err := viper.Unmarshal(&cfg); err != nil { + return c.JSON(http.StatusInternalServerError, map[string]string{ + "error": err.Error(), + }) + } + + bucket := c.Param("bucket") + document := c.Param("document") + + allowed := false + for _, bucket_allowed := range cfg.Server.Documents.Buckets { + if bucket == bucket_allowed { + allowed = true + } + } + + if !allowed { + return c.JSON(http.StatusNotFound, map[string]string{"message": "Not Found"}) + } + + ctx, cancel := context.WithCancel(context.Background()) + + defer cancel() + + mediaClient, err := media.NewMediaClientFromViper() + if err != nil { + return c.JSON(http.StatusInternalServerError, map[string]string{ + "message": "Error during media.NewMediaClientFromViper()", + "error": err.Error(), + }) + } + + bucket_exists, err := mediaClient.MinioClient.BucketExists(ctx, bucket) + if err != nil { + return c.JSON(http.StatusInternalServerError, "Error during minio#BucketExists") + } + + if !bucket_exists { + return c.JSON(http.StatusNotFound, map[string]string{"message": "Not Found"}) + } + + document_info, err := mediaClient.MinioClient.StatObject(ctx, bucket, document, minio.StatObjectOptions{}) + + if err != nil { + if err.Error() == "The specified key does not exist." { + + return c.JSON(http.StatusNotFound, map[string]string{"message": "Not Found"}) + } + + return c.JSON(http.StatusInternalServerError, map[string]interface{}{ + "message": "Error during minio#StatObject", + }) + } + + _ = document_info + + document_object, err := mediaClient.MinioClient.GetObject(ctx, bucket, document, minio.GetObjectOptions{}) + if err != nil { + return c.JSON(http.StatusInternalServerError, map[string]string{ + "message": "Error during minio#GetObject", + }) + } + + defer document_object.Close() + + return c.Stream(http.StatusOK, document_info.ContentType, document_object) +} + +// HandleV1DocumentUpdate permet de mettre à jour certains champs d'un object, comme le Content-Type ou le Filename +func HandleV1DocumentUpdate(c echo.Context) error { + return c.JSON(http.StatusNotImplemented, map[string]string{ + "message": "Not Implemented", + }) +} + +// HandleV1DocumentDelete permet de supprimer un object +func HandleV1DocumentDelete(c echo.Context) error { + var cfg config.Config + if err := viper.Unmarshal(&cfg); err != nil { + return c.JSON(http.StatusInternalServerError, map[string]string{ + "error": err.Error(), + }) + } + bucket := c.Param("bucket") + document := c.Param("document") + + allowed := false + for _, bucket_allowed := range cfg.Server.Documents.Buckets { + if bucket == bucket_allowed { + allowed = true + } + } + + if !allowed { + /* + return c.JSON(http.StatusBadRequest, map[string]string{ + "message": "Bucket is not allowed in server.documents.buckets", + }) + */ + return c.JSON(http.StatusNotFound, map[string]string{"message": "Not Found"}) + } + + ctx, cancel := context.WithCancel(context.Background()) + + defer cancel() + + mediaClient, err := media.NewMediaClientFromViper() + if err != nil { + return c.JSON(http.StatusInternalServerError, map[string]string{ + "message": "Error during media.NewMediaClientFromViper()", + "error": err.Error(), + }) + } + + bucket_exists, err := mediaClient.MinioClient.BucketExists(ctx, bucket) + if err != nil { + return c.JSON(http.StatusInternalServerError, "Error during minio#BucketExists") + } + + if !bucket_exists { + return c.JSON(http.StatusNotFound, map[string]string{"message": "Not Found"}) + } + + document_info, err := mediaClient.MinioClient.StatObject(ctx, bucket, document, minio.StatObjectOptions{}) + if err != nil { + if err.Error() == "The specified key does not exist." { + + return c.JSON(http.StatusNotFound, map[string]string{"message": "Not Found"}) + } + + return c.JSON(http.StatusInternalServerError, map[string]interface{}{ + "message": "Error during minio#StatObject", + }) + } + + //TODO Add error validation + _ = document_info + + err = mediaClient.MinioClient.RemoveObject(ctx, bucket, document, minio.RemoveObjectOptions{}) + if err != nil { + return c.JSON(http.StatusInternalServerError, map[string]string{ + "message": "Error during minio#RemoveObject", + }) + } + + return c.JSON(http.StatusOK, map[string]string{ + "message": "Document deleted", + }) +}