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 }