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