diff --git a/geneit_backend/Cargo.lock b/geneit_backend/Cargo.lock index 5051c6b..f25d179 100644 --- a/geneit_backend/Cargo.lock +++ b/geneit_backend/Cargo.lock @@ -271,6 +271,21 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anstream" version = "0.3.2" @@ -361,9 +376,9 @@ checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" [[package]] name = "bcrypt" -version = "0.14.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9df288bec72232f78c1ec5fe4e8f1d108aa0265476e93097593c803c8c02062a" +checksum = "28d1c9c15093eb224f0baa400f38fcd713fc1391a6f1c389d886beef146d60a3" dependencies = [ "base64", "blowfish", @@ -466,6 +481,19 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "serde", + "winapi", +] + [[package]] name = "cipher" version = "0.4.4" @@ -594,6 +622,62 @@ dependencies = [ "typenum", ] +[[package]] +name = "csv" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "626ae34994d3d8d668f4269922248239db4ae42d538b14c398b74a52208e8086" +dependencies = [ + "csv-core", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "csv-core" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" +dependencies = [ + "memchr", +] + +[[package]] +name = "darling" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0209d94da627ab5605dcccf08bb18afa5009cfbef48d8a8b7d7bdbc79be25c5e" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "177e3443818124b357d8e76f53be906d60937f0d3a90773a664fa63fa253e621" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.23", +] + +[[package]] +name = "darling_macro" +version = "0.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836a9bbc7ad63342d6d6e7b815ccab164bc77a2d95d84bc3117a8c0d5c98e2d5" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.23", +] + [[package]] name = "derive_more" version = "0.99.17" @@ -651,6 +735,27 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "email-encoding" version = "0.2.0" @@ -667,6 +772,12 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2153bd83ebc09db15bcbdc3e2194d901804952e3dc96967e1cd3b0c5c32d112" +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + [[package]] name = "encoding_rs" version = "0.8.32" @@ -848,8 +959,10 @@ dependencies = [ "mailchecker", "rand", "redis", + "rust_iso3166", "serde", "serde_json", + "serde_with", "thiserror", ] @@ -917,6 +1030,12 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + [[package]] name = "hostname" version = "0.3.1" @@ -1005,6 +1124,35 @@ dependencies = [ "tokio-native-tls", ] +[[package]] +name = "iana-time-zone" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "0.3.0" @@ -1033,6 +1181,7 @@ checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown", + "serde", ] [[package]] @@ -1289,6 +1438,15 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "num-traits" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +dependencies = [ + "autocfg", +] + [[package]] name = "num_cpus" version = "1.16.0" @@ -1376,7 +1534,7 @@ checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.3.5", "smallvec", "windows-targets", ] @@ -1393,6 +1551,48 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_macros", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_macros" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.23", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project-lite" version = "0.2.10" @@ -1426,6 +1626,20 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "prettytable-rs" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eea25e07510aa6ab6547308ebe3c036016d162b8da920dbb079e3ba8acf3d95a" +dependencies = [ + "csv", + "encode_unicode", + "is-terminal", + "lazy_static", + "term", + "unicode-width", +] + [[package]] name = "proc-macro2" version = "1.0.63" @@ -1494,6 +1708,15 @@ dependencies = [ "url", ] +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "redox_syscall" version = "0.3.5" @@ -1503,6 +1726,17 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom", + "redox_syscall 0.2.16", + "thiserror", +] + [[package]] name = "regex" version = "1.8.4" @@ -1557,6 +1791,18 @@ dependencies = [ "winreg", ] +[[package]] +name = "rust_iso3166" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e19f23f46e5d2f3f1e917ecf5cc988e23ba7fc2a2ce3ef09cb17033842d0ef8" +dependencies = [ + "js-sys", + "phf", + "prettytable-rs", + "wasm-bindgen", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -1599,6 +1845,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + [[package]] name = "ryu" version = "1.0.14" @@ -1692,6 +1944,34 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_with" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21e47d95bc83ed33b2ecf84f4187ad1ab9685d18ff28db000c99deac8ce180e3" +dependencies = [ + "base64", + "chrono", + "hex", + "indexmap", + "serde", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea3cee93715c2e266b9338b7544da68a9f24e227722ba482bd1c024367c77c65" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.23", +] + [[package]] name = "sha1" version = "0.10.5" @@ -1718,6 +1998,12 @@ dependencies = [ "libc", ] +[[package]] +name = "siphasher" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" + [[package]] name = "slab" version = "0.4.8" @@ -1786,11 +2072,22 @@ dependencies = [ "autocfg", "cfg-if", "fastrand", - "redox_syscall", + "redox_syscall 0.3.5", "rustix 0.37.22", "windows-sys", ] +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", +] + [[package]] name = "termcolor" version = "1.2.0" @@ -1964,6 +2261,12 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + [[package]] name = "url" version = "2.4.0" @@ -2121,6 +2424,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.48.0" diff --git a/geneit_backend/Cargo.toml b/geneit_backend/Cargo.toml index 4f81b7f..d8084dc 100644 --- a/geneit_backend/Cargo.toml +++ b/geneit_backend/Cargo.toml @@ -22,6 +22,8 @@ mailchecker = "5.0.9" redis = "0.23.0" lettre = "0.10.4" rand = "0.8.5" -bcrypt = "0.14.0" +bcrypt = "0.15.0" light-openid = "1.0.1" -thiserror = "1.0.40" \ No newline at end of file +thiserror = "1.0.40" +serde_with = "3.1.0" +rust_iso3166 = "0.1.10" \ No newline at end of file diff --git a/geneit_backend/assets/iso-3166_country_french.json b/geneit_backend/assets/iso-3166_country_french.json new file mode 100644 index 0000000..249fae6 --- /dev/null +++ b/geneit_backend/assets/iso-3166_country_french.json @@ -0,0 +1,253 @@ +{ + "AD" : "Andorre", + "AE" : "Émirats Arabes Unis", + "AF" : "Afghanistan", + "AG" : "Antigua-Et-Barbuda", + "AI" : "Anguilla", + "AL" : "Albanie", + "AM" : "Arménie", + "AO" : "Angola", + "AP" : "Région Asie/Pacifique", + "AQ" : "Antarctique", + "AR" : "Argentine", + "AS" : "Samoa Américaines", + "AT" : "Autriche", + "AU" : "Australie", + "AW" : "Aruba", + "AX" : "Îles Åland", + "AZ" : "Azerbaïdjan", + "BA" : "Bosnie-Herzégovine", + "BB" : "Barbad", + "BD" : "Bangladesh", + "BE" : "Belgique", + "BF" : "Burkina Faso", + "BG" : "Bulgarie", + "BH" : "Bahreïn", + "BI" : "Burundi", + "BJ" : "Bénin", + "BL" : "Saint-Barthélemy", + "BM" : "Bermudes", + "BN" : "Brunei Darussalam", + "BO" : "État Plurinational De Bolivie", + "BQ" : "Bonaire, Saint-Eustache Et Saba", + "BR" : "Brésil", + "BS" : "Bahamas", + "BT" : "Bhoutan", + "BV" : "Île Bouvet", + "BW" : "Botswana", + "BY" : "Biélorussie", + "BZ" : "Belize", + "CA" : "Canada", + "CC" : "Îles Cocos", + "CD" : "République Démocratique Du Congo", + "CF" : "République Centrafricaine", + "CG" : "Congo", + "CH" : "Suisse", + "CI" : "Côte D'Ivoire", + "CK" : "Îles Cook", + "CL" : "Chili", + "CM" : "Cameroun", + "CN" : "Chine", + "CO" : "Colombie", + "CR" : "Costa Rica", + "CU" : "Cuba", + "CV" : "Cap-Vert", + "CW" : "Curaçao", + "CX" : "Île Christmas", + "CY" : "Chypre", + "CZ" : "République Tchèque", + "DE" : "Allemagne", + "DJ" : "Djibouti", + "DK" : "Denmark", + "DM" : "Dominique", + "DO" : "République Dominicaine", + "DZ" : "Algérie", + "EC" : "Équateur", + "EE" : "Estonie", + "EG" : "Égypte", + "EH" : "Sahara Occidental", + "ER" : "Érythrée", + "ES" : "Espagne", + "ET" : "Éthiopie", + "EU" : "Europe", + "FI" : "Finlande", + "FJ" : "Fidji", + "FK" : "Îles Malouines", + "FM" : "États Fédérés De Micronésie", + "FO" : "Îles Féroé", + "FR" : "France", + "GA" : "Gabon", + "GB" : "Royaume-Uni", + "GD" : "Grenade", + "GE" : "Géorgie", + "GF" : "Guyane", + "GG" : "Guernesey", + "GH" : "Ghana", + "GI" : "Gibraltar", + "GL" : "Groenland", + "GM" : "Gambie", + "GN" : "Guinée", + "GP" : "Guadeloupe", + "GQ" : "Guinée Équatoriale", + "GR" : "Grèce", + "GS" : "Géorgie Du Sud-Et-Les Îles Sandwich Du Sud", + "GT" : "Guatemala", + "GU" : "Guam", + "GW" : "Guinée-Bissau", + "GY" : "Guyana", + "HK" : "Hong Kong", + "HM" : "Îles Heard-Et-MacDonald", + "HN" : "Honduras", + "HR" : "Croatie", + "HT" : "Haïti", + "HU" : "Hongrie", + "ID" : "Indonésie", + "IE" : "Irlande", + "IL" : "Israël", + "IM" : "Île De Man", + "IN" : "Inde", + "IO" : "Territoire Britannique De L'océan Indien", + "IQ" : "Irak", + "IR" : "République Islamique D'Iran", + "IS" : "Islande", + "IT" : "Italie", + "JE" : "Jersey", + "JM" : "Jamaïque", + "JO" : "Jordanie", + "JP" : "Japon", + "KE" : "Kenya", + "KG" : "Kirghizistan", + "KH" : "Cambodge", + "KI" : "Kiribati", + "KM" : "Comores", + "KN" : "Saint-Christophe-et-Niévès", + "KP" : "République Populaire Démocratique De Corée", + "KR" : "République De Corée", + "KW" : "Koweït", + "KY" : "Îles Caïmans", + "KZ" : "Kazakhstan", + "LA" : "République Démocratique Populaire Lao", + "LB" : "Liban", + "LC" : "Sainte-Lucie", + "LI" : "Liechtenstein", + "LK" : "Sri Lanka", + "LR" : "Liberia", + "LS" : "Lesotho", + "LT" : "Lituanie", + "LU" : "Luxembourg", + "LV" : "Lettonie", + "LY" : "Libye", + "MA" : "Maroc", + "MC" : "Monaco", + "MD" : "République De Moldavie", + "ME" : "Monténégro", + "MF" : "Saint-Martin (Partie Française)", + "MG" : "Madagascar", + "MH" : "Îles Marshall", + "MK" : "Macédoine", + "ML" : "Mali", + "MM" : "Birmanie", + "MN" : "Mongolie", + "MO" : "Macao", + "MP" : "Îles Mariannes Du Nord", + "MQ" : "Martinique", + "MR" : "Mauritanie", + "MS" : "Montserrat", + "MT" : "Malte", + "MU" : "Maurice", + "MV" : "Maldives", + "MW" : "Malawi", + "MX" : "Mexique", + "MY" : "Malaisie", + "MZ" : "Mozambique", + "NA" : "Namibie", + "NC" : "Nouvelle-Calédonie", + "NE" : "Niger", + "NF" : "Île Norfolk", + "NG" : "Nigéria", + "NI" : "Nicaragua", + "NL" : "Pays-Bas", + "NO" : "Norvège", + "NP" : "Népal", + "NR" : "Nauru", + "NU" : "Niue", + "NZ" : "Nouvelle-Zélande", + "OM" : "Oman", + "PA" : "Panama", + "PE" : "Pérou", + "PF" : "Polynésie Française", + "PG" : "Papouasie-Nouvelle-Guinée", + "PH" : "Philippines", + "PK" : "Pakistan", + "PL" : "Pologne", + "PM" : "Saint-Pierre-Et-Miquelon", + "PN" : "Pitcairn", + "PR" : "Porto Rico", + "PS" : "Territoires Palestiniens Occupés", + "PT" : "Portugal", + "PW" : "Palaos", + "PY" : "Paraguay", + "QA" : "Qatar", + "RE" : "Réunion", + "RO" : "Roumanie", + "RS" : "Serbie", + "RU" : "Fédération De Russie", + "RW" : "Rwanda", + "SA" : "Arabie Saoudite", + "SB" : "Îles Salomon", + "SC" : "Seychelles", + "SD" : "Soudan", + "SE" : "Suède", + "SG" : "Singapour", + "SH" : "Sainte-Hélène", + "SI" : "Slovénie", + "SJ" : "Svalbard Et Jan Mayen", + "SK" : "Slovaquie", + "SL" : "Sierra Leone", + "SM" : "Saint-Marin", + "SN" : "Sénégal", + "SO" : "Somalie", + "SR" : "Suriname", + "SS" : "Soudan Du Sud", + "ST" : "Sao Tomé-Et-Principe", + "SV" : "République Du Salvador", + "SX" : "Saint-Martin (Partie Néerlandaise)", + "SY" : "République Arabe Syrienne", + "SZ" : "Swaziland", + "TC" : "Îles Turks-Et-Caïcos", + "TD" : "Tchad", + "TF" : "Terres Australes Françaises", + "TG" : "Togo", + "TH" : "Thaïlande", + "TJ" : "Tadjikistan", + "TK" : "Tokelau", + "TL" : "Timor-Leste", + "TM" : "Turkménistan", + "TN" : "Tunisie", + "TO" : "Tonga", + "TR" : "Turquie", + "TT" : "Trinité-Et-Tobago", + "TV" : "Tuvalu", + "TW" : "Taïwan", + "TZ" : "République-Unie De Tanzanie", + "UA" : "Ukraine", + "UG" : "Ouganda", + "UM" : "Îles Mineures Éloignées Des États-Unis", + "US" : "États-Unis", + "UY" : "Uruguay", + "UZ" : "Ouzbékistan", + "VA" : "Saint-Siège (État De La Cité Du Vatican)", + "VC" : "Saint-Vincent-Et-Les Grenadines", + "VE" : "Venezuela", + "VG" : "Îles Vierges Britanniques", + "VI" : "Îles Vierges Des États-Unis", + "VN" : "Viet Nam", + "VU" : "Vanuatu", + "WF" : "Wallis Et Futuna", + "WS" : "Samoa", + "YE" : "Yémen", + "YT" : "Mayotte", + "ZA" : "Afrique Du Sud", + "ZM" : "Zambie", + "ZW" : "Zimbabwe" +} \ No newline at end of file diff --git a/geneit_backend/migrations/2023-05-24-102711_create_users/up.sql b/geneit_backend/migrations/2023-05-24-102711_create_users/up.sql index 0a76005..8537071 100644 --- a/geneit_backend/migrations/2023-05-24-102711_create_users/up.sql +++ b/geneit_backend/migrations/2023-05-24-102711_create_users/up.sql @@ -42,8 +42,8 @@ CREATE TABLE members ( address VARCHAR (155) NULL, city VARCHAR(150) NULL, postal_code VARCHAR(12) NULL, - country VARCHAR(30) NULL, - sex VARCHAR(1) NOT NULL, + country VARCHAR(2) NULL, + sex VARCHAR(1) NULL, time_create BIGINT NOT NULL, time_update BIGINT NOT NULL, mother integer NULL REFERENCES members, diff --git a/geneit_backend/src/constants.rs b/geneit_backend/src/constants.rs index 8429909..4ad4634 100644 --- a/geneit_backend/src/constants.rs +++ b/geneit_backend/src/constants.rs @@ -1,6 +1,6 @@ use std::time::Duration; -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize)] pub struct SizeConstraint { min: usize, max: usize, @@ -17,6 +17,23 @@ impl SizeConstraint { } } +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct NumberValueConstraint { + min: i64, + max: i64, +} + +impl NumberValueConstraint { + pub fn new(min: i64, max: i64) -> Self { + Self { min, max } + } + + pub fn validate(&self, val: impl Into) -> bool { + let val = val.into(); + val >= self.min && val <= self.max + } +} + #[derive(Debug, Clone, serde::Serialize)] pub struct StaticConstraints { pub mail_len: SizeConstraint, @@ -24,6 +41,22 @@ pub struct StaticConstraints { pub password_len: SizeConstraint, pub family_name_len: SizeConstraint, pub invitation_code_len: SizeConstraint, + + pub member_first_name: SizeConstraint, + pub member_last_name: SizeConstraint, + pub member_birth_last_name: SizeConstraint, + pub member_email: SizeConstraint, + pub member_phone: SizeConstraint, + pub member_address: SizeConstraint, + pub member_city: SizeConstraint, + pub member_postal_code: SizeConstraint, + pub member_country: SizeConstraint, + pub member_sex: SizeConstraint, + pub member_note: SizeConstraint, + + pub date_year: NumberValueConstraint, + pub date_month: NumberValueConstraint, + pub date_day: NumberValueConstraint, } impl Default for StaticConstraints { @@ -37,6 +70,20 @@ impl Default for StaticConstraints { FAMILY_INVITATION_CODE_LEN, FAMILY_INVITATION_CODE_LEN, ), + member_first_name: SizeConstraint::new(0, 30), + member_last_name: SizeConstraint::new(0, 30), + member_birth_last_name: SizeConstraint::new(0, 30), + member_email: SizeConstraint::new(0, 255), + member_phone: SizeConstraint::new(0, 30), + member_address: SizeConstraint::new(0, 155), + member_city: SizeConstraint::new(0, 150), + member_postal_code: SizeConstraint::new(0, 12), + member_country: SizeConstraint::new(0, 2), + member_sex: SizeConstraint::new(0, 1), + member_note: SizeConstraint::new(0, 35000), + date_year: NumberValueConstraint::new(1, 2050), + date_month: NumberValueConstraint::new(1, 12), + date_day: NumberValueConstraint::new(1, 31), } } } diff --git a/geneit_backend/src/controllers/members_controller.rs b/geneit_backend/src/controllers/members_controller.rs new file mode 100644 index 0000000..276be12 --- /dev/null +++ b/geneit_backend/src/controllers/members_controller.rs @@ -0,0 +1,242 @@ +use crate::constants::{SizeConstraint, StaticConstraints}; +use crate::controllers::HttpResult; +use crate::extractors::family_extractor::FamilyInPath; +use crate::models::{Member, MemberID, Sex}; +use crate::services::members_service; +use crate::utils::countries_utils; +use actix_web::{web, HttpResponse}; + +serde_with::with_prefix!(prefix_birth "birth_"); +serde_with::with_prefix!(prefix_death "death_"); + +#[derive(serde::Deserialize)] +pub struct RequestDate { + pub year: Option, + pub month: Option, + pub day: Option, +} + +impl RequestDate { + pub fn check(&self) -> bool { + let c = StaticConstraints::default(); + + self.year.map(|y| c.date_year.validate(y)).unwrap_or(true) + && self.month.map(|y| c.date_month.validate(y)).unwrap_or(true) + && self.day.map(|y| c.date_day.validate(y)).unwrap_or(true) + } +} + +#[derive(serde::Deserialize)] +pub struct MemberRequest { + first_name: Option, + last_name: Option, + birth_last_name: Option, + email: Option, + phone: Option, + address: Option, + city: Option, + postal_code: Option, + country: Option, + sex: Option, + mother: Option, + father: Option, + #[serde(flatten, with = "prefix_birth")] + birth: Option, + #[serde(flatten, with = "prefix_death")] + death: Option, + note: Option, +} + +#[derive(thiserror::Error, Debug)] +enum MemberControllerErr { + #[error("Malformed first name!")] + MalformedFirstname, + #[error("Malformed last name!")] + MalformedLastname, + #[error("Malformed birth last name!")] + MalformedBirthLastname, + #[error("Malformed email address!")] + MalformedEmailAddress, + #[error("Invalid email address!")] + InvalidEmailAddress, + #[error("Malformed phone number!")] + MalformedPhoneNumber, + #[error("Malformed address!")] + MalformedAddress, + #[error("Malformed city!")] + MalformedCity, + #[error("Malformed postal code!")] + MalformedPostalCode, + #[error("Malformed country!")] + MalformedCountry, + #[error("Invalid country code!")] + InvalidCountryCode, + #[error("Malformed date of birth!")] + MalformedDateOfBirth, + #[error("Malformed date of death!")] + MalformedDateOfDeath, + #[error("Malformed note!")] + MalformedNote, + #[error("Mother does not exists!")] + MotherNotExisting, + #[error("Father does not exists!")] + FatherNotExisting, +} + +fn check_opt_str_val( + val: &Option, + c: SizeConstraint, + err: MemberControllerErr, +) -> anyhow::Result<()> { + if let Some(v) = val { + if !c.validate(v) { + return Err(err.into()); + } + } + Ok(()) +} + +impl MemberRequest { + pub async fn to_member(self, member: &mut Member) -> anyhow::Result<()> { + let c = StaticConstraints::default(); + check_opt_str_val( + &self.first_name, + c.member_first_name, + MemberControllerErr::MalformedFirstname, + )?; + + check_opt_str_val( + &self.last_name, + c.member_last_name, + MemberControllerErr::MalformedLastname, + )?; + + check_opt_str_val( + &self.birth_last_name, + c.member_birth_last_name, + MemberControllerErr::MalformedBirthLastname, + )?; + + check_opt_str_val( + &self.email, + c.member_email, + MemberControllerErr::MalformedEmailAddress, + )?; + + if let Some(mail) = &self.email { + if !mailchecker::is_valid(mail) { + return Err(MemberControllerErr::InvalidEmailAddress.into()); + } + } + + check_opt_str_val( + &self.phone, + c.member_phone, + MemberControllerErr::MalformedPhoneNumber, + )?; + + check_opt_str_val( + &self.address, + c.member_address, + MemberControllerErr::MalformedAddress, + )?; + + check_opt_str_val( + &self.city, + c.member_city, + MemberControllerErr::MalformedCity, + )?; + + check_opt_str_val( + &self.postal_code, + c.member_postal_code, + MemberControllerErr::MalformedPostalCode, + )?; + + check_opt_str_val( + &self.country, + c.member_country, + MemberControllerErr::MalformedCountry, + )?; + + if let Some(c) = &self.country { + if !countries_utils::is_code_valid(c) { + return Err(MemberControllerErr::InvalidCountryCode.into()); + } + } + + if let Some(d) = &self.birth { + if !d.check() { + return Err(MemberControllerErr::MalformedDateOfBirth.into()); + } + } + + if let Some(d) = &self.death { + if !d.check() { + return Err(MemberControllerErr::MalformedDateOfDeath.into()); + } + } + + check_opt_str_val( + &self.note, + c.member_note, + MemberControllerErr::MalformedNote, + )?; + + if let Some(mother) = self.mother { + if !members_service::exists(member.family_id(), mother).await? { + return Err(MemberControllerErr::MotherNotExisting.into()); + } + } + + if let Some(father) = self.father { + if !members_service::exists(member.family_id(), father).await? { + return Err(MemberControllerErr::FatherNotExisting.into()); + } + } + + member.first_name = self.first_name; + member.last_name = self.last_name; + member.birth_last_name = self.birth_last_name; + member.email = self.email; + member.phone = self.phone; + member.address = self.address; + member.city = self.city; + member.postal_code = self.postal_code; + member.country = self.country; + member.set_sex(self.sex); + member.set_mother(self.mother); + member.set_father(self.father); + + member.birth_year = self.birth.as_ref().map(|m| m.year).unwrap_or_default(); + member.birth_month = self.birth.as_ref().map(|m| m.month).unwrap_or_default(); + member.birth_day = self.birth.as_ref().map(|m| m.day).unwrap_or_default(); + + member.death_year = self.death.as_ref().map(|m| m.year).unwrap_or_default(); + member.death_month = self.death.as_ref().map(|m| m.month).unwrap_or_default(); + member.death_day = self.death.as_ref().map(|m| m.day).unwrap_or_default(); + + member.note = self.note; + + Ok(()) + } +} + +/// Create a new family member +pub async fn create(f: FamilyInPath, req: web::Json) -> HttpResult { + let mut member = members_service::create(f.family_id()).await?; + + if let Err(e) = req.0.to_member(&mut member).await { + log::error!("Failed to apply member information! {e}"); + members_service::delete(&member).await?; + return Ok(HttpResponse::BadRequest().body(e.to_string())); + } + + if let Err(e) = members_service::update(&mut member).await { + log::error!("Failed to update member information! {e}"); + members_service::delete(&member).await?; + return Ok(HttpResponse::InternalServerError().finish()); + } + + Ok(HttpResponse::Ok().json(member)) +} diff --git a/geneit_backend/src/controllers/mod.rs b/geneit_backend/src/controllers/mod.rs index 78186b8..bf68f95 100644 --- a/geneit_backend/src/controllers/mod.rs +++ b/geneit_backend/src/controllers/mod.rs @@ -6,6 +6,7 @@ use std::fmt::{Debug, Display, Formatter}; pub mod auth_controller; pub mod families_controller; +pub mod members_controller; pub mod server_controller; pub mod users_controller; diff --git a/geneit_backend/src/controllers/server_controller.rs b/geneit_backend/src/controllers/server_controller.rs index 02f5a8a..21c3295 100644 --- a/geneit_backend/src/controllers/server_controller.rs +++ b/geneit_backend/src/controllers/server_controller.rs @@ -1,5 +1,7 @@ use crate::app_config::{AppConfig, OIDCProvider}; use crate::constants::StaticConstraints; +use crate::utils::countries_utils; +use crate::utils::countries_utils::CountryCode; use actix_web::{HttpResponse, Responder}; /// Default hello route @@ -12,6 +14,7 @@ struct ServerConfig<'a> { constraints: StaticConstraints, mail: &'static str, oidc_providers: Vec>, + countries: Vec, } impl Default for ServerConfig<'_> { @@ -20,6 +23,7 @@ impl Default for ServerConfig<'_> { mail: AppConfig::get().mail_sender.as_str(), constraints: StaticConstraints::default(), oidc_providers: AppConfig::get().openid_providers(), + countries: countries_utils::get_list(), } } } diff --git a/geneit_backend/src/main.rs b/geneit_backend/src/main.rs index f937068..e0b6cf0 100644 --- a/geneit_backend/src/main.rs +++ b/geneit_backend/src/main.rs @@ -4,7 +4,7 @@ use actix_web::middleware::Logger; use actix_web::{web, App, HttpServer}; use geneit_backend::app_config::AppConfig; use geneit_backend::controllers::{ - auth_controller, families_controller, server_controller, users_controller, + auth_controller, families_controller, members_controller, server_controller, users_controller, }; #[actix_web::main] @@ -122,6 +122,11 @@ async fn main() -> std::io::Result<()> { "/family/{id}/user/{user_id}", web::delete().to(families_controller::delete_membership), ) + // Members controller + .route( + "/family/{id}/member/create", + web::post().to(members_controller::create), + ) }) .bind(AppConfig::get().listen_address.as_str())? .run() diff --git a/geneit_backend/src/models.rs b/geneit_backend/src/models.rs index 1041c43..ddcb903 100644 --- a/geneit_backend/src/models.rs +++ b/geneit_backend/src/models.rs @@ -1,4 +1,4 @@ -use crate::schema::{families, memberships, users}; +use crate::schema::{families, members, memberships, users}; use diesel::prelude::*; /// User ID holder @@ -116,3 +116,105 @@ pub struct FamilyMembership { pub count_members: i64, pub count_admins: i64, } + +/// Member ID holder +#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)] +pub struct MemberID(pub i32); + +#[derive(serde::Serialize, serde::Deserialize)] +pub enum Sex { + #[serde(rename = "M")] + Male, + #[serde(rename = "F")] + Female, +} + +impl Sex { + pub fn from_str(s: &str) -> Option { + match s { + "M" => Some(Sex::Male), + "F" => Some(Sex::Female), + _ => None, + } + } + + pub fn to_str(&self) -> &'static str { + match self { + Sex::Male => "M", + Sex::Female => "F", + } + } +} + +#[derive(Queryable, Debug, serde::Serialize)] +pub struct Member { + id: i32, + family_id: i32, + pub first_name: Option, + pub last_name: Option, + pub birth_last_name: Option, + pub photo_id: Option, + pub email: Option, + pub phone: Option, + pub address: Option, + pub city: Option, + pub postal_code: Option, + pub country: Option, + sex: Option, + time_create: i64, + pub time_update: i64, + mother: Option, + father: Option, + pub birth_year: Option, + pub birth_month: Option, + pub birth_day: Option, + pub death_year: Option, + pub death_month: Option, + pub death_day: Option, + pub note: Option, +} + +impl Member { + pub fn id(&self) -> MemberID { + MemberID(self.id) + } + + pub fn family_id(&self) -> FamilyID { + FamilyID(self.family_id) + } + + pub fn sex(&self) -> Option { + self.sex + .as_deref() + .map(|s| Sex::from_str(s)) + .unwrap_or_default() + } + + pub fn set_sex(&mut self, s: Option) { + self.sex = s.map(|s| s.to_str().to_string()) + } + + pub fn mother(&self) -> Option { + self.mother.map(MemberID) + } + + pub fn set_mother(&mut self, p: Option) { + self.mother = p.map(|p| p.0); + } + + pub fn father(&self) -> Option { + self.father.map(MemberID) + } + + pub fn set_father(&mut self, p: Option) { + self.father = p.map(|p| p.0); + } +} + +#[derive(Insertable)] +#[diesel(table_name = members)] +pub struct NewMember { + pub family_id: i32, + pub time_create: i64, + pub time_update: i64, +} diff --git a/geneit_backend/src/schema.rs b/geneit_backend/src/schema.rs index 7f7a9cb..c2a0ba1 100644 --- a/geneit_backend/src/schema.rs +++ b/geneit_backend/src/schema.rs @@ -37,7 +37,7 @@ diesel::table! { city -> Nullable, postal_code -> Nullable, country -> Nullable, - sex -> Varchar, + sex -> Nullable, time_create -> Int8, time_update -> Int8, mother -> Nullable, @@ -82,10 +82,4 @@ diesel::joinable!(members -> families (family_id)); diesel::joinable!(memberships -> families (family_id)); diesel::joinable!(memberships -> users (user_id)); -diesel::allow_tables_to_appear_in_same_query!( - couples, - families, - members, - memberships, - users, -); +diesel::allow_tables_to_appear_in_same_query!(couples, families, members, memberships, users,); diff --git a/geneit_backend/src/services/members_service.rs b/geneit_backend/src/services/members_service.rs new file mode 100644 index 0000000..ff4dbaf --- /dev/null +++ b/geneit_backend/src/services/members_service.rs @@ -0,0 +1,91 @@ +use crate::connections::db_connection; +use crate::models::{FamilyID, Member, MemberID, NewMember}; +use crate::schema::members; +use crate::utils::time_utils::time; +use diesel::prelude::*; +use diesel::RunQueryDsl; + +/// Create a new family member +pub async fn create(family_id: FamilyID) -> anyhow::Result { + db_connection::execute(|conn| { + let res: Member = diesel::insert_into(members::table) + .values(&NewMember { + family_id: family_id.0, + time_create: time() as i64, + time_update: time() as i64, + }) + .get_result(conn)?; + + Ok(res) + }) +} + +/// Get the information of a member +pub async fn get_by_id(id: MemberID) -> anyhow::Result { + db_connection::execute(|conn| members::table.filter(members::dsl::id.eq(id.0)).first(conn)) +} + +/// Check whether a member with a given id exists or not +pub async fn exists(family_id: FamilyID, member_id: MemberID) -> anyhow::Result { + db_connection::execute(|conn| { + let count: i64 = members::table + .filter( + members::id + .eq(member_id.0) + .and(members::family_id.eq(family_id.0)), + ) + .count() + .get_result(conn)?; + + Ok(count != 0) + }) +} + +/// Update the information of a member +pub async fn update(member: &mut Member) -> anyhow::Result<()> { + member.time_update = time() as i64; + + db_connection::execute(|conn| { + diesel::update(members::dsl::members.filter(members::dsl::id.eq(member.id().0))) + .set(( + members::dsl::first_name.eq(member.first_name.clone()), + members::dsl::last_name.eq(member.last_name.clone()), + members::dsl::birth_last_name.eq(member.birth_last_name.clone()), + members::dsl::photo_id.eq(member.photo_id.clone()), + members::dsl::email.eq(member.email.clone()), + members::dsl::phone.eq(member.phone.clone()), + members::dsl::address.eq(member.address.clone()), + members::dsl::city.eq(member.city.clone()), + members::dsl::postal_code.eq(member.postal_code.clone()), + members::dsl::country.eq(member.country.clone()), + members::dsl::sex.eq(member.sex().map(|s| s.to_str().to_string())), + members::dsl::time_update.eq(member.time_update), + members::dsl::mother.eq(member.mother().map(|m| m.0)), + members::dsl::father.eq(member.father().map(|m| m.0)), + members::dsl::birth_year.eq(member.birth_year), + members::dsl::birth_month.eq(member.birth_month), + members::dsl::birth_day.eq(member.birth_day), + members::dsl::death_year.eq(member.death_year), + members::dsl::death_month.eq(member.death_month), + members::dsl::death_day.eq(member.death_day), + members::dsl::note.eq(member.note.clone()), + )) + .execute(conn) + })?; + + Ok(()) +} + +/// Delete a member +pub async fn delete(member: &Member) -> anyhow::Result<()> { + // TODO : remove associated couple + // TODO : remove user photo + + // Remove the member + db_connection::execute(|conn| { + diesel::delete(members::dsl::members.filter(members::dsl::id.eq(member.id().0))) + .execute(conn) + })?; + + Ok(()) +} diff --git a/geneit_backend/src/services/mod.rs b/geneit_backend/src/services/mod.rs index 4ccdf03..5f183b8 100644 --- a/geneit_backend/src/services/mod.rs +++ b/geneit_backend/src/services/mod.rs @@ -3,6 +3,7 @@ pub mod families_service; pub mod login_token_service; pub mod mail_service; +pub mod members_service; pub mod openid_service; pub mod rate_limiter_service; pub mod users_service; diff --git a/geneit_backend/src/utils/countries_utils.rs b/geneit_backend/src/utils/countries_utils.rs new file mode 100644 index 0000000..3954055 --- /dev/null +++ b/geneit_backend/src/utils/countries_utils.rs @@ -0,0 +1,48 @@ +use lazy_static::lazy_static; +use std::collections::HashMap; + +lazy_static! { + static ref COUNTRIES_FR: HashMap = + serde_json::from_str(include_str!("../../assets/iso-3166_country_french.json")).unwrap(); +} + +pub fn is_code_valid(code: &str) -> bool { + rust_iso3166::ALL_ALPHA2.contains(&code) +} + +#[derive(serde::Serialize, Debug, Copy, Clone)] +pub struct CountryCode { + code: &'static str, + en: &'static str, + fr: &'static str, +} + +/// Get the entire list of countries +pub fn get_list() -> Vec { + rust_iso3166::ALL + .iter() + .map(|c| CountryCode { + code: c.alpha2, + en: c.name, + fr: COUNTRIES_FR + .get(c.alpha2) + .map(|s| s.as_str()) + .unwrap_or(c.name), + }) + .collect() +} + +#[cfg(test)] +mod test { + use crate::utils::countries_utils::is_code_valid; + + #[test] + fn test_code_fr() { + assert!(is_code_valid("FR")) + } + + #[test] + fn test_code_bad() { + assert!(!is_code_valid("ZZ")) + } +} diff --git a/geneit_backend/src/utils/mod.rs b/geneit_backend/src/utils/mod.rs index 1af8d13..43fcc8d 100644 --- a/geneit_backend/src/utils/mod.rs +++ b/geneit_backend/src/utils/mod.rs @@ -1,4 +1,5 @@ //! # App utilities +pub mod countries_utils; pub mod string_utils; pub mod time_utils;