2 Commits

Author SHA1 Message Date
8f5131429f Automatically delete members when families are deleted 2023-08-04 19:17:51 +02:00
f344765dd8 Can create a family member 2023-08-04 19:03:46 +02:00
18 changed files with 1151 additions and 23 deletions

View File

@@ -13,6 +13,7 @@ export function AsyncWidget(p: {
errMsg: string;
build: () => React.ReactElement;
ready?: boolean;
errAdditionalElement?: () => React.ReactElement;
}): React.ReactElement {
const [state, setState] = useState(State.Loading);
@@ -62,6 +63,8 @@ export function AsyncWidget(p: {
</Alert>
<Button onClick={load}>Réessayer</Button>
{p.errAdditionalElement && p.errAdditionalElement()}
</Box>
);

View File

@@ -55,6 +55,11 @@ export function BaseAuthenticatedPage(): React.ReactElement {
loadKey="1"
load={load}
errMsg="Echec du chargement des informations utilisateur !"
errAdditionalElement={() => (
<>
<Button onClick={signOut}>Déconnexion</Button>
</>
)}
build={() => (
<UserContextK.Provider
value={{

View File

@@ -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"

View File

@@ -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"
thiserror = "1.0.40"
serde_with = "3.1.0"
rust_iso3166 = "0.1.10"

View File

@@ -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"
}

View File

@@ -42,12 +42,12 @@ 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,
father integer NULL REFERENCES members,
mother integer NULL REFERENCES members ON DELETE SET NULL,
father integer NULL REFERENCES members ON DELETE SET NULL,
birth_year smallint NULL,
birth_month smallint NULL,
birth_day smallint NULL,

View File

@@ -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<i64>) -> 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),
}
}
}

View File

@@ -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<i16>,
pub month: Option<i16>,
pub day: Option<i16>,
}
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<String>,
last_name: Option<String>,
birth_last_name: Option<String>,
email: Option<String>,
phone: Option<String>,
address: Option<String>,
city: Option<String>,
postal_code: Option<String>,
country: Option<String>,
sex: Option<Sex>,
mother: Option<MemberID>,
father: Option<MemberID>,
#[serde(flatten, with = "prefix_birth")]
birth: Option<RequestDate>,
#[serde(flatten, with = "prefix_death")]
death: Option<RequestDate>,
note: Option<String>,
}
#[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<String>,
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<MemberRequest>) -> 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))
}

View File

@@ -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;

View File

@@ -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<OIDCProvider<'a>>,
countries: Vec<CountryCode>,
}
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(),
}
}
}

View File

@@ -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()

View File

@@ -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,102 @@ 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 parse_str(s: &str) -> Option<Self> {
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<String>,
pub last_name: Option<String>,
pub birth_last_name: Option<String>,
pub photo_id: Option<String>,
pub email: Option<String>,
pub phone: Option<String>,
pub address: Option<String>,
pub city: Option<String>,
pub postal_code: Option<String>,
pub country: Option<String>,
sex: Option<String>,
time_create: i64,
pub time_update: i64,
mother: Option<i32>,
father: Option<i32>,
pub birth_year: Option<i16>,
pub birth_month: Option<i16>,
pub birth_day: Option<i16>,
pub death_year: Option<i16>,
pub death_month: Option<i16>,
pub death_day: Option<i16>,
pub note: Option<String>,
}
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<Sex> {
self.sex.as_deref().map(Sex::parse_str).unwrap_or_default()
}
pub fn set_sex(&mut self, s: Option<Sex>) {
self.sex = s.map(|s| s.to_str().to_string())
}
pub fn mother(&self) -> Option<MemberID> {
self.mother.map(MemberID)
}
pub fn set_mother(&mut self, p: Option<MemberID>) {
self.mother = p.map(|p| p.0);
}
pub fn father(&self) -> Option<MemberID> {
self.father.map(MemberID)
}
pub fn set_father(&mut self, p: Option<MemberID>) {
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,
}

View File

@@ -37,7 +37,7 @@ diesel::table! {
city -> Nullable<Varchar>,
postal_code -> Nullable<Varchar>,
country -> Nullable<Varchar>,
sex -> Varchar,
sex -> Nullable<Varchar>,
time_create -> Int8,
time_update -> Int8,
mother -> Nullable<Int4>,
@@ -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,);

View File

@@ -5,7 +5,7 @@ use crate::models::{
Family, FamilyID, FamilyMembership, Membership, NewFamily, NewMembership, UserID,
};
use crate::schema::{families, memberships};
use crate::services::users_service;
use crate::services::{members_service, users_service};
use crate::utils::string_utils::rand_str;
use crate::utils::time_utils::time;
use diesel::prelude::*;
@@ -183,7 +183,10 @@ pub async fn update_family(family: &Family) -> anyhow::Result<()> {
/// Delete a family
pub async fn delete_family(family_id: FamilyID) -> anyhow::Result<()> {
// TODO : delete members and couples
// TODO : delete couples
// Remove all family members
members_service::delete_all_family(family_id).await?;
// Remove all memberships
db_connection::execute(|conn| {

View File

@@ -0,0 +1,108 @@
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<Member> {
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<Member> {
db_connection::execute(|conn| members::table.filter(members::dsl::id.eq(id.0)).first(conn))
}
/// Get all the members of a family
pub async fn get_all_of_family(id: FamilyID) -> anyhow::Result<Vec<Member>> {
db_connection::execute(|conn| {
members::table
.filter(members::dsl::family_id.eq(id.0))
.get_results(conn)
})
}
/// Check whether a member with a given id exists or not
pub async fn exists(family_id: FamilyID, member_id: MemberID) -> anyhow::Result<bool> {
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(())
}
/// Delete all the members of a family
pub async fn delete_all_family(family_id: FamilyID) -> anyhow::Result<()> {
for m in get_all_of_family(family_id).await? {
delete(&m).await?;
}
Ok(())
}

View File

@@ -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;

View File

@@ -0,0 +1,48 @@
use lazy_static::lazy_static;
use std::collections::HashMap;
lazy_static! {
static ref COUNTRIES_FR: HashMap<String, String> =
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<CountryCode> {
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"))
}
}

View File

@@ -1,4 +1,5 @@
//! # App utilities
pub mod countries_utils;
pub mod string_utils;
pub mod time_utils;