github.com/benoitkugler/goacve@v0.0.0-20201217100549-151ce6e55dc8/server/shared/utils.go (about)

     1  // Implémente divers utilitaires communs
     2  // aux controllers du serveur.
     3  package shared
     4  
     5  import (
     6  	"database/sql"
     7  	"fmt"
     8  	"io"
     9  	"mime/multipart"
    10  	"net/http"
    11  	"net/url"
    12  	"strconv"
    13  	"strings"
    14  
    15  	rd "github.com/benoitkugler/goACVE/server/core/rawdata"
    16  	"github.com/labstack/echo"
    17  
    18  	"github.com/benoitkugler/goACVE/logs"
    19  )
    20  
    21  type Controller struct {
    22  	SMTP    logs.SMTP
    23  	DB      *sql.DB
    24  	Signing logs.Encrypteur
    25  }
    26  
    27  type CampMeta struct {
    28  	Id         int64     `json:"id"`
    29  	Label      rd.String `json:"label"`
    30  	Terminated bool      `json:"terminated"`
    31  }
    32  
    33  type Camp struct {
    34  	CampMeta
    35  	Description    string            `json:"description"`
    36  	Prix           rd.Euros          `json:"prix"`
    37  	Options        rd.OptionsCamp    `json:"options"`
    38  	OptionPrix     rd.OptionPrixCamp `json:"option_prix"`
    39  	SchemaPaiement rd.SchemaPaiement `json:"schema_paiement"`
    40  	DateDebut      rd.Date           `json:"date_debut"`
    41  	DateFin        rd.Date           `json:"date_fin"`
    42  	AgeMin         rd.Int            `json:"age_min"`
    43  	AgeMax         rd.Int            `json:"age_max"`
    44  	IsSimple       bool              `json:"is_simple"`
    45  	Infos          rd.String         `json:"infos"`
    46  }
    47  
    48  func (c *Camp) From(camp rd.Camp) {
    49  	c.Id = camp.Id
    50  	c.Label = camp.Label()
    51  	c.Description = camp.Description()
    52  	c.Terminated = camp.IsTerminated()
    53  	c.Prix = camp.Prix
    54  	c.Options = camp.Options
    55  	c.OptionPrix = camp.OptionPrix
    56  	c.SchemaPaiement = camp.SchemaPaiement
    57  	c.AgeMax = camp.AgeMax
    58  	c.AgeMin = camp.AgeMin
    59  	c.DateDebut = camp.DateDebut
    60  	c.DateFin = camp.DateFin
    61  	c.IsSimple = bool(camp.InscriptionSimple)
    62  	c.Infos = camp.Infos
    63  }
    64  
    65  // Rollback tente un rollback et forward la raison ou renvoie
    66  // l'erreur du rollback
    67  func Rollback(tx *sql.Tx, raison error) error {
    68  	if errRb := tx.Rollback(); errRb != nil {
    69  		return fmt.Errorf("Erreur pendant l'annulation de la transaction : %s", errRb)
    70  	}
    71  	if _, isErrorSQL := raison.(formattedError); isErrorSQL {
    72  		// on n'ajoute pas de contexte
    73  		return raison
    74  	}
    75  	return FormatErr("La requête SQL a échouée.", raison)
    76  }
    77  
    78  type formattedError struct {
    79  	error
    80  }
    81  
    82  func FormatErr(explication string, details error) error {
    83  	return formattedError{fmt.Errorf(explication+"<br/> Détails : <i>%s</i>", details)}
    84  }
    85  
    86  func BuildUrl(host, path string, params map[string]string) string {
    87  	pm := url.Values{}
    88  	for k, v := range params {
    89  		pm.Add(k, v)
    90  	}
    91  	u := url.URL{
    92  		Host:     host,
    93  		Scheme:   "https",
    94  		Path:     path,
    95  		RawQuery: pm.Encode(),
    96  	}
    97  	if strings.HasPrefix(host, "localhost") {
    98  		u.Scheme = "http"
    99  	}
   100  	return u.String()
   101  }
   102  
   103  // DeletePersonne supprime la personne temporaire ainsi que ses documents.
   104  // Si la personne n'est pas temporaire rien n'est fait.
   105  //
   106  // Ne commit PAS, ne rollback PAS
   107  //
   108  // Renvoi les ids des documents supprimés.
   109  func DeletePersonne(tx rd.DB, idPersonne int64) (isTemporaire bool, docs rd.Ids, err error) {
   110  	personne, err := rd.SelectPersonne(tx, idPersonne)
   111  	if err != nil {
   112  		return false, nil, err
   113  	}
   114  	if !personne.IsTemporaire {
   115  		return false, nil, nil
   116  	}
   117  
   118  	rows, err := tx.Query("DELETE FROM document_personnes WHERE id_personne = $1 RETURNING id_document", idPersonne)
   119  	if err != nil {
   120  		return false, nil, err
   121  	}
   122  	deletedDocs, err := rd.ScanIds(rows)
   123  	if err != nil {
   124  		return false, nil, err
   125  	}
   126  
   127  	_, err = rd.DeleteDocumentsByIds(tx, deletedDocs...)
   128  	if err != nil {
   129  		return false, nil, err
   130  	}
   131  
   132  	_, err = rd.DeletePersonneById(tx, personne.Id)
   133  	if err != nil {
   134  		return false, nil, err
   135  	}
   136  	return true, deletedDocs, nil
   137  }
   138  
   139  type DocumentsData struct {
   140  	Documents   rd.Documents
   141  	Contraintes rd.Contraintes
   142  	Liens       map[int64]rd.DocumentPersonne
   143  }
   144  
   145  // LoadDocuments renvoie les documents des personnes demandées
   146  func (ct Controller) LoadDocuments(idsPersonnes rd.Ids) (DocumentsData, error) {
   147  	var out DocumentsData
   148  	docPers, err := rd.SelectDocumentPersonnesByIdPersonnes(ct.DB, idsPersonnes...)
   149  	if err != nil {
   150  		return out, err
   151  	}
   152  	out.Liens = docPers.ByIdDocument()
   153  
   154  	rows, err := ct.DB.Query(`SELECT documents.* FROM documents
   155  		JOIN document_personnes ON document_personnes.id_document = documents.id
   156  		WHERE document_personnes.id_personne = ANY($1)`, idsPersonnes.AsSQL())
   157  	if err != nil {
   158  		return out, err
   159  	}
   160  	out.Documents, err = rd.ScanDocuments(rows)
   161  	if err != nil {
   162  		return out, err
   163  	}
   164  
   165  	rows, err = ct.DB.Query(`SELECT contraintes.* FROM contraintes 
   166  		JOIN document_personnes ON document_personnes.id_contrainte = contraintes.id
   167  		WHERE document_personnes.id_document = ANY($1)`, out.Documents.Ids().AsSQL())
   168  	if err != nil {
   169  		return out, err
   170  	}
   171  	out.Contraintes, err = rd.ScanContraintes(rows)
   172  
   173  	return out, err
   174  }
   175  
   176  // GetNewKey renvoie une clé de facture non utilisée.
   177  func GetNewKey(tx *sql.Tx) (string, error) {
   178  	rows, err := tx.Query("SELECT key FROM factures")
   179  	if err != nil {
   180  		return "", err
   181  	}
   182  	defer rows.Close()
   183  	keys := make(map[string]bool)
   184  	for rows.Next() {
   185  		key := new(sql.NullString)
   186  		if err = rows.Scan(key); err != nil {
   187  			return "", err
   188  		}
   189  		if key.Valid {
   190  			keys[key.String] = true
   191  		}
   192  	}
   193  
   194  	if err = rows.Err(); err != nil {
   195  		return "", err
   196  	}
   197  	newKey := rd.RandString(64, false)
   198  	for keys[newKey] { // on essaie jusqu'à trouver une nouvelle
   199  		newKey = rd.RandString(64, false)
   200  	}
   201  	return newKey, nil
   202  }
   203  
   204  // ParseId renvoie le paramètre 'field' de l'url
   205  func ParseId(c echo.Context, field string) (int64, error) {
   206  	idS := c.QueryParam(field)
   207  	id, err := strconv.ParseInt(idS, 10, 0)
   208  	if err != nil {
   209  		return 0, FormatErr("Identifiant invalide.", err)
   210  	}
   211  	return id, nil
   212  }
   213  
   214  // WriteMultipart crée une réponse multipart et écrit
   215  // chaque champs présent dans `data`. Retourne aussi FormDataContentType
   216  func WriteMultipart(header http.Header, w io.Writer, data map[string][]byte) error {
   217  	mw := multipart.NewWriter(w)
   218  	header.Set("Content-Type", mw.FormDataContentType())
   219  	for key, value := range data {
   220  		fw, err := mw.CreateFormField(key)
   221  		if err != nil {
   222  			return err
   223  		}
   224  		if _, err := fw.Write(value); err != nil {
   225  			return err
   226  		}
   227  	}
   228  	err := mw.Close()
   229  	return err
   230  }