276 lines
8.2 KiB
Go
276 lines
8.2 KiB
Go
package handlers
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/google/uuid"
|
|
"gitea.maison43.duckdns.org/gilles/matosbox/internal/data/ent"
|
|
"gitea.maison43.duckdns.org/gilles/matosbox/internal/data/ent/piecejointe"
|
|
)
|
|
|
|
// UploadPiecesJointes gere l'upload multiple des fichiers d'un objet.
|
|
// @Summary Uploader des pieces jointes
|
|
// @Tags PiecesJointes
|
|
// @Accept multipart/form-data
|
|
// @Produce json
|
|
// @Param id path string true "ID objet"
|
|
// @Param fichiers formData file true "Fichiers (multi)"
|
|
// @Success 201 {object} map[string]any
|
|
// @Failure 400 {object} map[string]string
|
|
// @Failure 404 {object} map[string]string
|
|
// @Failure 413 {object} map[string]string
|
|
// @Failure 500 {object} map[string]string
|
|
// @Router /objets/{id}/pieces_jointes [post]
|
|
func (h *Handler) UploadPiecesJointes(c *gin.Context) {
|
|
id, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"erreur": "identifiant invalide"})
|
|
return
|
|
}
|
|
|
|
if _, err := h.client.Objet.Get(c.Request.Context(), id); err != nil {
|
|
if ent.IsNotFound(err) {
|
|
c.JSON(http.StatusNotFound, gin.H{"erreur": "objet introuvable"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusInternalServerError, gin.H{"erreur": "impossible de charger l'objet"})
|
|
return
|
|
}
|
|
|
|
form, err := c.MultipartForm()
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"erreur": "formulaire invalide"})
|
|
return
|
|
}
|
|
|
|
files := form.File["fichiers"]
|
|
if len(files) == 0 {
|
|
c.JSON(http.StatusBadRequest, gin.H{"erreur": "aucun fichier fourni"})
|
|
return
|
|
}
|
|
|
|
maxBytes := maxUploadBytes()
|
|
|
|
hasPrincipale, err := h.client.PieceJointe.Query().
|
|
Where(
|
|
piecejointe.ObjetID(id),
|
|
piecejointe.EstPrincipale(true),
|
|
).
|
|
Exist(c.Request.Context())
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"erreur": "impossible de verifier la piece jointe principale"})
|
|
return
|
|
}
|
|
|
|
baseDir := os.Getenv("ATTACHMENTS_DIR")
|
|
if baseDir == "" {
|
|
baseDir = "./data/pieces_jointes"
|
|
}
|
|
if err := os.MkdirAll(baseDir, 0o755); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"erreur": "impossible de creer le dossier de stockage"})
|
|
return
|
|
}
|
|
|
|
var saved []map[string]any
|
|
for idx, file := range files {
|
|
ext := strings.ToLower(filepath.Ext(file.Filename))
|
|
if file.Size > maxBytes {
|
|
c.JSON(http.StatusRequestEntityTooLarge, gin.H{"erreur": "fichier trop volumineux"})
|
|
return
|
|
}
|
|
|
|
mime := file.Header.Get("Content-Type")
|
|
if !isAllowedUpload(mime, ext) {
|
|
c.JSON(http.StatusBadRequest, gin.H{"erreur": "type de fichier non supporte"})
|
|
return
|
|
}
|
|
|
|
uuidName := uuid.New().String() + ext
|
|
path := filepath.Join(baseDir, uuidName)
|
|
|
|
if err := c.SaveUploadedFile(file, path); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"erreur": "echec enregistrement fichier"})
|
|
return
|
|
}
|
|
|
|
categorie := resolveCategorie(ext, mime)
|
|
estPrincipale := idx == 0 && !hasPrincipale
|
|
|
|
pj, err := h.client.PieceJointe.Create().
|
|
SetObjetID(id).
|
|
SetNomFichier(file.Filename).
|
|
SetChemin(path).
|
|
SetTypeMime(mime).
|
|
SetCategorie(piecejointe.Categorie(categorie)).
|
|
SetEstPrincipale(estPrincipale).
|
|
Save(c.Request.Context())
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"erreur": "impossible d'enregistrer la piece jointe"})
|
|
return
|
|
}
|
|
|
|
saved = append(saved, map[string]any{
|
|
"id": pj.ID,
|
|
"nom_fichier": pj.NomFichier,
|
|
"chemin": pj.Chemin,
|
|
"type_mime": pj.TypeMime,
|
|
"categorie": pj.Categorie,
|
|
"est_principale": pj.EstPrincipale,
|
|
})
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, gin.H{"pieces_jointes": saved})
|
|
}
|
|
|
|
// ListPiecesJointes retourne la liste des pieces jointes d'un objet.
|
|
// @Summary Lister les pieces jointes
|
|
// @Tags PiecesJointes
|
|
// @Produce json
|
|
// @Param id path string true "ID objet"
|
|
// @Param page query int false "Page"
|
|
// @Param limit query int false "Limite"
|
|
// @Success 200 {object} map[string]any
|
|
// @Failure 400 {object} map[string]string
|
|
// @Failure 500 {object} map[string]string
|
|
// @Router /objets/{id}/pieces_jointes [get]
|
|
func (h *Handler) ListPiecesJointes(c *gin.Context) {
|
|
objetID, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"erreur": "identifiant invalide"})
|
|
return
|
|
}
|
|
|
|
limit, offset, page := parsePagination(c.Query("page"), c.Query("limit"))
|
|
query := h.client.PieceJointe.Query().
|
|
Where(piecejointe.ObjetID(objetID))
|
|
total, err := query.Count(c.Request.Context())
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"erreur": "impossible de compter les pieces jointes"})
|
|
return
|
|
}
|
|
|
|
items, err := query.
|
|
Order(ent.Desc(piecejointe.FieldCreatedAt)).
|
|
Limit(limit).
|
|
Offset(offset).
|
|
All(c.Request.Context())
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"erreur": "impossible de lister les pieces jointes"})
|
|
return
|
|
}
|
|
respondPaginated(c, items, total, page, limit)
|
|
}
|
|
|
|
// DeletePieceJointe supprime une piece jointe et son fichier.
|
|
// @Summary Supprimer une piece jointe
|
|
// @Tags PiecesJointes
|
|
// @Param id path string true "ID piece jointe"
|
|
// @Success 204 {string} string "No Content"
|
|
// @Failure 400 {object} map[string]string
|
|
// @Failure 404 {object} map[string]string
|
|
// @Failure 500 {object} map[string]string
|
|
// @Router /pieces_jointes/{id} [delete]
|
|
func (h *Handler) DeletePieceJointe(c *gin.Context) {
|
|
pieceID, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"erreur": "identifiant invalide"})
|
|
return
|
|
}
|
|
|
|
item, err := h.client.PieceJointe.Get(c.Request.Context(), pieceID)
|
|
if err != nil {
|
|
if ent.IsNotFound(err) {
|
|
c.JSON(http.StatusNotFound, gin.H{"erreur": "piece jointe introuvable"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusInternalServerError, gin.H{"erreur": "impossible de charger la piece jointe"})
|
|
return
|
|
}
|
|
|
|
if item.Chemin != "" {
|
|
if err := os.Remove(item.Chemin); err != nil && !errors.Is(err, os.ErrNotExist) {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"erreur": "impossible de supprimer le fichier"})
|
|
return
|
|
}
|
|
}
|
|
|
|
if err := h.client.PieceJointe.DeleteOneID(pieceID).Exec(c.Request.Context()); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"erreur": "impossible de supprimer la piece jointe"})
|
|
return
|
|
}
|
|
c.Status(http.StatusNoContent)
|
|
}
|
|
|
|
// SetPieceJointePrincipale marque une piece jointe comme principale.
|
|
// @Summary Definir la piece jointe principale
|
|
// @Tags PiecesJointes
|
|
// @Produce json
|
|
// @Param id path string true "ID piece jointe"
|
|
// @Success 200 {object} ent.PieceJointe
|
|
// @Failure 400 {object} map[string]string
|
|
// @Failure 404 {object} map[string]string
|
|
// @Failure 500 {object} map[string]string
|
|
// @Router /pieces_jointes/{id}/principale [put]
|
|
func (h *Handler) SetPieceJointePrincipale(c *gin.Context) {
|
|
pieceID, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"erreur": "identifiant invalide"})
|
|
return
|
|
}
|
|
|
|
item, err := h.client.PieceJointe.Get(c.Request.Context(), pieceID)
|
|
if err != nil {
|
|
if ent.IsNotFound(err) {
|
|
c.JSON(http.StatusNotFound, gin.H{"erreur": "piece jointe introuvable"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusInternalServerError, gin.H{"erreur": "impossible de charger la piece jointe"})
|
|
return
|
|
}
|
|
|
|
tx, err := h.client.Tx(c.Request.Context())
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"erreur": "impossible d'ouvrir la transaction"})
|
|
return
|
|
}
|
|
defer func() {
|
|
if err != nil {
|
|
_ = tx.Rollback()
|
|
}
|
|
}()
|
|
|
|
if _, err = tx.PieceJointe.Update().
|
|
Where(piecejointe.ObjetID(item.ObjetID)).
|
|
SetEstPrincipale(false).
|
|
Save(c.Request.Context()); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"erreur": "impossible de reinitialiser la principale"})
|
|
_ = tx.Rollback()
|
|
return
|
|
}
|
|
|
|
if _, err = tx.PieceJointe.UpdateOneID(pieceID).
|
|
SetEstPrincipale(true).
|
|
Save(c.Request.Context()); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"erreur": "impossible de definir la principale"})
|
|
_ = tx.Rollback()
|
|
return
|
|
}
|
|
|
|
if err = tx.Commit(); err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"erreur": "impossible de finaliser la transaction"})
|
|
return
|
|
}
|
|
|
|
updated, err := h.client.PieceJointe.Get(c.Request.Context(), pieceID)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"erreur": "impossible de charger la piece jointe"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, updated)
|
|
}
|