Compare commits
No commits in common. "d08e9ffddd2980611e5573b60fa97f6bba47bcad" and "339feb2e52b0a4b6b2d209d2756b6a9688899c93" have entirely different histories.
d08e9ffddd
...
339feb2e52
18 changed files with 715 additions and 1112 deletions
|
@ -1,4 +1,4 @@
|
||||||
FROM golang:1.21.1 as build
|
FROM golang:1.21.0 as build
|
||||||
|
|
||||||
LABEL author="Victor Lacasse-Beaudoin <vlbeaudoin@agecem.org>"
|
LABEL author="Victor Lacasse-Beaudoin <vlbeaudoin@agecem.org>"
|
||||||
|
|
||||||
|
@ -12,17 +12,13 @@ ADD cmd/ cmd/
|
||||||
|
|
||||||
ADD api/ api/
|
ADD api/ api/
|
||||||
|
|
||||||
ADD api_handlers/ api_handlers/
|
|
||||||
|
|
||||||
ADD config/ config/
|
ADD config/ config/
|
||||||
|
|
||||||
ADD media/ media/
|
ADD media/ media/
|
||||||
|
|
||||||
ADD models/ models/
|
|
||||||
|
|
||||||
ADD templates/ templates/
|
ADD templates/ templates/
|
||||||
|
|
||||||
ADD web_handlers/ web_handlers/
|
ADD serverhandlers/ serverhandlers/
|
||||||
|
|
||||||
RUN CGO_ENABLED=0 go build -a -installsuffix cgo -o agecem-org .
|
RUN CGO_ENABLED=0 go build -a -installsuffix cgo -o agecem-org .
|
||||||
|
|
||||||
|
|
18
README.md
18
README.md
|
@ -44,21 +44,3 @@ Voir les logs des containers
|
||||||
Détruire les containers
|
Détruire les containers
|
||||||
|
|
||||||
`$ docker-compose down`
|
`$ docker-compose down`
|
||||||
|
|
||||||
### Exemple de développement sans base de données ou docker
|
|
||||||
|
|
||||||
Pour un environnement simplifié sans minio ou docker, seul le toolchain `go` devrait être nécessaire au démarrage du serveur.
|
|
||||||
|
|
||||||
Pour une exécution sans installation permanente, veuillez utiliser:
|
|
||||||
|
|
||||||
`$ go run . server`
|
|
||||||
|
|
||||||
Si nécessaire, un fichier de config peut être déposé dans `$HOME/.agecem-org.yaml` ou spécifié tel que:
|
|
||||||
|
|
||||||
`$ go run . server --config agecem-org.yaml`
|
|
||||||
|
|
||||||
où `agecem-org.yaml` doit être remplacé par le fichier de config désiré.
|
|
||||||
|
|
||||||
Pour un exemple de fichier de config en format JSON, voir le résultat de:
|
|
||||||
|
|
||||||
`go run . config`
|
|
||||||
|
|
58
api/api.go
58
api/api.go
|
@ -6,12 +6,12 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"git.agecem.com/agecem/agecem-org/config"
|
"git.agecem.com/agecem/agecem-org/config"
|
||||||
"git.agecem.com/agecem/agecem-org/models"
|
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -30,9 +30,17 @@ type APIOptions struct {
|
||||||
Password string
|
Password string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewApiClientFromViper returns a pointer to a new API object,
|
type UploadDocumentResponse struct {
|
||||||
// provided the configuration options are managed by
|
Info UploadDocumentResponseInfo `json:"info"`
|
||||||
// https://git.agecem.com/agecem/agecem-org/config
|
Message string `json:"message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UploadDocumentResponseInfo struct {
|
||||||
|
Bucket string `json:"bucket"`
|
||||||
|
Object string `json:"key"`
|
||||||
|
Size float64 `json:"size"`
|
||||||
|
}
|
||||||
|
|
||||||
func NewApiClientFromViper() (*API, error) {
|
func NewApiClientFromViper() (*API, error) {
|
||||||
var config config.Config
|
var config config.Config
|
||||||
|
|
||||||
|
@ -99,7 +107,7 @@ func (a *API) Call(method, route string) ([]byte, error) {
|
||||||
|
|
||||||
defer response.Body.Close()
|
defer response.Body.Close()
|
||||||
|
|
||||||
body, err := io.ReadAll(response.Body)
|
body, err := ioutil.ReadAll(response.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -127,7 +135,7 @@ func (a *API) Call(method, route string) ([]byte, error) {
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
// Read Response Body
|
// Read Response Body
|
||||||
respBody, err := io.ReadAll(resp.Body)
|
respBody, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -138,8 +146,7 @@ func (a *API) Call(method, route string) ([]byte, error) {
|
||||||
return nil, errors.New(fmt.Sprintf("method must be 'GET' or 'DELETE', got '%s'", method))
|
return nil, errors.New(fmt.Sprintf("method must be 'GET' or 'DELETE', got '%s'", method))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *API) UploadDocument(bucket string, file_header *multipart.FileHeader) (models.V1DocumentCreateResponse, error) {
|
func (a *API) UploadDocument(bucket string, file_header *multipart.FileHeader) (UploadDocumentResponse, error) {
|
||||||
var response models.V1DocumentCreateResponse
|
|
||||||
endpoint := fmt.Sprintf("%s://%s:%d",
|
endpoint := fmt.Sprintf("%s://%s:%d",
|
||||||
a.Protocol,
|
a.Protocol,
|
||||||
a.Host,
|
a.Host,
|
||||||
|
@ -155,34 +162,34 @@ func (a *API) UploadDocument(bucket string, file_header *multipart.FileHeader) (
|
||||||
// Add the file to the request
|
// Add the file to the request
|
||||||
file, err := file_header.Open()
|
file, err := file_header.Open()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response, fmt.Errorf("UploadDocument#file_header.Open: %s", err)
|
return UploadDocumentResponse{}, fmt.Errorf("UploadDocument#file_header.Open: %s", err)
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
filename_processed, err := url.QueryUnescape(file_header.Filename)
|
filename_processed, err := url.QueryUnescape(file_header.Filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response, fmt.Errorf("UploadDocument#url.QueryUnescape: %s", err)
|
return UploadDocumentResponse{}, fmt.Errorf("UploadDocument#url.QueryUnescape: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
part, err := writer.CreateFormFile("document", filename_processed)
|
part, err := writer.CreateFormFile("document", filename_processed)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response, fmt.Errorf("UploadDocument#writer.CreateFormFile: %s", err)
|
return UploadDocumentResponse{}, fmt.Errorf("UploadDocument#writer.CreateFormFile: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = io.Copy(part, file)
|
_, err = io.Copy(part, file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response, fmt.Errorf("UploadDocument#io.Copy: %s", err)
|
return UploadDocumentResponse{}, fmt.Errorf("UploadDocument#io.Copy: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = writer.Close()
|
err = writer.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response, fmt.Errorf("UploadDocument#writer.Close: %s", err)
|
return UploadDocumentResponse{}, fmt.Errorf("UploadDocument#writer.Close: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new HTTP request with the multipart body
|
// Create a new HTTP request with the multipart body
|
||||||
req, err := http.NewRequest(http.MethodPost, current_url, body)
|
req, err := http.NewRequest(http.MethodPost, current_url, body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response, fmt.Errorf("UploadDocument#http.NewRequest: %s", err)
|
return UploadDocumentResponse{}, fmt.Errorf("UploadDocument#http.NewRequest: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Set("Content-Type", writer.FormDataContentType())
|
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||||
|
@ -195,12 +202,15 @@ func (a *API) UploadDocument(bucket string, file_header *multipart.FileHeader) (
|
||||||
client := &http.Client{}
|
client := &http.Client{}
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return response, fmt.Errorf("UploadDocument#client.Do: %s", err)
|
return UploadDocumentResponse{}, fmt.Errorf("UploadDocument#client.Do: %s", err)
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
err = json.NewDecoder(resp.Body).Decode(&response)
|
// Handle the response
|
||||||
return response, err
|
var res UploadDocumentResponse
|
||||||
|
|
||||||
|
json.NewDecoder(resp.Body).Decode(&res)
|
||||||
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// CallWithData takes data and returns a string representing a response body.
|
// CallWithData takes data and returns a string representing a response body.
|
||||||
|
@ -271,17 +281,3 @@ func (a *API) CallWithData(method, route string, data []byte) (string, error) {
|
||||||
//return "", errors.New(fmt.Sprintf("method must be 'POST' or 'PUT', got '%s'", method))
|
//return "", errors.New(fmt.Sprintf("method must be 'POST' or 'PUT', got '%s'", method))
|
||||||
return "", errors.New(fmt.Sprintf("method must be 'POST', got '%s'", method))
|
return "", errors.New(fmt.Sprintf("method must be 'POST', got '%s'", method))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *API) ListBuckets() (models.V1BucketListResponse, error) {
|
|
||||||
var response models.V1BucketListResponse
|
|
||||||
result, err := a.Call(http.MethodGet, "/v1/bucket")
|
|
||||||
if err != nil {
|
|
||||||
return response, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = json.Unmarshal(result, &response); err != nil {
|
|
||||||
return response, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return response, nil
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,313 +0,0 @@
|
||||||
package api_handlers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net/http"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"git.agecem.com/agecem/agecem-org/config"
|
|
||||||
"git.agecem.com/agecem/agecem-org/media"
|
|
||||||
"git.agecem.com/agecem/agecem-org/models"
|
|
||||||
"github.com/labstack/echo/v4"
|
|
||||||
"github.com/minio/minio-go/v7"
|
|
||||||
)
|
|
||||||
|
|
||||||
type V1Handler struct {
|
|
||||||
Config config.Config
|
|
||||||
MediaClient *media.MediaClient
|
|
||||||
}
|
|
||||||
|
|
||||||
// API Handlers
|
|
||||||
|
|
||||||
// HandleV1 affiche les routes accessibles.
|
|
||||||
// Les routes sont triées selon .Path, pour les rendre plus facilement navigables.
|
|
||||||
func (h *V1Handler) 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 (h *V1Handler) HandleV1Seed(c echo.Context) error {
|
|
||||||
var response models.V1SeedResponse
|
|
||||||
|
|
||||||
new_buckets, err := h.MediaClient.Seed()
|
|
||||||
response.Data.Buckets = new_buckets
|
|
||||||
if err != nil {
|
|
||||||
response.StatusCode = http.StatusInternalServerError
|
|
||||||
response.Message = "Error during mediaClient.Seed()"
|
|
||||||
response.Error = err.Error()
|
|
||||||
|
|
||||||
return c.JSON(response.StatusCode, response)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(new_buckets) == 0 {
|
|
||||||
response.Message = "All buckets already exist"
|
|
||||||
|
|
||||||
} else {
|
|
||||||
response.Message = "Buckets successfully created"
|
|
||||||
}
|
|
||||||
|
|
||||||
response.StatusCode = http.StatusOK
|
|
||||||
|
|
||||||
return c.JSON(response.StatusCode, response)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleV1BucketList affiche les buckets permis par server.documents.buckets, qui existent.
|
|
||||||
func (h *V1Handler) HandleV1BucketList(c echo.Context) error {
|
|
||||||
var response models.V1BucketListResponse
|
|
||||||
|
|
||||||
var buckets = make(map[string]string)
|
|
||||||
|
|
||||||
for bucket_name, bucket_display_name := range h.Config.Server.Documents.Buckets {
|
|
||||||
exists, err := h.MediaClient.MinioClient.BucketExists(context.Background(), bucket_name)
|
|
||||||
if err != nil {
|
|
||||||
response.StatusCode = http.StatusInternalServerError
|
|
||||||
response.Message = "Error during minio#BucketExists"
|
|
||||||
// response.Error = err.Error()
|
|
||||||
|
|
||||||
return c.JSON(response.StatusCode, response)
|
|
||||||
}
|
|
||||||
|
|
||||||
if exists {
|
|
||||||
buckets[bucket_name] = bucket_display_name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
response.StatusCode = http.StatusOK
|
|
||||||
response.Message = "Buckets list successful"
|
|
||||||
response.Data.Buckets = buckets
|
|
||||||
|
|
||||||
return c.JSON(response.StatusCode, response)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *V1Handler) HandleV1BucketRead(c echo.Context) error {
|
|
||||||
var response models.V1BucketReadResponse
|
|
||||||
|
|
||||||
bucket := c.Param("bucket")
|
|
||||||
|
|
||||||
allowed := false
|
|
||||||
for bucket_allowed := range h.Config.Server.Documents.Buckets {
|
|
||||||
if bucket == bucket_allowed {
|
|
||||||
allowed = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !allowed {
|
|
||||||
return c.JSON(models.NotFoundResponse())
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
exists, err := h.MediaClient.MinioClient.BucketExists(ctx, bucket)
|
|
||||||
if err != nil {
|
|
||||||
response.StatusCode = http.StatusInternalServerError
|
|
||||||
response.Message = "Error during minio#BucketExists"
|
|
||||||
response.Error = err.Error()
|
|
||||||
|
|
||||||
return c.JSON(response.StatusCode, response)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !exists {
|
|
||||||
return c.JSON(models.NotFoundResponse())
|
|
||||||
}
|
|
||||||
|
|
||||||
objectCh := h.MediaClient.MinioClient.ListObjects(ctx, bucket, minio.ListObjectsOptions{})
|
|
||||||
for object := range objectCh {
|
|
||||||
if object.Err != nil {
|
|
||||||
response.StatusCode = http.StatusInternalServerError
|
|
||||||
response.Message = "Error during minio#ListObjects"
|
|
||||||
//TODO make sure this is safe
|
|
||||||
//response.Error = object.Err.Error()
|
|
||||||
|
|
||||||
return c.JSON(response.StatusCode, response)
|
|
||||||
}
|
|
||||||
|
|
||||||
response.Data.Keys = append(response.Data.Keys, object.Key)
|
|
||||||
}
|
|
||||||
|
|
||||||
response.StatusCode = http.StatusOK
|
|
||||||
response.Message = "V1BucketRead ok"
|
|
||||||
|
|
||||||
return c.JSON(response.StatusCode, response)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleV1DocumentCreate permet d'ajouter un object dans un bucket, par multipart/form-data
|
|
||||||
func (h *V1Handler) HandleV1DocumentCreate(c echo.Context) error {
|
|
||||||
var response models.V1DocumentCreateResponse
|
|
||||||
|
|
||||||
bucket := c.Param("bucket")
|
|
||||||
|
|
||||||
form_file, err := c.FormFile("document")
|
|
||||||
if err != nil {
|
|
||||||
response.StatusCode = http.StatusBadRequest
|
|
||||||
response.Message = "Error during HandleV1DocumentCreate's echo#Context.FormFile"
|
|
||||||
response.Error = err.Error()
|
|
||||||
|
|
||||||
return c.JSON(response.StatusCode, response)
|
|
||||||
}
|
|
||||||
|
|
||||||
allowed := false
|
|
||||||
for bucket_allowed := range h.Config.Server.Documents.Buckets {
|
|
||||||
if bucket == bucket_allowed {
|
|
||||||
allowed = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !allowed {
|
|
||||||
return c.JSON(models.NotFoundResponse())
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
src, err := form_file.Open()
|
|
||||||
if err != nil {
|
|
||||||
response.StatusCode = http.StatusBadRequest
|
|
||||||
response.Message = "Error during form_file.Open()"
|
|
||||||
response.Error = err.Error()
|
|
||||||
|
|
||||||
return c.JSON(response.StatusCode, response)
|
|
||||||
}
|
|
||||||
defer src.Close()
|
|
||||||
|
|
||||||
info, err := h.MediaClient.MinioClient.PutObject(ctx, bucket, form_file.Filename, src, form_file.Size, minio.PutObjectOptions{
|
|
||||||
ContentType: form_file.Header.Get("Content-Type"),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
response.StatusCode = http.StatusInternalServerError
|
|
||||||
response.Message = "Error during minio#PutObject"
|
|
||||||
//response.Error = err.Error()
|
|
||||||
|
|
||||||
return c.JSON(response.StatusCode, response)
|
|
||||||
}
|
|
||||||
|
|
||||||
response.StatusCode = http.StatusOK
|
|
||||||
response.Message = "ok"
|
|
||||||
response.Data.Bucket = info.Bucket
|
|
||||||
response.Data.Key = info.Key
|
|
||||||
response.Data.Size = info.Size
|
|
||||||
|
|
||||||
return c.JSON(response.StatusCode, response)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleV1DocumentRead permet de lire le contenu d'un fichier et protentiellement de le télécharger
|
|
||||||
func (h *V1Handler) HandleV1DocumentRead(c echo.Context) error {
|
|
||||||
bucket := c.Param("bucket")
|
|
||||||
document := c.Param("document")
|
|
||||||
|
|
||||||
allowed := false
|
|
||||||
for bucket_allowed := range h.Config.Server.Documents.Buckets {
|
|
||||||
if bucket == bucket_allowed {
|
|
||||||
allowed = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !allowed {
|
|
||||||
return c.JSON(models.NotFoundResponse())
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
bucket_exists, err := h.MediaClient.MinioClient.BucketExists(ctx, bucket)
|
|
||||||
if err != nil {
|
|
||||||
return c.JSON(http.StatusInternalServerError, "Error during minio#BucketExists")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !bucket_exists {
|
|
||||||
return c.JSON(models.NotFoundResponse())
|
|
||||||
}
|
|
||||||
|
|
||||||
document_info, err := h.MediaClient.MinioClient.StatObject(ctx, bucket, document, minio.StatObjectOptions{})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
if err.Error() == "The specified key does not exist." {
|
|
||||||
|
|
||||||
return c.JSON(models.NotFoundResponse())
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{
|
|
||||||
"message": "Error during minio#StatObject",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = document_info
|
|
||||||
|
|
||||||
document_object, err := h.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 (h *V1Handler) HandleV1DocumentUpdate(c echo.Context) error {
|
|
||||||
return c.JSON(models.NotImplementedResponse())
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleV1DocumentDelete permet de supprimer un object
|
|
||||||
func (h *V1Handler) HandleV1DocumentDelete(c echo.Context) error {
|
|
||||||
bucket := c.Param("bucket")
|
|
||||||
document := c.Param("document")
|
|
||||||
|
|
||||||
allowed := false
|
|
||||||
for bucket_allowed := range h.Config.Server.Documents.Buckets {
|
|
||||||
if bucket == bucket_allowed {
|
|
||||||
allowed = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !allowed {
|
|
||||||
return c.JSON(models.NotFoundResponse())
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
|
||||||
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
bucket_exists, err := h.MediaClient.MinioClient.BucketExists(ctx, bucket)
|
|
||||||
if err != nil {
|
|
||||||
return c.JSON(http.StatusInternalServerError, "Error during minio#BucketExists")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !bucket_exists {
|
|
||||||
return c.JSON(models.NotFoundResponse())
|
|
||||||
}
|
|
||||||
|
|
||||||
document_info, err := h.MediaClient.MinioClient.StatObject(ctx, bucket, document, minio.StatObjectOptions{})
|
|
||||||
if err != nil {
|
|
||||||
if err.Error() == "The specified key does not exist." {
|
|
||||||
|
|
||||||
return c.JSON(models.NotFoundResponse())
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.JSON(http.StatusInternalServerError, map[string]interface{}{
|
|
||||||
"message": "Error during minio#StatObject",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO Add error validation
|
|
||||||
_ = document_info
|
|
||||||
|
|
||||||
err = h.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",
|
|
||||||
})
|
|
||||||
}
|
|
322
cmd/server.go
322
cmd/server.go
|
@ -5,8 +5,10 @@ package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/subtle"
|
"crypto/subtle"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"sort"
|
||||||
|
|
||||||
"embed"
|
"embed"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
@ -17,12 +19,11 @@ import (
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
|
||||||
"git.agecem.com/agecem/agecem-org/api"
|
"git.agecem.com/agecem/agecem-org/api"
|
||||||
"git.agecem.com/agecem/agecem-org/api_handlers"
|
|
||||||
"git.agecem.com/agecem/agecem-org/config"
|
"git.agecem.com/agecem/agecem-org/config"
|
||||||
"git.agecem.com/agecem/agecem-org/media"
|
"git.agecem.com/agecem/agecem-org/media"
|
||||||
"git.agecem.com/agecem/agecem-org/public"
|
"git.agecem.com/agecem/agecem-org/public"
|
||||||
|
"git.agecem.com/agecem/agecem-org/serverhandlers"
|
||||||
"git.agecem.com/agecem/agecem-org/templates"
|
"git.agecem.com/agecem/agecem-org/templates"
|
||||||
"git.agecem.com/agecem/agecem-org/web_handlers"
|
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"github.com/labstack/echo/v4/middleware"
|
"github.com/labstack/echo/v4/middleware"
|
||||||
)
|
)
|
||||||
|
@ -48,17 +49,16 @@ var serverCmd = &cobra.Command{
|
||||||
}
|
}
|
||||||
|
|
||||||
mediaClient, err := media.NewMediaClientFromViper()
|
mediaClient, err := media.NewMediaClientFromViper()
|
||||||
switch err != nil {
|
if err != nil {
|
||||||
case true:
|
log.Fatal(err)
|
||||||
log.Printf("media.NewMediaClientFromViper error: %s", err)
|
}
|
||||||
case false:
|
|
||||||
new_buckets, err := mediaClient.Seed()
|
new_buckets, err := mediaClient.Seed()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("(*media.MediaClient).Seed error: %s", err)
|
log.Fatal(err)
|
||||||
} else {
|
}
|
||||||
|
|
||||||
log.Printf("Seeded %d buckets.\n", len(new_buckets))
|
log.Printf("Seeded %d buckets.\n", len(new_buckets))
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RunServer()
|
RunServer()
|
||||||
},
|
},
|
||||||
|
@ -199,69 +199,52 @@ func RunServer() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// API Routes
|
// API Routes
|
||||||
mediaClient, err := media.NewMediaClientFromViper()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Error during NewMediaClientFromViper for API handlers")
|
|
||||||
}
|
|
||||||
|
|
||||||
v1Handler := api_handlers.V1Handler{
|
groupV1.GET("", serverhandlers.HandleV1)
|
||||||
Config: cfg,
|
|
||||||
MediaClient: mediaClient,
|
|
||||||
}
|
|
||||||
|
|
||||||
groupV1.GET("", v1Handler.HandleV1)
|
groupV1.POST("/seed", serverhandlers.HandleV1Seed)
|
||||||
|
|
||||||
groupV1.POST("/seed", v1Handler.HandleV1Seed)
|
groupV1.GET("/bucket", serverhandlers.HandleV1BucketList)
|
||||||
|
|
||||||
groupV1.GET("/bucket", v1Handler.HandleV1BucketList)
|
groupV1.GET("/bucket/:bucket", serverhandlers.HandleV1BucketRead)
|
||||||
|
|
||||||
groupV1.GET("/bucket/:bucket", v1Handler.HandleV1BucketRead)
|
groupV1.POST("/bucket/:bucket", serverhandlers.HandleV1DocumentCreate)
|
||||||
|
|
||||||
groupV1.POST("/bucket/:bucket", v1Handler.HandleV1DocumentCreate)
|
groupV1.GET("/bucket/:bucket/:document", serverhandlers.HandleV1DocumentRead)
|
||||||
|
|
||||||
groupV1.GET("/bucket/:bucket/:document", v1Handler.HandleV1DocumentRead)
|
groupV1.PUT("/bucket/:bucket/:document", serverhandlers.HandleV1DocumentUpdate)
|
||||||
|
|
||||||
groupV1.PUT("/bucket/:bucket/:document", v1Handler.HandleV1DocumentUpdate)
|
groupV1.DELETE("/bucket/:bucket/:document", serverhandlers.HandleV1DocumentDelete)
|
||||||
|
|
||||||
groupV1.DELETE("/bucket/:bucket/:document", v1Handler.HandleV1DocumentDelete)
|
|
||||||
|
|
||||||
// HTML Routes
|
// HTML Routes
|
||||||
apiClient, err := api.NewApiClientFromViper()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Error during NewMediaClientFromViper for API handlers")
|
|
||||||
}
|
|
||||||
|
|
||||||
webHandler := web_handlers.WebHandler{
|
e.GET("/", handleIndex)
|
||||||
ApiClient: apiClient,
|
|
||||||
}
|
|
||||||
|
|
||||||
e.GET("/", web_handlers.HandleIndex)
|
//e.GET("/a-propos", handleAPropos)
|
||||||
|
|
||||||
//e.GET("/a-propos", web_handlers.HandleAPropos)
|
//e.GET("/actualite", handleActualite)
|
||||||
|
|
||||||
//e.GET("/actualite", web_handlers.HandleActualite)
|
//e.GET("/actualite/:article", handleActualiteArticle)
|
||||||
|
|
||||||
//e.GET("/actualite/:article", web_handlers.HandleActualiteArticle)
|
e.GET("/vie-etudiante", handleVieEtudiante)
|
||||||
|
|
||||||
e.GET("/vie-etudiante", web_handlers.HandleVieEtudiante)
|
e.GET("/vie-etudiante/:organisme", handleVieEtudianteOrganisme)
|
||||||
|
|
||||||
e.GET("/vie-etudiante/:organisme", web_handlers.HandleVieEtudianteOrganisme)
|
e.GET("/documentation", handleDocumentation)
|
||||||
|
|
||||||
e.GET("/documentation", webHandler.HandleDocumentation)
|
e.GET("/formulaires", handleFormulaires)
|
||||||
|
|
||||||
e.GET("/formulaires", web_handlers.HandleFormulaires)
|
|
||||||
|
|
||||||
// Public Routes
|
// Public Routes
|
||||||
|
|
||||||
e.GET("/public/documentation/:bucket/:document", webHandler.HandlePublicDocumentation)
|
e.GET("/public/documentation/:bucket/:document", handlePublicDocumentation)
|
||||||
|
|
||||||
// Admin Routes
|
// Admin Routes
|
||||||
|
|
||||||
groupAdmin.GET("", web_handlers.HandleAdmin)
|
groupAdmin.GET("", handleAdmin)
|
||||||
|
|
||||||
groupAdmin.GET("/documents/upload", webHandler.HandleAdminDocumentsUpload)
|
groupAdmin.GET("/documents/upload", handleAdminDocumentsUpload)
|
||||||
|
|
||||||
groupAdmin.POST("/documents/upload", webHandler.HandleAdminDocumentsUploadPOST)
|
groupAdmin.POST("/documents/upload", handleAdminDocumentsUploadPOST)
|
||||||
|
|
||||||
e.Logger.Fatal(e.Start(
|
e.Logger.Fatal(e.Start(
|
||||||
fmt.Sprintf(":%d", cfg.Server.Port)))
|
fmt.Sprintf(":%d", cfg.Server.Port)))
|
||||||
|
@ -270,3 +253,248 @@ func RunServer() {
|
||||||
func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
|
func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
|
||||||
return t.templates.ExecuteTemplate(w, name, data)
|
return t.templates.ExecuteTemplate(w, name, data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HTML Handlers
|
||||||
|
|
||||||
|
func handleIndex(c echo.Context) error {
|
||||||
|
return c.Render(http.StatusOK, "index-html", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
func handleAPropos(c echo.Context) error {
|
||||||
|
return c.Render(http.StatusOK, "a-propos-html", nil)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
func handleActualite(c echo.Context) error {
|
||||||
|
return c.Render(http.StatusOK, "actualite-html", nil)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
func handleActualiteArticle(c echo.Context) error {
|
||||||
|
article := c.Param("article")
|
||||||
|
return c.String(http.StatusOK, fmt.Sprintf("Article: %s", article))
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
func handleVieEtudiante(c echo.Context) error {
|
||||||
|
return c.Render(http.StatusOK, "vie-etudiante-html", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleVieEtudianteOrganisme(c echo.Context) error {
|
||||||
|
organisme := c.Param("organisme")
|
||||||
|
return c.String(http.StatusOK, fmt.Sprintf("Organisme: %s", organisme))
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleDocumentation(c echo.Context) error {
|
||||||
|
client, err := api.NewApiClientFromViper()
|
||||||
|
if err != nil {
|
||||||
|
return c.Render(http.StatusInternalServerError, "documentation-html", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := client.Call(http.MethodGet, "/v1/bucket")
|
||||||
|
if err != nil {
|
||||||
|
return c.Render(http.StatusInternalServerError, "documentation-html", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
var buckets map[string]string
|
||||||
|
|
||||||
|
err = json.Unmarshal(result, &buckets)
|
||||||
|
if err != nil {
|
||||||
|
return c.Render(http.StatusInternalServerError, "documentation-html", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Bucket struct {
|
||||||
|
Name string
|
||||||
|
DisplayName string
|
||||||
|
Documents []string
|
||||||
|
}
|
||||||
|
|
||||||
|
var data []Bucket
|
||||||
|
|
||||||
|
for bucket, displayName := range buckets {
|
||||||
|
content, err := client.Call(http.MethodGet, fmt.Sprintf("/v1/bucket/%s", bucket))
|
||||||
|
if err != nil {
|
||||||
|
return c.Render(http.StatusInternalServerError, "documentation-html", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
var documents []string
|
||||||
|
|
||||||
|
err = json.Unmarshal(content, &documents)
|
||||||
|
if err != nil {
|
||||||
|
return c.Render(http.StatusInternalServerError, "documentation-html", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ce bloc retire tous les caractères spéciaux d'une string
|
||||||
|
// N'est pas présentement activé, car les fichiers sont processed
|
||||||
|
// à la création de toute façon.
|
||||||
|
/*
|
||||||
|
reg, err := regexp.Compile("[^.a-zA-Z0-9_-]+")
|
||||||
|
if err != nil {
|
||||||
|
return c.Render(http.StatusInternalServerError, "documentation-html", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
var documents_processed []string
|
||||||
|
|
||||||
|
for _, document := range documents {
|
||||||
|
document_processed := reg.ReplaceAllString(document, "")
|
||||||
|
documents_processed = append(documents_processed, document_processed)
|
||||||
|
}
|
||||||
|
documents_processed := documents
|
||||||
|
*/
|
||||||
|
|
||||||
|
data = append(data, Bucket{
|
||||||
|
Name: bucket,
|
||||||
|
DisplayName: displayName,
|
||||||
|
Documents: documents,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.SliceStable(data, func(i, j int) bool { return data[i].Name < data[j].Name })
|
||||||
|
|
||||||
|
return c.Render(http.StatusOK, "documentation-html", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleFormulaires(c echo.Context) error {
|
||||||
|
return c.Render(http.StatusOK, "formulaires-html", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handlePublicDocumentation(c echo.Context) error {
|
||||||
|
client, err := api.NewApiClientFromViper()
|
||||||
|
if err != nil {
|
||||||
|
return c.JSON(http.StatusNotFound, map[string]string{"message": "Not Found"})
|
||||||
|
}
|
||||||
|
|
||||||
|
bucket := c.Param("bucket")
|
||||||
|
document := c.Param("document")
|
||||||
|
|
||||||
|
result, err := client.Call(http.MethodGet, fmt.Sprintf("/v1/bucket/%s/%s", bucket, document))
|
||||||
|
if err != nil {
|
||||||
|
return c.JSON(http.StatusNotFound, map[string]string{"message": "Not Found"})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if result can fit inside a map containing a message
|
||||||
|
var result_map map[string]string
|
||||||
|
|
||||||
|
err = json.Unmarshal(result, &result_map)
|
||||||
|
if err == nil {
|
||||||
|
return c.JSON(http.StatusBadRequest, result_map)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Blob(http.StatusOK, "application/octet-stream", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleAdmin(c echo.Context) error {
|
||||||
|
return c.Render(http.StatusOK, "admin-html", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleAdminDocumentsUpload(c echo.Context) error {
|
||||||
|
client, err := api.NewApiClientFromViper()
|
||||||
|
if err != nil {
|
||||||
|
return c.Render(http.StatusInternalServerError, "documentation-html", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := client.Call(http.MethodGet, "/v1/bucket")
|
||||||
|
if err != nil {
|
||||||
|
return c.Render(http.StatusInternalServerError, "documentation-html", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
var buckets map[string]string
|
||||||
|
|
||||||
|
err = json.Unmarshal(result, &buckets)
|
||||||
|
if err != nil {
|
||||||
|
return c.Render(http.StatusInternalServerError, "documentation-html", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Bucket struct {
|
||||||
|
Name string
|
||||||
|
DisplayName string
|
||||||
|
Documents []string
|
||||||
|
}
|
||||||
|
|
||||||
|
var data struct {
|
||||||
|
Buckets []Bucket
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
for bucketName, displayName := range buckets {
|
||||||
|
data.Buckets = append(data.Buckets, Bucket{
|
||||||
|
Name: bucketName,
|
||||||
|
DisplayName: displayName,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Render(http.StatusOK, "admin-upload-html", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleAdminDocumentsUploadPOST(c echo.Context) error {
|
||||||
|
type Bucket struct {
|
||||||
|
Name string
|
||||||
|
DisplayName string
|
||||||
|
Documents []string
|
||||||
|
}
|
||||||
|
|
||||||
|
var data struct {
|
||||||
|
Buckets []Bucket
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := api.New(cfg.Server.Api.Protocol, cfg.Server.Api.Host, cfg.Server.Port, api.APIOptions{
|
||||||
|
KeyAuth: cfg.Server.Api.Auth,
|
||||||
|
Key: cfg.Server.Api.Key,
|
||||||
|
BasicAuth: cfg.Server.Admin.Auth,
|
||||||
|
Username: cfg.Server.Admin.Username,
|
||||||
|
Password: cfg.Server.Admin.Password,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
data.Message = fmt.Sprintf("handleAdminDocumentsUploadPOST#api.New: %s", err)
|
||||||
|
return c.Render(http.StatusInternalServerError, "admin-upload-html", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := client.Call(http.MethodGet, "/v1/bucket")
|
||||||
|
if err != nil {
|
||||||
|
data.Message = "Error during GET /v1/bucket"
|
||||||
|
return c.Render(http.StatusInternalServerError, "documentation-html", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
var buckets map[string]string
|
||||||
|
|
||||||
|
err = json.Unmarshal(result, &buckets)
|
||||||
|
if err != nil {
|
||||||
|
return c.Render(http.StatusInternalServerError, "documentation-html", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
for bucketName, displayName := range buckets {
|
||||||
|
data.Buckets = append(data.Buckets, Bucket{
|
||||||
|
Name: bucketName,
|
||||||
|
DisplayName: displayName,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
bucket := c.FormValue("bucket")
|
||||||
|
|
||||||
|
document, err := c.FormFile("document")
|
||||||
|
if err != nil {
|
||||||
|
data.Message = fmt.Sprintf("handleAdminDocumentsUploadPOST#c.FormFile: %s", err)
|
||||||
|
return c.Render(http.StatusBadRequest, "admin-upload-html", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := client.UploadDocument(bucket, document)
|
||||||
|
if err != nil {
|
||||||
|
data.Message = fmt.Sprintf("handleAdminDocumentsUploadPOST#client.UploadDocument: %s", err)
|
||||||
|
return c.Render(http.StatusInternalServerError, "admin-upload-html", data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format response
|
||||||
|
var info, status string
|
||||||
|
|
||||||
|
info = fmt.Sprintf("[%.0f] /public/documentation/%s/%s", response.Info.Size, response.Info.Bucket, response.Info.Object)
|
||||||
|
|
||||||
|
status = response.Message
|
||||||
|
|
||||||
|
data.Message = fmt.Sprintf("%s - %s", status, info)
|
||||||
|
|
||||||
|
return c.Render(http.StatusOK, "admin-upload-html", data)
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +0,0 @@
|
||||||
package models
|
|
||||||
|
|
||||||
type Bucket struct {
|
|
||||||
Name string
|
|
||||||
DisplayName string
|
|
||||||
Documents []string
|
|
||||||
}
|
|
|
@ -1,92 +0,0 @@
|
||||||
package models
|
|
||||||
|
|
||||||
import "net/http"
|
|
||||||
|
|
||||||
type Responder interface {
|
|
||||||
Respond() Responder
|
|
||||||
}
|
|
||||||
|
|
||||||
type Response struct {
|
|
||||||
StatusCode int `json:"status_code"`
|
|
||||||
Message string
|
|
||||||
Error string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r Response) Respond() Responder {
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
type SimpleResponse struct {
|
|
||||||
Message string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r SimpleResponse) Respond() Responder {
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
func NotFoundResponse() (int, SimpleResponse) {
|
|
||||||
return http.StatusNotFound, SimpleResponse{
|
|
||||||
Message: "Not Found",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NotImplementedResponse() (int, SimpleResponse) {
|
|
||||||
return http.StatusNotImplemented, SimpleResponse{
|
|
||||||
Message: "Not Implemented",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type HandleAdminDocumentsUploadResponse struct {
|
|
||||||
Response
|
|
||||||
Data struct {
|
|
||||||
Buckets []Bucket
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type HandleDocumentationResponse struct {
|
|
||||||
Response
|
|
||||||
Data struct {
|
|
||||||
Buckets []Bucket
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type UploadDocumentResponse struct {
|
|
||||||
Response
|
|
||||||
Data UploadDocumentResponseData
|
|
||||||
}
|
|
||||||
|
|
||||||
type UploadDocumentResponseData struct {
|
|
||||||
Bucket string
|
|
||||||
Object string
|
|
||||||
Size float64
|
|
||||||
}
|
|
||||||
|
|
||||||
type V1SeedResponse struct {
|
|
||||||
Response
|
|
||||||
Data struct {
|
|
||||||
Buckets []string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type V1BucketListResponse struct {
|
|
||||||
Response
|
|
||||||
Data struct {
|
|
||||||
Buckets map[string]string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type V1BucketReadResponse struct {
|
|
||||||
Response
|
|
||||||
Data struct {
|
|
||||||
Keys []string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type V1DocumentCreateResponse struct {
|
|
||||||
Response
|
|
||||||
Data struct {
|
|
||||||
Bucket string
|
|
||||||
Key string
|
|
||||||
Size int64
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,120 +0,0 @@
|
||||||
.adminUploadForm {
|
|
||||||
font-family: 'Poppins';
|
|
||||||
display: flex;
|
|
||||||
flex-flow: column;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.formContent {
|
|
||||||
display: flex;
|
|
||||||
flex-flow: column;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.formSelectDiv {
|
|
||||||
display: flex;
|
|
||||||
flex-flow: column;
|
|
||||||
margin: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.formLabel {
|
|
||||||
font-family: 'Poppins';
|
|
||||||
font-size: 0.875rem;
|
|
||||||
font-weight: 600;
|
|
||||||
margin: 0;
|
|
||||||
padding-top: 10px;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
color: #394596;
|
|
||||||
}
|
|
||||||
|
|
||||||
.formSelect {
|
|
||||||
font-family: 'Poppins';
|
|
||||||
font-size: 0.875rem;
|
|
||||||
font-weight: 400;
|
|
||||||
border: 1px #C4C4C4 solid;
|
|
||||||
padding: 10px;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/*La flèche de l'élément*/
|
|
||||||
.formSelect:after {
|
|
||||||
color: #000
|
|
||||||
}
|
|
||||||
|
|
||||||
.formOption {
|
|
||||||
font-family: 'Poppins';
|
|
||||||
font-size: 0.875rem;
|
|
||||||
font-weight: 400;
|
|
||||||
margin-top: 5px;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.formOption:hover {
|
|
||||||
background-color: #C4C4C4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.formDocUploadDiv {
|
|
||||||
display: flex;
|
|
||||||
flex-flow: column;
|
|
||||||
margin: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.formDocUpload {
|
|
||||||
font-family: 'Poppins';
|
|
||||||
font-size: 0.875rem;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.formDocUpload::file-selector-button {
|
|
||||||
font-family: 'Poppins';
|
|
||||||
font-size: 0.875rem;
|
|
||||||
font-weight: 400;
|
|
||||||
background-color: #FF563C;
|
|
||||||
padding: 7px;
|
|
||||||
color: #fff;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
margin-right: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.formSubmit {
|
|
||||||
font-family: 'Poppins';
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: 500;
|
|
||||||
background-color: #FF563C;
|
|
||||||
padding: 10px;
|
|
||||||
color: #fff;
|
|
||||||
border: none;
|
|
||||||
cursor: pointer;
|
|
||||||
margin: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.confirmationMessage {
|
|
||||||
font-family: 'Poppins';
|
|
||||||
font-size: 0.75rem;
|
|
||||||
font-weight: 500;
|
|
||||||
text-align: center;
|
|
||||||
margin: 10px;
|
|
||||||
padding: 5px;
|
|
||||||
background-color: #C4C4C4;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (min-width: 768px) {
|
|
||||||
|
|
||||||
.formContent {
|
|
||||||
display: flex;
|
|
||||||
flex-flow: row;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (min-width: 1140px) {
|
|
||||||
|
|
||||||
.confirmationMessage {
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: 500;
|
|
||||||
margin: 20px;
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
.snackbar {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 0;
|
|
||||||
background-color: #FF563C;
|
|
||||||
color: #fff;
|
|
||||||
width: 100%;
|
|
||||||
padding: 5px 0;
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.snackbar-is-closed {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.snackbarWrapper {
|
|
||||||
display: flex;
|
|
||||||
flex-flow: row;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
line-height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
span {
|
|
||||||
margin: 10px 10px 10px 0;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
/*12px*/
|
|
||||||
font-family: 'Poppins';
|
|
||||||
font-weight: 600;
|
|
||||||
/*semi-bold*/
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (min-width: 375px) {
|
|
||||||
span {
|
|
||||||
font-size: 1rem;
|
|
||||||
/*16px*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.snackbarFermer {
|
|
||||||
height: 30px;
|
|
||||||
width: 30px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<g clip-path="url(#clip0_1412_1060)">
|
|
||||||
<path d="M256 16C123.45 16 16 123.45 16 256C16 388.55 123.45 496 256 496C388.55 496 496 388.55 496 256C496 123.45 388.55 16 256 16ZM256 76C355.41 76 436 156.59 436 256C436 355.41 355.41 436 256 436C156.59 436 76 355.41 76 256C76 156.59 156.59 76 256 76ZM175.375 136C174.405 135.995 173.369 136.112 172.312 136.313V136.281C154.015 139.717 127.048 171.024 138.937 182.907L212.094 256.032L138.938 329.158C124.308 343.783 168.213 387.692 182.844 373.064L256 299.906L329.156 373.062C343.786 387.69 387.693 343.782 373.062 329.156L299.906 256.031L373.062 182.907C387.692 168.282 343.787 124.407 329.156 139.032L256 212.157L182.844 139.032C180.784 136.986 178.284 136.017 175.374 136.002L175.375 136Z" fill="white"/>
|
|
||||||
</g>
|
|
||||||
<defs>
|
|
||||||
<clipPath id="clip0_1412_1060">
|
|
||||||
<rect width="512" height="512" fill="white"/>
|
|
||||||
</clipPath>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
Before Width: | Height: | Size: 965 B |
381
serverhandlers/serverhandlers.go
Normal file
381
serverhandlers/serverhandlers.go
Normal file
|
@ -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 = make(map[string]string)
|
||||||
|
|
||||||
|
for bucket_name, bucket_display_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[bucket_name] = bucket_display_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",
|
||||||
|
})
|
||||||
|
}
|
|
@ -5,31 +5,24 @@
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<title>AGECEM</title>
|
<title>AGECEM</title>
|
||||||
{{ template "general-html" }}
|
{{ template "general-html" }}
|
||||||
<link rel="stylesheet" href="/public/css/admin-upload.css">
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
{{ template "header-html" }}
|
{{ template "header-html" }}
|
||||||
<div class="wrapper adminUploadWrapper">
|
<h1 class="heading1">Upload</h1>
|
||||||
<h1 class="heading1">Ajouter un document</h1>
|
|
||||||
<form class="form adminUploadForm" action="/admin/documents/upload" method="post" enctype="multipart/form-data">
|
<form class="form adminUploadForm" action="/admin/documents/upload" method="post" enctype="multipart/form-data">
|
||||||
<div class="formContent">
|
<label class="formLabel" for="bucket">Type de document:</label>
|
||||||
<div class="formDocUploadDiv">
|
|
||||||
<p class="formLabel">Document à téléverser</p>
|
|
||||||
<input class="formDocUpload" type="file" name="document">
|
|
||||||
</div>
|
|
||||||
<div class="formSelectDiv">
|
|
||||||
<label class="formLabel" for="bucket">Type de document</label>
|
|
||||||
<select class="formSelect" name="bucket" id="bucket">
|
<select class="formSelect" name="bucket" id="bucket">
|
||||||
{{ range .Buckets }}
|
{{ range .Buckets }}
|
||||||
<option class="formOption" value="{{ .Name }}">{{ .DisplayName }}</option>
|
<option class="formOption" value="{{ .Name }}">{{ .DisplayName }}</option>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
<br>
|
||||||
</div>
|
Document: <input class="formDocUpload" type="file" name="document">
|
||||||
<input class="formSubmit" type="submit" value="Ajouter le document">
|
<br>
|
||||||
|
<br>
|
||||||
|
<input class="formSubmit" type="submit" value="Submit">
|
||||||
</form>
|
</form>
|
||||||
<p class="confirmationMessage"><strong>Confirmation:</strong> {{ .Message }}</p>
|
<p>{{ .Message }}</p>
|
||||||
</div>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
<div class="wrapper adminWrapper">
|
<div class="wrapper adminWrapper">
|
||||||
<h1 class="heading1">Admin</h1>
|
<h1 class="heading1">Admin</h1>
|
||||||
<div class="adminOptions">
|
<div class="adminOptions">
|
||||||
<button class="adminOption" onclick="location.href = '/admin/documents/upload'">Ajouter un document</a>
|
<button class="adminOption" onclick="location.href = '/admin/documents/upload'">Ajout de document</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -12,14 +12,12 @@
|
||||||
<div class="wrapper documentationWrapper">
|
<div class="wrapper documentationWrapper">
|
||||||
<h1 class="heading1">Documentation</h1>
|
<h1 class="heading1">Documentation</h1>
|
||||||
<p>
|
<p>
|
||||||
{{ if not .Data.Buckets }}
|
{{ range . }}
|
||||||
Documentation non-accessible pour l'instant, merci de votre patience
|
|
||||||
{{ else }}
|
|
||||||
{{ range .Data.Buckets }}
|
|
||||||
{{ $bucket_name := .Name }}
|
{{ $bucket_name := .Name }}
|
||||||
{{ $bucket_display_name := .DisplayName }}
|
{{ $bucket_display_name := .DisplayName }}
|
||||||
<details class="documentationCategorie">
|
<details class="documentationCategorie">
|
||||||
<summary class="documentationDescription">{{ $bucket_display_name }}</summary>
|
<summary class="documentationDescription">{{ $bucket_display_name }}</summary>
|
||||||
|
|
||||||
<ul class="documentationListe">
|
<ul class="documentationListe">
|
||||||
{{ range .Documents }}
|
{{ range .Documents }}
|
||||||
<a class ="documentationLien" href="/public/documentation/{{ $bucket_name }}/{{ . }}"><li class="documentationDocument">{{ . }}</li></a>
|
<a class ="documentationLien" href="/public/documentation/{{ $bucket_name }}/{{ . }}"><li class="documentationDocument">{{ . }}</li></a>
|
||||||
|
@ -27,13 +25,8 @@ Documentation non-accessible pour l'instant, merci de votre patience
|
||||||
</ul>
|
</ul>
|
||||||
</details>
|
</details>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{ end }}
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
{{ .Message }}
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{{ template "snackbar-html" }}
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
|
@ -12,29 +12,7 @@
|
||||||
<div class="wrapper indexWrapper">
|
<div class="wrapper indexWrapper">
|
||||||
<h1 class="heading1">AGECEM</h1>
|
<h1 class="heading1">AGECEM</h1>
|
||||||
<h2 class="heading2">Association Générale Étudiante du Cégep Édouard-Montpetit</h2>
|
<h2 class="heading2">Association Générale Étudiante du Cégep Édouard-Montpetit</h2>
|
||||||
<p>
|
|
||||||
Fondée en 1976, l’Association Étudiante est un organisme sans but lucratif voué à la défense des étudiant·e·s inscrit·e·s à l’enseignement régulier du Campus de Longueuil du Cégep Édouard-Montpetit, qu’iels étudient de jour ou de soir, à temps plein ou à temps partiel.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Forte d’environ 6000 membres, elle veille à promouvoir un milieu pédagogique sain en se consacrant à les représenter, tant au niveau académique, politique, social, qu’environnemental.
|
|
||||||
</p>
|
|
||||||
<br>
|
|
||||||
<h2 class="heading2">Contact</h2>
|
|
||||||
<h3>Courriel</h3>
|
|
||||||
permanence@agecem.org
|
|
||||||
<h3>Téléphone</h3>
|
|
||||||
(450) 679-7375
|
|
||||||
<h3>Addresse de coordination</h3>
|
|
||||||
945 Chemin de Chambly, Longueuil, QC J4H 3M6
|
|
||||||
<h3>Local</h3>
|
|
||||||
B-31
|
|
||||||
<h3>Réseaux sociaux</h3>
|
|
||||||
<ul>
|
|
||||||
<li><a href="https://www.facebook.com/asso.agecem">Facebook</a></li>
|
|
||||||
<li><a href="https://www.instagram.com/agecem_officiel"/>Instagram</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
{{ template "snackbar-html" }}
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
{{ define "snackbar-html" }}
|
|
||||||
<link rel="stylesheet" href="/public/css/snackbar.css">
|
|
||||||
<script>
|
|
||||||
function closeSnackbar() {
|
|
||||||
var snackbar = document.querySelector(".snackbar");
|
|
||||||
snackbar.classList.add('snackbar-is-closed');
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<div class="snackbar">
|
|
||||||
<div class="wrapper snackbarWrapper">
|
|
||||||
<span class="snackbarTexte">Ce site web est présentement en construction.</span>
|
|
||||||
<img src="/public/icones/fermer.svg" class="snackbarFermer" onclick="closeSnackbar()"></img>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{{ end }}
|
|
|
@ -10,147 +10,7 @@
|
||||||
{{ template "header-html" }}
|
{{ template "header-html" }}
|
||||||
<div class="wrapper vieEtudianteWrapper">
|
<div class="wrapper vieEtudianteWrapper">
|
||||||
<h1 class="heading1">Vie étudiante</h1>
|
<h1 class="heading1">Vie étudiante</h1>
|
||||||
<h3>Organismes thématiques</h3>
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td>Nom</td>
|
|
||||||
<td>Local</td>
|
|
||||||
<td>Poste téléphonique</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>AME</td>
|
|
||||||
<td>C-060</td>
|
|
||||||
<td>7919</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>BEAM</td>
|
|
||||||
<td>F-024a</td>
|
|
||||||
<td>5930</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>CIC</td>
|
|
||||||
<td>F-027c</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Club Aventurier</td>
|
|
||||||
<td>F-011b</td>
|
|
||||||
<td>2730</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Équipe Santé</td>
|
|
||||||
<td>F-011</td>
|
|
||||||
<td>2361</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Montpetit Donjon</td>
|
|
||||||
<td>C-067</td>
|
|
||||||
<td>2299</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>MAEL</td>
|
|
||||||
<td>F-027b</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>OGRE</td>
|
|
||||||
<td>F-011c</td>
|
|
||||||
<td>5647</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Radio</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>ORGASME</td>
|
|
||||||
<td>F-027d</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>SOI</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>MotDit</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<hr>
|
|
||||||
<h3>Associations de programme</h3>
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td>Nom</td>
|
|
||||||
<td>Local</td>
|
|
||||||
<td>Poste téléphonique</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>ADEPT</td>
|
|
||||||
<td>F-045</td>
|
|
||||||
<td>2286</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>ASI</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>ATIM</td>
|
|
||||||
<td>F-041</td>
|
|
||||||
<td>2652</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>AEALC</td>
|
|
||||||
<td>A-125r</td>
|
|
||||||
<td>2873</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>PAPI</td>
|
|
||||||
<td>F-023</td>
|
|
||||||
<td>2795</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>TEE</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>TGE</td>
|
|
||||||
<td>C-063</td>
|
|
||||||
<td>2638</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
<hr>
|
|
||||||
<h3>Comités</h3>
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<td>Nom</td>
|
|
||||||
<td>Local</td>
|
|
||||||
<td>Poste téléphonique</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>CAP</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>ESPACE</td>
|
|
||||||
<td>F-011d</td>
|
|
||||||
<td>2418</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>CFEM</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>ASEG</td>
|
|
||||||
<td>B-06</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Comité Mob</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>EUMC-CEM</td>
|
|
||||||
<td>C-054</td>
|
|
||||||
<td>2356</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>CÉSI</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Friperie</td>
|
|
||||||
<td>F-027a</td>
|
|
||||||
<td>2248</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
{{ template "snackbar-html" }}
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
|
@ -1,204 +0,0 @@
|
||||||
package web_handlers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"git.agecem.com/agecem/agecem-org/api"
|
|
||||||
"git.agecem.com/agecem/agecem-org/models"
|
|
||||||
"github.com/labstack/echo/v4"
|
|
||||||
)
|
|
||||||
|
|
||||||
type WebHandler struct {
|
|
||||||
ApiClient *api.API
|
|
||||||
}
|
|
||||||
|
|
||||||
func HandleIndex(c echo.Context) error {
|
|
||||||
return c.Render(http.StatusOK, "index-html", nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
func HandleAPropos(c echo.Context) error {
|
|
||||||
return c.Render(http.StatusOK, "a-propos-html", nil)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
func HandleActualite(c echo.Context) error {
|
|
||||||
return c.Render(http.StatusOK, "actualite-html", nil)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
func HandleActualiteArticle(c echo.Context) error {
|
|
||||||
article := c.Param("article")
|
|
||||||
return c.String(http.StatusOK, fmt.Sprintf("Article: %s", article))
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
func HandleVieEtudiante(c echo.Context) error {
|
|
||||||
return c.Render(http.StatusOK, "vie-etudiante-html", nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func HandleVieEtudianteOrganisme(c echo.Context) error {
|
|
||||||
organisme := c.Param("organisme")
|
|
||||||
return c.String(http.StatusOK, fmt.Sprintf("Organisme: %s", organisme))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *WebHandler) HandleDocumentation(c echo.Context) error {
|
|
||||||
var response models.HandleDocumentationResponse
|
|
||||||
|
|
||||||
v1BucketListResponse, err := h.ApiClient.ListBuckets()
|
|
||||||
if err != nil {
|
|
||||||
response.StatusCode = v1BucketListResponse.StatusCode
|
|
||||||
response.Message = v1BucketListResponse.Message
|
|
||||||
response.Error = err.Error()
|
|
||||||
|
|
||||||
return c.Render(response.StatusCode, "documentation-html", response)
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO check v1BucketListRespone StatusCode and Error
|
|
||||||
|
|
||||||
for bucket, displayName := range v1BucketListResponse.Data.Buckets {
|
|
||||||
// TODO move call to dedicated API client method
|
|
||||||
content, err := h.ApiClient.Call(http.MethodGet, fmt.Sprintf("/v1/bucket/%s", bucket))
|
|
||||||
if err != nil {
|
|
||||||
response.StatusCode = http.StatusInternalServerError
|
|
||||||
response.Message = "Error during /v1/bucket/:bucket"
|
|
||||||
response.Error = err.Error()
|
|
||||||
|
|
||||||
return c.Render(response.StatusCode, "documentation-html", response)
|
|
||||||
}
|
|
||||||
|
|
||||||
var v1BucketReadResponse models.V1BucketReadResponse
|
|
||||||
|
|
||||||
err = json.Unmarshal(content, &v1BucketReadResponse)
|
|
||||||
if err != nil {
|
|
||||||
response.StatusCode = http.StatusInternalServerError
|
|
||||||
response.Message = "Error during json.Unmarshal /v1/bucket/:bucket"
|
|
||||||
response.Error = err.Error()
|
|
||||||
|
|
||||||
return c.Render(response.StatusCode, "documentation-html", response)
|
|
||||||
}
|
|
||||||
|
|
||||||
response.Data.Buckets = append(response.Data.Buckets, models.Bucket{
|
|
||||||
Name: bucket,
|
|
||||||
DisplayName: displayName,
|
|
||||||
Documents: v1BucketReadResponse.Data.Keys,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.SliceStable(response.Data.Buckets, func(i, j int) bool { return response.Data.Buckets[i].Name < response.Data.Buckets[j].Name })
|
|
||||||
|
|
||||||
response.StatusCode = http.StatusOK
|
|
||||||
//response.Message = "HandleDocumentation ok"
|
|
||||||
|
|
||||||
// TODO render .Message
|
|
||||||
return c.Render(response.StatusCode, "documentation-html", response)
|
|
||||||
//return c.Render(response.StatusCode, "documentation-html", response.Data.Buckets)
|
|
||||||
}
|
|
||||||
|
|
||||||
func HandleFormulaires(c echo.Context) error {
|
|
||||||
return c.Render(http.StatusOK, "formulaires-html", nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *WebHandler) HandlePublicDocumentation(c echo.Context) error {
|
|
||||||
bucket := c.Param("bucket")
|
|
||||||
document := c.Param("document")
|
|
||||||
|
|
||||||
result, err := h.ApiClient.Call(http.MethodGet, fmt.Sprintf("/v1/bucket/%s/%s", bucket, document))
|
|
||||||
if err != nil {
|
|
||||||
return c.JSON(models.NotFoundResponse())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if result can fit inside a map containing a message
|
|
||||||
var result_map map[string]string
|
|
||||||
|
|
||||||
err = json.Unmarshal(result, &result_map)
|
|
||||||
if err == nil {
|
|
||||||
return c.JSON(http.StatusBadRequest, result_map)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Blob(http.StatusOK, "application/octet-stream", result)
|
|
||||||
}
|
|
||||||
|
|
||||||
func HandleAdmin(c echo.Context) error {
|
|
||||||
return c.Render(http.StatusOK, "admin-html", nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *WebHandler) HandleAdminDocumentsUpload(c echo.Context) error {
|
|
||||||
var response models.HandleAdminDocumentsUploadResponse
|
|
||||||
|
|
||||||
v1BucketListResponse, err := h.ApiClient.ListBuckets()
|
|
||||||
if err != nil {
|
|
||||||
response.StatusCode = v1BucketListResponse.StatusCode
|
|
||||||
response.Error = err.Error()
|
|
||||||
response.Message = v1BucketListResponse.Message
|
|
||||||
|
|
||||||
return c.Render(response.StatusCode, "admin-upload-html", nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
for bucketName, displayName := range v1BucketListResponse.Data.Buckets {
|
|
||||||
response.Data.Buckets = append(response.Data.Buckets, models.Bucket{
|
|
||||||
Name: bucketName,
|
|
||||||
DisplayName: displayName,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
response.StatusCode = http.StatusOK
|
|
||||||
|
|
||||||
return c.Render(response.StatusCode, "admin-upload-html", response)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *WebHandler) HandleAdminDocumentsUploadPOST(c echo.Context) error {
|
|
||||||
var response models.HandleAdminDocumentsUploadResponse
|
|
||||||
|
|
||||||
v1BucketListResponse, err := h.ApiClient.ListBuckets()
|
|
||||||
if err != nil {
|
|
||||||
response.StatusCode = v1BucketListResponse.StatusCode
|
|
||||||
response.Message = v1BucketListResponse.Message
|
|
||||||
response.Error = err.Error()
|
|
||||||
|
|
||||||
return c.Render(response.StatusCode, "admin-upload-html", response)
|
|
||||||
}
|
|
||||||
|
|
||||||
for bucketName, displayName := range v1BucketListResponse.Data.Buckets {
|
|
||||||
response.Data.Buckets = append(response.Data.Buckets, models.Bucket{
|
|
||||||
Name: bucketName,
|
|
||||||
DisplayName: displayName,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
bucket := c.FormValue("bucket")
|
|
||||||
|
|
||||||
document, err := c.FormFile("document")
|
|
||||||
if err != nil {
|
|
||||||
response.StatusCode = http.StatusBadRequest
|
|
||||||
response.Message = "Formulaire invalide"
|
|
||||||
response.Error = err.Error()
|
|
||||||
|
|
||||||
return c.Render(response.StatusCode, "admin-upload-html", response)
|
|
||||||
}
|
|
||||||
|
|
||||||
uploadDocumentResponse, err := h.ApiClient.UploadDocument(bucket, document)
|
|
||||||
if err != nil {
|
|
||||||
response.StatusCode = uploadDocumentResponse.StatusCode
|
|
||||||
response.Message = uploadDocumentResponse.Message
|
|
||||||
response.Error = err.Error()
|
|
||||||
|
|
||||||
return c.Render(response.StatusCode, "admin-upload-html", response)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format response
|
|
||||||
var info, status string
|
|
||||||
|
|
||||||
info = fmt.Sprintf("[%d] /public/documentation/%s/%s", uploadDocumentResponse.Data.Size, uploadDocumentResponse.Data.Bucket, uploadDocumentResponse.Data.Key)
|
|
||||||
|
|
||||||
status = uploadDocumentResponse.Message
|
|
||||||
|
|
||||||
response.StatusCode = http.StatusOK
|
|
||||||
response.Message = fmt.Sprintf("%s - %s", status, info)
|
|
||||||
|
|
||||||
return c.Render(response.StatusCode, "admin-upload-html", response)
|
|
||||||
}
|
|
Loading…
Reference in a new issue