Compare commits

..

No commits in common. "d08e9ffddd2980611e5573b60fa97f6bba47bcad" and "339feb2e52b0a4b6b2d209d2756b6a9688899c93" have entirely different histories.

18 changed files with 715 additions and 1112 deletions

View file

@ -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>"
@ -12,17 +12,13 @@ ADD cmd/ cmd/
ADD api/ api/
ADD api_handlers/ api_handlers/
ADD config/ config/
ADD media/ media/
ADD models/ models/
ADD templates/ templates/
ADD web_handlers/ web_handlers/
ADD serverhandlers/ serverhandlers/
RUN CGO_ENABLED=0 go build -a -installsuffix cgo -o agecem-org .

View file

@ -44,21 +44,3 @@ Voir les logs des containers
Détruire les containers
`$ 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`
`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`

View file

@ -6,12 +6,12 @@ import (
"errors"
"fmt"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"net/url"
"git.agecem.com/agecem/agecem-org/config"
"git.agecem.com/agecem/agecem-org/models"
"github.com/spf13/viper"
)
@ -30,9 +30,17 @@ type APIOptions struct {
Password string
}
// NewApiClientFromViper returns a pointer to a new API object,
// provided the configuration options are managed by
// https://git.agecem.com/agecem/agecem-org/config
type UploadDocumentResponse struct {
Info UploadDocumentResponseInfo `json:"info"`
Message string `json:"message"`
}
type UploadDocumentResponseInfo struct {
Bucket string `json:"bucket"`
Object string `json:"key"`
Size float64 `json:"size"`
}
func NewApiClientFromViper() (*API, error) {
var config config.Config
@ -99,7 +107,7 @@ func (a *API) Call(method, route string) ([]byte, error) {
defer response.Body.Close()
body, err := io.ReadAll(response.Body)
body, err := ioutil.ReadAll(response.Body)
if err != nil {
return nil, err
}
@ -127,7 +135,7 @@ func (a *API) Call(method, route string) ([]byte, error) {
defer resp.Body.Close()
// Read Response Body
respBody, err := io.ReadAll(resp.Body)
respBody, err := ioutil.ReadAll(resp.Body)
if err != nil {
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))
}
func (a *API) UploadDocument(bucket string, file_header *multipart.FileHeader) (models.V1DocumentCreateResponse, error) {
var response models.V1DocumentCreateResponse
func (a *API) UploadDocument(bucket string, file_header *multipart.FileHeader) (UploadDocumentResponse, error) {
endpoint := fmt.Sprintf("%s://%s:%d",
a.Protocol,
a.Host,
@ -155,34 +162,34 @@ func (a *API) UploadDocument(bucket string, file_header *multipart.FileHeader) (
// Add the file to the request
file, err := file_header.Open()
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()
filename_processed, err := url.QueryUnescape(file_header.Filename)
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)
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)
if err != nil {
return response, fmt.Errorf("UploadDocument#io.Copy: %s", err)
return UploadDocumentResponse{}, fmt.Errorf("UploadDocument#io.Copy: %s", err)
}
err = writer.Close()
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
req, err := http.NewRequest(http.MethodPost, current_url, body)
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())
@ -195,12 +202,15 @@ func (a *API) UploadDocument(bucket string, file_header *multipart.FileHeader) (
client := &http.Client{}
resp, err := client.Do(req)
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()
err = json.NewDecoder(resp.Body).Decode(&response)
return response, err
// Handle the response
var res UploadDocumentResponse
json.NewDecoder(resp.Body).Decode(&res)
return res, nil
}
// 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', 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
}

View file

@ -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",
})
}

View file

@ -5,8 +5,10 @@ package cmd
import (
"crypto/subtle"
"encoding/json"
"fmt"
"log"
"sort"
"embed"
"html/template"
@ -17,12 +19,11 @@ import (
"github.com/spf13/viper"
"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/media"
"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/web_handlers"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
@ -48,18 +49,17 @@ var serverCmd = &cobra.Command{
}
mediaClient, err := media.NewMediaClientFromViper()
switch err != nil {
case true:
log.Printf("media.NewMediaClientFromViper error: %s", err)
case false:
new_buckets, err := mediaClient.Seed()
if err != nil {
log.Printf("(*media.MediaClient).Seed error: %s", err)
} else {
log.Printf("Seeded %d buckets.\n", len(new_buckets))
}
if err != nil {
log.Fatal(err)
}
new_buckets, err := mediaClient.Seed()
if err != nil {
log.Fatal(err)
}
log.Printf("Seeded %d buckets.\n", len(new_buckets))
RunServer()
},
}
@ -199,69 +199,52 @@ func RunServer() {
}
// API Routes
mediaClient, err := media.NewMediaClientFromViper()
if err != nil {
log.Fatal("Error during NewMediaClientFromViper for API handlers")
}
v1Handler := api_handlers.V1Handler{
Config: cfg,
MediaClient: mediaClient,
}
groupV1.GET("", serverhandlers.HandleV1)
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", v1Handler.HandleV1DocumentDelete)
groupV1.DELETE("/bucket/:bucket/:document", serverhandlers.HandleV1DocumentDelete)
// HTML Routes
apiClient, err := api.NewApiClientFromViper()
if err != nil {
log.Fatal("Error during NewMediaClientFromViper for API handlers")
}
webHandler := web_handlers.WebHandler{
ApiClient: apiClient,
}
e.GET("/", handleIndex)
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", web_handlers.HandleFormulaires)
e.GET("/formulaires", handleFormulaires)
// Public Routes
e.GET("/public/documentation/:bucket/:document", webHandler.HandlePublicDocumentation)
e.GET("/public/documentation/:bucket/:document", handlePublicDocumentation)
// 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(
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 {
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)
}

View file

@ -1,7 +0,0 @@
package models
type Bucket struct {
Name string
DisplayName string
Documents []string
}

View file

@ -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
}
}

View file

@ -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;
}
}

View file

@ -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;
}

View file

@ -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

View 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",
})
}

View file

@ -5,31 +5,24 @@
<meta charset="utf-8">
<title>AGECEM</title>
{{ template "general-html" }}
<link rel="stylesheet" href="/public/css/admin-upload.css">
</head>
<body>
{{ template "header-html" }}
<div class="wrapper adminUploadWrapper">
<h1 class="heading1">Ajouter un document</h1>
<form class="form adminUploadForm" action="/admin/documents/upload" method="post" enctype="multipart/form-data">
<div class="formContent">
<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">
{{ range .Buckets }}
<option class="formOption" value="{{ .Name }}">{{ .DisplayName }}</option>
{{ end }}
</select>
</div>
</div>
<input class="formSubmit" type="submit" value="Ajouter le document">
</form>
<p class="confirmationMessage"><strong>Confirmation:</strong> {{ .Message }}</p>
</div>
<h1 class="heading1">Upload</h1>
<form class="form adminUploadForm" action="/admin/documents/upload" method="post" enctype="multipart/form-data">
<label class="formLabel" for="bucket">Type de document:</label>
<select class="formSelect" name="bucket" id="bucket">
{{ range .Buckets }}
<option class="formOption" value="{{ .Name }}">{{ .DisplayName }}</option>
{{ end }}
</select>
<br>
Document: <input class="formDocUpload" type="file" name="document">
<br>
<br>
<input class="formSubmit" type="submit" value="Submit">
</form>
<p>{{ .Message }}</p>
</body>
</html>
{{ end }}

View file

@ -12,7 +12,7 @@
<div class="wrapper adminWrapper">
<h1 class="heading1">Admin</h1>
<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>
</body>

View file

@ -12,28 +12,21 @@
<div class="wrapper documentationWrapper">
<h1 class="heading1">Documentation</h1>
<p>
{{ if not .Data.Buckets }}
Documentation non-accessible pour l'instant, merci de votre patience
{{ else }}
{{ range .Data.Buckets }}
{{ $bucket_name := .Name }}
{{ $bucket_display_name := .DisplayName }}
<details class="documentationCategorie">
<summary class="documentationDescription">{{ $bucket_display_name }}</summary>
<ul class="documentationListe">
{{ range .Documents }}
<a class ="documentationLien" href="/public/documentation/{{ $bucket_name }}/{{ . }}"><li class="documentationDocument">{{ . }}</li></a>
{{ end}}
</ul>
</details>
{{ end }}
{{ range . }}
{{ $bucket_name := .Name }}
{{ $bucket_display_name := .DisplayName }}
<details class="documentationCategorie">
<summary class="documentationDescription">{{ $bucket_display_name }}</summary>
<ul class="documentationListe">
{{ range .Documents }}
<a class ="documentationLien" href="/public/documentation/{{ $bucket_name }}/{{ . }}"><li class="documentationDocument">{{ . }}</li></a>
{{ end}}
</ul>
</details>
{{ end }}
</p>
<p>
{{ .Message }}
</p>
</div>
{{ template "snackbar-html" }}
</body>
</html>
{{ end }}

View file

@ -12,29 +12,7 @@
<div class="wrapper indexWrapper">
<h1 class="heading1">AGECEM</h1>
<h2 class="heading2">Association Générale Étudiante du Cégep Édouard-Montpetit</h2>
<p>
Fondée en 1976, lAssociation Étudiante est un organisme sans but lucratif voué à la défense des étudiant·e·s inscrit·e·s à lenseignement régulier du Campus de Longueuil du Cégep Édouard-Montpetit, quiels étudient de jour ou de soir, à temps plein ou à temps partiel.
</p>
<p>
Forte denviron 6000 membres, elle veille à promouvoir un milieu pédagogique sain en se consacrant à les représenter, tant au niveau académique, politique, social, quenvironnemental.
</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>
{{ template "snackbar-html" }}
</body>
</html>
{{ end }}

View file

@ -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 }}

View file

@ -10,147 +10,7 @@
{{ template "header-html" }}
<div class="wrapper vieEtudianteWrapper">
<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>
{{ template "snackbar-html" }}
</body>
</html>
{{ end }}

View file

@ -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)
}