Files
matosbox/backend/internal/handlers/pieces_jointes.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)
}