backend api, swagger, tooling, frontend skeleton
This commit is contained in:
275
backend/internal/handlers/pieces_jointes.go
Normal file
275
backend/internal/handlers/pieces_jointes.go
Normal file
@@ -0,0 +1,275 @@
|
||||
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)
|
||||
}
|
||||
Reference in New Issue
Block a user