diff --git a/.github/workflows/cli.yml b/.github/workflows/cli.yml index 02d8d9a84..5460dd71c 100644 --- a/.github/workflows/cli.yml +++ b/.github/workflows/cli.yml @@ -23,19 +23,19 @@ jobs: - os_name: Linux-x86_64 os: ubuntu-20.04 target: x86_64-unknown-linux-gnu - bin: daikokucli-linux-amd64 + bin: daikoku-linux-amd64 - os_name: Windows-x86_64 os: windows-latest target: x86_64-pc-windows-msvc - bin: daikokucli-amd64.exe + bin: daikoku-amd64.exe - os_name: macOS-x86_64 os: macOS-latest target: x86_64-apple-darwin - bin: daikokucli-darwin-amd64 + bin: daikoku-darwin-amd64 - os_name: macOS-aarch64 os: macOS-latest target: aarch64-apple-darwin - bin: daikokucli-darwin-arm64 + bin: daikoku-darwin-arm64 toolchain: - stable steps: @@ -50,10 +50,10 @@ jobs: args: "--release" strip: true - name: Rename binary (linux and macos) - run: mv ./cli/target/${{ matrix.platform.target }}/release/daikokucli ./cli/target/${{ matrix.platform.target }}/release/${{ matrix.platform.bin }} + run: mv ./cli/target/${{ matrix.platform.target }}/release/daikoku ./cli/target/${{ matrix.platform.target }}/release/${{ matrix.platform.bin }} if: matrix.platform.os_name != 'Windows-x86_64' - name: Rename binary (windows) - run: mv ./cli/target/${{ matrix.platform.target }}/release/daikokucli.exe ./cli/target/${{ matrix.platform.target }}/release/${{ matrix.platform.bin }} + run: mv ./cli/target/${{ matrix.platform.target }}/release/daikoku.exe ./cli/target/${{ matrix.platform.target }}/release/${{ matrix.platform.bin }} if: matrix.platform.os_name == 'Windows-x86_64' - name: Generate SHA-256 run: shasum -a 256 ./cli/target/${{ matrix.platform.target }}/release/${{ matrix.platform.bin }} | cut -d ' ' -f 1 > ./cli/target/${{ matrix.platform.target }}/release/${{ matrix.platform.bin }}.sha256 diff --git a/cli/Cargo.lock b/cli/Cargo.lock index 2715731a5..31784e65d 100644 --- a/cli/Cargo.lock +++ b/cli/Cargo.lock @@ -4,24 +4,24 @@ version = 3 [[package]] name = "addr2line" -version = "0.21.0" +version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] [[package]] -name = "adler" -version = "1.0.2" +name = "adler2" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "aes" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac1f845298e95f983ff1944b728ae08b8cebab80d684f0a832ed0fc74dfa27e2" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", @@ -30,9 +30,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] @@ -54,67 +54,69 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.4" +version = "0.6.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" +checksum = "23a1e53f0f5d86382dafe1cf314783b2044280f406e7e1506368220ad11b1338" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.4" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" +checksum = "8365de52b16c035ff4fcafe0092ba9390540e3e352870ac09933bebcaa2c8c56" [[package]] name = "anstyle-parse" -version = "0.2.2" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" +checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.0" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.1" +version = "3.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" +checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" dependencies = [ "anstyle", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] name = "anyhow" -version = "1.0.75" +version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +checksum = "c042108f3ed77fd83760a5fd79b53be043192bb3b9dba91d8c574c0ada7850c8" [[package]] name = "assert_cmd" -version = "2.0.12" +version = "2.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88903cb14723e4d4003335bb7f8a14f27691649105346a0f0957466c096adfe6" +checksum = "dc1835b7f27878de8525dc71410b5a31cdcc5f230aed5ba5df968e09c201b23d" dependencies = [ "anstyle", "bstr", "doc-comment", + "libc", "predicates", "predicates-core", "predicates-tree", @@ -123,9 +125,9 @@ dependencies = [ [[package]] name = "assert_fs" -version = "1.0.13" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f070617a68e5c2ed5d06ee8dd620ee18fb72b99f6c094bed34cf8ab07c875b48" +checksum = "7efdb1fdb47602827a342857666feb372712cbc64b414172bd6b167a02927674" dependencies = [ "anstyle", "doc-comment", @@ -138,59 +140,76 @@ dependencies = [ [[package]] name = "async-recursion" -version = "1.0.5" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async-trait" +version = "0.1.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd55a5ba1179988837d24ab4c7cc8ed6efdeff578ede0416b4225a5fca35bd0" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" -version = "1.1.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backtrace" -version = "0.3.69" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", - "cc", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", + "windows-targets 0.52.6", ] [[package]] name = "base64" -version = "0.21.5" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] -name = "base64ct" -version = "1.6.0" +name = "base64" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] -name = "bitflags" -version = "1.3.2" +name = "base64ct" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "bitflags" -version = "2.4.1" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "block-buffer" @@ -201,21 +220,61 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bollard" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d41711ad46fda47cd701f6908e59d1bd6b9a2b7464c0d0aeab95c6d37096ff8a" +dependencies = [ + "base64 0.22.1", + "bollard-stubs", + "bytes", + "futures-core", + "futures-util", + "hex", + "home", + "http 1.1.0", + "http-body-util", + "hyper 1.5.0", + "hyper-named-pipe", + "hyper-rustls", + "hyper-util", + "hyperlocal", + "log", + "pin-project-lite", + "rustls", + "rustls-native-certs", + "rustls-pemfile", + "rustls-pki-types", + "serde", + "serde_derive", + "serde_json", + "serde_repr", + "serde_urlencoded", + "thiserror", + "tokio", + "tokio-util", + "tower-service", + "url", + "winapi", +] + [[package]] name = "bollard-stubs" -version = "1.42.0-rc.3" +version = "1.45.0-rc.26.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed59b5c00048f48d7af971b71f800fdf23e858844a6f9e4d32ca72e9399e7864" +checksum = "6d7c5415e3a6bc6d3e99eff6268e488fd4ee25e7b28c10f08fa6760bd9de16e4" dependencies = [ "serde", + "serde_repr", "serde_with", ] [[package]] name = "bstr" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "542f33a8835a0884b006a0c3df3dadd99c0c3f296ed26c2fdc8028e01ad6230c" +checksum = "40723b8fb387abc38f4f4a37c09073622e41dd12327033091ef8950659e6dc0c" dependencies = [ "memchr", "regex-automata", @@ -224,9 +283,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.14.0" +version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "byteorder" @@ -236,9 +295,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.5.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" [[package]] name = "bzip2" @@ -263,12 +322,13 @@ dependencies = [ [[package]] name = "cc" -version = "1.0.83" +version = "1.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f" dependencies = [ "jobserver", "libc", + "shlex", ] [[package]] @@ -285,16 +345,17 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.31" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", + "serde", "wasm-bindgen", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -309,9 +370,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.10" +version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41fffed7514f420abec6d183b1d3acfd9099c79c3a10a06ade4f8203f1411272" +checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" dependencies = [ "clap_builder", "clap_derive", @@ -319,9 +380,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.9" +version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63361bae7eef3771745f02d8d892bec2fee5f6e34af316ba556e7f97a7069ff1" +checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" dependencies = [ "anstream", "anstyle", @@ -331,33 +392,33 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.4.7" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.50", + "syn", ] [[package]] name = "clap_lex" -version = "0.6.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" [[package]] name = "combine" -version = "4.6.6" +version = "4.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" dependencies = [ "bytes", "memchr", @@ -365,9 +426,9 @@ dependencies = [ [[package]] name = "configparser" -version = "3.0.4" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec6d3da8e550377a85339063af6e3735f4b1d9392108da4e083a1b3b9820288" +checksum = "e57e3272f0190c3f1584272d613719ba5fc7df7f4942fe542e63d949cf3a649b" [[package]] name = "constant_time_eq" @@ -377,9 +438,9 @@ checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" [[package]] name = "core-foundation" -version = "0.9.3" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", @@ -387,60 +448,52 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.4" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.11" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" dependencies = [ "libc", ] [[package]] name = "crc32fast" -version = "1.3.2" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] [[package]] name = "crossbeam-deque" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" dependencies = [ - "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" -version = "0.9.15" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ - "autocfg", - "cfg-if", "crossbeam-utils", - "memoffset", - "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.16" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" -dependencies = [ - "cfg-if", -] +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crypto-common" @@ -453,13 +506,13 @@ dependencies = [ ] [[package]] -name = "daikokucli" -version = "0.0.1" +name = "daikoku" +version = "1.0.0" dependencies = [ "assert_cmd", "assert_fs", "async-recursion", - "base64", + "base64 0.21.7", "bytes", "chrono", "clap", @@ -470,13 +523,14 @@ dependencies = [ "futures-util", "home", "http-body-util", - "hyper 1.1.0", - "hyper-tls", + "hyper 1.5.0", + "hyper-tls 0.5.0", "hyper-util", "lazy_static", "once_cell", "paris", "predicates", + "reqwest", "serde", "serde_json", "serde_yaml", @@ -495,9 +549,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.13.4" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" dependencies = [ "darling_core", "darling_macro", @@ -505,62 +559,50 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.13.4" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", - "syn 1.0.109", + "syn", ] [[package]] name = "darling_macro" -version = "0.13.4" +version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 1.0.109", -] - -[[package]] -name = "dashmap" -version = "5.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" -dependencies = [ - "cfg-if", - "hashbrown", - "lock_api", - "once_cell", - "parking_lot_core", + "syn", ] [[package]] name = "data-encoding" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" [[package]] name = "deranged" -version = "0.3.9" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f32d04922c60427da6f9fef14d042d9edddef64cb9d4ce0d64d0685fbeb1fd3" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", + "serde", ] [[package]] name = "deunicode" -version = "1.4.3" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6e854126756c496b8c81dec88f9a706b15b875c5849d4097a3854476b9fdf94" +checksum = "339544cc9e2c4dc3fc7149fd630c5f22263a4fdf18a98afd0075784968b5cf00" [[package]] name = "difflib" @@ -606,11 +648,31 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +[[package]] +name = "docker_credential" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31951f49556e34d90ed28342e1df7e1cb7a229c4cab0aecc627b5d91edd41d07" +dependencies = [ + "base64 0.21.7", + "serde", + "serde_json", +] + [[package]] name = "either" -version = "1.9.0" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "encoding_rs" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] [[package]] name = "equivalent" @@ -620,9 +682,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", @@ -640,15 +702,15 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.0.1" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" [[package]] name = "flate2" -version = "1.0.28" +version = "1.0.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0" dependencies = [ "crc32fast", "miniz_oxide", @@ -695,9 +757,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.29" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", @@ -710,9 +772,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.29" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", @@ -720,15 +782,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.29" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" -version = "0.3.29" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", @@ -737,38 +799,38 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.29" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" -version = "0.3.29" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn", ] [[package]] name = "futures-sink" -version = "0.3.29" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.29" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.29" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", @@ -794,9 +856,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.11" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", @@ -805,15 +867,15 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.1" +version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "globset" -version = "0.4.14" +version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" +checksum = "15f1ce686646e7f1e19bf7d5533fe443a45dbfb990e00629110797578b42fb19" dependencies = [ "aho-corasick", "bstr", @@ -824,28 +886,28 @@ dependencies = [ [[package]] name = "globwalk" -version = "0.8.1" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc" +checksum = "0bf760ebf69878d9fd8f110c89703d90ce35095324d1f1edcb595c63945ee757" dependencies = [ - "bitflags 1.3.2", + "bitflags", "ignore", "walkdir", ] [[package]] name = "h2" -version = "0.4.2" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31d030e59af851932b72ceebadf4a2b5986dba4c3b99dd2493f8273a0f151943" +checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" dependencies = [ + "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "futures-util", - "http 1.0.0", - "indexmap", + "http 1.1.0", + "indexmap 2.6.0", "slab", "tokio", "tokio-util", @@ -854,21 +916,27 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" [[package]] name = "heck" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.3.3" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hex" @@ -887,18 +955,18 @@ dependencies = [ [[package]] name = "home" -version = "0.5.5" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "http" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ "bytes", "fnv", @@ -907,9 +975,9 @@ dependencies = [ [[package]] name = "http" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b32afd38673a8016f7c9ae69e5af41a58f81b1d31689040f2f1959594ce194ea" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" dependencies = [ "bytes", "fnv", @@ -918,43 +986,43 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", - "http 0.2.11", + "http 0.2.12", "pin-project-lite", ] [[package]] name = "http-body" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.0.0", + "http 1.1.0", ] [[package]] name = "http-body-util" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41cb79eb393015dadd30fc252023adb0b2400a0caee0fa2a077e6e21a551e840" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ "bytes", "futures-util", - "http 1.0.0", - "http-body 1.0.0", + "http 1.1.0", + "http-body 1.0.1", "pin-project-lite", ] [[package]] name = "httparse" -version = "1.8.0" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "httpdate" @@ -964,21 +1032,21 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "0.14.27" +version = "0.14.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +checksum = "8c08302e8fa335b151b788c775ff56e7a03ae64ff85c548ee820fecb70356e85" dependencies = [ "bytes", "futures-channel", "futures-core", "futures-util", - "http 0.2.11", - "http-body 0.4.5", + "http 0.2.12", + "http-body 0.4.6", "httparse", "httpdate", "itoa", "pin-project-lite", - "socket2 0.4.10", + "socket2", "tokio", "tower-service", "tracing", @@ -987,24 +1055,57 @@ dependencies = [ [[package]] name = "hyper" -version = "1.1.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5aa53871fc917b1a9ed87b683a5d86db645e23acb32c2e0785a353e522fb75" +checksum = "bbbff0a806a4728c99295b254c8838933b5b082d75e3cb70c8dab21fdfbcfa9a" dependencies = [ "bytes", "futures-channel", "futures-util", "h2", - "http 1.0.0", - "http-body 1.0.0", + "http 1.1.0", + "http-body 1.0.1", "httparse", "httpdate", "itoa", "pin-project-lite", + "smallvec", "tokio", "want", ] +[[package]] +name = "hyper-named-pipe" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73b7d8abf35697b81a825e386fc151e0d503e8cb5fcb93cc8669c376dfd6f278" +dependencies = [ + "hex", + "hyper 1.5.0", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", + "winapi", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" +dependencies = [ + "futures-util", + "http 1.1.0", + "hyper 1.5.0", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + [[package]] name = "hyper-tls" version = "0.5.0" @@ -1012,37 +1113,67 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes", - "hyper 0.14.27", + "hyper 0.14.31", + "native-tls", + "tokio", + "tokio-native-tls", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper 1.5.0", + "hyper-util", "native-tls", "tokio", "tokio-native-tls", + "tower-service", ] [[package]] name = "hyper-util" -version = "0.1.3" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" +checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.0.0", - "http-body 1.0.0", - "hyper 1.1.0", + "http 1.1.0", + "http-body 1.0.1", + "hyper 1.5.0", "pin-project-lite", - "socket2 0.5.5", + "socket2", "tokio", - "tower", "tower-service", "tracing", ] +[[package]] +name = "hyperlocal" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "986c5ce3b994526b3cd75578e62554abd09f0899d6206de48b3e96ab34ccc8c7" +dependencies = [ + "hex", + "http-body-util", + "hyper 1.5.0", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + [[package]] name = "iana-time-zone" -version = "0.1.58" +version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -1079,9 +1210,9 @@ dependencies = [ [[package]] name = "ignore" -version = "0.4.21" +version = "0.4.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "747ad1b4ae841a78e8aba0d63adbfbeaea26b517b63705d47856b73015d27060" +checksum = "6d89fd380afde86567dfba715db065673989d6253f42b88179abd3eae47bda4b" dependencies = [ "crossbeam-deque", "globset", @@ -1095,12 +1226,24 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.2.3" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.0", + "serde", ] [[package]] @@ -1113,19 +1256,22 @@ dependencies = [ ] [[package]] -name = "itertools" -version = "0.11.0" +name = "ipnet" +version = "2.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" -dependencies = [ - "either", -] +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itoa" -version = "1.0.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jni" @@ -1151,56 +1297,55 @@ checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" [[package]] name = "jobserver" -version = "0.1.27" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" dependencies = [ "libc", ] [[package]] name = "js-sys" -version = "0.3.66" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" dependencies = [ "wasm-bindgen", ] [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.150" +version = "0.2.161" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" +checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" [[package]] name = "libredox" -version = "0.0.1" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ - "bitflags 2.4.1", + "bitflags", "libc", - "redox_syscall", ] [[package]] name = "linux-raw-sys" -version = "0.4.11" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "lock_api" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -1208,9 +1353,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.20" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "malloc_buf" @@ -1223,46 +1368,43 @@ dependencies = [ [[package]] name = "memchr" -version = "2.6.4" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] -name = "memoffset" -version = "0.9.0" +name = "mime" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" -dependencies = [ - "autocfg", -] +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "miniz_oxide" -version = "0.7.1" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" dependencies = [ - "adler", + "adler2", ] [[package]] name = "mio" -version = "0.8.9" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ + "hermit-abi", "libc", "wasi", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "native-tls" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" dependencies = [ - "lazy_static", "libc", "log", "openssl", @@ -1287,22 +1429,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" [[package]] -name = "num-traits" -version = "0.2.17" +name = "num-conv" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" -dependencies = [ - "autocfg", -] +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] -name = "num_cpus" -version = "1.16.0" +name = "num-traits" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ - "hermit-abi", - "libc", + "autocfg", ] [[package]] @@ -1316,26 +1454,26 @@ dependencies = [ [[package]] name = "object" -version = "0.32.1" +version = "0.36.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" +checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.18.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "openssl" -version = "0.10.60" +version = "0.10.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79a4c6c3a2b158f7f8f2a2fc5a969fa3a068df6fc9dbb4a43845436e3af7c800" +checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" dependencies = [ - "bitflags 2.4.1", + "bitflags", "cfg-if", "foreign-types", "libc", @@ -1352,7 +1490,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn", ] [[package]] @@ -1363,9 +1501,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.96" +version = "0.9.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3812c071ba60da8b5677cc12bcb1d42989a65553772897a7e0355545a819838f" +checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" dependencies = [ "cc", "libc", @@ -1387,9 +1525,9 @@ checksum = "8fecab3723493c7851f292cb060f3ee1c42f19b8d749345d0d7eaf3fd19aa62d" [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", @@ -1397,15 +1535,40 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.48.5", + "windows-targets 0.52.6", +] + +[[package]] +name = "parse-display" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "914a1c2265c98e2446911282c6ac86d8524f495792c38c5bd884f80499c7538a" +dependencies = [ + "parse-display-derive", + "regex", + "regex-syntax", +] + +[[package]] +name = "parse-display-derive" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ae7800a4c974efd12df917266338e79a7a74415173caf7e70aa0a0707345281" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "regex-syntax", + "structmeta", + "syn", ] [[package]] @@ -1437,31 +1600,11 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" -[[package]] -name = "pin-project" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0302c4a0442c456bd56f841aee5c3bfd17967563f6fadc9ceb9f9c23cf3807e0" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.50", -] - [[package]] name = "pin-project-lite" -version = "0.2.13" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" +checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" [[package]] name = "pin-utils" @@ -1471,9 +1614,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" -version = "0.3.27" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "powerfmt" @@ -1483,20 +1626,22 @@ checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] [[package]] name = "predicates" -version = "3.0.4" +version = "3.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dfc28575c2e3f19cb3c73b93af36460ae898d426eba6fc15b9bd2a5220758a0" +checksum = "7e9086cc7640c29a356d1a29fd134380bee9d8f79a17410aa76e7ad295f42c97" dependencies = [ "anstyle", "difflib", "float-cmp", - "itertools", "normalize-line-endings", "predicates-core", "regex", @@ -1504,15 +1649,15 @@ dependencies = [ [[package]] name = "predicates-core" -version = "1.0.6" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174" +checksum = "ae8177bee8e75d6846599c6b9ff679ed51e882816914eec639944d7c9aa11931" [[package]] name = "predicates-tree" -version = "1.0.9" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf" +checksum = "41b740d195ed3166cd147c8047ec98db0e22ec019eb8eeb76d343b795304fb13" dependencies = [ "predicates-core", "termtree", @@ -1520,18 +1665,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.78" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.35" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -1574,18 +1719,18 @@ checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" [[package]] name = "redox_syscall" -version = "0.4.1" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ - "bitflags 1.3.2", + "bitflags", ] [[package]] name = "redox_users" -version = "0.4.4" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom", "libredox", @@ -1594,9 +1739,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.2" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", @@ -1606,9 +1751,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.3" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", @@ -1617,43 +1762,154 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.2" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "reqwest" +version = "0.12.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.5.0", + "hyper-rustls", + "hyper-tls 0.6.0", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-registry", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] [[package]] name = "rustc-demangle" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc_version" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] [[package]] name = "rustix" -version = "0.38.25" +version = "0.38.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e" +checksum = "aa260229e6538e52293eeb577aabd09945a09d6d9cc0fc550ed7529056c2e32a" dependencies = [ - "bitflags 2.4.1", + "bitflags", "errno", "libc", "linux-raw-sys", - "windows-sys 0.48.0", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.23.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eee87ff5d9b36712a58574e12e9f0ea80f915a5b0ac518d322b24a465617925e" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-native-certs" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" +dependencies = [ + "openssl-probe", + "rustls-pemfile", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", ] [[package]] name = "ryu" -version = "1.0.15" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "same-file" @@ -1664,13 +1920,22 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "scc" +version = "2.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8d25269dd3a12467afe2e510f69fb0b46b698e5afb296b59f2145259deaf8e8" +dependencies = [ + "sdd", +] + [[package]] name = "schannel" -version = "0.1.22" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" +checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -1679,13 +1944,19 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sdd" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49c1eeaf4b6a87c7479688c6d52b9f1153cedd3c489300564f932b065c6eab95" + [[package]] name = "security-framework" -version = "2.9.2" +version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05b64fb303737d99b81884b2c63433e9ae28abebe5eb5045dcdd175dc2ecf4de" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ - "bitflags 1.3.2", + "bitflags", "core-foundation", "core-foundation-sys", "libc", @@ -1694,9 +1965,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.9.1" +version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e932934257d3b408ed8f30db49d85ea163bfe74961f017f405b025af298f0c7a" +checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" dependencies = [ "core-foundation-sys", "libc", @@ -1704,79 +1975,111 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.20" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.197" +version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +checksum = "f55c3193aca71c12ad7890f1785d2b73e1b9f63a0bbc353c08ef26fe03fc56b5" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.197" +version = "1.0.214" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "de523f781f095e28fa605cdce0f8307e451cc0fd14e2eb4cd2e98a355b147766" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn", ] [[package]] name = "serde_json" -version = "1.0.108" +version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" dependencies = [ "itoa", + "memchr", "ryu", "serde", ] +[[package]] +name = "serde_repr" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "serde_spanned" -version = "0.6.4" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12022b835073e5b11e90a14f86838ceb1c8fb0325b72416845c487ac0fa95e80" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "serde_with" -version = "1.14.0" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff" +checksum = "8e28bdad6db2b8340e449f7108f020b3b092e8583a9e3fb82713e1d4e71fe817" dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.6.0", "serde", + "serde_derive", + "serde_json", "serde_with_macros", + "time", ] [[package]] name = "serde_with_macros" -version = "1.5.2" +version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" +checksum = "9d846214a9854ef724f3da161b426242d8de7c1fc7de2f89bb1efcb154dca79d" dependencies = [ "darling", "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] name = "serde_yaml" -version = "0.9.32" +version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fd075d994154d4a774f95b51fb96bdc2832b0ea48425c92546073816cda1f2f" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap", + "indexmap 2.6.0", "itoa", "ryu", "serde", @@ -1785,27 +2088,27 @@ dependencies = [ [[package]] name = "serial_test" -version = "2.0.0" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e56dd856803e253c8f298af3f4d7eb0ae5e23a737252cd90bb4f3b435033b2d" +checksum = "4b4b487fe2acf240a021cf57c6b2b4903b1e78ca0ecd862a71b71d2a51fed77d" dependencies = [ - "dashmap", "futures", - "lazy_static", "log", + "once_cell", "parking_lot", + "scc", "serial_test_derive", ] [[package]] name = "serial_test_derive" -version = "2.0.0" +version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f" +checksum = "82fe9db325bcef1fbcde82e078a5cc4efdf787e96b3b9cf45b50b529f2083d67" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn", ] [[package]] @@ -1830,11 +2133,17 @@ dependencies = [ "digest", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] @@ -1850,9 +2159,9 @@ dependencies = [ [[package]] name = "slug" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bd94acec9c8da640005f8e135a39fc0372e74535e6b368b7a04b875f784c8c4" +checksum = "882a80f72ee45de3cc9a5afeb2da0331d58df69e4e7d8eeb5d3c7784ae67e724" dependencies = [ "deunicode", "wasm-bindgen", @@ -1860,75 +2169,113 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.11.2" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" -version = "0.4.10" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", - "winapi", + "windows-sys 0.52.0", ] [[package]] -name = "socket2" -version = "0.5.5" +name = "spin" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" -dependencies = [ - "libc", - "windows-sys 0.48.0", -] +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" [[package]] name = "strsim" -version = "0.10.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] -name = "subtle" -version = "2.5.0" +name = "structmeta" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +checksum = "2e1575d8d40908d70f6fd05537266b90ae71b15dbbe7a8b7dffa2b759306d329" +dependencies = [ + "proc-macro2", + "quote", + "structmeta-derive", + "syn", +] [[package]] -name = "syn" -version = "1.0.109" +name = "structmeta-derive" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +checksum = "152a0b65a590ff6c3da95cabe2353ee04e6167c896b28e3b14478c2636c922fc" dependencies = [ "proc-macro2", "quote", - "unicode-ident", + "syn", ] +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" -version = "2.0.50" +version = "2.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74f1bdc9872430ce9b75da68329d1c1746faf50ffac5f19e02b71e37ff881ffb" +checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +dependencies = [ + "futures-core", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tempfile" -version = "3.8.1" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" +checksum = "f0f2c9fc62d0beef6951ccffd757e241266a2c833136efbe35af6cd2567dca5b" dependencies = [ "cfg-if", "fastrand", - "redox_syscall", + "once_cell", "rustix", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] @@ -1939,51 +2286,65 @@ checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" [[package]] name = "testcontainers" -version = "0.15.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f83d2931d7f521af5bae989f716c3fa43a6af9af7ec7a5e21b59ae40878cec00" +checksum = "2ef8374cea2c164699681ecc39316c3e1d953831a7a5721e36c7736d974e15fa" dependencies = [ + "async-trait", + "bollard", "bollard-stubs", + "bytes", + "dirs", + "docker_credential", + "either", "futures", - "hex", - "hmac", "log", - "rand", + "memchr", + "parse-display", + "pin-project-lite", "serde", "serde_json", - "sha2", + "serde_with", + "thiserror", + "tokio", + "tokio-stream", + "tokio-util", + "url", ] [[package]] name = "thiserror" -version = "1.0.50" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +checksum = "5d11abd9594d9b38965ef50805c5e469ca9cc6f197f883f717e0269a3057b3d5" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.50" +version = "1.0.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +checksum = "ae71770322cbd277e69d762a16c444af02aa0575ac0d174f0b9562d3b37f8602" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn", ] [[package]] name = "time" -version = "0.3.30" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", + "itoa", + "num-conv", "powerfmt", "serde", "time-core", + "time-macros", ] [[package]] @@ -1992,11 +2353,21 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" dependencies = [ "tinyvec_macros", ] @@ -2009,32 +2380,31 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.34.0" +version = "1.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0c014766411e834f7af5b8f4cf46257aab4036ca95e9d2c144a10f59ad6f5b9" +checksum = "145f3413504347a2be84393cc8a7d2fb4d863b375909ea59f2158261aa258bbb" dependencies = [ "backtrace", "bytes", "libc", "mio", - "num_cpus", "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.5", + "socket2", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] name = "tokio-macros" -version = "2.2.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn", ] [[package]] @@ -2047,6 +2417,28 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls", + "rustls-pki-types", + "tokio", +] + +[[package]] +name = "tokio-stream" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + [[package]] name = "tokio-tungstenite" version = "0.20.1" @@ -2063,23 +2455,22 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.10" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", - "tracing", ] [[package]] name = "toml" -version = "0.8.8" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" dependencies = [ "serde", "serde_spanned", @@ -2089,53 +2480,31 @@ dependencies = [ [[package]] name = "toml_datetime" -version = "0.6.5" +version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ "serde", ] [[package]] name = "toml_edit" -version = "0.21.0" +version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ - "indexmap", + "indexmap 2.6.0", "serde", "serde_spanned", "toml_datetime", "winnow", ] -[[package]] -name = "tower" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" -dependencies = [ - "futures-core", - "futures-util", - "pin-project", - "pin-project-lite", - "tokio", - "tower-layer", - "tower-service", - "tracing", -] - -[[package]] -name = "tower-layer" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" - [[package]] name = "tower-service" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" @@ -2143,7 +2512,6 @@ version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "log", "pin-project-lite", "tracing-core", ] @@ -2159,9 +2527,9 @@ dependencies = [ [[package]] name = "try-lock" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "tungstenite" @@ -2172,7 +2540,7 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http 0.2.11", + "http 0.2.12", "httparse", "log", "native-tls", @@ -2191,40 +2559,47 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicode-bidi" -version = "0.3.13" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-normalization" -version = "0.1.22" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] [[package]] name = "unsafe-libyaml" -version = "0.2.10" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + +[[package]] +name = "untrusted" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab4c90930b95a82d00dc9e9ac071b4991924390d46cbd0dfe566148667605e4b" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.5.0" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", ] [[package]] @@ -2235,15 +2610,15 @@ checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" [[package]] name = "utf8parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.7.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f00cc9702ca12d3c81455259621e676d0f7251cec66a21e98fe2e9a37db93b2a" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" dependencies = [ "getrandom", "rand", @@ -2252,13 +2627,13 @@ dependencies = [ [[package]] name = "uuid-macro-internal" -version = "1.7.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7abb14ae1a50dad63eaa768a458ef43d298cd1bd44951677bd10b732a9ba2a2d" +checksum = "6b91f57fe13a38d0ce9e28a03463d8d3c2468ed03d75375110ec71d93b449a08" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn", ] [[package]] @@ -2269,9 +2644,9 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "void" @@ -2315,34 +2690,47 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.89" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" dependencies = [ "cfg-if", + "once_cell", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.89" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.50", + "syn", "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" -version = "0.2.89" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2350,28 +2738,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.89" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", - "syn 2.0.50", + "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.89" +version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" [[package]] name = "web-sys" -version = "0.3.66" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f" +checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" dependencies = [ "js-sys", "wasm-bindgen", @@ -2379,9 +2767,9 @@ dependencies = [ [[package]] name = "webbrowser" -version = "0.8.13" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1b04c569c83a9bb971dd47ec6fd48753315f4bf989b9b04a2e7ca4d7f0dc950" +checksum = "db67ae75a9405634f5882791678772c94ff5f16a66535aae186e26aa0841fc8b" dependencies = [ "core-foundation", "home", @@ -2412,11 +2800,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.6" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "winapi", + "windows-sys 0.59.0", ] [[package]] @@ -2427,11 +2815,41 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-core" -version = "0.51.1" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.48.5", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", ] [[package]] @@ -2458,7 +2876,16 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", ] [[package]] @@ -2493,17 +2920,18 @@ dependencies = [ [[package]] name = "windows-targets" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.0", - "windows_aarch64_msvc 0.52.0", - "windows_i686_gnu 0.52.0", - "windows_i686_msvc 0.52.0", - "windows_x86_64_gnu 0.52.0", - "windows_x86_64_gnullvm 0.52.0", - "windows_x86_64_msvc 0.52.0", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", ] [[package]] @@ -2520,9 +2948,9 @@ checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" @@ -2538,9 +2966,9 @@ checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" @@ -2556,9 +2984,15 @@ checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" @@ -2574,9 +3008,9 @@ checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" @@ -2592,9 +3026,9 @@ checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" @@ -2610,9 +3044,9 @@ checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" @@ -2628,19 +3062,46 @@ checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.52.0" +version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.5.19" +version = "0.6.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "829846f3e3db426d4cee4510841b71a8e58aa2a76b1132579487ae430ccd9c7b" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + [[package]] name = "zip" version = "0.6.6" @@ -2691,9 +3152,9 @@ dependencies = [ [[package]] name = "zstd-sys" -version = "2.0.9+zstd.1.5.5" +version = "2.0.13+zstd.1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" +checksum = "38ff0f21cfee8f97d94cef41359e0c89aa6113028ab0291aa8ca0038995a95aa" dependencies = [ "cc", "pkg-config", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 91627ca13..2cdbe9607 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "daikokucli" +name = "daikoku" version = "1.0.0" edition = "2021" authors = ["Zwiterrion "] @@ -12,7 +12,7 @@ keywords = ["cli", "wasm", "build", "webassembly"] categories = ["command-line-utilities"] [[bin]] -name = "daikokucli" +name = "daikoku" path = "src/bin.rs" [dependencies] @@ -24,6 +24,7 @@ hyper = { version = "1", features = ["full"] } hyper-util = { version = "0.1", features = ["full"] } tokio-tungstenite = { version = "0.20.1", features = ["native-tls"] } tokio = { version = "1", features = ["full"] } +reqwest = { version = "0.12", features = ["json"] } serde_json = "1.0.107" lazy_static = "1.4.0" serde = { version = "1.0.189", features = ["derive"] } @@ -44,7 +45,7 @@ serde_yaml = "0.9.32" void = "1.0.2" walkdir = "2.5.0" webbrowser = "0.8.13" -testcontainers = "0.15.0" +testcontainers = "0.22.0" slug = "0.1.5" [dependencies.uuid] @@ -52,7 +53,8 @@ version = "1.7.0" features = ["v4", "fast-rng", "macro-diagnostics"] [dev-dependencies] -serial_test = "2.0.0" +serial_test = "3.1.1" assert_cmd = "2.0.12" predicates = "3.0.4" assert_fs = "1.0.13" + diff --git a/cli/README.md b/cli/README.md index 231c6cb81..e03656d4b 100644 --- a/cli/README.md +++ b/cli/README.md @@ -1,5 +1,5 @@
-

daikokucli

+

daikoku

![CLI architecture](architecture.png "Architecture") @@ -10,10 +10,10 @@ This project can be installed and compiled from source with this Cargo command: ``` -$ cargo install daikokucli +$ cargo install daikoku or -$ brew tap maif/daikokucli -$ brew install daikokucli +$ brew tap maif/daikoku +$ brew install daikoku ``` Additionally there are [precompiled artifacts built on CI][artifacts] which are @@ -24,90 +24,131 @@ available for download as well. Installation can be confirmed with: ``` -$ daikokucli version +$ daikoku version ``` Subcommands can be explored with: ``` -$ daikokucli help +$ daikoku help ``` # Core commands Daikokucli uses your home folder to store the list of projects inside a `.daikoku` file. Each project created with the CLI should contain a `src` folder and a `.daikoku/.environments`. This file will contain -You can start a new project +You can start a new project from scratch ```sh -daikokucli create --name= --path= +daikoku cms init --name= --path= +``` + +or import an existing one + +```sh +daikoku cms migrate --name= --path= --server= --apikey= ``` then add a default Daikoku environment ```sh -daikokucli environments add --name= --server= +daikoku environments add --name= --server= --apikey= ``` > The Daikoku server has to be reachable and will be checked before saving the configuration + +you can sync the new project with your Daikoku instance and fetch mails and apis + +```sh +daikoku pull +``` + you can start to develop and watch file changes ```sh -daikokucli watch +daikoku watch ``` -Common practices involve utilizing the directives within the Daikoku CMS to access private entities based on the connected user's permissions. You have the option to configure the token for accessing your CMS with an authenticated user by pasting the token from your Daikoku profile page. +Common practices involve utilizing the directives within the Daikoku CMS to access private entities based on the connected user's permissions. ```sh -daikokucli login --token= +daikoku login ``` -If you have many environments you can switch between us simply using +You can start to follow your changes using ```sh -daikokucli watch --environment= +daikoku watch --environment= ``` or permanently by changing the default project or environment ```sh -daikokucli environments default --name= -daikokucli projects default --name= +daikoku environments switch --name= +daikoku cms switch --name= ``` you can view the currently used project and the others ```sh -daikokucli projects list +daikoku cms list ``` At anytime, you can track an existing CMS folder or update its information ```sh -daikokucli projects add --name= --path= --overwrite= +daikoku cms add --name= --path= --overwrite= ``` -Once ready, you can synchronize your sources with the Daikoku environment +Once ready, you can push your sources with the Daikoku environment ```sh -daikokucli sync +daikoku push ``` ## Start a new project by importing an existing one If you already have a legacy CMS on your Daikoku, you can start by importing it ```sh -daikokucli projects import --name= \ +daikoku projects migrate --name= \ --path= \ --server= \ - --token= + --apikey= ``` +# CMS Structure + +The CMS projects adhere to the following strict file structure: + +- `.daikoku`: This hidden folder is used exclusively by Daikoku to store environments, secrets, and credentials. The only file you can edit here is the .daikokuignore, which allows you to exclude specific files from being pushed. + +- `assets`: Files placed in this folder can be uploaded to the Daikoku S3 Bucket associated with your project. They can then be accessed using a generated slug. + +- `src`: This folder contains all other source files, organized into the following subdirectories: + - `apis`: Lists all APIs available in your Daikoku. Each API has its own subfolder containing a header and description folder. + - `data`: Contains external data files such as JSON, YAML, CSV, and others. + - `pages`: Stores all source files that are not categorized under apis, data, scripts, mails, or styles. + - `scripts`: Contains JavaScript (JS) files. + - `styles`: Contains CSS files. + - `documentations` : Contains files that can be used as documentation page of APIs + +# Nested routing + +The CLI uses file-system routing where folders are used to create nested routes. Each folder represents a route segment that maps to a URL segment. + +You can create separate UIs for each route using page.html files. `page.html` is a special CLI file that contains html content. + +To create a nested route, you can nest folders inside each other and add page.html files inside them. For example: + +`src/pages/page.html`: is associated with the `/` path. +`src/pages/invoices/page.html`: is associated with the `/invoices` path. +`src/pages/offres.html`: is associated with the `/offres` path. + # Manage your assets You can manage your images, diagrams, or any type of files directly by creating a `/assets` folder inside your CMS project. Each asset is save in the S3 of your Daikoku using the following command ```sh -daikokucli assets add --filename= \ +daikoku assets push --filename= \ --path= \ --desc= \ --title= @@ -119,25 +160,17 @@ If you require a particular `slug` for your asset, you have the option to replac To delete your asset you have to give the `filename` and the `slug` iif it differs ```sh -daikokucli assets remove --slug= --filename= +daikoku assets remove --slug= --filename= ``` As others commands, you can display all registered assets ```sh -daikokucli assets list +daikoku assets list ``` If you prefer to synchronize all assets with a single command, it offers speed advantages over doing so individually, albeit with reduced configurability. ```sh -daikokucli assets sync -``` - -# Authorized applications - -Just before running the `daikoku login` command, you have to configure your tenant by adding the CLI server. By default, the server is set to `http://localhost:3334` but you can overwrite it using the `WATCHING_PORT` environment variable. - -```sh -daikokucli login +daikoku assets sync ``` # CMS Directives @@ -478,4 +511,10 @@ This project is licensed under the Apache 2.0 license with the LLVM exception. Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project by you, as defined in the Apache-2.0 license, -shall be licensed as above, without any additional terms or conditions. \ No newline at end of file +shall be licensed as above, without any additional terms or conditions. + + +#### Run tests +``` +cargo test --test -- --nocapture --test-threads 1 +``` diff --git a/cli/src/bin.rs b/cli/src/bin.rs index b410386f8..acff32bbb 100644 --- a/cli/src/bin.rs +++ b/cli/src/bin.rs @@ -1,15 +1,16 @@ mod commands; +mod helpers; +mod interactive; mod logging; mod models; mod utils; use clap::{Parser, Subcommand}; use logging::{error::DaikokuResult, logger}; -use utils::absolute_path; /// A fictional versioning CLI #[derive(Debug, Parser)] // requires `derive` feature -#[command(name = "daikokucli")] +#[command(name = "daikoku")] #[command(about = "Daikoku CLI", long_about = None, version = env!("CARGO_PKG_VERSION"))] struct Cli { #[command(subcommand)] @@ -20,31 +21,8 @@ struct Cli { pub enum Commands { /// Get installed version Version {}, - /// Initialize a new CMS project from template at specific path. Currently adding project as default project - #[command()] - Create { - /// The template to clone - #[arg( - value_name = "TEMPLATE", - short = 't', - long = "template", - value_parser = ["empty"], - require_equals = true, - )] - template: Option, - /// Project name - #[arg(value_name = "NAME", short = 'n', long = "name", required = false)] - name: String, - /// Path where initialize the project - #[arg(value_name = "PATH", short = 'p', long = "path", required = false)] - path: Option, - }, - /// Add a token to the current project. The token must be pasted from your Daikoku profile page. - Login { - /// Token can be found on your Daikoku profile page and used by Daikoku to access authenticated resources - #[arg(value_name = "TOKEN", short = 't', long = "token")] - token: Option, - }, + /// Add a cookie to the current project + Login {}, /// Watch project changes and serve pages on :3333 (or on WATCHING_PORT=) #[command()] Watch { @@ -56,7 +34,7 @@ pub enum Commands { required = false )] environment: Option, - /// Enable/Disable token usage - really useful for testing authenticated pages + /// Enable/Disable cookie usage - really useful for testing authenticated pages #[arg(value_name = "AUTHENTICATION", short = 'a', long = "authentication")] authentication: Option, }, @@ -66,17 +44,30 @@ pub enum Commands { command: EnvironmentsCommands, }, /// Manage your CMS projects - Projects { + Cms { #[command(subcommand)] - command: ProjectCommands, + command: CmsCommands, }, /// ⚠️ synchronize projects file with Daikoku - Sync {}, + Push { + #[arg(value_name = "DRY_RUN", short = 'd', long = "dry_run")] + dry_run: Option, + #[arg(value_name = "FILE_PATH", short = 'f', long = "file_path")] + file_path: Option, + }, + Pull { + #[command(subcommand)] + command: PullCommands, + }, /// Manage your CMS assets Assets { #[command(subcommand)] command: AssetsCommands, }, + Generate { + #[command(subcommand)] + command: GenerateCommands, + }, } #[derive(Debug, Subcommand)] @@ -87,27 +78,27 @@ pub enum EnvironmentsCommands { name: String, #[arg(value_name = "SERVER", short = 's', long = "server")] server: String, - #[arg(value_name = "TOKEN", short = 'a', long = "token")] - token: Option, + #[arg(value_name = "APIKEY", short = 'a', long = "apikey")] + apikey: String, #[arg(value_name = "OVERWRITE", long = "overwrite", required = false)] overwrite: Option, - #[arg(value_name = "FORCE", long = "force", required = false)] - force: Option, }, - /// update default environment by adding auth token - PathDefault { - #[arg( - value_name = "TOKEN", - short = 't', - long = "token", - require_equals = true - )] - token: String, + /// update default environment + Config { + #[arg(value_name = "APIKEY", short = 'a', long = "apikey")] + apikey: Option, + #[arg(value_name = "COOKIE", short = 'c', long = "cookie")] + cookie: Option, + // #[arg(value_name = "NAME", short = 'n', long = "name")] + // name: Option, }, /// ⚠️ be careful, this will clear all environments - Clear {}, + Clear { + #[arg(value_name = "FORCE", short = 'f', long = "force")] + force: Option, + }, /// change the default environment to the specified name - Default { + Switch { #[arg(value_name = "NAME", short = 'n', long = "name")] name: String, }, @@ -117,16 +108,18 @@ pub enum EnvironmentsCommands { name: String, }, /// show information of the specified environment - Env { + Info { #[arg(value_name = "NAME", short = 'n', long = "name")] name: String, + #[arg(value_name = "FULL", short = 'f', long = "full")] + full: Option, }, /// list all environments List {}, } #[derive(Debug, Subcommand)] -pub enum ProjectCommands { +pub enum CmsCommands { /// register a new project to the CLI Add { #[arg(value_name = "NAME", short = 'n', long = "name")] @@ -137,7 +130,7 @@ pub enum ProjectCommands { overwrite: Option, }, /// change the default project to the specified name - Default { + Switch { #[arg(value_name = "NAME", short = 'n', long = "name")] name: String, }, @@ -145,30 +138,39 @@ pub enum ProjectCommands { Remove { #[arg(value_name = "NAME", short = 'n', long = "name")] name: String, - #[arg(value_name = "FILES", short = 'f', long = "files")] + #[arg(value_name = "REMOVE_FILES", short = 'f', long = "remove_files")] remove_files: bool, }, /// list all projects List {}, /// ⚠️ be careful, this will clear all projects - Clear {}, + Clear { + #[arg(value_name = "FORCE", short = 'f', long = "force")] + force: Option, + }, /// ⚠️ import legacy projects from Daikoku - Import { + Migrate { #[arg(value_name = "NAME", short = 'n', long = "name")] name: String, #[arg(value_name = "PATH", short = 'p', long = "path")] - path: String, - #[arg(value_name = "TOKEN", short = 't', long = "token")] - token: String, + path: Option, + #[arg(value_name = "APIKEY", short = 'a', long = "apikey")] + apikey: String, #[arg(value_name = "SERVER", short = 's', long = "server")] server: String, }, + Init { + #[arg(value_name = "NAME", short = 'n', long = "name")] + name: String, + #[arg(value_name = "PATH", short = 'p', long = "path")] + path: Option, + }, } #[derive(Debug, Subcommand)] pub enum AssetsCommands { /// register a new asset - Add { + Push { #[arg(value_name = "FILENAME", short = 'f', long = "filename")] filename: String, #[arg(value_name = "TITLE", short = 't', long = "title")] @@ -195,23 +197,42 @@ pub enum AssetsCommands { Sync {}, } +#[derive(Debug, Subcommand)] +pub enum PullCommands { + Apis { + #[arg(value_name = "ID", short = 'i', long = "od")] + id: Option, + }, + Mails {}, +} + +#[derive(Debug, Subcommand)] +pub enum GenerateCommands { + /// create a new documentation page for your api + Documentation { + #[arg(value_name = "FILENAME", short = 'f', long = "filename")] + filename: String, + #[arg(value_name = "TITLE", short = 't', long = "title")] + title: String, + #[arg(value_name = "DESC", short = 'd', long = "desc")] + desc: String, + }, +} + async fn process(command: Commands) -> DaikokuResult<()> { match command { Commands::Version {} => commands::version::run(), - Commands::Create { - template, - name, - path, - } => commands::creation::run(template, name, path.map(|p| absolute_path(p).unwrap())).await, Commands::Watch { environment, authentication, } => commands::watch::run(environment, authentication).await, - Commands::Environments { command } => commands::enviroments::run(command).await, - Commands::Projects { command } => commands::projects::run(command).await, - Commands::Login { token } => commands::login::run(token).await, - Commands::Sync {} => commands::sync::run().await, + Commands::Environments { command } => commands::environments::run(command).await, + Commands::Cms { command } => commands::cms::run(command).await, + Commands::Login {} => commands::login::run().await, + Commands::Pull { command } => commands::pull::run(command).await, + Commands::Push { dry_run, file_path } => commands::push::run(dry_run, file_path).await, Commands::Assets { command } => commands::assets::run(command).await, + Commands::Generate { command } => commands::generate::run(command).await, } } diff --git a/cli/src/commands/assets.rs b/cli/src/commands/assets.rs index f2ab2285f..74db8ba1e 100644 --- a/cli/src/commands/assets.rs +++ b/cli/src/commands/assets.rs @@ -6,27 +6,24 @@ use std::{ }; use crate::{ + helpers::daikoku_cms_api_post, logging::{ error::{DaikokuCliError, DaikokuResult}, logger, }, - utils::{absolute_path, frame_to_bytes_body}, + utils::absolute_path, AssetsCommands, }; -use async_recursion::async_recursion; use bytes::Bytes; -use http_body_util::{Empty, Full}; -use hyper::{header, Request}; -use hyper_util::rt::TokioIo; +use hyper::header; use serde::{Deserialize, Serialize}; -use tokio::net::TcpStream; use walkdir::WalkDir; use super::{ - enviroments::{get_default_environment, read_cookie_from_environment}, - projects::{self, get_default_project}, + cms::{self, get_default_project}, + environments::{get_default_environment, read_apikey_from_secrets}, }; #[derive(Deserialize, Serialize, Debug)] @@ -39,7 +36,7 @@ struct Asset { pub(crate) async fn run(command: AssetsCommands) -> DaikokuResult<()> { match command { - AssetsCommands::Add { + AssetsCommands::Push { filename, title, desc, @@ -73,7 +70,7 @@ async fn exists(filename: String) -> DaikokuResult<()> { .replace("http://", "") .replace("https://", ""); - let cookie = read_cookie_from_environment(true)?; + let apikey = read_apikey_from_secrets(true)?; let url: String = format!( "{}/tenant-assets/{}", @@ -81,38 +78,19 @@ async fn exists(filename: String) -> DaikokuResult<()> { slug::slugify(filename.clone()), ); - let req = Request::head(&url) - .header(header::HOST, &host) - .header(header::COOKIE, cookie) - .body(Empty::::new()) - .expect("failed to build a request"); - - let stream = TcpStream::connect(&host).await.map_err(|err| { - DaikokuCliError::DaikokuErrorWithMessage("failed to join the server".to_string(), err) - })?; - let io = TokioIo::new(stream); - - let (mut sender, conn) = hyper::client::conn::http1::handshake(io) - .await - .map_err(|err| DaikokuCliError::HyperError(err))?; - - tokio::task::spawn(async move { - if let Err(err) = conn.await { - logger::error(format!("Connection error {:?}", err)); - } - }); - - let upstream_resp = sender - .send_request(req) + let resp = reqwest::Client::new() + .head(url) + .header(header::HOST, host) + .header(header::AUTHORIZATION, format!("Basic {}", apikey)) + .send() .await - .map_err(|err| DaikokuCliError::ParsingError(err.to_string()))?; + .map_err(|err| DaikokuCliError::DaikokuStrError(err.to_string()))?; - let status = upstream_resp.status(); + let status = resp.status().as_u16(); - println!("{:?}", status); if status == 303 { Err(DaikokuCliError::DaikokuStrError( - "Whoops, your token has expired. daikokucli login --token is required".to_string(), + "Whoops, your session has expired. daikoku login is required".to_string(), )) } else if status != 404 { Err(DaikokuCliError::DaikokuStrError( @@ -123,7 +101,6 @@ async fn exists(filename: String) -> DaikokuResult<()> { } } -#[async_recursion] async fn add( filename: String, title: String, @@ -131,7 +108,7 @@ async fn add( path: Option, slug: Option, ) -> DaikokuResult<()> { - logger::loading("Creating new assets".to_string()); + logger::loading("Creating and pushing new assets".to_string()); exists(match &slug { Some(s) => s.clone(), @@ -139,18 +116,8 @@ async fn add( }) .await?; - let environment = get_default_environment()?; - - let host = environment - .server - .replace("http://", "") - .replace("https://", ""); - - let cookie = read_cookie_from_environment(true)?; - let url: String = format!( - "{}/tenant-assets?filename={}&title={}&desc={}&slug={}", - environment.server, + "/tenant-assets?filename={}&title={}&desc={}&slug={}", filename.clone(), title, desc, @@ -173,53 +140,11 @@ async fn add( .read_to_end(&mut contents) .map_err(|err| DaikokuCliError::FileSystem(err.to_string())); - let req = Request::post(&url) - .header(header::HOST, &host) - .header(header::COOKIE, cookie) - .body(Full::new(Bytes::from(contents))) - .expect("failed to build a request"); - - let stream = TcpStream::connect(&host).await.map_err(|err| { - DaikokuCliError::DaikokuErrorWithMessage("failed to join the server".to_string(), err) - })?; - let io = TokioIo::new(stream); + let _ = daikoku_cms_api_post(&url, Bytes::from(contents), false).await?; - let (mut sender, conn) = hyper::client::conn::http1::handshake(io) - .await - .map_err(|err| DaikokuCliError::HyperError(err))?; - - tokio::task::spawn(async move { - if let Err(err) = conn.await { - logger::error(format!("Connection error {:?}", err)); - } - }); - - let upstream_resp = sender - .send_request(req) - .await - .map_err(|err| DaikokuCliError::ParsingError(err.to_string()))?; + logger::success("New asset has been pushed".to_string()); - let ( - hyper::http::response::Parts { - headers: _, status, .. - }, - body, - ) = upstream_resp.into_parts(); - - let status = status.as_u16(); - - if status == 200 { - let zip_bytes: Vec = frame_to_bytes_body(body).await; - - println!("{:?}", String::from_utf8(zip_bytes).unwrap()); - - Ok(()) - } else { - Err(DaikokuCliError::DaikokuStrError(format!( - "failed to reach the Daikoku server {}", - status - ))) - } + Ok(()) } async fn remove(filename: String, path: Option, slug: Option) -> DaikokuResult<()> { @@ -232,48 +157,23 @@ async fn remove(filename: String, path: Option, slug: Option) -> .replace("http://", "") .replace("https://", ""); - let cookie = read_cookie_from_environment(true)?; + let apikey = read_apikey_from_secrets(true)?; let url: String = format!( - "{}/tenant-assets/{}", + "{}/cms-api/tenant-assets/{}", environment.server, slug.unwrap_or(slug::slugify(filename.clone())) ); - let req = Request::delete(&url) - .header(header::HOST, &host) - .header(header::COOKIE, cookie) - .body(Empty::::new()) - .expect("failed to build a request"); - - let stream = TcpStream::connect(&host).await.map_err(|err| { - DaikokuCliError::DaikokuErrorWithMessage("failed to join the server".to_string(), err) - })?; - let io = TokioIo::new(stream); - - let (mut sender, conn) = hyper::client::conn::http1::handshake(io) + let req = reqwest::Client::new() + .delete(url) + .header(header::HOST, host) + .header(header::AUTHORIZATION, format!("Basic {}", apikey)) + .send() .await - .map_err(|err| DaikokuCliError::HyperError(err))?; - - tokio::task::spawn(async move { - if let Err(err) = conn.await { - logger::error(format!("Connection error {:?}", err)); - } - }); - - let upstream_resp = sender - .send_request(req) - .await - .map_err(|err| DaikokuCliError::ParsingError(err.to_string()))?; - - let ( - hyper::http::response::Parts { - headers: _, status, .. - }, - _body, - ) = upstream_resp.into_parts(); + .map_err(|err| DaikokuCliError::DaikokuStrError(err.to_string()))?; - let status = status.as_u16(); + let status = req.status().as_u16(); if status == 200 { let project = get_default_project()?; @@ -295,7 +195,9 @@ async fn remove(filename: String, path: Option, slug: Option) -> } async fn list() -> DaikokuResult<()> { - logger::loading(format!("Retrieving assets")); + logger::loading(format!( + "Retrieving assets, limited to those identified by slugs." + )); let environment = get_default_environment()?; @@ -304,49 +206,27 @@ async fn list() -> DaikokuResult<()> { .replace("http://", "") .replace("https://", ""); - let cookie = read_cookie_from_environment(true)?; + let apikey = read_apikey_from_secrets(true)?; - let url: String = format!("{}/tenant-assets/slugified", environment.server); + let url: String = format!("{}/cms-api/tenant-assets/slugified", environment.server); - let req = Request::get(&url) - .header(header::HOST, &host) - .header(header::COOKIE, cookie) - .body(Empty::::new()) - .expect("failed to build a request"); - - let stream = TcpStream::connect(&host).await.map_err(|err| { - DaikokuCliError::DaikokuErrorWithMessage("failed to join the server".to_string(), err) - })?; - let io = TokioIo::new(stream); - - let (mut sender, conn) = hyper::client::conn::http1::handshake(io) - .await - .map_err(|err| DaikokuCliError::HyperError(err))?; - - tokio::task::spawn(async move { - if let Err(err) = conn.await { - logger::error(format!("Connection error {:?}", err)); - } - }); - - let upstream_resp = sender - .send_request(req) + let req = reqwest::Client::new() + .get(url) + .header(header::HOST, host) + .header(header::AUTHORIZATION, format!("Basic {}", apikey)) + .send() .await - .map_err(|err| DaikokuCliError::ParsingError(err.to_string()))?; - - let ( - hyper::http::response::Parts { - headers: _, status, .. - }, - body, - ) = upstream_resp.into_parts(); + .map_err(|err| DaikokuCliError::DaikokuStrError(err.to_string()))?; - let status = status.as_u16(); + let status = req.status().as_u16(); if status == 200 { - let bytes = frame_to_bytes_body(body).await; + let bytes = req + .bytes() + .await + .map_err(|err| DaikokuCliError::DaikokuStrError(err.to_string()))?; - let assets: String = String::from_utf8(bytes).map_err(|_err| { + let assets: String = String::from_utf8(bytes.to_vec()).map_err(|_err| { DaikokuCliError::ParsingError("failed to convert assets body".to_string()) })?; @@ -358,6 +238,12 @@ async fn list() -> DaikokuResult<()> { .iter() .for_each(|asset| logger::println(asset.slug.clone())); + if assets.is_empty() { + logger::println("no assets found".to_string()); + } + + logger::success("".to_string()); + Ok(()) } else { Err(DaikokuCliError::DaikokuStrError(format!( @@ -370,7 +256,7 @@ async fn list() -> DaikokuResult<()> { async fn sync() -> DaikokuResult<()> { logger::loading("Syncing assets folder".to_string()); - let project = projects::get_default_project()?; + let project = cms::get_default_project()?; let mut pages: Vec = Vec::new(); @@ -403,17 +289,6 @@ async fn sync() -> DaikokuResult<()> { } } - let environment = get_default_environment()?; - - let host = environment - .server - .replace("http://", "") - .replace("https://", ""); - - let cookie = read_cookie_from_environment(true)?; - - let url: String = format!("{}/tenant-assets/bulk", environment.server,); - let files = pages .iter() .map(|page| File::open(page.path.clone().unwrap()).unwrap()) @@ -422,6 +297,12 @@ async fn sync() -> DaikokuResult<()> { let contents = pages .iter() .enumerate() + .filter(|(_idx, page)| { + page.filename + .clone() + .map(|filename| !filename.starts_with(".")) + .unwrap_or(false) + }) .map(|(idx, page)| { let mut content = Vec::new(); let _ = files.get(idx).unwrap().read_to_end(&mut content).unwrap(); @@ -434,52 +315,21 @@ async fn sync() -> DaikokuResult<()> { }) .collect::>(); - let req = Request::post(&url) - .header(header::HOST, &host) - .header(header::COOKIE, cookie) - .body(Full::new(Bytes::from( - serde_json::to_string(&contents).map_err(|_err| { - DaikokuCliError::ParsingError("failed to convert assets to json array".to_string()) - })?, - ))) - .expect("failed to build a request"); - - let stream = TcpStream::connect(&host).await.map_err(|err| { - DaikokuCliError::DaikokuErrorWithMessage("failed to join the server".to_string(), err) - })?; - let io = TokioIo::new(stream); - - let (mut sender, conn) = hyper::client::conn::http1::handshake(io) - .await - .map_err(|err| DaikokuCliError::HyperError(err))?; - - tokio::task::spawn(async move { - if let Err(err) = conn.await { - logger::error(format!("Connection error {:?}", err)); - } - }); - - let upstream_resp = sender - .send_request(req) - .await - .map_err(|err| DaikokuCliError::ParsingError(err.to_string()))?; - - let ( - hyper::http::response::Parts { - headers: _, status, .. - }, - _body, - ) = upstream_resp.into_parts(); + if contents.is_empty() { + logger::println("already up to date".to_string()); + logger::done(); + return Ok(()); + } - let status = status.as_u16(); + let _resp: Vec = daikoku_cms_api_post( + "/tenant-assets/bulk", + Bytes::from(serde_json::to_string(&contents).map_err(|_err| { + DaikokuCliError::ParsingError("failed to convert assets to json array".to_string()) + })?), + false, + ) + .await?; - if status == 200 { - logger::success("synchronization done".to_string()); - list().await - } else { - Err(DaikokuCliError::DaikokuStrError(format!( - "failed to reach the Daikoku server {}", - status - ))) - } + logger::success("synchronization done".to_string()); + list().await } diff --git a/cli/src/commands/cms.rs b/cli/src/commands/cms.rs new file mode 100644 index 000000000..3d467fe37 --- /dev/null +++ b/cli/src/commands/cms.rs @@ -0,0 +1,932 @@ +use std::{ + collections::HashMap, + fs::{self, File}, + io::Write, + path::{Path, PathBuf}, + str::FromStr, +}; + +use async_recursion::async_recursion; +use configparser::ini::Ini; +use serde::{Deserialize, Serialize}; + +use crate::{ + helpers::{ + bytes_to_struct, bytes_to_vec_of_struct, map_error_to_filesystem_error, + raw_daikoku_cms_api_get, + }, + interactive::prompt, + logging::{ + error::{DaikokuCliError, DaikokuResult}, + logger, + }, + models::folder::{Ext, SourceExtension}, + process, + utils::absolute_path, + CmsCommands, Commands, +}; + +const ZIP_CMS: &[u8] = include_bytes!("../../templates/cms.zip"); + +#[derive(Clone)] +pub(crate) struct Project { + pub(crate) path: String, +} + +#[derive(Clone, Deserialize, Serialize, Debug)] +pub(crate) struct CmsPage { + pub(crate) _id: String, + visible: bool, + authenticated: bool, + pub(crate) name: String, + #[serde(alias = "contentType")] + content_type: String, + pub(crate) path: Option, + exact: bool, + #[serde(alias = "lastPublishedDate")] + last_published_date: Option, + #[serde(alias = "body")] + content: String, +} + +#[derive(Clone, Deserialize, Serialize, Debug)] +struct MailTemplate { + _id: String, + _tenant: String, + language: String, + key: String, + value: String, + #[serde(alias = "lastModificationAt")] + last_modification_at: Option, +} + +#[derive(Clone, Deserialize, Serialize, Debug)] +struct IntlTranslation { + _id: String, + translations: Vec, + content: String, +} + +#[derive(Clone, Deserialize, Serialize, Debug)] +pub(crate) struct IntlTranslationBody { + translations: Vec, +} + +#[derive(Clone, Deserialize, Serialize, Debug)] +pub(crate) struct TenantMailBody { + #[serde(alias = "mailerSettings")] + mailer_settings: Option, +} + +#[derive(Clone, Deserialize, Serialize, Debug)] +struct MailerSettings { + template: Option, +} + +#[derive(Clone, Deserialize, Serialize, Debug)] +pub(crate) struct Api { + pub(crate) _id: String, + #[serde(alias = "_humanReadableId")] + human_readable_id: String, + header: Option, + description: Option, +} + +pub(crate) const EXCLUDE_API: [&'static str; 2] = + ["admin-api-tenant-default", "cms-api-tenant-default"]; + +pub(crate) async fn run(command: CmsCommands) -> DaikokuResult<()> { + match command { + CmsCommands::Add { + name, + path, + overwrite, + } => add(name, absolute_path(path)?, overwrite.unwrap_or(false)), + CmsCommands::Switch { name } => switch_cms(name), + CmsCommands::Remove { name, remove_files } => delete(name, remove_files), + CmsCommands::List {} => list(), + CmsCommands::Clear { force } => clear(force.unwrap_or(false)), + CmsCommands::Migrate { + name, + path, + server, + apikey, + } => { + migrate( + name, + absolute_path(path.unwrap_or("./".to_string()))?, + server, + apikey, + ) + .await + } + CmsCommands::Init { name, path } => { + init(name, absolute_path(path.unwrap_or("./".to_string()))?).await + } + } +} + +pub(crate) fn get_default_project() -> DaikokuResult { + let config = read(false)?; + + let default_project_name = + config + .get("default", "project") + .ok_or(DaikokuCliError::Configuration( + "missing default project or values in project. See cms commands".to_string(), + ))?; + + let project = config + .get_map() + .map(|m| m[&default_project_name].clone()) + .ok_or(DaikokuCliError::Configuration( + "missing default project or values in project. See cms commands".to_string(), + ))?; + + match (&project["name"], &project["path"]) { + (Some(_name), Some(path)) => Ok(Project { + // name: name.to_string(), + path: path.to_string(), + }), + (_, _) => Err(DaikokuCliError::Configuration( + "missing default project or values in project. See cms commands".to_string(), + )), + } +} + +fn add(name: String, path: String, overwrite: bool) -> DaikokuResult<()> { + logger::loading("Initialize path to project ...".to_string()); + + let mut config: Ini = read(false)?; + + if config.get(&name, "path").is_some() && !overwrite { + return Err(DaikokuCliError::Configuration( + format!("project already exists in the configuration file. Run daikoku cms remove --name={} to remove it", name), + )); + } + + if !Path::new(&path).exists() { + return Err(DaikokuCliError::Configuration( + "failed to find project at path".to_string(), + )); + } + + config.set(&name, "path", Some(path)); + config.set(&name, "name", Some(name.clone())); + config.set("default", "project", Some(name.clone())); + + match config.write(&get_path()?) { + Ok(()) => { + logger::println("New entry added".to_string()); + let _ = get_project(name); + Ok(()) + } + Err(err) => Err(DaikokuCliError::Configuration(err.to_string())), + } +} + +fn switch_cms(name: String) -> DaikokuResult<()> { + logger::loading("Updating default project".to_string()); + let mut config: Ini = read(false)?; + + if config.get(&name, "path").is_none() { + return Err(DaikokuCliError::Configuration( + "a non-existing section cannot be set as default".to_string(), + )); + } + + config.set("default", "project", Some(name.clone())); + + match config.write(&get_path()?) { + Ok(()) => { + logger::println("Defaut updated".to_string()); + let _ = get_project(name.clone()); + Ok(()) + } + Err(err) => Err(DaikokuCliError::Configuration(err.to_string())), + } +} + +pub(crate) fn get_project(name: String) -> DaikokuResult { + let config = read(false)?; + + let projects = config.get_map().ok_or(DaikokuCliError::Configuration( + "missing project or values in project.".to_string(), + ))?; + + match projects.get(&name) { + Some(project) => { + match (&project["name"], &project["path"]) { + (Some(_name), Some(path)) => { + logger::info(serde_json::to_string_pretty(&project).unwrap()); + Ok(Project { + // name: name.to_string(), + path: path.to_string(), + }) + } + (_, _) => Err(DaikokuCliError::Configuration( + "missing project or values in project.".to_string(), + )), + } + } + None => { + return Err(DaikokuCliError::Configuration( + "project is missing".to_string(), + )) + } + } +} + +fn internal_get_project(name: String) -> Option { + let config = read(false).ok()?; + + let projects = config.get_map()?; + + projects + .get(&name) + .map(|project| match (&project["name"], &project["path"]) { + (Some(_name), Some(path)) => Some(Project { + path: path.to_string(), + }), + (_, _) => None, + }) + .flatten() +} + +fn force_clearing_default_project() -> DaikokuResult<()> { + let mut config: Ini = read(false)?; + + config.remove_section("default"); + + config + .write(&get_path()?) + .map_err(|err| DaikokuCliError::Configuration(err.to_string())) +} + +fn list() -> DaikokuResult<()> { + let config: Ini = read(false)?; + + let map = config.get_map().map(Ok).unwrap_or(Ok(HashMap::new()))?; + + logger::info(serde_json::to_string_pretty(&map).unwrap()); + + Ok(()) +} + +fn delete(name: String, remove_files: bool) -> DaikokuResult<()> { + logger::loading("Deleting project".to_string()); + let mut config: Ini = read(false)?; + + if name.to_lowercase() == "default" { + return Err(DaikokuCliError::Configuration( + "protected project cant be deleted".to_string(), + )); + } + + if config.remove_section(&name).is_none() { + return Err(DaikokuCliError::Configuration( + "a non-existing section cannot be delete".to_string(), + )); + }; + + if remove_files { + if let Some(folder_path) = config.get(&name, "path") { + let _ = fs::remove_dir_all(folder_path); + } + } + + match config.write(&get_path()?) { + Ok(()) => { + logger::println(format!("{} deleted", &name)); + Ok(()) + } + Err(err) => Err(DaikokuCliError::Configuration(err.to_string())), + } +} + +fn get_path() -> DaikokuResult { + let home = get_home()?; + + Ok(home + .join(".daikoku") + .into_os_string() + .into_string() + .unwrap()) +} + +fn get_home() -> DaikokuResult { + match dirs::home_dir() { + Some(p) => Ok(p), + None => Err(DaikokuCliError::FileSystem( + "Failed getting your home dir!".to_string(), + )), + } +} + +fn read(last_attempt: bool) -> DaikokuResult { + let mut config = Ini::new(); + + match config.load(&get_path()?) { + Ok(_) => Ok(config), + Err(_) if !last_attempt => match std::fs::File::create(&get_path()?) { + Ok(_) => read(true), + Err(e) => Err(DaikokuCliError::Configuration(e.to_string())), + }, + Err(e) => Err(DaikokuCliError::Configuration(e.to_string())), + } +} + +fn remove_cms() -> DaikokuResult<()> { + let mut config = Ini::new(); + config.clear(); + match config.write(&get_path()?) { + Ok(_) => { + logger::println("Projects erased".to_string()); + Ok(()) + } + Err(e) => Err(DaikokuCliError::FileSystem(format!( + "failed to reset the projects file : {}", + e.to_string() + ))), + } +} + +fn clear(force: bool) -> DaikokuResult<()> { + if force { + return remove_cms(); + } + + logger::error("Are you to delete all cms ? [yN]".to_string()); + + let choice = prompt()?; + + if choice.trim() == "y" { + remove_cms() + } else { + Ok(()) + } +} + +#[async_recursion] +async fn init(name: String, path: String) -> DaikokuResult<()> { + logger::loading("Initializing project ...".to_string()); + + let manifest_dir = std::env::temp_dir(); + + let zip_name = "cms.zip".to_string(); + + let zip_path = Path::new(&manifest_dir).join(zip_name.clone()); + + let _ = fs::remove_dir(PathBuf::from(&zip_path)); + + logger::indent_println(format!( + "convert template bytes to zip file, {}", + &zip_path.to_string_lossy() + )); + match fs::File::create(&zip_path) { + Ok(mut file) => match file.write_all(ZIP_CMS) { + Err(err) => return Err(DaikokuCliError::FileSystem(err.to_string())), + Ok(()) => (), + }, + Err(e) => return Err(DaikokuCliError::FileSystem(e.to_string())), + }; + + logger::indent_println("Unzipping the template ...".to_string()); + let zip_action = zip_extensions::read::zip_extract(&PathBuf::from(zip_path), &manifest_dir); + + match zip_action { + Ok(()) => rename_plugin("cms".to_string(), name, Some(path)).await, + Err(er) => Err(DaikokuCliError::FileSystem(er.to_string())), + } +} + +#[async_recursion] +async fn rename_plugin(template: String, name: String, path: Option) -> DaikokuResult<()> { + let complete_path = match &path { + Some(p) => Path::new(p).join(&name), + None => Path::new("./").join(&name), + }; + + let _ = match &path { + Some(p) => fs::create_dir_all(p), + None => Result::Ok(()), + }; + + let manifest_dir = std::env::temp_dir(); + + logger::indent_println(format!( + "Write plugin from {} to {}", + &Path::new(&manifest_dir) + .join(format!("{}", template)) + .to_string_lossy(), + &complete_path.to_string_lossy() + )); + + match std::fs::rename( + Path::new(&manifest_dir).join(format!("{}", template)), + &complete_path, + ) { + Ok(()) => { + process(Commands::Cms { + command: crate::CmsCommands::Add { + name: name, + path: complete_path.into_os_string().into_string().unwrap(), + overwrite: None, + }, + }) + .await?; + logger::println("CMS created".to_string()); + Ok(()) + } + Err(e) => Err(DaikokuCliError::CmsCreationFile(e.to_string())), + } +} + +#[async_recursion] +async fn migrate(name: String, path: String, server: String, apikey: String) -> DaikokuResult<()> { + logger::loading("Converting legacy project from Daikoku environment".to_string()); + + if internal_get_project(name.clone()).is_some() { + return Err(DaikokuCliError::Configuration( + "Project already exists".to_string(), + )); + } else { + force_clearing_default_project()? + } + + if raw_daikoku_cms_api_get("/health", &server.clone(), &apikey) + .await? + .status + != 200 + { + return Err(DaikokuCliError::Configuration( + "Failed to join Daikoku server".to_string(), + )); + } + + if raw_daikoku_cms_api_get("/version", &server, &apikey) + .await? + .status + != 404 + { + return Err(DaikokuCliError::DaikokuStrError( + "The CMS version in Daikoku is too recent to be migrated".to_string(), + )); + } + + let project_path = PathBuf::from_str(&path) + .map_err(|err| DaikokuCliError::FileSystem(err.to_string()))? + .join(name.clone()); + + let sources_path = project_path.join("src"); + + let root_mail_tenant = bytes_to_struct::( + raw_daikoku_cms_api_get("/tenants/default", &server, &apikey) + .await? + .response, + )?; + + let root_mail_user_translations = bytes_to_struct::( + raw_daikoku_cms_api_get( + "/translations/_mail?domain=tenant.mail.template", + &server, + &apikey, + ) + .await? + .response, + )?; + + let mail_user_template = bytes_to_struct::( + raw_daikoku_cms_api_get("/translations/_mail?domain=mail", &server, &apikey) + .await? + .response, + )?; + + let apis_informations: Vec = bytes_to_vec_of_struct::( + raw_daikoku_cms_api_get( + "/apis?fields=_id,_humanReadableId,header,description", + &server, + &apikey, + ) + .await? + .response, + )? + .into_iter() + .filter(|api| !EXCLUDE_API.contains(&api._id.as_str())) + .collect(); + + create_mail_tenant(root_mail_tenant, sources_path.clone())?; + create_mail_folder(root_mail_user_translations, sources_path.clone(), true)?; + create_mail_folder(mail_user_template, sources_path.clone(), false)?; + + create_api_folder(apis_informations, sources_path.clone())?; + + logger::info("create_cms_pages".to_string()); + create_cms_pages(&sources_path, &server, &apikey).await?; + + logger::info("create_daikoku_hidden_files".to_string()); + create_daikoku_hidden_files(project_path.clone())?; + + logger::info("Trying to create project".to_string()); + create_project(name.clone(), project_path.clone()).await?; + + logger::info("Trying to create environment".to_string()); + create_environment(name, server, apikey).await?; + + logger::println("Migration endded".to_string()); + + Ok(()) +} + +fn replace_ids(items: Vec) -> DaikokuResult> { + let identifiers = items + .iter() + .map(|item| item._id.clone()) + .collect::>(); + + let mut paths: HashMap = HashMap::new(); + items.iter().for_each(|item| { + if let Ok(path) = get_cms_page_path(item) + .unwrap() + .into_os_string() + .into_string() + { + paths.insert(item._id.clone(), format!("/{}", path)); + } + }); + + let mut updated_pages = Vec::new(); + + items.iter().for_each(|item| { + let mut new_page = item.clone(); + identifiers.iter().for_each(|identifier| { + new_page.content = new_page.content.replace( + identifier, + paths.get(identifier).unwrap_or(&item._id.clone()), + ); + }); + updated_pages.push(new_page) + }); + + Ok(updated_pages) +} + +pub(crate) fn create_api_folder( + apis: Vec, + project_path: PathBuf, +) -> DaikokuResult> { + let mut created: Vec = Vec::new(); + + apis.iter().for_each(|item| { + let file_path = project_path.clone().join( + PathBuf::from_str("apis") + .unwrap() + .join(item.human_readable_id.clone()), + ); + + if !file_path.exists() { + let _ = fs::create_dir_all(file_path.clone()); + + created.push(item.human_readable_id.clone()); + + let mut config: Ini = Ini::new(); + config.set(&"default", "id", Some(item._id.clone())); + let _ = config.write(file_path.clone().join(".daikoku_data")); + + if let Some(description) = &item.description { + let _description = create_path_and_file( + file_path.join("description").join("page.html").clone(), + description.clone(), + item._id.clone(), + HashMap::new(), + SourceExtension::HTML, + ); + } else { + let _ = fs::create_dir_all(file_path.join("description")) + .map_err(|err| map_error_to_filesystem_error(err, "")); + } + + if let Some(header) = &item.header { + let _header = create_path_and_file( + file_path.join("header").join("page.html").clone(), + header.clone(), + item._id.clone(), + HashMap::new(), + SourceExtension::HTML, + ); + } else { + let _ = fs::create_dir_all(file_path.join("header")) + .map_err(|err| map_error_to_filesystem_error(err, "")); + } + } + }); + + Ok(created) +} + +pub(crate) fn create_mail_tenant( + mail_settings: TenantMailBody, + project_path: PathBuf, +) -> DaikokuResult<()> { + let filename = "page.html".to_string(); + + let file_path = project_path + .clone() + .join(get_mail_page_path(&filename, true).unwrap()); + + let _ = create_path_and_file( + file_path, + mail_settings + .mailer_settings + .map(|mailer| mailer.template.unwrap_or("".to_string())) + .unwrap_or("".to_string()), + filename, + HashMap::new(), + SourceExtension::HTML, + ); + + Ok(()) +} + +pub(crate) fn create_mail_folder( + intl_translation: IntlTranslationBody, + project_path: PathBuf, + is_root_mail: bool, +) -> DaikokuResult<()> { + intl_translation.translations.iter().for_each(|item| { + let file_path = project_path + .clone() + .join(get_mail_page_path(&item._id.clone().replace(".", "-"), is_root_mail).unwrap()); + + let mut config: Ini = Ini::new(); + + config.set(&"default", "id", Some(item._id.clone())); + + let _ = config.write(file_path.clone().join(".daikoku_data")); + + item.translations.iter().for_each(|translation| { + let _ = create_path_and_file( + file_path + .clone() + .join(translation.language.clone()) + .join("page.html"), + translation.value.clone(), + translation._id.clone(), + HashMap::new(), + SourceExtension::HTML, + ); + }) + }); + + Ok(()) +} + +async fn create_cms_pages( + sources_path: &PathBuf, + server: &String, + apikey: &String, +) -> DaikokuResult<()> { + let items = bytes_to_vec_of_struct::( + raw_daikoku_cms_api_get("/pages", &server, &apikey) + .await? + .response, + )?; + + let new_pages = replace_ids(items)?; + + convert_cms_pages(new_pages, sources_path.clone()) +} + +// fn clone_new_project(items: Vec, project_path: &PathBuf) -> DaikokuResult<()> { +// println!("cloning new project"); + +// items +// .iter() +// .filter(|item| { +// !item.path.clone().unwrap().starts_with("apis") +// && !item.path.clone().unwrap().starts_with("mails") +// }) +// .for_each(|item| { +// let extension = SourceExtension::from_str(&item.content_type).unwrap(); + +// let item_path = item.path.clone().unwrap().clone(); + +// // Remove the slash if the path starts with one. +// let mut file_path = project_path.clone().join(if item_path.starts_with("/") { +// &item_path[1..item_path.len()] +// } else { +// item_path.as_str() +// }); + +// let split_path = item_path.split("/"); + +// // if the path didn't start with folder, we will place the page in the default pages folder +// if split_path +// .clone() +// .into_iter() +// .find(|part| part.is_empty()) +// .is_some() +// || split_path.collect::>().len() == 1 +// { +// file_path = +// project_path +// .clone() +// .join("pages") +// .join(if item_path.starts_with("/") { +// &item_path[1..item_path.len()] +// } else { +// item_path.as_str() +// }); +// } + +// let metadata = extract_metadata(item).unwrap_or(HashMap::new()); + +// let _ = create_path_and_file( +// file_path, +// item.content.clone(), +// item.name.clone(), +// metadata, +// extension, +// ); +// }); + +// Ok(()) +// } + +fn convert_cms_pages(items: Vec, project_path: PathBuf) -> DaikokuResult<()> { + items.iter().for_each(|item| { + let extension = SourceExtension::from_str(&item.content_type).unwrap(); + + let file_path = project_path.clone().join(get_cms_page_path(item).unwrap()); + + let metadata = extract_metadata(item).unwrap_or(HashMap::new()); + + let _ = create_path_and_file( + file_path, + item.content.clone(), + item.name.clone(), + metadata, + extension, + ); + }); + + Ok(()) +} + +fn get_mail_page_path(filename: &String, is_root_mail: bool) -> DaikokuResult { + let folder = if is_root_mail { "mails/root" } else { "mails" }; + + let folder_path = PathBuf::from_str(folder).unwrap().join(filename); + + Ok(folder_path) +} + +fn get_cms_page_path(item: &CmsPage) -> DaikokuResult { + let extension = SourceExtension::from_str(&item.content_type).unwrap(); + + let folder = match extension { + SourceExtension::HTML => item.path.clone().map(|_| "pages").unwrap_or("blocks"), + SourceExtension::CSS => "styles", + SourceExtension::Javascript => "scripts", + SourceExtension::JSON => "data", + }; + + let router_path = item.path.clone().map(|p| p.replace("/", "")).map(|path| { + if path == "/" || path.is_empty() { + item.name.clone() + } else { + path + } + }); + + let folder_path = if router_path + .clone() + .unwrap_or(item.name.clone()) + .starts_with(folder) + { + PathBuf::from_str(folder).unwrap().join( + router_path + .unwrap_or(item.name.clone()) + .replacen(folder, "", 1), + ) + } else { + PathBuf::from_str(folder).unwrap().join(format!( + "{}{}", + router_path.unwrap_or(item.name.clone()), + extension.ext() + )) + }; + + let parent = folder_path.parent().unwrap(); + + if !parent.exists() { + fs::create_dir_all(parent).map_err(|err| map_error_to_filesystem_error(err, ""))?; + } + + Ok(folder_path) +} + +pub fn create_path_and_file( + file_buf: PathBuf, + content: String, + // item: &CmsPage, + name: String, + metadata: HashMap, + content_type: SourceExtension, +) -> DaikokuResult<()> { + let parent = + file_buf + .parent() + .map(|path| Ok(path)) + .unwrap_or(Err(DaikokuCliError::FileSystem( + "failed to recursively create paths".to_string(), + )))?; + + if !parent.exists() { + fs::create_dir_all(parent).map_err(|err| map_error_to_filesystem_error(err, ""))?; + } + + let mut file_path = file_buf.clone(); + + // set extension if missing + file_path + .clone() + .as_path() + .as_os_str() + .to_str() + .unwrap() + .split("/") + .last() + .filter(|part| !part.contains(".")) + .map(|_| file_path.set_extension(content_type.ext()[1..].to_string())); + + logger::println(format!("Creating {} {:?}", name, file_path)); + + if content_type == SourceExtension::HTML { + if metadata.is_empty() { + Ok(fs::write(file_path, content) + .map_err(|err| map_error_to_filesystem_error(err, ""))?) + } else { + let metadata_header = serde_yaml::to_string(&metadata).map_err(|_err| { + DaikokuCliError::ParsingError(format!("failed parsing metadata {}", &name)) + })?; + + Ok( + fs::write(file_path, format!("{}\n---\n{}", metadata_header, content)) + .map_err(|err| map_error_to_filesystem_error(err, ""))?, + ) + } + } else { + Ok(fs::write(file_path, content).map_err(|err| map_error_to_filesystem_error(err, ""))?) + } +} + +fn extract_metadata(item: &CmsPage) -> DaikokuResult> { + let mut metadata: HashMap = HashMap::new(); + + metadata.insert("_authenticated".to_string(), item.authenticated.to_string()); + metadata.insert("_visible".to_string(), item.visible.to_string()); + metadata.insert("_exact".to_string(), item.exact.to_string()); + item.last_published_date + .map(|value| metadata.insert("_last_published_date".to_string(), value.to_string())); + Ok(metadata) +} + +fn create_daikoku_hidden_files(complete_path: PathBuf) -> DaikokuResult { + fs::create_dir_all(complete_path.join(".daikoku")) + .map_err(|err| DaikokuCliError::FileSystem(err.to_string()))?; + + fs::File::create(complete_path.join(".daikoku").join(".environments")) + .map_err(|err| DaikokuCliError::FileSystem(err.to_string()))?; + + fs::File::create(complete_path.join(".daikoku").join(".daikokuignore")) + .map_err(|err| DaikokuCliError::FileSystem(err.to_string()))?; + + fs::File::create(complete_path.join(".daikoku").join(".secrets")) + .map_err(|err| DaikokuCliError::FileSystem(err.to_string())) +} + +async fn create_project(name: String, complete_path: PathBuf) -> DaikokuResult<()> { + process(Commands::Cms { + command: crate::CmsCommands::Add { + name: name, + path: complete_path.into_os_string().into_string().unwrap(), + overwrite: None, + }, + }) + .await?; + Ok(()) +} + +async fn create_environment(name: String, server: String, apikey: String) -> DaikokuResult<()> { + process(Commands::Environments { + command: crate::EnvironmentsCommands::Add { + name: name, + server, + apikey, + overwrite: Some(true), + }, + }) + .await?; + Ok(()) +} diff --git a/cli/src/commands/commands.md b/cli/src/commands/commands.md new file mode 100644 index 000000000..bad9a83be --- /dev/null +++ b/cli/src/commands/commands.md @@ -0,0 +1,55 @@ +# CMS +daikoku cms init +daikoku cms migrate +crash si le fichier .daikoku_metadata est present + +daikoku cms list +daikoku cms add +daikoku cms switch // change default en switch +daikoku cms remove +daikoku cms clear +avec confirmation pour faire plaisir + +# PUSH +daikoku push +qui peut être un dossier + +# ASSETS +daikoku assets push <DESC> <PATH> <SLUG> +daikoku assets remove <FILENAME> <PATH> <SLUG> +daikoku assets list +daikoku assets sync + +# ENVIRONMENTS +daikoku environments clear <FORCE> +daikoku environments add <NAME> <SERVER> <OVERWRITE> +daikoku environments switch <NAME> // rename du default +daikoku environments remove <NAME> +daikoku environments info <NAME> <FULL> // rename from env +daikoku environments list +daikoku environments config <APIKEY> +// editer le .gitignore pour mettre le .secrets du .daikoku + +# GENERATE +daikoku generate documentation <FILENAME> <TITLE> <DESC> + +# LOGIN (plus de liste de cms autorisés) +daikoku login + +# PULL +daikoku pull apis +daikoku pull mails + +# VERSION +daikoku version + +# WATCH +daikoku watch + +# CMS API +à créer + +# documentations folder sorti des apis + +# créer un fichier .secrets +avec les apikeys et les cookies \ No newline at end of file diff --git a/cli/src/commands/creation.rs b/cli/src/commands/creation.rs deleted file mode 100644 index 7eb28b905..000000000 --- a/cli/src/commands/creation.rs +++ /dev/null @@ -1,101 +0,0 @@ -use std::{ - fs, - path::{Path, PathBuf}, -}; - -use async_recursion::async_recursion; - -use crate::{ - logging::{ - error::{DaikokuCliError, DaikokuResult}, - logger, - }, process, Commands -}; - -use std::io::Write; - -const ZIP_CMS: &[u8] = include_bytes!("../../templates/empty.zip"); - -pub(crate) async fn run( - template: Option<String>, - name: String, - path: Option<String>, -) -> DaikokuResult<()> { - logger::loading("<yellow>Creating</> plugin ...".to_string()); - - let manifest_dir = std::env::temp_dir(); - - let template = template.unwrap_or("empty".to_string()); - - let zip_bytes = match template.as_str() { - _ => ZIP_CMS, - }; - let zip_path = Path::new(&manifest_dir).join(format!("{}.zip", template)); - - match std::path::Path::new(&zip_path).exists() { - true => (), - false => { - logger::indent_println(format!( - "turn template bytes to zip file, {}", - &zip_path.to_string_lossy() - )); - match fs::File::create(&zip_path) { - Ok(mut file) => match file.write_all(zip_bytes) { - Err(err) => return Err(DaikokuCliError::FileSystem(err.to_string())), - Ok(()) => (), - }, - Err(e) => return Err(DaikokuCliError::FileSystem(e.to_string())), - }; - } - } - - logger::indent_println("<yellow>Unzipping</> the template ...".to_string()); - let zip_action = zip_extensions::read::zip_extract(&PathBuf::from(zip_path), &manifest_dir); - - match zip_action { - Ok(()) => rename_plugin(template, name, path).await, - Err(er) => Err(DaikokuCliError::FileSystem(er.to_string())), - } -} - -#[async_recursion] -async fn rename_plugin(template: String, name: String, path: Option<String>) -> DaikokuResult<()> { - let complete_path = match &path { - Some(p) => Path::new(p).join(&name), - None => Path::new("./").join(&name), - }; - - let _ = match &path { - Some(p) => fs::create_dir_all(p), - None => Result::Ok(()), - }; - - let manifest_dir = std::env::temp_dir(); - - logger::indent_println(format!( - "<yellow>Write</> plugin from {} to {}", - &Path::new(&manifest_dir) - .join(format!("{}", template)) - .to_string_lossy(), - &complete_path.to_string_lossy() - )); - - match std::fs::rename( - Path::new(&manifest_dir).join(format!("{}", template)), - &complete_path, - ) { - Ok(()) => { - process(Commands::Projects { - command: crate::ProjectCommands::Add { - name: name, - path: complete_path.into_os_string().into_string().unwrap(), - overwrite: None, - }, - }) - .await?; - logger::println("<green>CMS created</>".to_string()); - Ok(()) - } - Err(e) => Err(DaikokuCliError::CmsCreationFile(e.to_string())), - } -} diff --git a/cli/src/commands/enviroments.rs b/cli/src/commands/enviroments.rs deleted file mode 100644 index 6fb29d884..000000000 --- a/cli/src/commands/enviroments.rs +++ /dev/null @@ -1,367 +0,0 @@ -use crate::{ - logging::{ - error::{DaikokuCliError, DaikokuResult}, - logger, - }, - EnvironmentsCommands, -}; -use bytes::Bytes; -use configparser::ini::Ini; -use http_body_util::Empty; -use hyper::{header, Method, Request}; -use hyper_util::rt::TokioIo; -use serde::{Deserialize, Serialize}; -use std::{ - collections::HashMap, - fs, - path::{Path, PathBuf}, -}; -use tokio::net::TcpStream; - -use super::projects; - -#[derive(Deserialize, Serialize, Clone, Debug)] -pub(crate) struct Environment { - pub(crate) server: String, - pub(crate) token: Option<String>, -} - -pub(crate) async fn run(command: EnvironmentsCommands) -> DaikokuResult<()> { - match command { - EnvironmentsCommands::Clear {} => clear(), - EnvironmentsCommands::Add { - name, - server, - token, - overwrite, - force, - } => { - add( - name, - server, - token, - overwrite.unwrap_or(false), - force.unwrap_or(false), - ) - .await - } - EnvironmentsCommands::Default { name } => update_default(name), - EnvironmentsCommands::Remove { name } => delete(name), - EnvironmentsCommands::Env { name } => { - let environment = get(name)?; - logger::info(serde_json::to_string_pretty(&environment).unwrap()); - Ok(()) - } - EnvironmentsCommands::List {} => list(), - EnvironmentsCommands::PathDefault { token } => patch_default(token), - } -} - -fn get_path() -> DaikokuResult<String> { - let project = projects::get_default_project()?; - - let environment_path = Path::new(&PathBuf::from(project.path)) - .join(".daikoku") - .join(".environments") - .into_os_string() - .into_string(); - - environment_path - .map(Ok) - .unwrap_or(Err(DaikokuCliError::Configuration( - "failed to read environments file".to_string(), - ))) -} - -fn read() -> DaikokuResult<Ini> { - let mut config = Ini::new(); - - match config.load(&get_path()?) { - Ok(_) => Ok(config), - Err(e) => Err(DaikokuCliError::Configuration(e.to_string())), - } -} - -fn set_content_file(content: &String) -> DaikokuResult<()> { - std::fs::write(get_path()?, content).map_err(|err| DaikokuCliError::FileSystem(err.to_string())) -} - -fn clear() -> DaikokuResult<()> { - match set_content_file(&"".to_string()) { - Ok(_) => { - logger::println("<green>Environments erased</>".to_string()); - Ok(()) - } - Err(e) => Err(DaikokuCliError::FileSystem(format!( - "failed to reset the environments file : {}", - e.to_string() - ))), - } -} - -pub(crate) async fn can_join_daikoku(server: &String) -> DaikokuResult<bool> { - let host = server.replace("http://", "").replace("https://", ""); - - let url: String = format!("{}/api/versions/_daikoku", server); - - let req = Request::builder() - .method(Method::GET) - .uri(&url) - .header(header::HOST, &host) - .body(Empty::<Bytes>::new()) - .unwrap(); - - let stream = TcpStream::connect(&host).await.map_err(|err| { - DaikokuCliError::DaikokuErrorWithMessage("failed to join the server".to_string(), err) - })?; - let io = TokioIo::new(stream); - - let (mut sender, conn) = hyper::client::conn::http1::handshake(io) - .await - .map_err(|err| DaikokuCliError::HyperError(err))?; - - tokio::task::spawn(async move { - if let Err(err) = conn.await { - logger::error(format!("Connection error {:?}", err)); - } - }); - - let upstream_resp = sender - .send_request(req) - .await - .map_err(|err| DaikokuCliError::ParsingError(err.to_string()))?; - - let ( - hyper::http::response::Parts { - headers: _, status, .. - }, - _body, - ) = upstream_resp.into_parts(); - - let status = status.as_u16(); - - logger::println(format!("Daikoku have returned : {}", status)); - - Ok(status == 200) -} - -async fn add( - name: String, - server: String, - token: Option<String>, - overwrite: bool, - force: bool, -) -> DaikokuResult<()> { - logger::loading("<yellow>Patching</> configuration".to_string()); - let mut config: Ini = read()?; - - let exists = config.get(&name, "server").is_some(); - - if name.to_lowercase() == "default" { - return Err(DaikokuCliError::Configuration( - "forbidden keyword usage".to_string(), - )); - } - - if exists && !overwrite { - return Err(DaikokuCliError::Configuration("configuration already exists. you maybe want to use --overwrite=true parameter to overwrite contents".to_string())); - } - - if !force && !can_join_daikoku(&server).await? { - return Err(DaikokuCliError::Configuration( - "failed to save configuration. The specified Daikoku server can not be reached" - .to_string(), - )); - } - - config.set(&name, "server", Some(server)); - config.set(&name, "token", token); - - config.set("default", "environment", Some(name.clone())); - - match config.write(&get_path()?) { - Ok(()) => { - logger::println(if exists { - "<green>Entry</> updated".to_string() - } else { - "<green>New entry</> added".to_string() - }); - logger::info(serde_json::to_string_pretty(&get(name)?).unwrap()); - Ok(()) - } - Err(err) => Err(DaikokuCliError::Configuration(err.to_string())), - } -} - -fn update_default(name: String) -> DaikokuResult<()> { - logger::loading("<yellow>Updating</> default environment".to_string()); - let mut config: Ini = read()?; - - if config.get(&name, "server").is_none() { - return Err(DaikokuCliError::Configuration( - "a non-existing section cannot be set as default".to_string(), - )); - } - - config.set("default", "environment", Some(name.clone())); - - match config.write(&get_path()?) { - Ok(()) => { - logger::println("<green>Defaut</> updated".to_string()); - let _ = get("default".to_string()); - Ok(()) - } - Err(err) => Err(DaikokuCliError::Configuration(err.to_string())), - } -} - -fn patch_default(token: String) -> DaikokuResult<()> { - logger::loading("<yellow>Patching</> default environment".to_string()); - let mut config: Ini = read()?; - - match config.get("default", "environment") { - None => { - return Err(DaikokuCliError::Configuration( - "no default environment is configured".to_string(), - )) - } - Some(environment) => { - config.set(&environment, "token", Some(token)); - - match config.write(&get_path()?) { - Ok(()) => { - logger::println("<green>Defaut</> updated".to_string()); - let _ = get(environment); - Ok(()) - } - Err(err) => Err(DaikokuCliError::Configuration(err.to_string())), - } - } - } -} - -pub(crate) fn format_cookie(str: String) -> String { - if str.starts_with("daikoku-session=") { - str.to_string() - } else { - format!("daikoku-session={}", str) - } -} - -pub(crate) fn read_cookie_from_environment(failed_if_not_present: bool) -> DaikokuResult<String> { - let config: Ini = read()?; - - if let Some(environment) = config.get("default", "environment") { - config - .get(&environment, "token") - .map(|cookie| Ok(format_cookie(cookie))) - .unwrap_or(if failed_if_not_present { - Err(DaikokuCliError::Configuration( - "missing token on default environment".to_string(), - )) - } else { - Ok("".to_string()) - }) - } else { - Err(DaikokuCliError::Configuration( - "missing default environment".to_string(), - )) - } -} - -fn delete(name: String) -> DaikokuResult<()> { - logger::loading("<yellow>Deleting</> environment".to_string()); - let mut config: Ini = read()?; - - if name.to_lowercase() == "default" { - return Err(DaikokuCliError::Configuration( - "protected environment cant be deleted".to_string(), - )); - } - - if config.remove_section(&name).is_none() { - return Err(DaikokuCliError::Configuration( - "a non-existing section cannot be delete".to_string(), - )); - }; - - match config.write(&get_path()?) { - Ok(()) => { - logger::println(format!("<green>{}</> deleted", &name)); - Ok(()) - } - Err(err) => Err(DaikokuCliError::Configuration(err.to_string())), - } -} - -fn get(name: String) -> DaikokuResult<Environment> { - let config: Ini = read()?; - - let values = config - .get_map() - .map(Ok) - .unwrap_or(Err(DaikokuCliError::Configuration( - "failed to access configuration file".to_string(), - )))?; - - match values.get(&name) { - Some(value) => serde_json::from_str(serde_json::to_string(&value).unwrap().as_str()) - .map(Ok) - .unwrap_or(Err(DaikokuCliError::Configuration( - "failed reading environment".to_string(), - ))), - None => Err(DaikokuCliError::Configuration( - "enviromnment not found".to_string(), - )), - } -} - -pub(crate) fn get_default_environment() -> DaikokuResult<Environment> { - let config: Ini = read()?; - - let default_environment = config.get("default", "environment").map(Ok).unwrap_or(Err( - DaikokuCliError::Configuration( - "default environment not found. see daikokucli environments help".to_string(), - ), - ))?; - - get(default_environment) -} - -pub(crate) fn check_environment_from_str(name: Option<String>) -> DaikokuResult<Environment> { - name.map(|project_name| get(project_name)) - .unwrap_or(get_default_environment()) -} - -fn list() -> DaikokuResult<()> { - let config: Ini = read()?; - - let map = config.get_map().unwrap_or(HashMap::new()); - - logger::info(serde_json::to_string_pretty(&map).unwrap()); - - Ok(()) -} - -pub(crate) fn get_daikokuignore() -> DaikokuResult<Vec<String>> { - let project = projects::get_default_project()?; - - let daikokuignore_path = Path::new(&PathBuf::from(project.path)) - .join(".daikoku") - .join(".daikokuignore") - .into_os_string() - .into_string(); - - match daikokuignore_path { - Ok(path) => { - let content = fs::read_to_string(path) - .map_err(|err| DaikokuCliError::Configuration(err.to_string()))?; - - Ok(content - .split("\n") - .map(|str| str.to_string()) - .collect::<Vec<String>>()) - } - Err(_) => Ok(Vec::new()), - } -} diff --git a/cli/src/commands/environments.rs b/cli/src/commands/environments.rs new file mode 100644 index 000000000..8c148229d --- /dev/null +++ b/cli/src/commands/environments.rs @@ -0,0 +1,427 @@ +use crate::{ + helpers::{daikoku_cms_api_get, raw_daikoku_cms_api_get}, + interactive::prompt, + logging::{ + error::{DaikokuCliError, DaikokuResult}, + logger, + }, + utils::apply_credentials_mask, + EnvironmentsCommands, +}; +use configparser::ini::Ini; +use serde::{Deserialize, Serialize}; +use std::{ + collections::HashMap, + fs, + path::{Path, PathBuf}, +}; + +use super::cms; + +#[derive(Deserialize, Serialize, Clone, Debug)] +pub(crate) struct Environment { + pub(crate) server: String, + pub(crate) cookie: Option<String>, + pub(crate) apikey: Option<String>, + pub(crate) name: String, +} + +pub(crate) async fn run(command: EnvironmentsCommands) -> DaikokuResult<()> { + match command { + EnvironmentsCommands::Clear { force } => clear(force.unwrap_or(false)), + EnvironmentsCommands::Add { + name, + server, + overwrite, + apikey, + } => add(name, server, overwrite.unwrap_or(false), apikey).await, + EnvironmentsCommands::Switch { name } => switch_environment(name), + EnvironmentsCommands::Remove { name } => remove(name), + EnvironmentsCommands::Info { name, full } => info(name, full.unwrap_or(false)), + EnvironmentsCommands::List {} => list(), + EnvironmentsCommands::Config { apikey, cookie } => configure(apikey, cookie).await, + } +} + +fn get_environments_path() -> DaikokuResult<String> { + get_hidden_file( + ".environments".to_string(), + "failed to read environments file".to_string(), + ) +} + +fn get_hidden_file(name: String, error_message: String) -> DaikokuResult<String> { + let project = cms::get_default_project()?; + + let path = Path::new(&PathBuf::from(project.path)) + .join(".daikoku") + .join(name) + .into_os_string() + .into_string(); + + path.map(Ok) + .unwrap_or(Err(DaikokuCliError::Configuration(error_message))) +} + +fn get_secrets_path() -> DaikokuResult<String> { + get_hidden_file( + ".secrets".to_string(), + "failed to read secrets file".to_string(), + ) +} + +fn read_environments() -> DaikokuResult<Ini> { + let mut config = Ini::new(); + + match config.load(&get_environments_path()?) { + Ok(_) => Ok(config), + Err(e) => Err(DaikokuCliError::Configuration(e.to_string())), + } +} + +fn read_secrets() -> DaikokuResult<Ini> { + let mut config = Ini::new(); + + match config.load(&get_secrets_path()?) { + Ok(_) => Ok(config), + Err(e) => Err(DaikokuCliError::Configuration(e.to_string())), + } +} + +fn set_content_file(content: &String) -> DaikokuResult<()> { + std::fs::write(get_environments_path()?, content) + .map_err(|err| DaikokuCliError::FileSystem(err.to_string())) +} + +fn clearing() -> DaikokuResult<()> { + match set_content_file(&"".to_string()) { + Ok(_) => { + logger::println("<green>Environments erased</>".to_string()); + Ok(()) + } + Err(e) => Err(DaikokuCliError::FileSystem(format!( + "failed to reset the environments file : {}", + e.to_string() + ))), + } +} + +fn clear(force: bool) -> DaikokuResult<()> { + if force { + return clearing(); + } + + logger::error("Are you to delete all environments ? [yN]".to_string()); + + let choice = prompt()?; + + if choice.trim() == "y" { + clearing() + } else { + Ok(()) + } +} + +pub(crate) async fn can_join_daikoku( + server: &String, + apikey: Option<&String>, +) -> DaikokuResult<bool> { + let host = server.replace("http://", "").replace("https://", ""); + + let url: String = format!("{}/health", host); + + let status = match apikey { + None => daikoku_cms_api_get(&url).await?.status, + Some(apikey) => { + raw_daikoku_cms_api_get("/health", &server.clone(), &apikey) + .await? + .status + } + }; + + logger::println(format!("Daikoku have returned : {}", status)); + + Ok(status == 200) +} + +async fn add(name: String, server: String, overwrite: bool, apikey: String) -> DaikokuResult<()> { + logger::loading("<yellow>Patching</> configuration".to_string()); + let mut config: Ini = read_environments()?; + + if name.to_lowercase() == "default" { + return Err(DaikokuCliError::Configuration( + "forbidden keyword usage".to_string(), + )); + } + + let exists = config.get(&name, "server").is_some(); + + if exists && !overwrite { + return Err(DaikokuCliError::Configuration("configuration already exists. you maybe want to use --overwrite=true parameter to overwrite contents".to_string())); + } + + if !can_join_daikoku(&server, Some(&apikey)).await? { + return Err(DaikokuCliError::Configuration( + "failed to save configuration. The specified Daikoku server can not be reached" + .to_string(), + )); + } + + config.set(&name, "server", Some(server)); + config.set("default", "environment", Some(name.clone())); + config.set(name.clone().as_str(), "name", Some(name.clone())); + + let mut secrets: Ini = read_secrets()?; + secrets.set(name.clone().as_str(), "apikey", Some(apikey)); + + secrets + .write(&get_secrets_path()?) + .map_err(|err| DaikokuCliError::DaikokuError(err))?; + + match config.write(&get_environments_path()?) { + Ok(()) => { + logger::println(if exists { + "<green>Entry</> updated".to_string() + } else { + "<green>New entry</> added".to_string() + }); + // logger::info(serde_json::to_string_pretty(&get(name)?).unwrap()); + Ok(()) + } + Err(err) => Err(DaikokuCliError::Configuration(err.to_string())), + } +} + +fn switch_environment(name: String) -> DaikokuResult<()> { + logger::loading("<yellow>Switch</> of default environment".to_string()); + let mut config: Ini = read_environments()?; + + if config.get(&name, "server").is_none() { + return Err(DaikokuCliError::Configuration( + "a non-existing section cannot be set as default".to_string(), + )); + } + + config.set("default", "environment", Some(name.clone())); + + match config.write(&get_environments_path()?) { + Ok(()) => { + logger::println("<green>Defaut</> updated".to_string()); + let _ = get("default".to_string()); + Ok(()) + } + Err(err) => Err(DaikokuCliError::Configuration(err.to_string())), + } +} + +async fn configure(apikey: Option<String>, cookie: Option<String>) -> DaikokuResult<()> { + logger::loading("<yellow>Updating</> default environment".to_string()); + + let environment = get_default_environment()?; + + let mut config: Ini = read_secrets()?; + + if let Some(new_apikey) = apikey { + config.set(&environment.name, "apikey", Some(new_apikey.clone())); + + if !can_join_daikoku(&environment.server, Some(&new_apikey)).await? { + return Err(DaikokuCliError::Configuration( + "failed to save configuration. The specified Daikoku server can not be reached" + .to_string(), + )); + } + + match config.write(&get_secrets_path()?) { + Ok(()) => { + logger::println("<green>apikey</> updated".to_string()); + } + Err(err) => return Err(DaikokuCliError::Configuration(err.to_string())), + } + } + + if let Some(new_cookie) = cookie { + config.set(&environment.name, "cookie", Some(new_cookie.clone())); + + match config.write(&get_secrets_path()?) { + Ok(()) => { + logger::println("<green>cookie</> updated".to_string()); + } + Err(err) => return Err(DaikokuCliError::Configuration(err.to_string())), + } + } + + Ok(()) +} + +pub(crate) fn format_cookie(str: String) -> String { + if str.starts_with("daikoku-session=") { + str.to_string() + } else { + format!("daikoku-session={}", str) + } +} + +pub(crate) fn read_cookie_from_environment(failed_if_not_present: bool) -> DaikokuResult<String> { + let config: Ini = read_environments()?; + + if let Some(environment) = config.get("default", "environment") { + let secrets: Ini = read_secrets()?; + secrets + .get(&environment, "cookie") + .map(|cookie| Ok(format_cookie(cookie))) + .unwrap_or(if failed_if_not_present { + Err(DaikokuCliError::Configuration( + "Missing cookie on default environment. Run daikoku login".to_string(), + )) + } else { + Ok("".to_string()) + }) + } else { + Err(DaikokuCliError::Configuration( + "missing default environment".to_string(), + )) + } +} + +pub(crate) fn read_apikey_from_secrets(failed_if_not_present: bool) -> DaikokuResult<String> { + let config: Ini = read_environments()?; + + if let Some(environment) = config.get("default", "environment") { + let secrets: Ini = read_secrets()?; + secrets + .get(&environment, "apikey") + .map(Ok) + .unwrap_or(if failed_if_not_present { + Err(DaikokuCliError::Configuration( + "Missing apikey on default environment. Run daikoku environments configure --apikey=<> with the apikey paste from your Daikoku CMS API".to_string(), + )) + } else { + Ok("".to_string()) + }) + } else { + Err(DaikokuCliError::Configuration( + "missing default environment".to_string(), + )) + } +} + +fn info(name: String, show_full_credentials: bool) -> DaikokuResult<()> { + let mut environment = get(name)?; + + let secrets = read_secrets()?; + + environment.apikey = secrets + .get(&environment.name, "apikey") + .map(|credential| apply_credentials_mask(&credential, show_full_credentials)); + environment.cookie = secrets + .get(&environment.name, "cookie") + .map(|credential| apply_credentials_mask(&credential, show_full_credentials)); + + logger::info(serde_json::to_string_pretty(&environment).unwrap()); + Ok(()) +} + +fn remove(name: String) -> DaikokuResult<()> { + logger::loading("<yellow>Deleting</> environment".to_string()); + let mut config: Ini = read_environments()?; + + if name.to_lowercase() == "default" { + return Err(DaikokuCliError::Configuration( + "protected environment cant be deleted".to_string(), + )); + } + + if config.remove_section(&name).is_none() { + return Err(DaikokuCliError::Configuration( + "a non-existing section cannot be delete".to_string(), + )); + }; + + let mut secrets: Ini = read_secrets()?; + secrets.remove_section(&name); + + match ( + config.write(&get_environments_path()?), + secrets.write(&get_secrets_path()?), + ) { + (Ok(()), Ok(())) => { + logger::println(format!("<green>{}</> deleted", &name)); + Ok(()) + } + _ => Err(DaikokuCliError::Configuration( + "failed to update environments and secrets files".to_string(), + )), + } +} + +fn get(name: String) -> DaikokuResult<Environment> { + let config: Ini = read_environments()?; + + let values = config + .get_map() + .map(Ok) + .unwrap_or(Err(DaikokuCliError::Configuration( + "failed to access configuration file".to_string(), + )))?; + + match values.get(&name) { + Some(value) => serde_json::from_str(serde_json::to_string(&value).unwrap().as_str()) + .map(Ok) + .unwrap_or(Err(DaikokuCliError::Configuration( + "failed reading environment".to_string(), + ))), + None => Err(DaikokuCliError::Configuration( + "enviromnment not found".to_string(), + )), + } +} + +pub(crate) fn get_default_environment() -> DaikokuResult<Environment> { + let config: Ini = read_environments()?; + + let default_environment = config.get("default", "environment").map(Ok).unwrap_or(Err( + DaikokuCliError::Configuration( + "default environment not found. see daikoku environments help".to_string(), + ), + ))?; + + get(default_environment) +} + +pub(crate) fn check_environment_from_str(name: Option<String>) -> DaikokuResult<Environment> { + name.map(|project_name| get(project_name)) + .unwrap_or(get_default_environment()) +} + +fn list() -> DaikokuResult<()> { + let config: Ini = read_environments()?; + + let map = config.get_map().unwrap_or(HashMap::new()); + + logger::info(serde_json::to_string_pretty(&map).unwrap()); + + Ok(()) +} + +pub(crate) fn get_daikokuignore() -> DaikokuResult<Vec<String>> { + let project = cms::get_default_project()?; + + let daikokuignore_path = Path::new(&PathBuf::from(project.path)) + .join(".daikoku") + .join(".daikokuignore") + .into_os_string() + .into_string(); + + match daikokuignore_path { + Ok(path) => { + let content = fs::read_to_string(path) + .map_err(|err| DaikokuCliError::Configuration(err.to_string()))?; + + Ok(content + .split("\n") + .map(|str| str.to_string()) + .collect::<Vec<String>>()) + } + Err(_) => Ok(Vec::new()), + } +} diff --git a/cli/src/commands/generate.rs b/cli/src/commands/generate.rs new file mode 100644 index 000000000..65e21e706 --- /dev/null +++ b/cli/src/commands/generate.rs @@ -0,0 +1,85 @@ +use std::{collections::HashMap, path::PathBuf, str::FromStr}; + +use uuid::Uuid; + +use crate::{ + commands::cms::create_path_and_file, + logging::{ + error::{DaikokuCliError, DaikokuResult}, + logger, + }, + models::folder::SourceExtension, + GenerateCommands, +}; + +use super::cms::get_default_project; + +pub(crate) async fn run(command: GenerateCommands) -> DaikokuResult<()> { + match command { + GenerateCommands::Documentation { + filename, + title, + desc, + } => generate_documentation_page(filename, title, desc).await, + } +} + +fn exists(filename: &String) -> DaikokuResult<()> { + let project = get_default_project()?; + + let file_path = PathBuf::from(&project.path).join(filename); + + if file_path.exists() { + return Err(DaikokuCliError::CmsCreationFile( + "A file with the same name already exists".to_string(), + )); + } + + Ok(()) +} + +async fn generate_documentation_page( + filename: String, + title: String, + desc: String, +) -> DaikokuResult<()> { + logger::loading("<yellow>Generating</> new documentation".to_string()); + + exists(&filename)?; + + let project = get_default_project()?; + + logger::done(); + + let documentations_path = PathBuf::from_str(&project.path) + .unwrap() + .join("src") + .join("documentations"); + + let page_path = documentations_path + .clone() + .join(format!("{}.html", filename)); + + if page_path.exists() { + return Err(DaikokuCliError::FileSystem( + "Documentation page alread exitsts in your project".to_string(), + )); + } + + let mut metadata = HashMap::new(); + metadata.insert("title".to_string(), title); + metadata.insert("description".to_string(), desc); + metadata.insert("id".to_string(), Uuid::new_v4().to_string()); + + create_path_and_file( + page_path, + format!("Documentation of the {} page", filename), + filename.to_string(), + metadata, + SourceExtension::HTML, + )?; + + logger::success("generation done".to_string()); + + Ok(()) +} diff --git a/cli/src/commands/login.rs b/cli/src/commands/login.rs index 04b9af6eb..0c6c16464 100644 --- a/cli/src/commands/login.rs +++ b/cli/src/commands/login.rs @@ -1,110 +1,126 @@ -use std::time::Duration; -use std::{process, thread}; - use async_recursion::async_recursion; +use base64::engine::general_purpose; +use base64::Engine; use http_body_util::Full; use hyper::body::Bytes; use hyper::header::{self, COOKIE}; use hyper::server::conn::http1; use hyper::service::service_fn; -use hyper::{Request, Response}; +use hyper::{Method, Request, Response}; use hyper_util::rt::TokioIo; +use serde::{Deserialize, Serialize}; use tokio::net::TcpListener; +use crate::helpers::{bytes_to_struct, daikoku_cms_api_get}; use crate::logging::error::{DaikokuCliError, DaikokuResult}; use crate::logging::logger::{self}; use crate::{process, Commands}; -use super::enviroments::{can_join_daikoku, get_default_environment}; +use super::environments::{can_join_daikoku, get_default_environment}; use super::watch::SESSION_EXPIRED; +#[derive(Clone, Deserialize, Serialize, Debug)] +struct LoginResponse { + token: String, +} + #[async_recursion] -pub(crate) async fn run(token: Option<String>) -> DaikokuResult<()> { - match token { - Some(token) => { - process(Commands::Environments { - command: crate::EnvironmentsCommands::PathDefault { token }, - }) - .await?; - Ok(()) - } - None => { - let environment = get_default_environment()?; +pub(crate) async fn run() -> DaikokuResult<()> { + let environment = get_default_environment()?; + + let _ = can_join_daikoku(&environment.server, None).await?; - let _ = can_join_daikoku(&environment.server).await?; + let port = std::env::var("WATCHING_PORT").unwrap_or("3334".to_string()); - let port = std::env::var("WATCHING_PORT").unwrap_or("3334".to_string()); + logger::loading(format!("<yellow>Listening</> on {}", port)); - logger::loading(format!("<yellow>Listening</> on {}", port)); + let listener = TcpListener::bind(format!("0.0.0.0:{}", port)) + .await + .unwrap(); - let listener = TcpListener::bind(format!("0.0.0.0:{}", port)) - .await - .unwrap(); + let environment = get_default_environment()?; - let environment = get_default_environment()?; + let host = environment.server; - let host = environment.server; + let response: LoginResponse = + bytes_to_struct::<LoginResponse>(daikoku_cms_api_get("/cli/login").await?.response)?; - webbrowser::open(&format!( - "{}/cli/login?redirect=http://localhost:{}", - host, port - )) - .expect("Failed to open a new browser tab"); + let redirect = general_purpose::STANDARD_NO_PAD + .encode(format!("http://localhost:{}?token={}", port, response.token).as_bytes()); - loop { - match listener.accept().await { - Err(err) => logger::error(format!("{}", err.to_string())), - Ok((stream, _)) => { - let io = TokioIo::new(stream); + webbrowser::open(&format!( + "{}/cms-api/cli/redirect?redirect={}", + host, redirect + )) + .expect("Failed to open a new browser tab"); - tokio::task::spawn(async move { - if let Err(err) = http1::Builder::new() - .serve_connection(io, service_fn(|req| watcher(req))) - .await - { - logger::error(format!("Error serving connection {:?}", err)); - } - }); + loop { + match listener.accept().await { + Err(err) => logger::error(format!("{}", err.to_string())), + Ok((stream, _)) => { + let io = TokioIo::new(stream); + + tokio::task::spawn(async move { + if let Err(err) = http1::Builder::new() + .serve_connection(io, service_fn(|req| watcher(req))) + .await + { + logger::error(format!("Error serving connection {:?}", err)); } - }; + }); } - } + }; } } async fn watcher( req: Request<hyper::body::Incoming>, ) -> Result<Response<Full<Bytes>>, DaikokuCliError> { - let headers = req.headers(); - - if let Some(cookie_header) = headers.get_all(COOKIE).iter().next() { - if let Ok(cookie_value) = cookie_header.to_str() { - process(Commands::Environments { - command: crate::EnvironmentsCommands::PathDefault { - token: cookie_value.to_string(), - }, - }) - .await?; + match (req.method(), req.uri().path()) { + (&Method::OPTIONS, _) => Ok(Response::builder() + .header("Access-Control-Allow-Origin", "*") + .header("Access-Control-Allow-Methods", "GET, POST, OPTIONS") + .header( + "Access-Control-Allow-Headers", + "Content-Type, Authorization", + ) + .body(http_body_util::Full::new(Bytes::from(""))) + .unwrap()), + _ => { + let headers = req.headers(); + + if let Some(cookie_header) = headers.get_all(COOKIE).iter().next() { + if let Ok(cookie_value) = cookie_header.to_str() { + process(Commands::Environments { + command: crate::EnvironmentsCommands::Config { + cookie: Some(cookie_value.to_string()), + apikey: None, + }, + }) + .await?; + } + } + + logger::println("Everything is configured. Run daikoku watch".to_string()); + + std::thread::spawn(move || { + std::thread::sleep(std::time::Duration::from_millis(2000)); + std::process::exit(0); + }); + + Ok(Response::builder() + .header(header::CONTENT_TYPE, "text/html") + .header("Access-Control-Allow-Origin", "*") + .body(Full::new(Bytes::from( + String::from_utf8(SESSION_EXPIRED.to_vec()) + .unwrap() + .replace( + "{{message}}", + "You've successfully log in. You can close the tab and start using the CMS", + ), + ))) + .unwrap()) } } - - thread::spawn(move || { - thread::sleep(Duration::from_millis(15000)); - process::exit(1); - }); - - logger::println("Everything is configured. Run daikokucli watch".to_string()); - - Ok(Response::builder() - .header(header::CONTENT_TYPE, "text/html") - .body(Full::new(Bytes::from( - String::from_utf8(SESSION_EXPIRED.to_vec()) - .unwrap() - .replace( - "{{message}}", - "You've successfully log in. You can close the tab and start using the CMS", - ), - ))) - .unwrap()) } diff --git a/cli/src/commands/mod.rs b/cli/src/commands/mod.rs index 06309b2e3..3aa94f9ec 100644 --- a/cli/src/commands/mod.rs +++ b/cli/src/commands/mod.rs @@ -1,8 +1,9 @@ -pub mod watch; -pub mod enviroments; -pub mod version; -pub mod creation; -pub mod projects; +pub mod assets; +pub mod cms; +pub mod environments; +pub mod generate; pub mod login; -pub mod sync; -pub mod assets; \ No newline at end of file +pub mod pull; +pub mod push; +pub mod version; +pub mod watch; diff --git a/cli/src/commands/projects.rs b/cli/src/commands/projects.rs deleted file mode 100644 index f3eef0654..000000000 --- a/cli/src/commands/projects.rs +++ /dev/null @@ -1,577 +0,0 @@ -use std::{ - collections::HashMap, - fs::{self, File}, - path::{Path, PathBuf}, - str::FromStr, -}; - -use async_recursion::async_recursion; -use bytes::Bytes; -use configparser::ini::Ini; -use http_body_util::Empty; -use hyper::{header, Method, Request}; -use hyper_util::rt::TokioIo; -use serde::{Deserialize, Serialize}; -use tokio::net::TcpStream; - -use crate::{ - logging::{ - error::{DaikokuCliError, DaikokuResult}, - logger::{self}, - }, - models::folder::{Ext, SourceExtension}, - process, - utils::{absolute_path, frame_to_bytes_body}, - Commands, ProjectCommands, -}; - -use super::enviroments::{can_join_daikoku, format_cookie, Environment}; - -#[derive(Clone)] -pub(crate) struct Project { - pub(crate) path: String, -} - -#[derive(Clone, Deserialize, Serialize, Debug)] -struct CmsPage { - _id: String, //: "651443d43d00001b85d2dc7a", - visible: bool, //: true, - authenticated: bool, //: false, - name: String, //: "parcours-affiliation-digitale", - #[serde(alias = "contentType")] - content_type: String, //: "text/html", - path: Option<String>, //: "/parcours-affiliation-digitale", - exact: bool, //: false, - #[serde(alias = "lastPublishedDate")] - last_published_date: Option<u64>, //: 1706520418595 - #[serde(alias = "body")] - content: String, -} - -pub(crate) async fn run(command: ProjectCommands) -> DaikokuResult<()> { - match command { - ProjectCommands::Add { - name, - path, - overwrite, - } => add(name, absolute_path(path)?, overwrite.unwrap_or(false)), - ProjectCommands::Default { name } => update_default(name), - ProjectCommands::Remove { name, remove_files } => delete(name, remove_files), - ProjectCommands::List {} => list(), - ProjectCommands::Clear {} => clear(), - ProjectCommands::Import { - name, - path, - server, - token, - } => import(name, absolute_path(path)?, server, token).await, - } -} - -pub(crate) fn get_default_project() -> DaikokuResult<Project> { - let config = read(false)?; - - let default_project_name = config - .get("default", "project") - .ok_or(DaikokuCliError::Configuration( - "missing default project or values in project. Specify a default project to use. See projects commands" - .to_string(), - ))?; - - let project = config - .get_map() - .map(|m| m[&default_project_name].clone()) - .ok_or(DaikokuCliError::Configuration( - "missing default project or values in project. Specify a default project to use. See projects commands" - .to_string(), - ))?; - - match (&project["name"], &project["path"]) { - (Some(_name), Some(path)) => Ok(Project { - // name: name.to_string(), - path: path.to_string() }), - (_, _) => Err(DaikokuCliError::Configuration( - "missing default project or values in project. Specify a default project to use. See projects commands" - .to_string(), - )), - } -} - -fn add(name: String, path: String, overwrite: bool) -> DaikokuResult<()> { - logger::loading("<yellow>Initialize</> path to project ...".to_string()); - - let mut config: Ini = read(false)?; - - if config.get(&name, "path").is_some() && !overwrite { - return Err(DaikokuCliError::Configuration( - "project already exists in the configuration file. use daikokucli projects list and remove it".to_string(), - )); - } - - if !Path::new(&path).exists() { - return Err(DaikokuCliError::Configuration( - "failed to find project at path".to_string(), - )); - } - - config.set(&name, "path", Some(path)); - config.set(&name, "name", Some(name.clone())); - config.set("default", "project", Some(name.clone())); - - match config.write(&get_path()?) { - Ok(()) => { - logger::println("<green>New entry</> added".to_string()); - let _ = get_project(name); - Ok(()) - } - Err(err) => Err(DaikokuCliError::Configuration(err.to_string())), - } -} - -fn update_default(name: String) -> DaikokuResult<()> { - logger::loading("<yellow>Updating</> default project".to_string()); - let mut config: Ini = read(false)?; - - if config.get(&name, "path").is_none() { - return Err(DaikokuCliError::Configuration( - "a non-existing section cannot be set as default".to_string(), - )); - } - - config.set("default", "project", Some(name.clone())); - - match config.write(&get_path()?) { - Ok(()) => { - logger::println("<green>Defaut</> updated".to_string()); - let _ = get_project(name.clone()); - Ok(()) - } - Err(err) => Err(DaikokuCliError::Configuration(err.to_string())), - } -} - -pub(crate) fn get_project(name: String) -> DaikokuResult<Project> { - let config = read(false)?; - - let projects = config.get_map().ok_or(DaikokuCliError::Configuration( - "missing project or values in project.".to_string(), - ))?; - - match projects.get(&name) { - Some(project) => { - match (&project["name"], &project["path"]) { - (Some(_name), Some(path)) => { - logger::info(serde_json::to_string_pretty(&project).unwrap()); - Ok(Project { - // name: name.to_string(), - path: path.to_string(), - }) - } - (_, _) => Err(DaikokuCliError::Configuration( - "missing project or values in project.".to_string(), - )), - } - } - None => { - return Err(DaikokuCliError::Configuration( - "project is missing".to_string(), - )) - } - } -} - -fn internal_get_project(name: String) -> Option<Project> { - let config = read(false).ok()?; - - let projects = config.get_map()?; - - projects - .get(&name) - .map(|project| match (&project["name"], &project["path"]) { - (Some(_name), Some(path)) => Some(Project { - path: path.to_string(), - }), - (_, _) => None, - }) - .flatten() -} - -fn force_clearing_default_project() -> DaikokuResult<()> { - let mut config: Ini = read(false)?; - - config.remove_section("default"); - - config - .write(&get_path()?) - .map_err(|err| DaikokuCliError::Configuration(err.to_string())) -} - -fn list() -> DaikokuResult<()> { - let config: Ini = read(false)?; - - let map = config.get_map().map(Ok).unwrap_or(Ok(HashMap::new()))?; - - logger::info(serde_json::to_string_pretty(&map).unwrap()); - - Ok(()) -} - -fn delete(name: String, remove_files: bool) -> DaikokuResult<()> { - logger::loading("<yellow>Deleting</> project".to_string()); - let mut config: Ini = read(false)?; - - if name.to_lowercase() == "default" { - return Err(DaikokuCliError::Configuration( - "protected project cant be deleted".to_string(), - )); - } - - if config.remove_section(&name).is_none() { - return Err(DaikokuCliError::Configuration( - "a non-existing section cannot be delete".to_string(), - )); - }; - - if remove_files { - if let Some(folder_path) = config.get(&name, "path") { - let _ = fs::remove_dir_all(folder_path); - } - } - - match config.write(&get_path()?) { - Ok(()) => { - logger::println(format!("<green>{}</> deleted", &name)); - Ok(()) - } - Err(err) => Err(DaikokuCliError::Configuration(err.to_string())), - } -} - -fn get_path() -> DaikokuResult<String> { - let home = get_home()?; - - Ok(home - .join(".daikoku") - .into_os_string() - .into_string() - .unwrap()) -} - -fn get_home() -> DaikokuResult<PathBuf> { - match dirs::home_dir() { - Some(p) => Ok(p), - None => Err(DaikokuCliError::FileSystem( - "Failed getting your home dir!".to_string(), - )), - } -} - -fn read(last_attempt: bool) -> DaikokuResult<Ini> { - let mut config = Ini::new(); - - match config.load(&get_path()?) { - Ok(_) => Ok(config), - Err(_) if !last_attempt => match std::fs::File::create(&get_path()?) { - Ok(_) => read(true), - Err(e) => Err(DaikokuCliError::Configuration(e.to_string())), - }, - Err(e) => Err(DaikokuCliError::Configuration(e.to_string())), - } -} - -fn clear() -> DaikokuResult<()> { - let mut config = Ini::new(); - - config.clear(); - - match config.write(&get_path()?) { - Ok(_) => { - logger::println("<green>Environments erased</>".to_string()); - Ok(()) - } - Err(e) => Err(DaikokuCliError::FileSystem(format!( - "failed to reset the environments file : {}", - e.to_string() - ))), - } -} - -#[async_recursion] -async fn import(name: String, path: String, server: String, token: String) -> DaikokuResult<()> { - logger::loading("<yellow>Converting</> legacy project from Daikoku environment".to_string()); - - if internal_get_project(name.clone()).is_some() { - return Err(DaikokuCliError::Configuration( - "Project already exists".to_string(), - )); - } else { - force_clearing_default_project()? - } - - if !can_join_daikoku(&server.clone()).await? { - return Err(DaikokuCliError::Configuration( - "Failed to join Daikoku server".to_string(), - )); - } - - let cookie = format_cookie(token); - - let environment = Environment { - server: server.clone(), - token: Some(cookie.clone()), - }; - - let host = environment - .server - .replace("http://", "") - .replace("https://", ""); - - let url: String = format!("{}/api/cms", environment.server); - - let req = Request::builder() - .method(Method::GET) - .uri(&url) - .header(header::HOST, &host) - .header(header::COOKIE, cookie.clone()) - .body(Empty::<Bytes>::new()) - .unwrap(); - - let stream = TcpStream::connect(&host).await.map_err(|err| { - DaikokuCliError::DaikokuErrorWithMessage("failed to join the server".to_string(), err) - })?; - let io = TokioIo::new(stream); - - let (mut sender, conn) = hyper::client::conn::http1::handshake(io) - .await - .map_err(|err| DaikokuCliError::HyperError(err))?; - - tokio::task::spawn(async move { - if let Err(err) = conn.await { - logger::error(format!("Connection error {:?}", err)); - } - }); - - let upstream_resp = sender - .send_request(req) - .await - .map_err(|err| DaikokuCliError::ParsingError(err.to_string()))?; - - let ( - hyper::http::response::Parts { - headers: _, status, .. - }, - body, - ) = upstream_resp.into_parts(); - - let status = status.as_u16(); - - if status == 200 { - let zip_bytes: Vec<u8> = frame_to_bytes_body(body).await; - - let items: Vec<CmsPage> = read_summary_file(zip_bytes)?; - - if items.iter().find(|p| p.path.is_none()).is_none() { - return Err(DaikokuCliError::DaikokuStrError( - "imported project is not a legacy project".to_string(), - )); - } - - let project_path = PathBuf::from_str(&path) - .map_err(|err| DaikokuCliError::FileSystem(err.to_string()))? - .join(name.clone()); - - let sources_path = project_path.join("src"); - - let new_pages = replace_ids(items)?; - - convert_cms_pages(new_pages, sources_path.clone())?; - - create_daikoku_hidden_files(project_path.clone())?; - - create_project(name.clone(), project_path.clone()).await?; - - create_environment(name, server, cookie).await?; - - Ok(()) - } else { - Err(DaikokuCliError::DaikokuStrError(format!( - "failed to reach the Daikoku server {}", - status - ))) - } -} - -fn replace_ids(items: Vec<CmsPage>) -> DaikokuResult<Vec<CmsPage>> { - let identifiers = items - .iter() - .map(|item| item._id.clone()) - .collect::<Vec<String>>(); - - let mut paths: HashMap<String, String> = HashMap::new(); - items.iter().for_each(|item| { - if let Ok(path) = get_cms_page_path(item) - .unwrap() - .into_os_string() - .into_string() - { - paths.insert(item._id.clone(), format!("/{}", path)); - } - }); - - let mut updated_pages = Vec::new(); - - items.iter().for_each(|item| { - let mut new_page = item.clone(); - identifiers.iter().for_each(|identifier| { - new_page.content = new_page.content.replace( - identifier, - paths.get(identifier).unwrap_or(&item._id.clone()), - ); - }); - updated_pages.push(new_page) - }); - - Ok(updated_pages) -} - -fn convert_cms_pages(items: Vec<CmsPage>, project_path: PathBuf) -> DaikokuResult<()> { - items.iter().for_each(|item| { - let extension = SourceExtension::from_str(&item.content_type).unwrap(); - - let file_path = project_path.clone().join(get_cms_page_path(item).unwrap()); - - let _ = create_path_and_file(file_path, item.content.clone(), item, extension); - }); - - Ok(()) -} - -fn get_cms_page_path(item: &CmsPage) -> DaikokuResult<PathBuf> { - let extension = SourceExtension::from_str(&item.content_type).unwrap(); - - let folder = match extension { - SourceExtension::HTML => item.path.clone().map(|_| "pages").unwrap_or("blocks"), - SourceExtension::CSS => "styles", - SourceExtension::Javascript => "scripts", - SourceExtension::JSON => "data", - }; - - let router_path = item.path.clone().map(|p| p.replace("/", "")).map(|path| { - if path == "/" || path.is_empty() { - item.name.clone() - } else { - path - } - }); - - let folder_path = PathBuf::from_str(folder).unwrap().join(format!( - "{}{}", - router_path.unwrap_or(item.name.clone()), - extension.ext() - )); - - let parent = folder_path.parent().unwrap(); - - if !parent.exists() { - fs::create_dir_all(parent).map_err(map_error_to_filesystem_error)?; - } - - Ok(folder_path) -} - -fn create_path_and_file( - file_buf: PathBuf, - content: String, - item: &CmsPage, - content_type: SourceExtension, -) -> DaikokuResult<()> { - let parent = - file_buf - .parent() - .map(|path| Ok(path)) - .unwrap_or(Err(DaikokuCliError::FileSystem( - "failed to recursively create paths".to_string(), - )))?; - - if !parent.exists() { - fs::create_dir_all(parent).map_err(map_error_to_filesystem_error)?; - } - - logger::println(format!("Creating {} {:?}", item.name, file_buf)); - - if content_type == SourceExtension::HTML { - let metadata = extract_metadata(item)?; - let metadata_header = serde_yaml::to_string(&metadata).map_err(|_err| { - DaikokuCliError::ParsingError(format!("failed parsing metadata {}", &item.name)) - })?; - - Ok( - fs::write(file_buf, format!("{}\n---\n{}", metadata_header, content)) - .map_err(map_error_to_filesystem_error)?, - ) - } else { - Ok(fs::write(file_buf, content).map_err(map_error_to_filesystem_error)?) - } -} - -fn extract_metadata(item: &CmsPage) -> DaikokuResult<HashMap<String, String>> { - let mut metadata: HashMap<String, String> = HashMap::new(); - - metadata.insert("_authenticated".to_string(), item.authenticated.to_string()); - metadata.insert("_visible".to_string(), item.visible.to_string()); - metadata.insert("_exact".to_string(), item.exact.to_string()); - item.last_published_date - .map(|value| metadata.insert("_last_published_date".to_string(), value.to_string())); - Ok(metadata) -} - -fn create_daikoku_hidden_files(complete_path: PathBuf) -> DaikokuResult<File> { - fs::create_dir_all(complete_path.join(".daikoku")) - .map_err(|err| DaikokuCliError::FileSystem(err.to_string()))?; - - fs::File::create(complete_path.join(".daikoku").join(".environments")) - .map_err(|err| DaikokuCliError::FileSystem(err.to_string()))?; - - fs::File::create(complete_path.join(".daikoku").join(".daikokuignore")) - .map_err(|err| DaikokuCliError::FileSystem(err.to_string())) -} - -async fn create_project(name: String, complete_path: PathBuf) -> DaikokuResult<()> { - process(Commands::Projects { - command: crate::ProjectCommands::Add { - name: name, - path: complete_path.into_os_string().into_string().unwrap(), - overwrite: None, - }, - }) - .await?; - Ok(()) -} - -async fn create_environment(name: String, server: String, token: String) -> DaikokuResult<()> { - process(Commands::Environments { - command: crate::EnvironmentsCommands::Add { - name: name, - server, - token: Some(token), - overwrite: Some(true), - force: Some(false), - }, - }) - .await?; - logger::println("<green>Migration endded</>".to_string()); - Ok(()) -} - -fn read_summary_file(content: Vec<u8>) -> DaikokuResult<Vec<CmsPage>> { - let content = String::from_utf8(content).map_err(map_error_to_filesystem_error)?; - - let summary: Vec<CmsPage> = - serde_json::from_str(&content).map_err(map_error_to_filesystem_error)?; - - Ok(summary) -} - -fn map_error_to_filesystem_error<T: std::error::Error>(err: T) -> DaikokuCliError { - DaikokuCliError::FileSystem(err.to_string()) -} diff --git a/cli/src/commands/pull.rs b/cli/src/commands/pull.rs new file mode 100644 index 000000000..d18429140 --- /dev/null +++ b/cli/src/commands/pull.rs @@ -0,0 +1,85 @@ +use std::path::PathBuf; + +use crate::{ + helpers::{bytes_to_struct, bytes_to_vec_of_struct, daikoku_cms_api_get}, + logging::{error::DaikokuResult, logger}, + PullCommands, +}; + +use super::cms::{ + self, create_api_folder, create_mail_folder, create_mail_tenant, Api, IntlTranslationBody, + TenantMailBody, EXCLUDE_API, +}; + +pub(crate) async fn run(commands: PullCommands) -> DaikokuResult<()> { + logger::loading(format!("<yellow>Pulling</> apis")); + logger::done(); + + let project = cms::get_default_project()?; + + match commands { + PullCommands::Apis { id } => apis_synchronization(&project, id).await?, + PullCommands::Mails {} => mails_synchronization(&project).await?, + }; + + logger::success("synchronization done".to_string()); + + Ok(()) +} + +async fn apis_synchronization(project: &cms::Project, api_id: Option<String>) -> DaikokuResult<()> { + logger::loading(format!("<yellow>Pulling</> apis")); + + let sources_path = PathBuf::from(project.path.clone()).join("src"); + + let apis_informations: Vec<Api> = bytes_to_vec_of_struct::<Api>( + daikoku_cms_api_get("/apis?fields=_id,_humanReadableId,header,description") + .await? + .response, + )? + .into_iter() + .filter(|api| { + !EXCLUDE_API.contains(&api._id.as_str()) + && api_id.clone().map(|id| api._id == id).unwrap_or(true) + }) + .collect(); + + let created = create_api_folder(apis_informations, sources_path.clone())?; + + if created.is_empty() { + logger::indent_println("nothing to pull".to_string()); + } + + logger::success(format!("<green>Pulling</> done")); + + Ok(()) +} + +async fn mails_synchronization(project: &cms::Project) -> DaikokuResult<()> { + logger::loading(format!("<yellow>Pulling</> apis")); + + let sources_path = PathBuf::from(project.path.clone()).join("src"); + + let root_mail_tenant = + bytes_to_struct::<TenantMailBody>(daikoku_cms_api_get("/tenants/default").await?.response)?; + + let root_mail_user_translations = bytes_to_struct::<IntlTranslationBody>( + daikoku_cms_api_get("/translations/_mail?domain=tenant.mail.template") + .await? + .response, + )?; + + let mail_user_template = bytes_to_struct::<IntlTranslationBody>( + daikoku_cms_api_get("/translations/_mail?domain=mail") + .await? + .response, + )?; + + create_mail_tenant(root_mail_tenant, sources_path.clone())?; + create_mail_folder(root_mail_user_translations, sources_path.clone(), true)?; + create_mail_folder(mail_user_template, sources_path.clone(), false)?; + + logger::success(format!("<green>Pulling</> done")); + + Ok(()) +} diff --git a/cli/src/commands/push.rs b/cli/src/commands/push.rs new file mode 100644 index 000000000..4bdb4e5c8 --- /dev/null +++ b/cli/src/commands/push.rs @@ -0,0 +1,118 @@ +use std::path::PathBuf; + +use bytes::Bytes; + +use crate::{ + helpers::daikoku_cms_api_post, + logging::{ + error::{DaikokuCliError, DaikokuResult}, + logger, + }, + models::folder::{read_sources_and_daikoku_metadata, CmsFile}, + utils::PathBufExt, +}; + +use super::{ + cms::{self}, + environments::get_daikokuignore, +}; + +pub(crate) async fn run(dry_run: Option<bool>, file_path: Option<String>) -> DaikokuResult<()> { + logger::loading(format!("<yellow>Pushing</> project")); + logger::done(); + + let project = cms::get_default_project()?; + + let path = PathBuf::from(project.path.clone()).join("src"); + + let mut local_pages = read_sources_and_daikoku_metadata(&path)?; + + if let Some(specific_path) = file_path { + local_pages.retain(|file| { + file.path() == specific_path + || path + .concat(&file.path()) + .to_str() + .expect("Failed to convert path to string") + .starts_with( + path.concat(&specific_path) + .to_str() + .expect("Failed to convert path to string"), + ) + }); + + if local_pages.len() == 0 { + return Err(DaikokuCliError::FileSystem(format!( + "file not found at path {}", + specific_path + ))); + } + } + + let _ = synchronization(&mut local_pages, dry_run.unwrap_or(false)).await?; + + if dry_run.unwrap_or(false) { + logger::success("[dry_run] synchronization done".to_string()); + } else { + logger::success("synchronization done".to_string()); + } + + Ok(()) +} + +fn apply_daikoku_ignore(items: &mut Vec<CmsFile>) -> DaikokuResult<()> { + let daikoku_ignore = get_daikokuignore()?; + + let rules: Vec<&String> = daikoku_ignore + .iter() + .filter(|line| !line.is_empty()) + .collect::<Vec<&String>>(); + + if !rules.is_empty() { + logger::println("Excluded files and folders".to_string()); + } + + items.retain(|file| { + let retained = daikoku_ignore + .iter() + .filter(|line| !line.is_empty()) + .find(|&line| { + line == &file.name || line == &file.path() || file.path().starts_with(line) + }) + .is_none(); + + if !retained { + logger::println(file.name.to_string()); + } + + retained + }); + + Ok(()) +} + +async fn synchronization(body: &mut Vec<CmsFile>, dry_run: bool) -> DaikokuResult<()> { + logger::loading("<yellow>Syncing</>".to_string()); + + logger::info(format!("Synchronization of {:?} pages", body.len())); + body.iter().for_each(|page| { + logger::info(format!( + "Synchronization of {:?} with path {:?}", + page.name, + page.path() + )) + }); + + apply_daikoku_ignore(body)?; + + let body = Bytes::from( + serde_json::to_string(&body) + .map_err(|err| DaikokuCliError::ParsingError(err.to_string()))?, + ); + + if !dry_run { + daikoku_cms_api_post("/sync", body, true).await?; + } + + Ok(()) +} diff --git a/cli/src/commands/sync.rs b/cli/src/commands/sync.rs deleted file mode 100644 index cac8af45d..000000000 --- a/cli/src/commands/sync.rs +++ /dev/null @@ -1,133 +0,0 @@ -use std::path::PathBuf; - -use bytes::Bytes; -use http_body_util::Full; -use hyper::{header, Method, Request}; -use hyper_util::rt::TokioIo; -use tokio::net::TcpStream; - -use crate::{ - logging::{ - error::{DaikokuCliError, DaikokuResult}, - logger, - }, - models::folder::{read_contents, CmsFile}, - utils::frame_to_bytes_body, -}; - -use super::{ - enviroments::{get_daikokuignore, get_default_environment, read_cookie_from_environment}, - projects, -}; - -pub(crate) async fn run() -> DaikokuResult<()> { - logger::loading(format!("<yellow>Syncing</> project")); - let environment = get_default_environment()?; - - let host = environment - .server - .replace("http://", "") - .replace("https://", ""); - - let url: String = format!("{}/api/cms/sync", environment.server); - - let project = projects::get_default_project()?; - - let mut body = read_contents(&PathBuf::from(&project.path))?; - - let daikoku_ignore = get_daikokuignore()?; - - body = body - .into_iter() - .filter(|file| { - daikoku_ignore - .iter() - .filter(|line| !line.is_empty()) - .find(|&line| { - line == &file.name || line == &file.path() || file.path().starts_with(line) - }) - .is_none() - }) - .collect::<Vec<CmsFile>>(); - - let rules: Vec<&String> = daikoku_ignore - .iter() - .filter(|line| !line.is_empty()) - .collect::<Vec<&String>>(); - - if !rules.is_empty() { - logger::println("Excluded files and folders".to_string()); - body.iter().for_each(|file| { - if rules - .iter() - .find(|&&line| { - line == &file.name || line == &file.path() || file.path().starts_with(line) - }) - .is_none() - { - logger::println(file.name.to_string()); - } - }); - } - - let body = Bytes::from( - serde_json::to_string(&body) - .map_err(|err| DaikokuCliError::ParsingError(err.to_string()))?, - ); - - let cookie = read_cookie_from_environment(true)?; - - let req = Request::builder() - .method(Method::POST) - .uri(&url) - .header(header::HOST, &host) - .header(header::CONTENT_TYPE, "application/json") - .header(header::COOKIE, cookie) - .body(Full::new(body)) - .unwrap(); - - let stream = TcpStream::connect(&host).await.map_err(|err| { - DaikokuCliError::DaikokuErrorWithMessage("failed to join the server".to_string(), err) - })?; - let io = TokioIo::new(stream); - - let (mut sender, conn) = hyper::client::conn::http1::handshake(io) - .await - .map_err(|err| DaikokuCliError::HyperError(err))?; - - tokio::task::spawn(async move { - if let Err(err) = conn.await { - logger::error(format!("Connection error {:?}", err)); - } - }); - - let upstream_resp = sender - .send_request(req) - .await - .map_err(|err| DaikokuCliError::ParsingError(err.to_string()))?; - - let ( - hyper::http::response::Parts { - headers: _, status, .. - }, - body, - ) = upstream_resp.into_parts(); - - let status = status.as_u16(); - - if status == 204 { - logger::success("synchronization done".to_string()); - } else { - let bytes_body: Vec<u8> = frame_to_bytes_body(body).await; - - logger::error(format!( - "failed to sync project : {:?}", - String::from_utf8(bytes_body) - .unwrap() - .as_str() - .replace("\n", "") - )); - } - - Ok(()) -} diff --git a/cli/src/commands/version.rs b/cli/src/commands/version.rs index fead310d7..0d58b03ff 100644 --- a/cli/src/commands/version.rs +++ b/cli/src/commands/version.rs @@ -1,6 +1,6 @@ use crate::logging::{error::DaikokuCliError, logger}; pub fn run() -> Result<(), DaikokuCliError> { - logger::success(format!("daikokucli version: {}", env!("CARGO_PKG_VERSION"))); + logger::success(format!("daikoku version: {}", env!("CARGO_PKG_VERSION"))); Ok(()) } diff --git a/cli/src/commands/watch.rs b/cli/src/commands/watch.rs index 0f9744f42..31dd1021e 100644 --- a/cli/src/commands/watch.rs +++ b/cli/src/commands/watch.rs @@ -19,10 +19,10 @@ use crate::logging::logger::{self}; use crate::models::folder::{read_contents, CmsFile, SourceExtension, UiCmsFile}; use crate::utils::frame_to_bytes_body; -use super::enviroments::{ +use super::cms::{self}; +use super::environments::{ can_join_daikoku, check_environment_from_str, read_cookie_from_environment, Environment, }; -use super::projects::{self}; pub(crate) const SESSION_EXPIRED: &[u8] = include_bytes!("../../templates/session_expired.html"); const MANAGER_PAGE: &[u8] = include_bytes!("../../templates/manager.html"); @@ -50,7 +50,7 @@ pub(crate) async fn run( ) -> DaikokuResult<()> { let environment = check_environment_from_str(incoming_environment.clone())?; - let _ = can_join_daikoku(&environment.server).await?; + let _ = can_join_daikoku(&environment.server, None).await?; let port = std::env::var("WATCHING_PORT").unwrap_or("3333".to_string()); @@ -90,7 +90,7 @@ pub(crate) async fn run( fn read_cms_pages() -> DaikokuResult<Summary> { Ok(Summary { - pages: read_contents(&PathBuf::from(projects::get_default_project()?.path))?, + pages: read_contents(&PathBuf::from(cms::get_default_project()?.path))?, }) } @@ -274,7 +274,7 @@ async fn render_page( page.name )); - let project = projects::get_default_project()?; + let project = cms::get_default_project()?; let content = read_contents(&PathBuf::from(&project.path))?; @@ -298,11 +298,17 @@ async fn render_page( environment.server, watch_path ); - let mut builder = Request::builder() - .method(Method::POST) - .uri(&url) - .header(header::HOST, &host) - .header(header::CONTENT_TYPE, "application/json"); + // let mut builder = Request::builder() + // .method(Method::POST) + // .uri(&url) + // .header(header::HOST, &host) + // .header(header::CONTENT_TYPE, "application/json"); + + let mut builder = reqwest::Client::new() + .post(url) + .header(header::HOST, host) + .header(header::CONTENT_TYPE, "application/json") + .body(body); if authentication && page.authenticated() { match read_cookie_from_environment(true) { @@ -319,38 +325,19 @@ async fn render_page( } } } - let req = builder.body(Full::new(body)).unwrap(); - - let stream = TcpStream::connect(&host) - .await - .map_err(|err| DaikokuCliError::DaikokuError(err))?; - let io = TokioIo::new(stream); - let (mut sender, conn) = hyper::client::conn::http1::handshake(io) + let resp = builder + .send() .await - .map_err(|err| DaikokuCliError::HyperError(err))?; - - tokio::task::spawn(async move { - if let Err(err) = conn.await { - logger::error(format!("Connection error {:?}", err)); - } - }); - - let upstream_resp = sender.send_request(req).await.map_err(|err| { - logger::error(format!("{:?}", err)); - DaikokuCliError::ParsingError(err.to_string()) - })?; - - let ( - hyper::http::response::Parts { - headers: _, status, .. - }, - body, - ) = upstream_resp.into_parts(); + .map_err(|err| DaikokuCliError::DaikokuStrError(err.to_string()))?; - let result: Vec<u8> = frame_to_bytes_body(body).await; + let status = resp.status().as_u16(); - let status = status.as_u16(); + let result: Vec<u8> = resp + .bytes() + .await + .map_err(|err| DaikokuCliError::DaikokuStrError(err.to_string()))? + .to_vec(); if status == 303 { Ok(Response::builder() @@ -358,7 +345,7 @@ async fn render_page( .body(Full::new(Bytes::from( String::from_utf8(SESSION_EXPIRED.to_vec()) .unwrap() - .replace("{{message}}", "Whoops, your token has expired"), + .replace("{{message}}", "Whoops, your session has expired"), ))) .unwrap()) } else if status >= 400 { @@ -385,23 +372,23 @@ async fn render_page( format!("<textarea readonly>{}</textarea>", src) }; - println!( - "{}", - String::from_utf8(MANAGER_PAGE.to_vec()) - .unwrap() - .replace( - "{{components}}", - serde_json::to_string( - &content - .iter() - .map(|file| file.to_ui_component()) - .collect::<Vec<UiCmsFile>>(), - ) - .unwrap() - .as_str(), - ) - .replace("{{children}}", children.as_str()) - ); + // println!( + // "{}", + // String::from_utf8(MANAGER_PAGE.to_vec()) + // .unwrap() + // .replace( + // "{{components}}", + // serde_json::to_string( + // &content + // .iter() + // .map(|file| file.to_ui_component()) + // .collect::<Vec<UiCmsFile>>(), + // ) + // .unwrap() + // .as_str(), + // ) + // .replace("{{children}}", children.as_str()) + // ); Ok(Response::builder() // .header(header::CONTENT_TYPE, &page.content_type()) diff --git a/cli/src/helpers.rs b/cli/src/helpers.rs new file mode 100644 index 000000000..de498f052 --- /dev/null +++ b/cli/src/helpers.rs @@ -0,0 +1,146 @@ +use std::any::type_name; + +use bytes::Buf; +use hyper::header; +use serde::Deserialize; + +use crate::{ + commands::environments::{get_default_environment, read_apikey_from_secrets}, + logging::error::{DaikokuCliError, DaikokuResult}, +}; + +#[derive(Debug)] +pub(crate) struct CmsApiResponse<T> { + pub(crate) status: u16, + pub(crate) response: T, +} + +pub(crate) fn bytes_to_struct<T: for<'a> Deserialize<'a>>(content: Vec<u8>) -> DaikokuResult<T> { + let name = type_name::<T>(); + + let content = + String::from_utf8(content).map_err(|err| map_error_to_filesystem_error(err, name))?; + + let summary: T = + serde_json::from_str(&content).map_err(|err| map_error_to_filesystem_error(err, name))?; + + Ok(summary) +} + +pub(crate) fn bytes_to_vec_of_struct<T: for<'a> Deserialize<'a>>( + content: Vec<u8>, +) -> DaikokuResult<Vec<T>> { + let name = type_name::<T>(); + + let content = + String::from_utf8(content).map_err(|err| map_error_to_filesystem_error(err, name))?; + + let summary: Vec<T> = + serde_json::from_str(&content).map_err(|err| map_error_to_filesystem_error(err, name))?; + + Ok(summary) +} + +pub(crate) async fn daikoku_cms_api_post<T: Buf + std::marker::Send + 'static>( + path: &str, + body: T, + is_json_content: bool, +) -> DaikokuResult<Vec<u8>> +where + reqwest::Body: From<T>, +{ + let environment = get_default_environment()?; + + let host = environment + .server + .replace("http://", "") + .replace("https://", ""); + + let apikey = read_apikey_from_secrets(true)?; + + let url: String = format!("{}/cms-api{}", environment.server, &path); + + let builder = reqwest::Client::new().post(url).header(header::HOST, host); + + let resp = if is_json_content { + builder.header(header::CONTENT_TYPE, "application/json") + } else { + builder + } + .header(header::AUTHORIZATION, format!("Basic {}", apikey)) + .body(body) + .send() + .await + .map_err(|err| DaikokuCliError::DaikokuStrError(err.to_string()))?; + + let status = resp.status().as_u16(); + + if status < 300 { + Ok(resp + .bytes() + .await + .map_err(|err| DaikokuCliError::DaikokuStrError(err.to_string()))? + .to_vec()) + } else { + Err(DaikokuCliError::DaikokuStrError(format!( + "failed to reach the Daikoku server {}", + status + ))) + } +} + +pub(crate) async fn raw_daikoku_cms_api_get( + path: &str, + server: &String, + apikey: &String, +) -> DaikokuResult<CmsApiResponse<Vec<u8>>> { + let host = server.replace("http://", "").replace("https://", ""); + + daikoku_cms_api_get_internal(path, &server, &apikey, &host).await +} + +pub(crate) async fn daikoku_cms_api_get(path: &str) -> DaikokuResult<CmsApiResponse<Vec<u8>>> { + let environment = get_default_environment()?; + + let host = environment + .server + .replace("http://", "") + .replace("https://", ""); + + let apikey = read_apikey_from_secrets(true)?; + + daikoku_cms_api_get_internal(path, &environment.server, &apikey, &host).await +} + +async fn daikoku_cms_api_get_internal( + path: &str, + server: &String, + apikey: &String, + host: &String, +) -> DaikokuResult<CmsApiResponse<Vec<u8>>> { + let url: String = format!("{}/cms-api{}", server, &path); + + let resp = reqwest::Client::new() + .get(url) + .header(header::HOST, host) + .header(header::AUTHORIZATION, format!("Basic {}", apikey)) + .send() + .await + .map_err(|err| DaikokuCliError::DaikokuStrError(err.to_string()))?; + + Ok(CmsApiResponse { + status: resp.status().as_u16(), + response: resp + .bytes() + .await + .map_err(|err| DaikokuCliError::DaikokuStrError(err.to_string()))? + .to_vec(), + }) +} + +pub(crate) fn map_error_to_filesystem_error<T: std::error::Error>( + err: T, + type_name: &str, +) -> DaikokuCliError { + DaikokuCliError::FileSystem(format!("{} : {}", type_name.to_string(), err.to_string())) +} diff --git a/cli/src/interactive.rs b/cli/src/interactive.rs new file mode 100644 index 000000000..29b057d10 --- /dev/null +++ b/cli/src/interactive.rs @@ -0,0 +1,10 @@ +use crate::logging::error::{DaikokuCliError, DaikokuResult}; + +pub(crate) fn prompt() -> DaikokuResult<String> { + let mut input = String::new(); + + match std::io::stdin().read_line(&mut input) { + Err(err) => Err(DaikokuCliError::ParsingError(err.to_string())), + Ok(_) => Ok(input), + } +} diff --git a/cli/src/logging/logger.rs b/cli/src/logging/logger.rs index 3d9a223a1..33c9608ad 100644 --- a/cli/src/logging/logger.rs +++ b/cli/src/logging/logger.rs @@ -35,6 +35,13 @@ pub fn check_loading() { } } +pub fn done() { + unsafe { + LOADING_LOGGER.done(); + *LOADING_MESSAGE = None; + } +} + pub fn error(str: String) { check_loading(); let mut logger = Logger::new(); diff --git a/cli/src/models/folder.rs b/cli/src/models/folder.rs index 42aa441e0..5f07e6c07 100644 --- a/cli/src/models/folder.rs +++ b/cli/src/models/folder.rs @@ -1,3 +1,4 @@ +use configparser::ini::Ini; use serde::{Deserialize, Serialize}; use walkdir::WalkDir; @@ -8,13 +9,14 @@ use std::{ str::FromStr, }; -use crate::logging::error::DaikokuResult; +use crate::logging::error::{DaikokuCliError, DaikokuResult}; -#[derive(Serialize, Deserialize, Clone, Debug)] +#[derive(Serialize, Deserialize, Clone, Debug, Default)] pub(crate) struct CmsFile { pub(crate) name: String, pub(crate) content: String, pub(crate) metadata: HashMap<String, String>, + pub(crate) daikoku_data: Option<HashMap<String, Option<String>>>, } #[derive(Serialize, Deserialize, Clone, Debug)] @@ -29,6 +31,7 @@ impl CmsFile { content: fs::read_to_string(file_path).unwrap(), name: file_name, metadata, + daikoku_data: None, } } @@ -94,6 +97,96 @@ pub(crate) fn read_sources(path: PathBuf) -> DaikokuResult<Vec<CmsFile>> { Ok(pages) } +pub(crate) fn read_sources_and_daikoku_metadata(path: &PathBuf) -> DaikokuResult<Vec<CmsFile>> { + let mut pages: Vec<CmsFile> = Vec::new(); + + let paths = fs::read_dir(path).unwrap(); + + for raw_path in paths { + let directory_name = raw_path.unwrap().file_name().into_string().unwrap(); + if directory_name != ".DS_Store" { + let sources = if directory_name == "apis" { + read_apis_folder(&path.join("apis")) + } else { + read_sources(path.join(directory_name)) + }?; + pages.extend(sources); + } + } + + for page in pages.iter_mut() { + if !page.path().starts_with("/") { + page.metadata + .insert("_path".to_string(), format!("/{}", page.path())); + } + } + + Ok(pages) +} + +fn read_apis_folder(path: &PathBuf) -> DaikokuResult<Vec<CmsFile>> { + let mut pages: Vec<CmsFile> = Vec::new(); + + let apis = fs::read_dir(path).unwrap(); + + for raw_path in apis { + let directory_name = raw_path.unwrap().file_name().into_string().unwrap(); + + if !directory_name.starts_with(".") { + let sources = read_api_folder(&path.join(directory_name))?; + pages.extend(sources); + } + } + + Ok(pages) +} + +fn read_api_folder(path: &PathBuf) -> DaikokuResult<Vec<CmsFile>> { + let mut pages: Vec<CmsFile> = Vec::new(); + + let mut daikoku_data = None; + + for entry in WalkDir::new(path).into_iter().filter_map(Result::ok) { + let f_name = String::from(entry.file_name().to_string_lossy()); + + if entry.metadata().unwrap().is_file() { + if entry.clone().file_name() == ".daikoku_data" { + let daikoku_data_path = entry.clone().path().to_path_buf(); + + let daikoku_data_file = read_file( + daikoku_data_path.clone(), + ".daikoku_data".to_string(), + ".metadata".to_string(), + ); + let mut data = Ini::new(); + let data = Ini::read(&mut data, daikoku_data_file.content).map_err(|_err| { + DaikokuCliError::FileSystem(format!( + "unable to read daikoku_data file in {:#?}", + daikoku_data_path + )) + })?; + let default_section = data + .get("default") + .map(|value| value.clone()) + .unwrap_or(HashMap::new()); + + daikoku_data = Some(default_section); + } else { + if let Some(extension) = entry.clone().path().extension() { + let mut new_file = read_file( + entry.clone().into_path(), + f_name, + extension.to_string_lossy().into_owned(), + ); + new_file.daikoku_data = daikoku_data.clone(); + pages.push(new_file); + } + } + } + } + Ok(pages) +} + fn read_file(file_path: PathBuf, file_name: String, extension: String) -> CmsFile { let content = fs::read_to_string(&file_path).unwrap(); @@ -113,7 +206,7 @@ fn read_file(file_path: PathBuf, file_name: String, extension: String) -> CmsFil .replace("pages/", "/") .replace("/page.html", "") .replace("/page.css", ""); - + if formatted_path == "" && file_name == "page.html" { formatted_path = "/".to_string(); } @@ -124,7 +217,7 @@ fn read_file(file_path: PathBuf, file_name: String, extension: String) -> CmsFil let parts = content.split("---"); let mut metadata: HashMap<String, String> = - serde_yaml::from_str(&parts.clone().nth(0).unwrap()).unwrap(); + serde_yaml::from_str(&parts.clone().nth(0).unwrap()).unwrap_or(HashMap::new()); let content = parts.into_iter().nth(1).unwrap(); metadata.insert("_path".to_string(), formatted_path.to_string()); @@ -143,6 +236,7 @@ fn read_file(file_path: PathBuf, file_name: String, extension: String) -> CmsFil content: content.to_string(), name: file_name, metadata, + daikoku_data: None, } } else { let mut metadata: HashMap<String, String> = HashMap::new(); diff --git a/cli/src/utils.rs b/cli/src/utils.rs index 9039b82c1..ff8727d9d 100644 --- a/cli/src/utils.rs +++ b/cli/src/utils.rs @@ -1,11 +1,13 @@ -use std::{fs, path::{Path, PathBuf}}; +use std::{ + fs, + path::{Path, PathBuf}, +}; use http_body_util::BodyExt; use std::io::Read; use crate::logging::error::{DaikokuCliError, DaikokuResult}; - pub fn absolute_path(path: String) -> DaikokuResult<String> { match expand_tilde(&path) { None => fs::canonicalize(path) @@ -15,7 +17,7 @@ pub fn absolute_path(path: String) -> DaikokuResult<String> { .map_err(|_p| DaikokuCliError::FileSystem("failed to canonicalize path".to_string())), Some(path) => fs::canonicalize(path) .map(|res| res.into_os_string().into_string().unwrap()) - .map_err(|p| DaikokuCliError::FileSystem(p.to_string())) + .map_err(|p| DaikokuCliError::FileSystem(p.to_string())), } } @@ -52,4 +54,28 @@ pub(crate) async fn frame_to_bytes_body(mut req: hyper::body::Incoming) -> Vec<u } result -} \ No newline at end of file +} + +pub(crate) trait PathBufExt { + fn concat(&self, suffix: &String) -> PathBuf; +} + +impl PathBufExt for PathBuf { + fn concat(&self, suffix: &String) -> PathBuf { + let mut suffix = suffix.clone(); + + if suffix.starts_with("/") { + suffix.remove(0); + } + + self.join(suffix) + } +} + +pub(crate) fn apply_credentials_mask(credential: &String, show_full_credentials: bool) -> String { + if show_full_credentials { + credential.clone() + } else { + credential.as_str()[1..10].to_string() + "*******" + } +} diff --git a/cli/templates/cms.zip b/cli/templates/cms.zip new file mode 100644 index 000000000..71efd4278 Binary files /dev/null and b/cli/templates/cms.zip differ diff --git a/cli/templates/empty/.daikoku/.daikokuignore b/cli/templates/cms/.daikoku/.daikokuignore similarity index 100% rename from cli/templates/empty/.daikoku/.daikokuignore rename to cli/templates/cms/.daikoku/.daikokuignore diff --git a/cli/templates/empty/.daikoku/.environments b/cli/templates/cms/.daikoku/.environments similarity index 100% rename from cli/templates/empty/.daikoku/.environments rename to cli/templates/cms/.daikoku/.environments diff --git a/cli/templates/empty/src/pages/.gitkeep b/cli/templates/cms/.daikoku/.secrets similarity index 100% rename from cli/templates/empty/src/pages/.gitkeep rename to cli/templates/cms/.daikoku/.secrets diff --git a/cli/templates/cms/assets/.gitkeep b/cli/templates/cms/assets/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/cli/templates/cms/src/apis/.gitkeep b/cli/templates/cms/src/apis/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/cli/templates/cms/src/data/.gitkeep b/cli/templates/cms/src/data/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/cli/templates/cms/src/mails/.gitkeep b/cli/templates/cms/src/mails/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/cli/templates/cms/src/pages/.gitkeep b/cli/templates/cms/src/pages/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/cli/templates/empty/src/pages/404.html b/cli/templates/cms/src/pages/404.html similarity index 100% rename from cli/templates/empty/src/pages/404.html rename to cli/templates/cms/src/pages/404.html diff --git a/cli/templates/empty/src/pages/page.html b/cli/templates/cms/src/pages/page.html similarity index 78% rename from cli/templates/empty/src/pages/page.html rename to cli/templates/cms/src/pages/page.html index 9d6d43b07..a80f17fcf 100644 --- a/cli/templates/empty/src/pages/page.html +++ b/cli/templates/cms/src/pages/page.html @@ -11,12 +11,11 @@ <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>My CMS -
-

Welcome your CMS

+

Welcome to your CMS

diff --git a/cli/templates/empty/src/pages/styles.css b/cli/templates/cms/src/pages/styles.css similarity index 100% rename from cli/templates/empty/src/pages/styles.css rename to cli/templates/cms/src/pages/styles.css diff --git a/cli/templates/cms/src/scripts/.gitkeep b/cli/templates/cms/src/scripts/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/cli/templates/cms/src/styles/.gitkeep b/cli/templates/cms/src/styles/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/cli/templates/empty.zip b/cli/templates/empty.zip deleted file mode 100644 index b1485acff..000000000 Binary files a/cli/templates/empty.zip and /dev/null differ diff --git a/cli/templates/manager.html b/cli/templates/manager.html index 679fd6dc8..4039bd291 100644 --- a/cli/templates/manager.html +++ b/cli/templates/manager.html @@ -129,10 +129,6 @@ return insert(acc, paths, page); }, {}); - console.log(pages) - console.log(Object - .entries(sections) - .sort((a, b) => a[0] < b[0] ? -1 : 1)) const components = document.getElementById("components") diff --git a/cli/templates/update.sh b/cli/templates/update.sh index bee4b4b7d..5ae389a61 100755 --- a/cli/templates/update.sh +++ b/cli/templates/update.sh @@ -2,4 +2,4 @@ rm -v **/.DS_Store rm -rf *.zip -zip -r empty.zip empty +zip -r cms.zip cms diff --git a/cli/tests/assets.rs b/cli/tests/assets.rs new file mode 100644 index 000000000..f1043c88f --- /dev/null +++ b/cli/tests/assets.rs @@ -0,0 +1,128 @@ +mod cli; + +use std::{fs, path::PathBuf}; + +use cli::commands::{ + assets, + cli::{run_test_with_s3, CustomRun}, + cms, environment, +}; + +use serial_test::serial; + +// #[tokio::test] +// #[serial] +// async fn push() -> Result<(), Box> { +// run_test_with_s3(|_| { +// cms::clear(true); + +// let project_path = cms::get_temporary_path(); +// cms::init("cms", project_path.clone()); + +// environment::add("prod", "localhost"); + +// let _ = fs::copy( +// "tests/resources/daikoku.svg", +// PathBuf::from(project_path) +// .join("cms") +// .join("assets") +// .join("daikoku.svg"), +// ); + +// assets::push( +// "daikoku.svg", +// "daikoku-logo", +// "daikoku logo svg", +// "daikoku-logo", +// ); +// }) +// .await +// } + +// #[tokio::test] +// #[serial] +// async fn remove() -> Result<(), Box> { +// run_test_with_s3(|_| { +// cms::clear(true); + +// let project_path = cms::get_temporary_path(); +// cms::init("cms", project_path.clone()); + +// environment::add("prod", "localhost"); + +// let _ = fs::copy( +// "tests/resources/daikoku.svg", +// PathBuf::from(project_path) +// .join("cms") +// .join("assets") +// .join("daikoku.svg"), +// ); + +// let slug = "daikoku-logo"; + +// assets::push("daikoku.svg", "daikoku-logo", "daikoku logo svg", slug); + +// assets::remove("daikoku.svg", slug); +// }) +// .await +// } + +// #[tokio::test] +// #[serial] +// async fn list() -> Result<(), Box> { +// run_test_with_s3(|_| { +// cms::clear(true); + +// let project_path = cms::get_temporary_path(); +// cms::init("cms", project_path.clone()); + +// environment::add("prod", "localhost"); + +// let _ = fs::copy( +// "tests/resources/daikoku.svg", +// PathBuf::from(project_path) +// .join("cms") +// .join("assets") +// .join("daikoku.svg"), +// ); + +// let slug = "daikoku-logo"; + +// assets::push("daikoku.svg", "daikoku-logo", "daikoku logo svg", slug); + +// assets::list().run_and_expect("daikoku-logo"); +// }) +// .await +// } + +#[tokio::test] +#[serial] +async fn sync() -> Result<(), Box> { + run_test_with_s3(|_| { + cms::clear(true); + + let project_path = cms::get_temporary_path(); + cms::init("cms", project_path.clone()); + + environment::add("prod", "localhost"); + + let _ = fs::copy( + "tests/resources/daikoku.svg", + PathBuf::from(project_path) + .join("cms") + .join("assets") + .join("daikoku.svg"), + ); + + let slug = "daikoku-logo"; + + assets::push("daikoku.svg", "daikoku-logo", "daikoku logo svg", slug); + + assets::sync(); + + assets::list().run_and_expect("daikoku-logo"); + + assets::remove("daikoku.svg", slug); + }) + .await +} diff --git a/cli/tests/cli/commands/assets.rs b/cli/tests/cli/commands/assets.rs new file mode 100644 index 000000000..6dd50120d --- /dev/null +++ b/cli/tests/cli/commands/assets.rs @@ -0,0 +1,38 @@ +use assert_cmd::assert::Assert; + +use super::cli::CLI; + +pub(crate) fn push( + filename: &str, + title: &str, + desc: &str, + // path: O&str, + slug: &str, +) -> Assert { + CLI::run([ + "assets", + "push", + format!("--filename={}", filename).as_str(), + format!("--title={}", title).as_str(), + format!("--desc={}", desc).as_str(), + // format!("--path={}", path).as_str(), + format!("--slug={}", slug).as_str(), + ]) +} + +pub(crate) fn remove(filename: &str, slug: &str) -> Assert { + CLI::run([ + "assets", + "remove", + format!("--slug={}", slug.to_string()).as_str(), + format!("--filename={}", filename.to_string()).as_str(), + ]) +} + +pub(crate) fn list() -> Assert { + CLI::run(["assets", "list"]) +} + +pub(crate) fn sync() -> Assert { + CLI::run(["assets", "sync"]) +} diff --git a/cli/tests/cli/commands/cli.rs b/cli/tests/cli/commands/cli.rs new file mode 100644 index 000000000..ca57679ae --- /dev/null +++ b/cli/tests/cli/commands/cli.rs @@ -0,0 +1,195 @@ +use std::path::PathBuf; +use std::{ffi, fs}; + +use assert_cmd::{ + assert::{Assert, OutputAssertExt}, + cargo::CommandCargoExt, +}; +use std::process::Command; + +use testcontainers::{ + core::{wait::HealthWaitStrategy, IntoContainerPort, Mount, WaitFor}, + runners::AsyncRunner, + ContainerAsync, GenericImage, ImageExt, +}; + +pub(crate) async fn run_test(test: T) -> Result<(), Box> +where + T: FnOnce(CLI) -> (), +{ + let cli = CLI::start().await.unwrap(); + + test(cli); + + Ok(()) +} + +pub(crate) async fn run_test_with_s3(test: T) -> Result<(), Box> +where + T: FnOnce(CLI) -> (), +{ + let cli = CLI::start_with_s3().await.unwrap(); + + test(cli); + + Ok(()) +} + +pub(crate) struct CLI { + pub postgres_container: ContainerAsync, + pub daikoku_container: ContainerAsync, +} + +impl CLI { + pub(crate) fn run(args: I) -> Assert + where + I: IntoIterator, + S: AsRef, + { + Command::cargo_bin("daikoku").unwrap().args(args).run() + } + + pub(crate) fn build(args: I) -> Assert + where + I: IntoIterator, + S: AsRef, + { + Command::cargo_bin("daikoku").unwrap().args(args).assert() + } + + pub(crate) async fn start() -> Result> { + let (postgres_container, daikoku_container) = Self::start_containers().await?; + + Ok(CLI { + postgres_container: postgres_container, + daikoku_container: daikoku_container, + }) + } + + pub(crate) async fn start_with_s3() -> Result> { + let root_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + + let content = + fs::read_to_string(root_path.clone().join("tests/resources/.s3_env")).unwrap(); + + let lines = content.split("\n").collect::>(); + + let s3_bucket = lines + .iter() + .find(|line| line.contains("S3_BUCKET")) + .unwrap() + .replace("S3_BUCKET=", ""); + + let s3_endpoint = lines + .iter() + .find(|line| line.contains("S3_ENDPOINT")) + .unwrap() + .replace("S3_ENDPOINT=", ""); + + let s3_key = lines + .iter() + .find(|line| line.contains("S3_KEY")) + .unwrap() + .replace("S3_KEY=", ""); + + let s3_secret = lines + .iter() + .find(|line| line.contains("S3_SECRET")) + .unwrap() + .replace("S3_SECRET=", ""); + + let _ = fs::write( + root_path + .clone() + .join("tests/resources/daikoku-state.ndjson"), + fs::read_to_string( + root_path + .clone() + .join("tests/resources/daikoku-state-template.ndjson"), + ) + .unwrap() + .replace("@@S3_ENDPOINT@@", s3_endpoint.trim()) + .replace("@@S3_KEY@@", s3_key.trim()) + .replace("@@S3_SECRET@@", s3_secret.trim()) + .replace("@@S3_BUCKET@@", s3_bucket.trim()), + ); + + let (postgres_container, daikoku_container) = Self::start_containers().await?; + let cli = CLI { + postgres_container: postgres_container, + daikoku_container: daikoku_container, + }; + Ok(cli) + } + + pub(crate) async fn start_containers() -> Result< + (ContainerAsync, ContainerAsync), + Box, + > { + let postgres = GenericImage::new("postgres", "13") + .with_wait_for(WaitFor::message_on_stdout( + "database system is ready to accept connections", + )) + .with_mapped_port(5432, 5432.tcp()) + .with_env_var("POSTGRES_USER", "postgres") + .with_env_var("POSTGRES_PASSWORD", "postgres") + .with_env_var("POSTGRES_DB", "daikoku"); + + let postgres_container = postgres.start().await?; + let host = postgres_container + .get_bridge_ip_address() + .await? + .to_string(); + + let mut state_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + state_path.push("tests/resources"); + + let daikoku = GenericImage::new("maif/daikoku", "17.4.0-dev") + .with_wait_for(WaitFor::message_on_stdout("Running missing evolutions")) + .with_wait_for(WaitFor::seconds(5)) + .with_mapped_port(8080, 8080.tcp()) + .with_env_var("DAIKOKU_INIT_DATA_FROM", "/tmp/daikoku-state.ndjson") + .with_env_var("DAIKOKU_POSTGRES_HOST", host) + .with_env_var("DAIKOKU_MODE", "prod") + .with_env_var("DAIKOKU_POSTGRES_DATABASE", "daikoku") + .with_env_var("DAIKOKU_EXPOSED_ON", "8080") + .with_mount(Mount::bind_mount( + state_path.into_os_string().into_string().unwrap(), + "/tmp", + )); + + let daikoku_container = daikoku.start().await?; + + Ok((postgres_container, daikoku_container)) + } +} + +pub trait AssertCommand { + fn run(&mut self) -> Assert; +} + +impl AssertCommand for Command { + fn run(&mut self) -> Assert { + self.assert().success() + } +} + +pub trait CustomRun { + fn run_and_expect(&mut self, expected: &str); + fn run_and_multiple_expect(&mut self, expected: Vec<&str>); +} + +impl CustomRun for Assert { + fn run_and_expect(&mut self, expected: &str) { + self.run_and_multiple_expect(vec![expected]); + } + + fn run_and_multiple_expect(&mut self, expected: Vec<&str>) { + let stdout = String::from_utf8(self.get_output().stdout.clone()).unwrap(); + let stderr = String::from_utf8(self.get_output().stderr.clone()).unwrap(); + + for expected_value in expected { + assert!(stdout.contains(expected_value) || stderr.contains(expected_value)); + } + } +} diff --git a/cli/tests/cli/commands/cms.rs b/cli/tests/cli/commands/cms.rs new file mode 100644 index 000000000..7851cea89 --- /dev/null +++ b/cli/tests/cli/commands/cms.rs @@ -0,0 +1,70 @@ +use std::fs; + +use assert_cmd::assert::Assert; +use uuid::Uuid; + +use super::{cli::CLI, environment::CMS_APIKEY}; + +pub(crate) fn clear(force: bool) -> Assert { + CLI::run([ + "cms", + "clear", + format!("--force={}", force.to_string()).as_str(), + ]) +} + +pub(crate) fn switch(name: &str) -> Assert { + CLI::run([ + "cms", + "switch", + format!("--name={}", name.to_string()).as_str(), + ]) +} + +pub(crate) fn remove(name: &str) -> Assert { + CLI::run(["cms", "remove", format!("--name={}", name).as_str()]) +} + +pub(crate) fn init(name: &str, path: String) -> Assert { + CLI::run([ + "cms", + "init", + format!("--name={}", name).as_str(), + format!("--path={}", path).as_str(), + ]) +} + +pub(crate) fn migrate(name: &str, path: &String) -> Assert { + CLI::run([ + "cms", + "migrate", + format!("--name={}", name).as_str(), + format!("--path={}", path).as_str(), + format!("--server=http://{}:8080", "localhost").as_str(), + format!("--apikey={}", CMS_APIKEY).as_str(), + ]) +} + +pub(crate) fn add(name: &str, path: String, overwrite: bool) -> Assert { + CLI::run([ + "cms", + "add", + format!("--name={}", name).as_str(), + format!("--path={}", path).as_str(), + format!("--overwrite={}", overwrite).as_str(), + ]) +} + +pub(crate) fn get_temporary_path() -> String { + let temporary_path = std::env::temp_dir() + .join(Uuid::new_v4().to_string()) + .into_os_string() + .into_string() + .unwrap(); + + let _ = fs::remove_dir_all(&temporary_path); + + let _ = fs::create_dir(&temporary_path); + + temporary_path +} diff --git a/cli/tests/cli/commands/environment.rs b/cli/tests/cli/commands/environment.rs new file mode 100644 index 000000000..40a6ca742 --- /dev/null +++ b/cli/tests/cli/commands/environment.rs @@ -0,0 +1,56 @@ +use assert_cmd::assert::Assert; + +use super::cli::CLI; + +pub static CMS_APIKEY: &str = "amJ1UWtEYWpZZThWVTU0a2RjVW1oWjhWM0I1Q0NmV1I6eTJXNmtYV21yRzBxdm8xU2psSjdYU1M0ZEE5cGc5dDlZZ25wMTlIOXR5cUJaZE5NSkZDQmRJUXVKQ3haMXk4VQ=="; + +pub(crate) fn info(name: &str) -> Assert { + CLI::run(["environments", "info", format!("--name={}", name).as_str()]) +} + +pub(crate) fn clear(force: bool) -> Assert { + CLI::run([ + "environments", + "clear", + format!("--force={}", force.to_string()).as_str(), + ]) +} + +pub(crate) fn add(name: &str, daikoku_ip: &str /* apikey: String*/) -> Assert { + CLI::run([ + "environments", + "add", + format!("--name={}", name).as_str(), + format!("--server=http://{}:8080", daikoku_ip).as_str(), + format!("--apikey={}", CMS_APIKEY).as_str(), + ]) +} + +pub(crate) fn config(apikey: &str, cookie: &str) -> Assert { + CLI::run([ + "environments", + "config", + format!("--apikey={}", apikey).as_str(), + format!("--cookie={}", cookie).as_str(), + ]) +} + +pub(crate) fn remove(name: &str) -> Assert { + CLI::run([ + "environments", + "remove", + format!("--name={}", name).as_str(), + ]) +} + +pub(crate) fn switch(name: &str) -> Assert { + CLI::run([ + "environments", + "switch", + format!("--name={}", name).as_str(), + ]) +} + +pub(crate) fn login() -> Assert { + CLI::run(["login"]) +} diff --git a/cli/tests/cli/commands/mod.rs b/cli/tests/cli/commands/mod.rs new file mode 100644 index 000000000..e2dad9916 --- /dev/null +++ b/cli/tests/cli/commands/mod.rs @@ -0,0 +1,4 @@ +pub mod cli; +pub mod cms; +pub mod environment; +pub mod assets; \ No newline at end of file diff --git a/cli/tests/cli/mod.rs b/cli/tests/cli/mod.rs new file mode 100644 index 000000000..6be336ee9 --- /dev/null +++ b/cli/tests/cli/mod.rs @@ -0,0 +1 @@ +pub mod commands; \ No newline at end of file diff --git a/cli/tests/cms.rs b/cli/tests/cms.rs new file mode 100644 index 000000000..b14783d96 --- /dev/null +++ b/cli/tests/cms.rs @@ -0,0 +1,92 @@ +mod cli; + +use std::path::PathBuf; + +use cli::commands::{ + cli::{run_test, CustomRun, CLI}, + cms, +}; + +use serial_test::serial; + +#[tokio::test] +#[serial] +async fn init() -> Result<(), Box> { + run_test(|_| { + cms::clear(true); + cms::init("cms", cms::get_temporary_path()); + }) + .await +} + +#[tokio::test] +#[serial] +async fn list() -> Result<(), Box> { + run_test(|_| { + cms::clear(true); + cms::init("cms", cms::get_temporary_path()); + CLI::build(["cms", "list"]).run_and_multiple_expect(vec!["cms"]); + }) + .await +} + +#[tokio::test] +#[serial] +async fn add() -> Result<(), Box> { + run_test(|_| { + cms::clear(true); + cms::add("cms", cms::get_temporary_path(), true); + CLI::build(["cms", "list"]).run_and_multiple_expect(vec!["cms"]); + }) + .await +} + +#[tokio::test] +#[serial] +async fn switch() -> Result<(), Box> { + run_test(|_| { + cms::clear(true); + let cms = cms::get_temporary_path(); + let cms2 = cms::get_temporary_path(); + + cms::add("cms", cms, true); + cms::add("cms2", cms2, true); + cms::switch("cms"); + }) + .await +} + +#[tokio::test] +#[serial] +async fn remove() -> Result<(), Box> { + run_test(|_| { + cms::clear(true); + cms::add("cms", cms::get_temporary_path(), true); + cms::remove("cms"); + }) + .await +} + +#[tokio::test] +#[serial] +async fn migrate() -> Result<(), Box> { + run_test(|_| { + cms::clear(true); + + let path = cms::get_temporary_path(); + cms::migrate("cms", &path); + + assert!(PathBuf::from(&path).exists()); + + let src = PathBuf::from(&path).join("cms").join("src"); + + assert!(src.join("blocks").exists()); + assert!(src.join("apis").exists()); + assert!(src.join("apis").join("un-test-pour-el-cli").exists()); + assert!(src.join("mails").exists()); + assert!(src.join("pages").exists()); + assert!(src.join("scripts").exists()); + assert!(src.join("styles").exists()); + }) + .await +} diff --git a/cli/tests/creation.rs b/cli/tests/creation.rs deleted file mode 100644 index 7922150aa..000000000 --- a/cli/tests/creation.rs +++ /dev/null @@ -1,47 +0,0 @@ -use assert_cmd::prelude::*; -use std::{fs, process::Command}; - -const WASMO_TEST_FOLDER: &str = "/tmp/daikokucli"; -struct Setup { - temporary_path: String, -} - -impl Setup { - fn new() -> Self { - let temporary_path = WASMO_TEST_FOLDER.to_string(); - - match fs::create_dir(&temporary_path) { - Err(err) => println!("{:?}", err), - Ok(v) => println!("{:?}", v), - } - Setup { - temporary_path: temporary_path, - } - } - - fn clean(&self) { - fs::remove_dir_all(&self.temporary_path).expect("Failed to remove folder") - } -} - -#[test] -fn run() -> Result<(), Box> { - let setup = Setup::new(); - - let mut cmd = Command::cargo_bin("daikokucli")?; - - cmd.args(["projects", "clear"]).assert().success(); - - cmd = Command::cargo_bin("daikokucli")?; - - cmd.args([ - "create", - "--name=cms", - "--template=empty", - format!("--path={}", &setup.temporary_path).as_str(), - ]); - cmd.assert().success(); - - setup.clean(); - Ok(()) -} diff --git a/cli/tests/enviroments.rs b/cli/tests/enviroments.rs deleted file mode 100644 index 20d72369b..000000000 --- a/cli/tests/enviroments.rs +++ /dev/null @@ -1,204 +0,0 @@ -use assert_cmd::prelude::*; -use serial_test::serial; -use std::{ - fs, - process::{Command, Output}, -}; - -const WASMO_TEST_FOLDER: &str = "/tmp/daikokucli"; -struct Setup { - temporary_path: String, -} - -impl Setup { - fn new() -> Self { - let temporary_path = WASMO_TEST_FOLDER.to_string(); - - let _ = fs::remove_dir_all(&temporary_path); - - match fs::create_dir(&temporary_path) { - Err(err) => println!("{:?}", err), - Ok(v) => println!("{:?}", v), - } - Setup { - temporary_path: temporary_path, - } - } - - fn new_with_project() -> Self { - let temporary_path = WASMO_TEST_FOLDER.to_string(); - - let _ = fs::remove_dir_all(&temporary_path); - - match fs::create_dir(&temporary_path) { - Err(err) => println!("{:?}", err), - Ok(v) => println!("{:?}", v), - } - - let mut cmd = Command::cargo_bin("daikokucli").unwrap(); - - cmd.args([ - "create", - "--name=cms", - "--template=empty", - format!("--path={}", &temporary_path).as_str(), - ]); - cmd.assert(); - - Setup { - temporary_path: temporary_path, - } - } - - fn new_with_project_and_environment() -> Self { - let temporary_path = WASMO_TEST_FOLDER.to_string(); - - let _ = fs::remove_dir_all(&temporary_path); - - match fs::create_dir(&temporary_path) { - Err(err) => println!("{:?}", err), - Ok(v) => println!("{:?}", v), - } - - let mut cmd = Command::cargo_bin("daikokucli").unwrap(); - - cmd.args([ - "create", - "--name=cms", - "--template=empty", - format!("--path={}", &temporary_path).as_str(), - ]); - cmd.assert(); - - cmd = Command::cargo_bin("daikokucli").unwrap(); - - cmd.args([ - "environments", - "add", - "--name=dev", - "--server=http://localhost:9999", - "--force=true", - ]) - .assert() - .success(); - - Setup { - temporary_path: temporary_path, - } - } - - fn clean(&self) { - fs::remove_dir_all(&self.temporary_path).expect("Failed to remove folder") - } -} - -#[test] -#[serial] -fn add_environment_on_missing_project() -> Result<(), Box> { - let setup = Setup::new(); - - let mut cmd = Command::cargo_bin("daikokucli")?; - - cmd.args([ - "environments", - "add", - "--name=dev", - "--server=http://localhost:9999", - "--force=true", - ]) - .assert() - .failure(); - - setup.clean(); - Ok(()) -} - -#[test] -#[serial] -fn add_environment_after_creating_project() -> Result<(), Box> { - let setup = Setup::new_with_project(); - - let mut cmd = Command::cargo_bin("daikokucli")?; - - cmd.args([ - "environments", - "add", - "--name=dev", - "--server=http://localhost:9999", - "--force=true", - ]) - .assert() - .success(); - - setup.clean(); - Ok(()) -} - -#[test] -#[serial] -fn clear_environments() -> Result<(), Box> { - let setup = Setup::new_with_project(); - - let mut cmd = Command::cargo_bin("daikokucli")?; - - cmd.args(["environments", "clear"]).assert().success(); - cmd.assert().success(); - - setup.clean(); - Ok(()) -} - -#[test] -#[serial] -fn remove_environment() -> Result<(), Box> { - let setup = Setup::new_with_project_and_environment(); - - let mut cmd = Command::cargo_bin("daikokucli")?; - - cmd.args(["environments", "remove", "--name=dev"]) - .assert() - .success(); - - setup.clean(); - Ok(()) -} - -#[test] -#[serial] -fn default_environment() -> Result<(), Box> { - let setup = Setup::new_with_project_and_environment(); - - let mut cmd = Command::cargo_bin("daikokucli")?; - - let content: Output = cmd.args(["environments", "list"]).output()?; - - assert_eq!( - String::from_utf8(content.stdout) - .unwrap() - .contains("default"), - true - ); - - setup.clean(); - Ok(()) -} - -#[test] -#[serial] -fn get_environment() -> Result<(), Box> { - let setup = Setup::new_with_project_and_environment(); - - let mut cmd = Command::cargo_bin("daikokucli")?; - - let content: Output = cmd.args(["environments", "env", "--name=dev"]).output()?; - - assert_eq!( - String::from_utf8(content.stdout) - .unwrap() - .contains("server"), - true - ); - - setup.clean(); - Ok(()) -} diff --git a/cli/tests/environments.rs b/cli/tests/environments.rs new file mode 100644 index 000000000..aa55d3ddb --- /dev/null +++ b/cli/tests/environments.rs @@ -0,0 +1,95 @@ +mod cli; + +use cli::commands::{ + cli::{run_test, CustomRun, CLI}, + cms, + environment::{self, CMS_APIKEY}, +}; + +use serial_test::serial; + +#[tokio::test] +#[serial] +async fn add() -> Result<(), Box> { + run_test(|_| { + cms::clear(true); + cms::init("cms", cms::get_temporary_path()); + environment::clear(true); + environment::add("test", "localhost"); + }) + .await +} + +#[tokio::test] +#[serial] +async fn switch() -> Result<(), Box> { + run_test(|_| { + cms::clear(true); + cms::init("cms", cms::get_temporary_path()); + environment::clear(true); + environment::add("test", "localhost"); + environment::info("test").run_and_expect("test"); + environment::add("prod", "localhost"); + environment::switch("prod"); + }) + .await +} + +#[tokio::test] +#[serial] +async fn remove() -> Result<(), Box> { + run_test(|_| { + cms::clear(true); + cms::init("cms", cms::get_temporary_path()); + environment::clear(true); + environment::add("test", "localhost"); + environment::remove("test"); + + CLI::build(["environments", "info", "--name=test"]) + .run_and_expect("enviromnment not found"); + }) + .await +} + +#[tokio::test] +#[serial] +async fn list() -> Result<(), Box> { + run_test(|_| { + cms::clear(true); + cms::init("cms", cms::get_temporary_path()); + environment::clear(true); + environment::add("test", "localhost"); + environment::add("prod", "localhost"); + + CLI::build(["environments", "list"]).run_and_multiple_expect(vec![ + "test", + "prod", + "localhost", + ]); + }) + .await +} + +#[tokio::test] +#[serial] +async fn config() -> Result<(), Box> { + run_test(|_| { + cms::clear(true); + cms::init("cms", cms::get_temporary_path()); + environment::clear(true); + environment::add("test", "localhost"); + + CLI::build([ + "environments", + "config", + format!("--apikey={}", "wrong-apikey").as_str(), + format!("--cookie={}", "cookie").as_str(), + ]) + .run_and_expect( + "failed to save configuration. The specified Daikoku server can not be reached", + ); + + environment::config(CMS_APIKEY, "COOKIE"); + }) + .await +} diff --git a/cli/tests/generate.rs b/cli/tests/generate.rs new file mode 100644 index 000000000..85fb7bc26 --- /dev/null +++ b/cli/tests/generate.rs @@ -0,0 +1,37 @@ +mod cli; + +use std::path::PathBuf; + +use cli::commands::{ + cli::CLI, + cms::{self, get_temporary_path}, +}; +use serial_test::serial; + +#[tokio::test] +#[serial] +async fn generate() -> Result<(), Box> { + CLI::start().await?; + + cms::clear(true); + + let path = get_temporary_path(); + cms::init("cms", path.clone()); + + CLI::run([ + "generate", + "documentation", + "--filename=test", + "--title=title", + "--desc=desc", + ]); + + assert!(PathBuf::from(path) + .join("cms") + .join("src") + .join("documentations") + .join("test.html") + .exists()); + + Ok(()) +} diff --git a/cli/tests/login.rs b/cli/tests/login.rs index 9c78b352d..5885823b7 100644 --- a/cli/tests/login.rs +++ b/cli/tests/login.rs @@ -1,55 +1,35 @@ -// // use assert_cmd::prelude::*; -// // use serial_test::serial; -// // use std::fs; -// use testcontainers::{clients, core::WaitFor, GenericImage}; - -// // const WASMO_TEST_FOLDER: &str = "/tmp/daikokucli"; -// // struct Setup { -// // temporary_path: String, -// // } - -// // impl Setup { -// // fn new() -> Self { -// // let temporary_path = WASMO_TEST_FOLDER.to_string(); - -// // let _ = fs::remove_dir_all(&temporary_path); - -// // match fs::create_dir(&temporary_path) { -// // Err(err) => println!("{:?}", err), -// // Ok(v) => println!("{:?}", v), -// // } -// // Setup { -// // temporary_path: temporary_path, -// // } -// // } - -// // fn clean(&self) { -// // fs::remove_dir_all(&self.temporary_path).expect("Failed to remove folder") -// // } -// // } - -// #[test] -// fn login() -> Result<(), Box> { -// let docker = clients::Cli::default(); - -// let postgres = docker.run( -// GenericImage::new("postgres", "12") -// .with_env_var("POSTGRES_USER", "postgres") -// .with_env_var("POSTGRES_PASSWORD", "postgres") -// .with_env_var("POSTGRES_DB", "daikoku"), -// ); - -// let daikoku = docker.run( -// GenericImage::new("daikoku", "17.1.2") -// .with_env_var("daikoku.mode", "dev") -// .with_env_var("Ddaikoku.postgres.database", "daikoku") -// .with_env_var("daikoku.exposedOn", "9000"), -// ); - -// postgres.start(); -// daikoku.start(); - -// WaitFor::seconds(60); - -// Ok(()) -// } +mod cli; + +use cli::commands::{ + cli::{run_test, CLI}, + cms::{self, get_temporary_path}, + environment, +}; +use serial_test::serial; + +fn test_check_info_of_environment() { + let result = environment::info("dev"); + let output = String::from_utf8(result.get_output().stdout.clone()).unwrap(); + + assert!(output.contains("http://localhost:8080")); + assert!(output.contains("dev")); +} + +#[tokio::test] +#[serial] +async fn login() -> Result<(), Box> { + run_test(|_| { + cms::clear(true); + + cms::init("cms", get_temporary_path()); + + environment::add("dev", "localhost"); + + test_check_info_of_environment(); + + environment::switch("dev"); + + environment::login(); + }) + .await +} diff --git a/cli/tests/projects.rs b/cli/tests/projects.rs deleted file mode 100644 index ccb0e8549..000000000 --- a/cli/tests/projects.rs +++ /dev/null @@ -1,138 +0,0 @@ -use assert_cmd::prelude::*; -use serial_test::serial; -use std::{fs, process::Command}; - -const WASMO_TEST_FOLDER: &str = "/tmp/daikokucli"; -struct Setup { - temporary_path: String, -} - -impl Setup { - fn new() -> Self { - let temporary_path = WASMO_TEST_FOLDER.to_string(); - - let _ = fs::remove_dir_all(&temporary_path); - - match fs::create_dir(&temporary_path) { - Err(err) => println!("{:?}", err), - Ok(v) => println!("{:?}", v), - } - - let mut cmd = Command::cargo_bin("daikokucli").unwrap(); - - cmd.args(["projects", "clear"]).assert().success(); - - cmd = Command::cargo_bin("daikokucli").unwrap(); - - cmd.args([ - "create", - "--name=cms", - "--template=empty", - format!("--path={}", &temporary_path).as_str(), - ]); - - Setup { - temporary_path: temporary_path, - } - } - - fn clean(&self) { - fs::remove_dir_all(&self.temporary_path).expect("Failed to remove folder") - } -} - -#[test] -#[serial] -fn add_project() -> Result<(), Box> { - let setup = Setup::new(); - - let mut cmd = Command::cargo_bin("daikokucli")?; - - cmd.args([ - "projects", - "add", - "--name=dev", - &format!("--path={}", WASMO_TEST_FOLDER), - ]) - .assert() - .success(); - - setup.clean(); - Ok(()) -} - -#[test] -#[serial] -fn default_project() -> Result<(), Box> { - let setup = Setup::new(); - - let mut cmd = Command::cargo_bin("daikokucli")?; - - cmd.args([ - "projects", - "add", - "--name=dev", - &format!("--path={}", WASMO_TEST_FOLDER), - ]) - .assert() - .success(); - - cmd = Command::cargo_bin("daikokucli")?; - - cmd.args(["projects", "default", "--name=dev"]) - .assert() - .success(); - - setup.clean(); - Ok(()) -} - -#[test] -#[serial] -fn remove_project() -> Result<(), Box> { - let setup = Setup::new(); - - let mut cmd = Command::cargo_bin("daikokucli")?; - - cmd.args([ - "projects", - "add", - "--name=dev", - &format!("--path={}", WASMO_TEST_FOLDER), - ]) - .assert() - .success(); - - cmd = Command::cargo_bin("daikokucli")?; - - cmd.args(["projects", "remove", "--name=dev"]) - .assert() - .success(); - - setup.clean(); - Ok(()) -} - -#[test] -#[serial] -fn clear_project() -> Result<(), Box> { - let setup = Setup::new(); - - let mut cmd = Command::cargo_bin("daikokucli")?; - - cmd.args([ - "projects", - "add", - "--name=dev", - &format!("--path={}", WASMO_TEST_FOLDER), - ]) - .assert() - .success(); - - cmd = Command::cargo_bin("daikokucli")?; - - cmd.args(["projects", "clear"]).assert().success(); - - setup.clean(); - Ok(()) -} diff --git a/cli/tests/pull.rs b/cli/tests/pull.rs new file mode 100644 index 000000000..02843592c --- /dev/null +++ b/cli/tests/pull.rs @@ -0,0 +1,81 @@ +mod cli; + +use std::path::PathBuf; + +use cli::commands::{ + cli::{run_test, CLI}, + cms::{self, get_temporary_path}, + environment, +}; + +use serial_test::serial; + +#[tokio::test] +#[serial] +async fn pull_apis() -> Result<(), Box> { + run_test(|_| { + cms::clear(true); + let path = get_temporary_path(); + cms::init("cms", path.clone()); + environment::add("prod", "localhost"); + + assert!(PathBuf::from(&path) + .join("cms") + .join("src") + .join("apis") + .exists()); + + let dir = PathBuf::from(&path) + .join("cms") + .join("src") + .join("apis") + .read_dir() + .unwrap(); + + assert!(dir.count() == 1); + + CLI::run(["pull", "apis"]); + + assert!(PathBuf::from(&path) + .join("cms") + .join("src") + .join("apis") + .join("un-test-pour-el-cli") + .exists()); + }) + .await +} + +#[tokio::test] +#[serial] +async fn pull_mails() -> Result<(), Box> { + run_test(|_| { + cms::clear(true); + let path = get_temporary_path(); + cms::init("cms", path.clone()); + environment::add("prod", "localhost"); + + let dir = PathBuf::from(&path) + .join("cms") + .join("src") + .join("mails") + .read_dir() + .unwrap(); + + assert!(dir.count() == 1); + + CLI::run(["pull", "mails"]); + + assert!( + PathBuf::from(&path) + .join("cms") + .join("src") + .join("mails") + .read_dir() + .unwrap() + .count() + > 1 + ); + }) + .await +} diff --git a/cli/tests/push.rs b/cli/tests/push.rs new file mode 100644 index 000000000..726e70af5 --- /dev/null +++ b/cli/tests/push.rs @@ -0,0 +1,73 @@ +mod cli; + +use cli::commands::{ + cli::{run_test, CustomRun, CLI}, + cms::{self, get_temporary_path}, + environment, +}; + +use serial_test::serial; + +#[tokio::test] +#[serial] +async fn failed_push_missing_environment() -> Result<(), Box> { + run_test(|_| { + cms::clear(true); + cms::init("cms", get_temporary_path()); + + CLI::build(["push"]).run_and_expect("default environment not found"); + }) + .await +} + +#[tokio::test] +#[serial] +async fn push() -> Result<(), Box> { + run_test(|_| { + cms::clear(true); + cms::init("cms", get_temporary_path()); + + environment::add("prod", "localhost"); + CLI::run(["push"]); + }) + .await +} + +#[tokio::test] +#[serial] +async fn dry_run() -> Result<(), Box> { + run_test(|_| { + cms::clear(true); + cms::init("cms", get_temporary_path()); + + environment::add("prod", "localhost"); + CLI::build(["push", "--dry-run=true"]).run_and_expect("dry_run"); + }) + .await +} + +#[tokio::test] +#[serial] +async fn failed_push_invalid_path() -> Result<(), Box> { + run_test(|_| { + cms::clear(true); + cms::migrate("cms", &get_temporary_path()); + + environment::add("prod", "localhost"); + CLI::build(["push", "--file_path=oto/un-test-pour-el-cli"]).failure(); + }) + .await +} + +#[tokio::test] +#[serial] +async fn push_specific_path() -> Result<(), Box> { + run_test(|_| { + cms::clear(true); + cms::migrate("cms", &get_temporary_path()); + + environment::add("prod", "localhost"); + CLI::run(["push", "--file_path=apis/un-test-pour-el-cli"]); + }) + .await +} diff --git a/cli/tests/resources/.gitignore b/cli/tests/resources/.gitignore new file mode 100644 index 000000000..402841784 --- /dev/null +++ b/cli/tests/resources/.gitignore @@ -0,0 +1 @@ +.s3_env \ No newline at end of file diff --git a/cli/tests/resources/daikoku-state-template.ndjson b/cli/tests/resources/daikoku-state-template.ndjson new file mode 100644 index 000000000..341b8fdcf --- /dev/null +++ b/cli/tests/resources/daikoku-state-template.ndjson @@ -0,0 +1,99 @@ +{"type":"tenants","payload":{"_id":"default","name":"Daikoku Default Tenant","style":{"js":"","css":"","logo":"/assets/images/daikoku.svg","jsUrl":null,"title":"Daikoku Default Tenant","cssUrl":null,"footer":null,"cacheTTL":60000,"colorTheme":":root {\n --error-color: #ff6347;\n --error-color: #ffa494;\n --success-color: #4F8A10;\n --success-color: #76cf18;\n\n --link-color: #7f96af;\n --link--hover-color: #8fa6bf;\n\n --body-bg-color: #fff;\n --body-text-color: #212529;\n --navbar-bg-color: #7f96af;\n --navbar-brand-color: #fff;\n --menu-bg-color: #fff;\n --menu-text-color: #212529;\n --menu-text-hover-bg-color: #9bb0c5;\n --menu-text-hover-color: #fff;\n --section-bg-color: #f8f9fa;\n --section-text-color: #6c757d;\n --section-bottom-color: #eee;\n --addContent-bg-color: #e9ecef;\n --addContent-text-color: #000;\n --sidebar-bg-color: #f8f9fa;\n\n --btn-bg-color: #fff;\n --btn-text-color: #495057;\n --btn-border-color: #97b0c7;\n\n --badge-tags-bg-color: #ffc107;\n --badge-tags-bg-color: #ffe1a7;\n --badge-tags-text-color: #212529;\n\n --pagination-text-color: #586069;\n --pagination-border-color: #586069;\n\n --table-bg-color: #f8f9fa;\n\n --apicard-visibility-color: #586069;\n --apicard-visibility-border-color: rgba(27,31,35,.15);\n --modal-selection-bg-color: rgba(27,31,35,.15);\n}","faviconUrl":null,"description":"A new organization to host very fine APIs","homeCmsPage":"aaWYws62IbbyHGqHKDMMnko0yk66LUTq","unloggedHome":"","fontFamilyUrl":null,"homePageVisible":true,"notFoundCmsPage":"aaWYws62IbbyHGqHKDMMnko0yk66LUTq","cmsHistoryLength":10,"authenticatedCmsPage":"aaWYws62IbbyHGqHKDMMnko0yk66LUTq"},"domain":"localhost","contact":"contact@foo.bar","display":"default","enabled":true,"_deleted":false,"adminApi":"admin-api-tenant-default","robotTxt":null,"isPrivate":false,"tenantMode":"Default","authProvider":"Local","environments":[],"bucketSettings":{"access":"@@S3_KEY@@","bucket":"@@S3_BUCKET@@","region":"eu-west-1","secret":"@@S3_SECRET@@","v4auth":true,"endpoint":"@@S3_ENDPOINT@@","chunkSize":8388608},"defaultMessage":null,"mailerSettings":{"type":"console","template":"Bienvenue !\n{{email}}"},"defaultLanguage":"En","_humanReadableId":"daikoku-default-tenant","auditTrailConfig":{"kafkaConfig":null,"alertsEmails":[],"auditWebhooks":[],"elasticConfigs":null},"creationSecurity":false,"otoroshiSettings":[],"adminSubscriptions":["wKmlBKvIpD6RMF4ahGtwdNgwVVnX8feZ","cIYrbHDhwvhwDpO0A4dx9w6FhBlaydBb"],"authProviderSettings":{"sessionMaxAge":86400},"subscriptionSecurity":false,"apiReferenceHideForGuest":true,"thirdPartyPaymentSettings":[],"aggregationApiKeysSecurity":false}} +{"type":"users","payload":{"_id":"oxrtSfdDVXe7b0jEYFaziUzATuFuWsAX","name":"Daikoku admin","email":"admin@daikoku.io","isGuest":false,"origins":["Otoroshi"],"picture":"https://www.gravatar.com/avatar/35486a5583d99e3b2773814f359e4631?size=128&d=robohash","tenants":["default"],"_deleted":false,"metadata":{},"password":"$2a$10$RIZnvDZNZbmKBVkIJU1toOjjhfyW6d1OePboAcksYaB7A2EDNYOWe","invitation":null,"lastTenant":"default","starredApis":[],"personalToken":"2pAuww5Onlfn8po79jBCdsyB3lqWYRu8","isDaikokuAdmin":true,"defaultLanguage":null,"_humanReadableId":"admin-daikoku.io","pictureFromProvider":true,"twoFactorAuthentication":null,"hardwareKeyRegistrations":[]}} +{"type":"user_sessions","payload":{"_id":"KDF7uHaE2csoj4k4XPmqqIpQ4s47KU0W","ttl":86400000,"userId":"oxrtSfdDVXe7b0jEYFaziUzATuFuWsAX","created":1722521844604,"expires":1726821444735,"userName":"Daikoku admin","sessionId":"aWTVLkEg9jPwsK5hqDdxXR78wepinfe8kLbxsaE81gjKK7qY6ipOSNYSsMubrMFe","userEmail":"admin@daikoku.io","impersonatorId":null,"impersonatorName":null,"impersonatorEmail":null,"impersonatorSessionId":null}} +{"type":"evolutions","payload":{"_id":"JLJNk2wsTjTty1rHM4obDrPfSqO2J9Iq","date":1718877359581,"applied":true,"version":"1.0.2"}} +{"type":"evolutions","payload":{"_id":"XdHwjuH0J7V1GYcT6QhYVDnv4rNfWSSQ","date":1718877359672,"applied":true,"version":"1.5.0"}} +{"type":"evolutions","payload":{"_id":"JaU5exr5jgVgCdHuqJlODdFYvFjOG8KZ","date":1718877359824,"applied":true,"version":"1.5.1"}} +{"type":"evolutions","payload":{"_id":"bXjq9ctGAm2sVWs62eGZddYrTwaCMjW9","date":1718877359943,"applied":true,"version":"1.5.5"}} +{"type":"evolutions","payload":{"_id":"8mHk5z9cFPb4MzzXvjBZZHUueznmefAM","date":1718877359960,"applied":true,"version":"1.5.7"}} +{"type":"evolutions","payload":{"_id":"WH6GZbFMyYCeiuTxXskLrVps33vPGjvp","date":1718877359996,"applied":true,"version":"1.5.7_b"}} +{"type":"evolutions","payload":{"_id":"TI8pshde6PWcEpqydZj64AINWIlKvOig","date":1718877360020,"applied":true,"version":"1.5.7_c"}} +{"type":"evolutions","payload":{"_id":"uS9ImhWnuXuHq29H2XWVOHMg6GFYKppy","date":1718877360049,"applied":true,"version":"16.1.2_a"}} +{"type":"evolutions","payload":{"_id":"OlLZeOhBoq4WOh6meoPleuAPlqYWNnzw","date":1718877360068,"applied":true,"version":"16.1.2_b"}} +{"type":"evolutions","payload":{"_id":"pTP0F8ahxLmHlT4IfZ5tfWzS1B9gf5zI","date":1722502534886,"applied":true,"version":"16.1.2_c"}} +{"type":"evolutions","payload":{"_id":"n7FBhRDFC74F5shFrHqZ4LVFLr0HGmGm","date":1722502534892,"applied":true,"version":"16.1.3"}} +{"type":"evolutions","payload":{"_id":"Ijn8BILDTpb55IOEed4e31F4AskgAjuu","date":1722502534894,"applied":true,"version":"16.1.3_b"}} +{"type":"evolutions","payload":{"_id":"WPLQDypKW5FMiUx08iNHIVcwJfP1mGQ0","date":1722502534898,"applied":true,"version":"16.3.0"}} +{"type":"evolutions","payload":{"_id":"j1xCrUA1S6j3tEZkAM7Lc4afo1vbtHBM","date":1722502534901,"applied":true,"version":"16.3.4"}} +{"type":"evolutions","payload":{"_id":"3w4QDIT2uF85dqd4B7XQl9cQwGoHQ7lu","date":1726646024172,"applied":true,"version":"17.5.0"}} +{"type":"teams","payload":{"_id":"Z336LuOwnaqYRslEfVMDLokjL75WndtlSZFh3rg5i1EZv2HRqpFg6YnFunkWowoS","name":"daikoku-default-tenant-admin-team","type":"Admin","users":[{"userId":"oxrtSfdDVXe7b0jEYFaziUzATuFuWsAX","teamPermission":"Administrator"}],"avatar":"/assets/images/daikoku.svg","_tenant":"default","contact":"contact@foo.bar","_deleted":false,"metadata":{},"verified":true,"description":"The admin team for the default tenant","_humanReadableId":"daikoku-default-tenant-admin-team","apiKeyVisibility":"User","apisCreationPermission":null,"authorizedOtoroshiEntities":null}} +{"type":"teams","payload":{"_id":"FnagTV9CDvPgJNPY7wCga03hdjyuE2HI","name":"Daikoku admin","type":"Personal","users":[{"userId":"oxrtSfdDVXe7b0jEYFaziUzATuFuWsAX","teamPermission":"Administrator"}],"avatar":"/assets/images/daikoku.svg","_tenant":"default","contact":"admin@daikoku.io","_deleted":false,"metadata":{},"verified":true,"description":"Daikoku admin's team","_humanReadableId":"daikoku-admin","apiKeyVisibility":"User","apisCreationPermission":null,"authorizedOtoroshiEntities":null}} +{"type":"teams","payload":{"_id":"8jHt9Kysthbt32Lt4VsdMh3S2q3bgwhs","name":"New Team","type":"Organization","users":[{"userId":"oxrtSfdDVXe7b0jEYFaziUzATuFuWsAX","teamPermission":"Administrator"}],"avatar":"/assets/images/daikoku.svg","_tenant":"default","contact":"contact@foo.bar","_deleted":false,"metadata":{},"verified":false,"description":"A new team","_humanReadableId":"new-team","apiKeyVisibility":"Administrator","apisCreationPermission":null,"authorizedOtoroshiEntities":null}} +{"type":"apis","payload":{"_id":"cms-api-tenant-default","apis":null,"name":"cms-api-tenant-default","tags":[],"team":"Z336LuOwnaqYRslEfVMDLokjL75WndtlSZFh3rg5i1EZv2HRqpFg6YnFunkWowoS","image":null,"posts":[],"stars":0,"state":"published","header":null,"issues":[],"parent":null,"_tenant":"default","swagger":{"url":"/cms-api/swagger.json","content":null,"headers":{},"additionalConf":null,"specificationType":"openapi"},"testing":null,"_deleted":false,"isDefault":true,"categories":[],"issuesTags":[],"lastUpdate":1726646024114,"visibility":"AdminOnly","description":"cms api","documentation":{"_id":"1ykF7i8ZOLXNh46Tq0AkzBFB26xntC9b","pages":[],"_tenant":"default","lastModificationAt":1726646024114},"currentVersion":"1.0.0","authorizedTeams":[],"_humanReadableId":"cms-api-tenant-default","defaultUsagePlan":"7Mv0NwlWXXKSdpiLspZOCWYcPw0XTVFnTdBDXPJh0KgmZHoOflAoxqfdmAwkxtWm","smallDescription":"cms api","supportedVersions":["1.0.0"],"descriptionCmsPage":null,"possibleUsagePlans":["7Mv0NwlWXXKSdpiLspZOCWYcPw0XTVFnTdBDXPJh0KgmZHoOflAoxqfdmAwkxtWm"],"customHeaderCmsPage":null}} +{"type":"apis","payload":{"_id":"y33XIGECvL0EhiRuosRTRyyE429hj3Vj","apis":null,"name":"CMS API","tags":[],"team":"FnagTV9CDvPgJNPY7wCga03hdjyuE2HI","image":null,"posts":["q7w5eLC6Ap0YdxQMR8rko6fCAChSlqwx"],"stars":0,"state":"created","header":"

Un header spécialisé

","issues":["yLyiSRBJlSuoNmgwCdNvQylPSXTz5Dou","BUq7LkJJ8xrvRfi3JqDguTGAexSFgGcv"],"parent":null,"_tenant":"default","swagger":{"url":"https://petstore3.swagger.io/api/v3/openapi.json","content":null,"headers":{},"additionalConf":null,"specificationType":"openapi"},"testing":null,"_deleted":true,"isDefault":true,"categories":[],"issuesTags":[],"lastUpdate":1722410834016,"visibility":"Public","description":"

\n Description perso auss\n

","documentation":{"_id":"YhtgkCN3jjgyKes0bAoTu5SHk3VwVOnf","pages":[{"id":"p3v0gupO5SI9y_P-epX0pLmLVZA8oZZe","title":"Une page","children":[]},{"id":"TJ90oJSKcSUz4i1LAKLp7RKAXb8nqf8Q","title":"une second epage","children":[]}],"_tenant":"default","lastModificationAt":1722410834016},"currentVersion":"1.0.0","authorizedTeams":[],"_humanReadableId":"cms-api","defaultUsagePlan":null,"smallDescription":"CMS API Desc","supportedVersions":[],"descriptionCmsPage":"mails-mail-create-post-title-fr","possibleUsagePlans":["9r3Eh6FN0CtRkqRhHeFs0QZgsF44lx6i"],"customHeaderCmsPage":"mails-mail-api-access-rejection-body-en"}} +{"type":"apis","payload":{"_id":"V80UfyIoYE6YTagwCGr3rHlD2QB0AAaE","apis":null,"name":"un test pour el CLI","tags":[],"team":"FnagTV9CDvPgJNPY7wCga03hdjyuE2HI","image":null,"posts":[],"stars":0,"state":"published","header":null,"issues":[],"parent":null,"_tenant":"default","swagger":null,"testing":null,"_deleted":false,"isDefault":true,"categories":[],"issuesTags":[],"lastUpdate":1726647292775,"visibility":"Public","description":"A new API","documentation":{"_id":"6THbGX08Lal1hzqsi8Ta5F7QPGmHe8jb","pages":[],"_tenant":"default","lastModificationAt":1726647292775},"currentVersion":"1.0.0","authorizedTeams":[],"_humanReadableId":"un-test-pour-el-cli","defaultUsagePlan":null,"smallDescription":"A new API","supportedVersions":["1.0.0"],"descriptionCmsPage":null,"possibleUsagePlans":[],"customHeaderCmsPage":null}} +{"type":"apis","payload":{"_id":"admin-api-tenant-default","apis":null,"name":"admin-api-tenant-default","tags":[],"team":"Z336LuOwnaqYRslEfVMDLokjL75WndtlSZFh3rg5i1EZv2HRqpFg6YnFunkWowoS","image":null,"posts":[],"stars":0,"state":"published","header":null,"issues":[],"parent":null,"_tenant":"default","swagger":{"url":"/admin-api/swagger.json","content":null,"headers":{},"additionalConf":null,"specificationType":"openapi"},"testing":{"auth":"Basic","name":null,"config":null,"enabled":false,"password":null,"username":null},"_deleted":false,"isDefault":true,"categories":[],"issuesTags":[],"lastUpdate":1718877357724,"visibility":"AdminOnly","description":"admin api","documentation":{"_id":"A92iE2DXAtfNPsvzH93NdlS4i5PAGLKE","pages":[],"_tenant":"default","lastModificationAt":1718877357799},"currentVersion":"1.0.0","authorizedTeams":[],"_humanReadableId":"admin-api-tenant-default","defaultUsagePlan":null,"smallDescription":"admin api","supportedVersions":["1.0.0"],"descriptionCmsPage":null,"possibleUsagePlans":["7p9AW4w7ZmGMl4hWzAp7YCLxAD9TvOiw"],"customHeaderCmsPage":null}} +{"type":"api_subscriptions","payload":{"by":"oxrtSfdDVXe7b0jEYFaziUzATuFuWsAX","_id":"wKmlBKvIpD6RMF4ahGtwdNgwVVnX8feZ","api":"cms-api-tenant-default","plan":"7Mv0NwlWXXKSdpiLspZOCWYcPw0XTVFnTdBDXPJh0KgmZHoOflAoxqfdmAwkxtWm","tags":[],"team":"Z336LuOwnaqYRslEfVMDLokjL75WndtlSZFh3rg5i1EZv2HRqpFg6YnFunkWowoS","apiKey":{"clientId":"jbuQkDajYe8VU54kdcUmhZ8V3B5CCfWR","clientName":"daikoku-api-key-cms-api-tenant-default-admin-daikoku-default-tenant-admin-team-1726646839925","clientSecret":"y2W6kXWmrG0qvo1SjlJ7XSS4dA9pg9t9Ygnp19H9tyqBZdNMJFCBdIQuJCxZ1y8U"},"parent":null,"_tenant":"default","enabled":true,"_deleted":false,"metadata":null,"rotation":{"enabled":true,"gracePeriod":168,"rotationEvery":744,"pendingRotation":false},"createdAt":1726646839925,"customName":null,"customMetadata":null,"customReadOnly":null,"adminCustomName":null,"customMaxPerDay":null,"integrationToken":"ZViMD9IfYLlVn72SHFRzyqadDEaC4pMxxBnLiHJRTPdjrxPJomtJi7gAg7TuX3Y0","customMaxPerMonth":null,"customMaxPerSecond":null,"thirdPartySubscriptionInformations":null}} +{"type":"api_subscriptions","payload":{"by":"oxrtSfdDVXe7b0jEYFaziUzATuFuWsAX","_id":"cIYrbHDhwvhwDpO0A4dx9w6FhBlaydBb","api":"admin-api-tenant-default","plan":"7p9AW4w7ZmGMl4hWzAp7YCLxAD9TvOiw","tags":[],"team":"Z336LuOwnaqYRslEfVMDLokjL75WndtlSZFh3rg5i1EZv2HRqpFg6YnFunkWowoS","apiKey":{"clientId":"MN63Hd1lehqqiE1iUwCeM35hVRllw76g","clientName":"daikoku-api-key-admin-api-tenant-default-freewithoutquotas-daikoku-default-tenant-admin-team-1726736293074","clientSecret":"ycD9zAAD3G0h14tWK14oOaIw7eGdEPBnMX29zRgKapSz2WE1itLs9eiJkv9aHuVz"},"parent":null,"_tenant":"default","enabled":true,"_deleted":false,"metadata":null,"rotation":{"enabled":true,"gracePeriod":168,"rotationEvery":744,"pendingRotation":false},"createdAt":1726736293074,"customName":null,"customMetadata":null,"customReadOnly":null,"adminCustomName":null,"customMaxPerDay":null,"integrationToken":"yuHAieGA8eaykMxtCEL1CrJXhAQMjEX3i99gl8LEzRoDnzmdpbTdI9lfo5dBCbn1","customMaxPerMonth":null,"customMaxPerSecond":null,"thirdPartySubscriptionInformations":null}} +{"type":"api_documentation_pages","payload":{"_id":"p3v0gupO5SI9y_P-epX0pLmLVZA8oZZe","title":"Une page","_tenant":"default","content":"# New page\n\nA new page","_deleted":false,"contentType":"text/markdown","_humanReadableId":"p3v0gupO5SI9y_P-epX0pLmLVZA8oZZe","remoteContentUrl":null,"lastModificationAt":1722410950203,"remoteContentEnabled":false,"remoteContentHeaders":{}}} +{"type":"api_documentation_pages","payload":{"_id":"TJ90oJSKcSUz4i1LAKLp7RKAXb8nqf8Q","title":"une second epage","_tenant":"default","cmsPage":"apis-cms-api-documentations-iard-sinistres-v4","content":"# New page\n\nA new page","_deleted":false,"contentType":"cms/page","_humanReadableId":"TJ90oJSKcSUz4i1LAKLp7RKAXb8nqf8Q","remoteContentUrl":null,"lastModificationAt":1722434664842,"remoteContentEnabled":false,"remoteContentHeaders":{}}} +{"type":"api_posts","payload":{"_id":"q7w5eLC6Ap0YdxQMR8rko6fCAChSlqwx","title":"Une news","_tenant":"default","content":"Une news description\n","_deleted":false,"_humanReadableId":"une-news","lastModificationAt":1722410969178}} +{"type":"api_issues","payload":{"by":"oxrtSfdDVXe7b0jEYFaziUzATuFuWsAX","_id":"yLyiSRBJlSuoNmgwCdNvQylPSXTz5Dou","open":false,"tags":[],"seqId":0,"title":"New issue","_tenant":"default","_deleted":false,"closedAt":null,"comments":[{"by":"oxrtSfdDVXe7b0jEYFaziUzATuFuWsAX","content":"description","createdAt":1722411029113,"lastModificationAt":1722411029113}],"createdAt":1722411015499,"apiVersion":"1.0.0","_humanReadableId":"0","lastModificationAt":1722411015499}} +{"type":"api_issues","payload":{"by":"oxrtSfdDVXe7b0jEYFaziUzATuFuWsAX","_id":"BUq7LkJJ8xrvRfi3JqDguTGAexSFgGcv","open":true,"tags":[],"seqId":1,"title":"sadasd","_tenant":"default","_deleted":false,"closedAt":null,"comments":[{"by":"oxrtSfdDVXe7b0jEYFaziUzATuFuWsAX","content":"asdasd","createdAt":1722411079010,"lastModificationAt":1722411079010}],"createdAt":1722411066705,"apiVersion":"1.0.0","_humanReadableId":"1","lastModificationAt":1722411066705}} +{"type":"notifications","payload":{"_id":"gWTPLN13NpdQptlXYLeEzAplPLfAQktn","date":1722411071780,"team":"FnagTV9CDvPgJNPY7wCga03hdjyuE2HI","action":{"type":"NewIssueOpen","linkTo":"/daikoku-admin/cms-api/1.0.0/issues/BUq7LkJJ8xrvRfi3JqDguTGAexSFgGcv","teamId":"FnagTV9CDvPgJNPY7wCga03hdjyuE2HI","apiName":"CMS API"},"sender":{"id":"oxrtSfdDVXe7b0jEYFaziUzATuFuWsAX","name":"Daikoku admin","email":"admin@daikoku.io"},"status":{"date":1722501444667,"status":"Accepted"},"_tenant":"default","_deleted":false,"notificationType":"AcceptOnly"}} +{"type":"notifications","payload":{"_id":"lIegg9eQnLbzUd0fm98ZNhOTLPKu5pVz","date":1722411029150,"team":"FnagTV9CDvPgJNPY7wCga03hdjyuE2HI","action":{"type":"NewIssueOpen","linkTo":"/daikoku-admin/cms-api/1.0.0/issues/yLyiSRBJlSuoNmgwCdNvQylPSXTz5Dou","teamId":"FnagTV9CDvPgJNPY7wCga03hdjyuE2HI","apiName":"CMS API"},"sender":{"id":"oxrtSfdDVXe7b0jEYFaziUzATuFuWsAX","name":"Daikoku admin","email":"admin@daikoku.io"},"status":{"date":1722501445331,"status":"Accepted"},"_tenant":"default","_deleted":false,"notificationType":"AcceptOnly"}} +{"type":"translations","payload":{"_id":"o2Wlf8078xzPPk29TwLBAfbiJa6KxyiF","key":"tenant.mail.template","value":"En francais je change le template par default\n[email]","_tenant":"default","language":"fr","lastModificationAt":1722515549597}} +{"type":"translations","payload":{"_id":"Xz31HG0cdkIWoUUaMPTaE46ZcW3ofsah","key":"mail.subscription.validation.body","value":"Hello, \n
\n[user] for the team [team] wants to subscribe your API,
[body]
Accept or Reject\n\n\n\n\nmail.subscription.validation.body je change aussi ici\n","_tenant":"default","language":"en","lastModificationAt":1722515570243}} +{"type":"translations","payload":{"_id":"PgEc6JKI6XOpWheo9GbjtKEDh9EPhGHV","key":"mail.subscription.validation.body","value":"Bonjour,
[user] pour l'équipe [team] voudrait souscrire a l'API [apiName],
[body]
Accept or Reject\n\n\nEt un bout du french aussi!","_tenant":"default","language":"fr","lastModificationAt":1722515581470}} +{"type":"translations","payload":{"_id":"KVMJHXnReOBmXQgPPLgpTan3FMnUxyCg","key":"tenant.mail.template","value":"En anglais je change le template par default asdasd\n[email]","_tenant":"default","language":"en","lastModificationAt":1722517991730}} +{"type":"translations","payload":{"_id":"K9qzedRJjY3o9xBTG9ccC2OzuCrZyxSx","key":"mail.acceptation.title","value":"Your Daikoku request has been accepted encore.","_tenant":"default","language":"en","lastModificationAt":1724417135045}} +{"type":"email_verifications","payload":{"_id":"tCJbgru2ckMBrTKhqzEDTB7xKDeRxy5j","teamId":"8jHt9Kysthbt32Lt4VsdMh3S2q3bgwhs","_tenant":"default","_deleted":false,"randomId":"egPP1YfGppPeBaDwjorUN2hObhOIyIiDDa6dMECKZUAVe9KcO9R3CG2HUZ9X7GDd","validUntil":1726737601618,"creationDate":1726736701618}} +{"type":"cmspages","payload":{"_id":"V80UfyIoYE6YTagwCGr3rHlD2QB0AAaE","body":"un super header","name":"page.html","path":"/apis/un-test-pour-el-cli/header","tags":[],"draft":"un super header","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{"from":"cli","_name":"page.html","_path":"/apis/un-test-pour-el-cli/header","_content_type":"text/html"},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":null}} +{"type":"cmspages","payload":{"_id":"62aae19a3600001707b32ec4","body":"
\n \n
\n
\n

Demande de contact

\n ×\n
\n

\n
\n

Le message a bien été envoyé.

\n
\n
\n
\n","name":"MODAL-success","path":null,"tags":[],"draft":"
\n \n
\n
\n

Demande de contact

\n ×\n
\n

\n
\n

Le message a bien été envoyé.

\n
\n
\n
\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1677069681802}} +{"type":"cmspages","payload":{"_id":"6514440c3d00001b85d2dc8a","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/parcours-api\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Devis

\n
\n
\n
\n

\n Le parcours Devis est la solution optimale pour intégrer une proposition d’assurance dans votre parcours utilisateur.\n

\n
\n Offres disponibles sur ce parcours\n
    \n
  • Offres AUTO, VÉLO et EDPM
  • \n
  • Offre MRH jeune
  • \n
  • Offre Travailleur Indépendant
  • \n
\n
\n
\n
\n \"parcours \n
\n
\n
\n
\n

Intégrez le devis d’assurance dans votre parcours utilisateur

\n
\n
\n
\n
\n \"icon\n Le parcours Devis vous permet d’intégrer sur votre environnement (app mobile ou site internet) la réalisation du devis d’assurance. Vos utilisateurs bénéficient d’un parcours sans couture.\n
\n
\n
\n
\n

Votre Site / App

\n

Votre utilisateur réalise de manière transparente son devis au sein de votre parcours, sans rupture.

\n

DEVIS

\n
\n
\n
\n
\n

MAIF.fr

\n

Votre utilisateur réalise sa souscription d’assurance sur MAIF.fr (via le lien reçu par email avec son devis)

\n

SOUSCRIPTION

\n
\n
\n
\n
\n

Partenaires Cibles

\n
\n
\n
\n Notre Parcours Devis est adapté pour les partenaires avec expertise assurance, avec un besoin d’intégration de l’assurance dans le parcours utilisateur.\n
\n \"parcours\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}","name":"parcours-devis","path":"/parcours-devis","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/parcours-api\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Devis

\n
\n
\n
\n

\n Le parcours Devis est la solution optimale pour intégrer une proposition d’assurance dans votre parcours utilisateur.\n

\n
\n Offres disponibles sur ce parcours\n
    \n
  • Offres AUTO, VÉLO et EDPM
  • \n
  • Offre MRH jeune
  • \n
  • Offre Travailleur Indépendant
  • \n
\n
\n
\n
\n \"parcours \n
\n
\n
\n
\n

Intégrez le devis d’assurance dans votre parcours utilisateur

\n
\n
\n
\n
\n \"icon\n Le parcours Devis vous permet d’intégrer sur votre environnement (app mobile ou site internet) la réalisation du devis d’assurance. Vos utilisateurs bénéficient d’un parcours sans couture.\n
\n
\n
\n
\n

Votre Site / App

\n

Votre utilisateur réalise de manière transparente son devis au sein de votre parcours, sans rupture.

\n

DEVIS

\n
\n
\n
\n
\n

MAIF.fr

\n

Votre utilisateur réalise sa souscription d’assurance sur MAIF.fr (via le lien reçu par email avec son devis)

\n

SOUSCRIPTION

\n
\n
\n
\n
\n

Partenaires Cibles

\n
\n
\n
\n Notre Parcours Devis est adapté pour les partenaires avec expertise assurance, avec un besoin d’intégration de l’assurance dans le parcours utilisateur.\n
\n \"parcours\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520456539}} +{"type":"cmspages","payload":{"_id":"655dcd5615000001e6aed967","body":"var sidenav = document.getElementById(\"mySidenav\");\nvar openBtn = document.getElementById(\"openBtn\");\nvar closeBtn = document.getElementById(\"closeBtn\");\n\nopenBtn.onclick = openNav;\ncloseBtn.onclick = closeNav;\n\n/* Set the width of the side navigation to 250px */\nfunction openNav() {\n sidenav.classList.add(\"active\");\n}\n\n/* Set the width of the side navigation to 0 */\nfunction closeNav() {\n sidenav.classList.remove(\"active\");\n}","name":"navbar-js","path":"/655dcd5615000001e6aed968","tags":[],"draft":"var sidenav = document.getElementById(\"mySidenav\");\nvar openBtn = document.getElementById(\"openBtn\");\nvar closeBtn = document.getElementById(\"closeBtn\");\n\nopenBtn.onclick = openNav;\ncloseBtn.onclick = closeNav;\n\n/* Set the width of the side navigation to 250px */\nfunction openNav() {\n sidenav.classList.add(\"active\");\n}\n\n/* Set the width of the side navigation to 0 */\nfunction closeNav() {\n sidenav.classList.remove(\"active\");\n}","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/javascript","authenticated":false,"lastPublishedDate":1700647278213}} +{"type":"cmspages","payload":{"_id":"654b95412100008ec5dcc05e","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-mobilite\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre MOTO

\n
\n
\n

\n Notre assurance moto et scooter s’ajuste aux besoins et aux budgets de vos utilisateurs : ils peuvent choisir parmi 4 formules au tiers ou tous risques, avec des garanties étendues et des options.\n

\n
\n
\n
\n

Des garanties solides adaptées aux motards

\n
\n
\n
\n
\n \"icon \n
\n
\n

Assistance sur la route 24h/24 et 7j/7

\n

Vos utilisateurs sont dépannés lors de leurs déplacements à plus de 50 km de chez eux. Et géolocalisés, avec l’application MAIF. Leur passager est également pris en charge et rapatrié.​

\n
\n
\n
\n
\n \"icon \n
\n
\n

Vol, incendie : même assuré au tiers

\n

La moto de vos utilisateurs est couverte dès la formule Tiers enrichie (Essentiel). Leur équipement de protection motard est pris en charge : casque, gants, gilet airbag, antivol.

\n
\n
\n
\n
\n \"icon \n
\n
\n

Protection du motard

\n

Un premier niveau de protection corporelle est inclus dans toutes nos formules, pour aider vos utilisateurs et leurs proches, en cas de blessure ou de décès.

\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","name":"offre-moto","path":"/offre-moto","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-mobilite\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre MOTO

\n
\n
\n

\n Notre assurance moto et scooter s’ajuste aux besoins et aux budgets de vos utilisateurs : ils peuvent choisir parmi 4 formules au tiers ou tous risques, avec des garanties étendues et des options.\n

\n
\n
\n
\n

Des garanties solides adaptées aux motards

\n
\n
\n
\n
\n \"icon \n
\n
\n

Assistance sur la route 24h/24 et 7j/7

\n

Vos utilisateurs sont dépannés lors de leurs déplacements à plus de 50 km de chez eux. Et géolocalisés, avec l’application MAIF. Leur passager est également pris en charge et rapatrié.​

\n
\n
\n
\n
\n \"icon \n
\n
\n

Vol, incendie : même assuré au tiers

\n

La moto de vos utilisateurs est couverte dès la formule Tiers enrichie (Essentiel). Leur équipement de protection motard est pris en charge : casque, gants, gilet airbag, antivol.

\n
\n
\n
\n
\n \"icon \n
\n
\n

Protection du motard

\n

Un premier niveau de protection corporelle est inclus dans toutes nos formules, pour aider vos utilisateurs et leurs proches, en cas de blessure ou de décès.

\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520379884}} +{"type":"cmspages","payload":{"_id":"656750ea2a00008e0f55a812","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n

UNIVERS Vie Quotidienne & Pro

\n
\n
\n
\n
\n \n \n
\n \"icon\n

Garanties Accidents de la Vie

\n
\n \n
\n \"icon\n

Propriétaire Bailleur

\n
\n
\n \"icon\n

Assurance Vie

\n
\n
\n
\n
\n \"icon\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","name":"univers-vie-quotidienne-pro","path":"/univers-vie-quotidienne-pro","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n

UNIVERS Vie Quotidienne & Pro

\n
\n
\n
\n
\n \n \n
\n \"icon\n

Garanties Accidents de la Vie

\n
\n \n
\n \"icon\n

Propriétaire Bailleur

\n
\n
\n \"icon\n

Assurance Vie

\n
\n
\n
\n
\n \"icon\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520479461}} +{"type":"cmspages","payload":{"_id":"65708cd52b0000c5d720a237","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-vie-quotidienne-pro\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre HABITATION JEUNES

\n
\n
\n

\n Pour les locataires ou colocataires de moins de 30 ans et sans enfant.\n

\n

\n S'assurer est une obligation légale pour tous les locataires.\n

\n

\n Notre Assurance Habitation Jeunes répond à ce devoir et va bien au-delà pour vos utilisateurs.\n

\n
\n \"offre \n
\n
\n
\n
\n

Garanties de l’offre habitation jeunes

\n
\n
\n
\n

Des garanties essentielles dans le logement…

\n
    \n
  • Incendie, explosion et dégâts des eaux
  • \n
  • Bris de vitre sur l’immobilier
  • \n
  • Evénement climatique
  • \n
  • Cambriolage…
  • \n
\n
\n
\n \"offre \n
\n
\n

… mais également en dehors

\n
    \n
  • Responsabilité Civile (dommages causés à un tiers)
  • \n
  • Vol des biens pendant le déménagement
  • \n
  • Assistance voyage rapatriement
  • \n
  • Extension Biens Nomades (assure partout les biens que vous transportez avec vous : Smartphone, PC portable, instrument de musique, matériel de sport…)
  • \n
\n
\n
\n
\n

Une assurance responsable adaptée à votre quotidien

\n
\n
\n
\n
\n \"icon \n
\n
\n

Prêter, emprunter, louer

\n

MAIF encourage le partage plutôt que l’achat. On assure les biens que vos utilisateurs louent, prêtent ou empruntent.

\n
\n
\n
\n
\n \"icon \n
\n
\n

Réparer plutôt que remplacer

\n

A chaque fois que cela est possible, en cas de sinistre, nous privilégions la réparation des biens plutôt que leur remplacement. Un petit geste pour la planète.

\n
\n
\n
\n
\n \"icon \n
\n
\n

Limiter vos frais en colocation

\n

Les colocataires sont tous assurés de la même manière sur le même contrat.

\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","name":"offre-mrh-jeunes","path":"/offre-mrh-jeunes","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-vie-quotidienne-pro\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre HABITATION JEUNES

\n
\n
\n

\n Pour les locataires ou colocataires de moins de 30 ans et sans enfant.\n

\n

\n S'assurer est une obligation légale pour tous les locataires.\n

\n

\n Notre Assurance Habitation Jeunes répond à ce devoir et va bien au-delà pour vos utilisateurs.\n

\n
\n \"offre \n
\n
\n
\n
\n

Garanties de l’offre habitation jeunes

\n
\n
\n
\n

Des garanties essentielles dans le logement…

\n
    \n
  • Incendie, explosion et dégâts des eaux
  • \n
  • Bris de vitre sur l’immobilier
  • \n
  • Evénement climatique
  • \n
  • Cambriolage…
  • \n
\n
\n
\n \"offre \n
\n
\n

… mais également en dehors

\n
    \n
  • Responsabilité Civile (dommages causés à un tiers)
  • \n
  • Vol des biens pendant le déménagement
  • \n
  • Assistance voyage rapatriement
  • \n
  • Extension Biens Nomades (assure partout les biens que vous transportez avec vous : Smartphone, PC portable, instrument de musique, matériel de sport…)
  • \n
\n
\n
\n
\n

Une assurance responsable adaptée à votre quotidien

\n
\n
\n
\n
\n \"icon \n
\n
\n

Prêter, emprunter, louer

\n

MAIF encourage le partage plutôt que l’achat. On assure les biens que vos utilisateurs louent, prêtent ou empruntent.

\n
\n
\n
\n
\n \"icon \n
\n
\n

Réparer plutôt que remplacer

\n

A chaque fois que cela est possible, en cas de sinistre, nous privilégions la réparation des biens plutôt que leur remplacement. Un petit geste pour la planète.

\n
\n
\n
\n
\n \"icon \n
\n
\n

Limiter vos frais en colocation

\n

Les colocataires sont tous assurés de la même manière sur le même contrat.

\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520526380}} +{"type":"cmspages","payload":{"_id":"623c6fe5350000e106e28d1b","body":"
\n
\n
\n
\n \n \n \n
\n \n
\n \n {{#if connected}}\n
\n \n \n \n \n \n
\n {{/if}}\n \n \n {{#unless connected}}\n \n {{/unless}} \n
\n \n
\n \n \n \n \n \n \n \n
\n ×\n \n
\n
\n
\n \n \n
\n
","name":"IAAS-BASIC-navbar","path":null,"tags":[],"draft":"
\n
\n
\n
\n \n \n \n
\n \n
\n \n {{#if connected}}\n
\n \n \n \n \n \n
\n {{/if}}\n \n \n {{#unless connected}}\n \n {{/unless}} \n
\n \n
\n \n \n \n \n \n \n \n
\n ×\n \n
\n
\n
\n \n \n
\n
","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520280358}} +{"type":"cmspages","payload":{"_id":"629f44d0440000921d1d297f","body":"// Modal Contact\nfunction closeModalContact(){\n document.getElementById(\"contact\").style.display = \"none\";\n}\n\nfunction closeModalSuccess(){\n document.getElementById(\"contact-success\").style.display = \"none\";\n}\n\nfunction openContact(){\n document.getElementById(\"contact\").style.display = \"block\";\n}\n\n// Modal Mentions Légales\nfunction openMentionsLegales() {\n document.getElementById(\"mentions-legales\").style.display = \"block\";\n}\n\nfunction closeMentionsLegales() {\n document.getElementById(\"mentions-legales\").style.display = \"none\";\n}\n\n// Modal Données personnelles\nfunction openDonneesPersonnelles() {\n document.getElementById(\"donnees-personnelles\").style.display = \"block\";\n}\n\nfunction closeDonneesPersonnelles() {\n document.getElementById(\"donnees-personnelles\").style.display = \"none\";\n}\n\n// Modal Commun\nwindow.onclick = function(event) {\n if (event.target == modalContact || event.target == modalContactSuccess || event.target == modalMentionsLegales || event.target == modalDonneesPersonnelles) {\n modalContact.style.display = \"none\";\n modalContactSuccess.style.display = \"none\";\n modalMentionsLegales.style.display = \"none\";\n modalDonneesPersonnelles.style.display = \"none\";\n }\n}","name":"MODAL-js","path":"/629f44d0440000921d1d2980","tags":[],"draft":"// Modal Contact\nfunction closeModalContact(){\n document.getElementById(\"contact\").style.display = \"none\";\n}\n\nfunction closeModalSuccess(){\n document.getElementById(\"contact-success\").style.display = \"none\";\n}\n\nfunction openContact(){\n document.getElementById(\"contact\").style.display = \"block\";\n}\n\n// Modal Mentions Légales\nfunction openMentionsLegales() {\n document.getElementById(\"mentions-legales\").style.display = \"block\";\n}\n\nfunction closeMentionsLegales() {\n document.getElementById(\"mentions-legales\").style.display = \"none\";\n}\n\n// Modal Données personnelles\nfunction openDonneesPersonnelles() {\n document.getElementById(\"donnees-personnelles\").style.display = \"block\";\n}\n\nfunction closeDonneesPersonnelles() {\n document.getElementById(\"donnees-personnelles\").style.display = \"none\";\n}\n\n// Modal Commun\nwindow.onclick = function(event) {\n if (event.target == modalContact || event.target == modalContactSuccess || event.target == modalMentionsLegales || event.target == modalDonneesPersonnelles) {\n modalContact.style.display = \"none\";\n modalContactSuccess.style.display = \"none\";\n modalMentionsLegales.style.display = \"none\";\n modalDonneesPersonnelles.style.display = \"none\";\n }\n}","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/javascript","authenticated":false,"lastPublishedDate":1705571631920}} +{"type":"cmspages","payload":{"_id":"62a8424a440000921d1d3f06","body":"
\n \n
\n
\n

Demande de contact

\n ×\n
\n

\n
\n
\n \n {{#if connected}}\n \n {{/if}}\n {{#unless connected}}\n \n {{/unless}}\n
\n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n \n \n \n
\n \n
\n
\n
\n
\n\n\n","name":"MODAL-formulaire-contact","path":null,"tags":[],"draft":"
\n \n
\n
\n

Demande de contact

\n ×\n
\n

\n
\n
\n \n {{#if connected}}\n \n {{/if}}\n {{#unless connected}}\n \n {{/unless}}\n
\n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n \n \n \n
\n \n
\n
\n
\n
\n\n\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1677069434665}} +{"type":"cmspages","payload":{"_id":"629f4106440000921d1d278f","body":"
\n \n
\n
\n

Mentions légales

\n ×\n
\n

\n
\n

MAIF - Société d’assurance mutuelle à cotisations variables – CS 90000 - 79038 Niort cedex 9.

\n
\n
\n

Entreprises régies par le Code des assurances.

\n
\n
\n
\n","name":"MODAL-mentions-legales","path":null,"tags":[],"draft":"
\n \n
\n
\n

Mentions légales

\n ×\n
\n

\n
\n

MAIF - Société d’assurance mutuelle à cotisations variables – CS 90000 - 79038 Niort cedex 9.

\n
\n
\n

Entreprises régies par le Code des assurances.

\n
\n
\n
\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1705570212987}} +{"type":"cmspages","payload":{"_id":"657076982b0000c5d7209a94","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-mobilite\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre EDPM (Engins de Déplacement Personnels Motorisés)

\n
\n
\n

\n Vos utilisateurs ont choisi de se déplacer autrement ?
Nous les accompagnons dans cette nouvelle mobilité plus respectueuse de l'environnement.​\n

\n

\n Notre offre assure tous les engins de déplacement personnels motorisés (EDPM).\n

\n
\n \"offre \n
\n
\n
\n
\n

3 raisons de choisir l’assurance MAIF

\n
\n
\n
\n
\n

1

\n

Les garanties

\n

Avec l’assurance EDPM la protection des personnes reste une priorité. Même assuré au tiers, vous bénéficiez de garanties étendues.

\n
\n
\n
\n
\n

2

\n

Assurance au tiers ou tous risques : à chacun sa formule

\n

Le meilleur contrat d’assurance EDPM est celui qui correspond à vos besoins.

\n
\n
\n
\n
\n

3

\n

L’option biens transportés accessible dans les deux formules

\n

Les effets personnels que vous emportez (smartphone, …) sont couverts en cas d’accident.

\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","name":"offre-edpm","path":"/offre-edpm","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-mobilite\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre EDPM (Engins de Déplacement Personnels Motorisés)

\n
\n
\n

\n Vos utilisateurs ont choisi de se déplacer autrement ?
Nous les accompagnons dans cette nouvelle mobilité plus respectueuse de l'environnement.​\n

\n

\n Notre offre assure tous les engins de déplacement personnels motorisés (EDPM).\n

\n
\n \"offre \n
\n
\n
\n
\n

3 raisons de choisir l’assurance MAIF

\n
\n
\n
\n
\n

1

\n

Les garanties

\n

Avec l’assurance EDPM la protection des personnes reste une priorité. Même assuré au tiers, vous bénéficiez de garanties étendues.

\n
\n
\n
\n
\n

2

\n

Assurance au tiers ou tous risques : à chacun sa formule

\n

Le meilleur contrat d’assurance EDPM est celui qui correspond à vos besoins.

\n
\n
\n
\n
\n

3

\n

L’option biens transportés accessible dans les deux formules

\n

Les effets personnels que vous emportez (smartphone, …) sont couverts en cas d’accident.

\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520582665}} +{"type":"cmspages","payload":{"_id":"623c6eac350000e106e28caa","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\"}}\n \n
\n
\n
\n
\n

Portail API MAIF

\n Le site dédié aux partenaires de distribution digitale des assurances MAIF\n
\n
\n

\n Proposez à vos utilisateurs des \n solutions d'assurance intégrées \n dans vos parcours digitaux

\n
\n
\n
\n \"image\n
\n
\n
\n{{/daikoku-template-wrapper}}","name":"home","path":"/","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\"}}\n \n
\n
\n
\n
\n

Portail API MAIF

\n Le site dédié aux partenaires de distribution digitale des assurances MAIF\n
\n
\n

\n Proposez à vos utilisateurs des \n solutions d'assurance intégrées \n dans vos parcours digitaux

\n
\n
\n
\n \"image\n
\n
\n
\n{{/daikoku-template-wrapper}}","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520690725}} +{"type":"cmspages","payload":{"_id":"6526b0273d00001b85d31feb","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n

UNIVERS MOBILITE

\n
\n
\n
\n \n
\n
\n \"icon\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","name":"univers-mobilite","path":"/univers-mobilite","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n

UNIVERS MOBILITE

\n
\n
\n
\n \n
\n
\n \"icon\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520659265}} +{"type":"cmspages","payload":{"_id":"651443f53d00001b85d2dc82","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/parcours-api\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Simulation

\n
\n
\n
\n

\n Le parcours Simulation est la solution idéale pour intégrer un premier niveau d’assurance dans votre parcours utilisateur, d’une manière intuitive et sans effort.\n

\n
\n Offres disponibles sur ce parcours\n
    \n
  • Offres AUTO, VÉLO et EDPM
  • \n
  • Offre MRH jeune
  • \n
  • Offre Travailleur Indépendant
  • \n
\n
\n
\n
\n \"parcours \n
\n
\n
\n
\n

Proposez une estimation tarifaire instantanée et personnalisée à vos utilisateurs

\n
\n
\n
\n
\n \"icon\n Intégrer la simulation à vos parcours permet de délivrer à vos utilisateurs, au regard des informations dont vous disposez, un premier niveau d'informations pour les guider dans leur choix d'assurance.\n
\n
\n
\n
\n

Votre Site / App

\n

Vous affichez à votre utilisateur une estimation tarifaire et l'invitez à réaliser un devis sur MAIF.fr

\n

SIMULATION

\n
\n
\n
\n
\n

MAIF.fr

\n

Votre utilisateur réalise son devis et sa souscription d’assurance sur MAIF.fr.

\n
\n
\n

DEVIS

\n
\n
\n

SOUSCRIPTION

\n
\n
\n
\n
\n
\n
\n

Partenaires Cibles

\n
\n
\n
\n Notre parcours Simulation est adapté pour les partenaires sans expertise assurance, mais dont les services ou produits proposés sont en lien direct ou indirect avec l’offre d’assurance.\n
\n \"parcours\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}","name":"parcours-simulation","path":"/parcours-simulation","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/parcours-api\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Simulation

\n
\n
\n
\n

\n Le parcours Simulation est la solution idéale pour intégrer un premier niveau d’assurance dans votre parcours utilisateur, d’une manière intuitive et sans effort.\n

\n
\n Offres disponibles sur ce parcours\n
    \n
  • Offres AUTO, VÉLO et EDPM
  • \n
  • Offre MRH jeune
  • \n
  • Offre Travailleur Indépendant
  • \n
\n
\n
\n
\n \"parcours \n
\n
\n
\n
\n

Proposez une estimation tarifaire instantanée et personnalisée à vos utilisateurs

\n
\n
\n
\n
\n \"icon\n Intégrer la simulation à vos parcours permet de délivrer à vos utilisateurs, au regard des informations dont vous disposez, un premier niveau d'informations pour les guider dans leur choix d'assurance.\n
\n
\n
\n
\n

Votre Site / App

\n

Vous affichez à votre utilisateur une estimation tarifaire et l'invitez à réaliser un devis sur MAIF.fr

\n

SIMULATION

\n
\n
\n
\n
\n

MAIF.fr

\n

Votre utilisateur réalise son devis et sa souscription d’assurance sur MAIF.fr.

\n
\n
\n

DEVIS

\n
\n
\n

SOUSCRIPTION

\n
\n
\n
\n
\n
\n
\n

Partenaires Cibles

\n
\n
\n
\n Notre parcours Simulation est adapté pour les partenaires sans expertise assurance, mais dont les services ou produits proposés sont en lien direct ou indirect avec l’offre d’assurance.\n
\n \"parcours\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520546449}} +{"type":"cmspages","payload":{"_id":"6579ca012a0000359b5c1e27","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n

NOS PARCOURS API

\n
\n
\n \n
\n \"icon\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","name":"parcours-api","path":"/parcours-api","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n

NOS PARCOURS API

\n
\n
\n \n
\n \"icon\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520703310}} +{"type":"cmspages","payload":{"_id":"65709cd72b0000c5d720aa98","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-vie-quotidienne-pro\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre TRAVAILLEUR INDÉPENDANT

\n
\n
\n

\n Une assurance qui couvre la responsabilité civile professionnelle (RC Pro) et les biens (PC, téléphone, locaux...) des auto-entrepreneurs, freelances, travailleurs indépendants.\n

\n
\n \"offre \n
\n
\n
\n
\n

Choisir MAIF pour que vos utilisateurs assurent leur(s) activité(s)

\n
\n
\n
\n
\n \"icon \n
\n
\n

Des garanties indispensables

\n

En plus des risques liés à l’activité de vos utilisateurs, leurs biens, leur local et même leur stock sont assurés.

\n
\n
\n
\n
\n \"icon \n
\n
\n

Le prêt d’objet couvert

\n

Tout bien que l’on prête à vos utilisateurs ou qu’ils prêtent dans le cadre de leur activité est couvert.

\n
\n
\n
\n
\n \"icon \n
\n
\n

Le + relationnel

\n

Des conseillers spécialisés pour guider vos utilisateurs dans leur activité.

\n
\n
\n
\n
\n

Les activités assurées par l’assurance MAIF

\n
\n
\n
\n

Les Activités éligibles

\n
    \n
  • Prestations intellectuelles
  • \n
  • Bien-être et Thérapie
  • \n
  • Petits Artisanats
  • \n
  • Culture, Loisirs, Art et Musique
  • \n
  • Sport
  • \n
  • Social & Services
  • \n
\n
\n
\n \"icon \n
\n
\n

Les Activités exclues

\n
    \n
  • BTP / Agriculture / Grossiste
  • \n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","name":"offre-travailleur-independant","path":"/offre-travailleur-independant","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-vie-quotidienne-pro\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre TRAVAILLEUR INDÉPENDANT

\n
\n
\n

\n Une assurance qui couvre la responsabilité civile professionnelle (RC Pro) et les biens (PC, téléphone, locaux...) des auto-entrepreneurs, freelances, travailleurs indépendants.\n

\n
\n \"offre \n
\n
\n
\n
\n

Choisir MAIF pour que vos utilisateurs assurent leur(s) activité(s)

\n
\n
\n
\n
\n \"icon \n
\n
\n

Des garanties indispensables

\n

En plus des risques liés à l’activité de vos utilisateurs, leurs biens, leur local et même leur stock sont assurés.

\n
\n
\n
\n
\n \"icon \n
\n
\n

Le prêt d’objet couvert

\n

Tout bien que l’on prête à vos utilisateurs ou qu’ils prêtent dans le cadre de leur activité est couvert.

\n
\n
\n
\n
\n \"icon \n
\n
\n

Le + relationnel

\n

Des conseillers spécialisés pour guider vos utilisateurs dans leur activité.

\n
\n
\n
\n
\n

Les activités assurées par l’assurance MAIF

\n
\n
\n
\n

Les Activités éligibles

\n
    \n
  • Prestations intellectuelles
  • \n
  • Bien-être et Thérapie
  • \n
  • Petits Artisanats
  • \n
  • Culture, Loisirs, Art et Musique
  • \n
  • Sport
  • \n
  • Social & Services
  • \n
\n
\n
\n \"icon \n
\n
\n

Les Activités exclues

\n
    \n
  • BTP / Agriculture / Grossiste
  • \n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520631296}} +{"type":"cmspages","payload":{"_id":"651443d43d00001b85d2dc7a","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/parcours-api\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Affiliation Digitale

\n
\n
\n
\n

\n Le parcours Affiliation Digitale est la solution la plus simple pour recommander les offres d’assurance MAIF auprès de vos utilisateurs.\n

\n
\n Offres disponibles sur ce parcours\n
    \n
  • Offres MOBILITÉ
  • \n
  • Offres MRH et MRH jeune
  • \n
  • Offre Travailleur Indépendant
  • \n
\n
\n
\n
\n \"parcours \n
\n
\n
\n
\n

Redirigez vos utilisateurs vers le parcours d’assurance dédié sur MAIF.fr

\n
\n
\n
\n
\n \"icon\n Un simple lien de redirection depuis votre site ou application mobile vers le parcours d’assurance dédié sur MAIF.fr.\n
\n
\n
\n
\n

Votre Site / App

\n

Votre utilisateur est informé du partenariat MAIF et invité à réaliser un devis en cliquant sur le lien tracké

\n

LIEN TRACKE

\n
\n
\n
\n
\n

MAIF.fr

\n

Votre utilisateur réalise l’intégralité de son expérience assurance sur MAIF.fr.

\n
\n
\n

DEVIS

\n
\n
\n

SOUSCRIPTION

\n
\n
\n
\n
\n
\n
\n

Partenaires Cibles

\n
\n
\n
\n Notre parcours Affiliation Digitale est adapté pour les partenaires sans expertise assurance, dont les services ou produits proposés ne sont pas en lien direct avec l’offre d’assurance.\n
\n \"parcours\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","name":"parcours-affiliation-digitale","path":"/parcours-affiliation-digitale","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/parcours-api\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Affiliation Digitale

\n
\n
\n
\n

\n Le parcours Affiliation Digitale est la solution la plus simple pour recommander les offres d’assurance MAIF auprès de vos utilisateurs.\n

\n
\n Offres disponibles sur ce parcours\n
    \n
  • Offres MOBILITÉ
  • \n
  • Offres MRH et MRH jeune
  • \n
  • Offre Travailleur Indépendant
  • \n
\n
\n
\n
\n \"parcours \n
\n
\n
\n
\n

Redirigez vos utilisateurs vers le parcours d’assurance dédié sur MAIF.fr

\n
\n
\n
\n
\n \"icon\n Un simple lien de redirection depuis votre site ou application mobile vers le parcours d’assurance dédié sur MAIF.fr.\n
\n
\n
\n
\n

Votre Site / App

\n

Votre utilisateur est informé du partenariat MAIF et invité à réaliser un devis en cliquant sur le lien tracké

\n

LIEN TRACKE

\n
\n
\n
\n
\n

MAIF.fr

\n

Votre utilisateur réalise l’intégralité de son expérience assurance sur MAIF.fr.

\n
\n
\n

DEVIS

\n
\n
\n

SOUSCRIPTION

\n
\n
\n
\n
\n
\n
\n

Partenaires Cibles

\n
\n
\n
\n Notre parcours Affiliation Digitale est adapté pour les partenaires sans expertise assurance, dont les services ou produits proposés ne sont pas en lien direct avec l’offre d’assurance.\n
\n \"parcours\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520418595}} +{"type":"cmspages","payload":{"_id":"659ec57d2a0000359b5ca6fc","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-vie-quotidienne-pro\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre HABITATION

\n
\n
\n

\n Propriétaire, locataire : le contrat d'Assurance Habitation MAIF protège votre logement et vos biens avec de solides garanties en cas de sinistre.\n

\n
\n \"offre \n
\n
\n
\n
\n

Bien plus qu’une assurance habitation

\n
\n
\n
\n
\n \"icon \n
\n
\n

Intempéries

\n

En cas d’inondation, même sans arrêté de catastrophes naturelles, vous êtes indemnisé.

\n \"offre \n
\n
\n
\n
\n \"icon\n
\n
\n

Assurance Scolaire

\n

Incluse pour chaque enfant sans surcoût.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Vélo à assistance électrique

\n

Assuré en formule 3 contre le vol et le bris en tous lieux (en option en formule 2).

\n
\n
\n
\n
\n

Choisir MAIF pour assurer votre mode de vie responsable

\n
\n
\n
\n
\n \"icon\n
\n
\n

Réparer plutôt que jeter

\n

Pour lutter contre le gaspillage, MAIF inclut une garantie panne pour vos appareils en formule 3.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Vos équipements écologiques sont couverts

\n

Sans supplément dès la formule de base.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Les biens que vous prêtez sont assurés

\n

Comme s’ils étaient chez vous.

\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","name":"offre-mrh","path":"/offre-mrh","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-vie-quotidienne-pro\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre HABITATION

\n
\n
\n

\n Propriétaire, locataire : le contrat d'Assurance Habitation MAIF protège votre logement et vos biens avec de solides garanties en cas de sinistre.\n

\n
\n \"offre \n
\n
\n
\n
\n

Bien plus qu’une assurance habitation

\n
\n
\n
\n
\n \"icon \n
\n
\n

Intempéries

\n

En cas d’inondation, même sans arrêté de catastrophes naturelles, vous êtes indemnisé.

\n \"offre \n
\n
\n
\n
\n \"icon\n
\n
\n

Assurance Scolaire

\n

Incluse pour chaque enfant sans surcoût.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Vélo à assistance électrique

\n

Assuré en formule 3 contre le vol et le bris en tous lieux (en option en formule 2).

\n
\n
\n
\n
\n

Choisir MAIF pour assurer votre mode de vie responsable

\n
\n
\n
\n
\n \"icon\n
\n
\n

Réparer plutôt que jeter

\n

Pour lutter contre le gaspillage, MAIF inclut une garantie panne pour vos appareils en formule 3.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Vos équipements écologiques sont couverts

\n

Sans supplément dès la formule de base.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Les biens que vous prêtez sont assurés

\n

Comme s’ils étaient chez vous.

\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520506557}} +{"type":"cmspages","payload":{"_id":"629f15aa440000921d1d2372","body":"
\n \n
\n
\n

Données personnnelles

\n ×\n
\n

Politique de confidentialité du Portail API MAIF

\n
\n

Données personnelles :

\n

Par donnée à caractère personnel, il y a lieu d’entendre toutes les informations qui permettent de vous identifier directement ou indirectement (croisement d’un ensemble de données) telles que noms, prénoms, adresse postale et de courrier électronique, numéro de téléphone… Lors de votre accès au portail de l’API MAIF, nous sommes amenés à collecter des données à caractère personnel, notamment dans le cadre de la création de votre compte ou lorsque vous nous contacter.

\n
\n\n
\n

Qui sont les responsables de traitements ?

\n

MAIF Société d’Assurance Mutuelle à cotisations variables, entreprise régie par le code des assurances. Adresse postale : 200 avenue Salvador Allende - CS 90000 - 79038 NIORT cedex 9.

\n
\n
\n

Comment utilisons-nous vos données ?

\n

Vos données personnelles sont utilisées dans le cadre de notre relation pour répondre à plusieurs finalités pour l’utilisation du portail :

\n
    \n
  • La création de votre compte utilisateur et votre authentification
  • \n
  • La prise en charge de vos demandes via le formulaire de contact
  • \n
\n
\n
\n

Qui sont les Destinataires des données ?

\n

Les données que nous collectons vous concernant, sont destinées aux entreprises du groupe MAIF et à nos prestataires de service, agissant en qualité de sous-traitants lorsqu’ils concourent à la réalisation des finalités pour lesquelles vos données sont collectées. Pour assurer la confidentialité de vos données personnelles, nous nous prévoyons dans les contrats des obligations de confidentialité à la charge des sous-traitants.

\n
\n
\n

Quels sont vos droits ?

\n

Le droit d’accès et de rectification : Vous pouvez demander l’accès et la rectification des données inexactes vous concernant dont nous disposons sur vous.

\n

Le droit à la portabilité : Vous pouvez demander à récupérer les données que vous avez fourni dans un format informatique pour un usage personnel ou pour les transmettre à un tiers de votre choix.

\n

Le droit à l’oubli : Il s’agit du droit d'obtenir la suppression des informations vous concernant détenues par la MAIF dans la mesure où elles ne sont plus nécessaires un contrat ou un service.

\n

Le droit d'opposition : Vous pouvez demander l’opposition au traitement de vos données. Pour se faire, vous devez exposer des raisons tenant à votre situation particulière.

\n

Le droit de limitation du traitement : Vous pouvez nous demander la limitation de certaines de vos données lorsqu’elles ne sont plus nécessaires ou ne sont plus nécessaires dans notre relation contractuelle.

\n

Le droit de définir le sort de vos données post mortem : Vous pouvez définir des directives relatives à la conservation, à l'effacement et à la communication de vos données après votre décès.

\n
\n
\n

Combien de temps conservons-nous vos données et quelle est leur localisation ?

\n

La durée de conservation de vos données personnelles ne dépassera pas 6 mois après la suppression de votre compte. Vos données sont traitées et conservées sur le territoire de l’Union Européenne. Toutefois, dans de très rares hypothèses, elles peuvent faire l’objet d’un transfert hors de ce territoire. Lorsque c’est le cas, vous bénéficiez d’une information spécifique lors de la collecte et nous mettons en œuvre des mesures pour assurer que la législation européenne est respectée afin de vous apporter une sécurité plus importante.

\n
\n
\n

Vos contacts au sein du groupe MAIF :

\n

Pour exercer vos droits, vous pouvez contacter directement : claire.du.mesnildot@maif.fr MAIF a désigné un délégué à la protection des données personnelles dont la mission est de vous aider pour exercer vos droits. Vous pouvez le contacter :

\n
    \n
  • Par courrier postal en écrivant à : Délégué à la protection des données, 200 avenue Salvador Allende - CS 90000 - 79038 NIORT cedex 9
  • \n
  • Par courrier électronique en écrivant à l’adresse de courriel : vosdonnees@maif.fr
  • \n
\n
\n
\n
","name":"MODAL-donnees-personnelles","path":null,"tags":[],"draft":"
\n \n
\n
\n

Données personnnelles

\n ×\n
\n

Politique de confidentialité du Portail API MAIF

\n
\n

Données personnelles :

\n

Par donnée à caractère personnel, il y a lieu d’entendre toutes les informations qui permettent de vous identifier directement ou indirectement (croisement d’un ensemble de données) telles que noms, prénoms, adresse postale et de courrier électronique, numéro de téléphone… Lors de votre accès au portail de l’API MAIF, nous sommes amenés à collecter des données à caractère personnel, notamment dans le cadre de la création de votre compte ou lorsque vous nous contacter.

\n
\n\n
\n

Qui sont les responsables de traitements ?

\n

MAIF Société d’Assurance Mutuelle à cotisations variables, entreprise régie par le code des assurances. Adresse postale : 200 avenue Salvador Allende - CS 90000 - 79038 NIORT cedex 9.

\n
\n
\n

Comment utilisons-nous vos données ?

\n

Vos données personnelles sont utilisées dans le cadre de notre relation pour répondre à plusieurs finalités pour l’utilisation du portail :

\n
    \n
  • La création de votre compte utilisateur et votre authentification
  • \n
  • La prise en charge de vos demandes via le formulaire de contact
  • \n
\n
\n
\n

Qui sont les Destinataires des données ?

\n

Les données que nous collectons vous concernant, sont destinées aux entreprises du groupe MAIF et à nos prestataires de service, agissant en qualité de sous-traitants lorsqu’ils concourent à la réalisation des finalités pour lesquelles vos données sont collectées. Pour assurer la confidentialité de vos données personnelles, nous nous prévoyons dans les contrats des obligations de confidentialité à la charge des sous-traitants.

\n
\n
\n

Quels sont vos droits ?

\n

Le droit d’accès et de rectification : Vous pouvez demander l’accès et la rectification des données inexactes vous concernant dont nous disposons sur vous.

\n

Le droit à la portabilité : Vous pouvez demander à récupérer les données que vous avez fourni dans un format informatique pour un usage personnel ou pour les transmettre à un tiers de votre choix.

\n

Le droit à l’oubli : Il s’agit du droit d'obtenir la suppression des informations vous concernant détenues par la MAIF dans la mesure où elles ne sont plus nécessaires un contrat ou un service.

\n

Le droit d'opposition : Vous pouvez demander l’opposition au traitement de vos données. Pour se faire, vous devez exposer des raisons tenant à votre situation particulière.

\n

Le droit de limitation du traitement : Vous pouvez nous demander la limitation de certaines de vos données lorsqu’elles ne sont plus nécessaires ou ne sont plus nécessaires dans notre relation contractuelle.

\n

Le droit de définir le sort de vos données post mortem : Vous pouvez définir des directives relatives à la conservation, à l'effacement et à la communication de vos données après votre décès.

\n
\n
\n

Combien de temps conservons-nous vos données et quelle est leur localisation ?

\n

La durée de conservation de vos données personnelles ne dépassera pas 6 mois après la suppression de votre compte. Vos données sont traitées et conservées sur le territoire de l’Union Européenne. Toutefois, dans de très rares hypothèses, elles peuvent faire l’objet d’un transfert hors de ce territoire. Lorsque c’est le cas, vous bénéficiez d’une information spécifique lors de la collecte et nous mettons en œuvre des mesures pour assurer que la législation européenne est respectée afin de vous apporter une sécurité plus importante.

\n
\n
\n

Vos contacts au sein du groupe MAIF :

\n

Pour exercer vos droits, vous pouvez contacter directement : claire.du.mesnildot@maif.fr MAIF a désigné un délégué à la protection des données personnelles dont la mission est de vous aider pour exercer vos droits. Vous pouvez le contacter :

\n
    \n
  • Par courrier postal en écrivant à : Délégué à la protection des données, 200 avenue Salvador Allende - CS 90000 - 79038 NIORT cedex 9
  • \n
  • Par courrier électronique en écrivant à l’adresse de courriel : vosdonnees@maif.fr
  • \n
\n
\n
\n
","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1705571459321}} +{"type":"cmspages","payload":{"_id":"659e65a42a0000359b5c58bc","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-mobilite\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n
\n \"icon \n \"icon \n
\n

Offre AUTO & Camping-Cars

\n
\n
\n

\n Au tiers ou tous risques, vos utilisateurs peuvent compter sur les garanties de notre assurance auto/camping-cars VAM.\n

\n

\n Formules, options, franchises : vos utilisateurs ont la possibilité d’ajuster selon leurs besoins et de maîtriser le prix de leur assurance.\n

\n
\n
\n
\n

Des garanties et un service client qui font la différence

\n
\n
\n
\n
\n \"icon \n
\n
\n

Assistance sur la route 24h/24 et 7j/7

\n

Une Panne ? Vos utilisateurs sont dépannés dès la formule au tiers pour leurs trajets à plus de 50 km du domicile. Et même devant chez eux avec la formule Plénitude ou en option.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Remboursements

\n

Le véhicule de vos utilisateurs irréparable ou volé est indemnisé à valeur d’achat jusqu’à ses 4 ans, avec la formule Plénitude.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Intempéries… tous assurés !

\n

Dès la formule au tiers, le véhicule de vos utilisateurs est pris en charge en cas de grêle, tempête, inondation.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Service Client

\n

Trouver des solutions, indemniser, c’est notre métier. Depuis 19 ans, MAIF est numéro 1 de la Relation Client1.

\n
\n
\n
\n
\n

En cas de sinistre, comment ça se passe ?

\n
\n
\n
\n
\n \"icon\n
\n
\n

Assistance et suivi de la dépanneuse

\n

Vos utilisateurs peuvent contacter l’assistance MAIF 24h/24, 7j/7 puis suivre le trajet de la dépanneuse sur leur smartphone avec l’application MAIF.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Constat et déclaration en ligne

\n

Un accident ? Vos utilisateurs peuvent réaliser un (e)constat et déclarer leur sinistre en ligne, sans attendre.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Réparer sans gaspiller

\n

Depuis 2013, MAIF propose des réparations avec pièces recyclées, avec l’accord de vos utilisateurs. La réparation est garantie à vie2.

\n
\n
\n
\n
\n
\n

1.

\n

N°1 du Podium de la Relation Client. Enquête réalisée par BearingPoint et Kantar fin 2022 auprès de 4000 personnes.

\n
\n
\n

2.

\n

Dans la limite du vieillissement des pièces dû au soleil, à l’usure normale, aux intempéries ou au défaut d’entretien.

\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","name":"offre-auto-cc","path":"/offre-auto-cc","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-mobilite\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n
\n \"icon \n \"icon \n
\n

Offre AUTO & Camping-Cars

\n
\n
\n

\n Au tiers ou tous risques, vos utilisateurs peuvent compter sur les garanties de notre assurance auto/camping-cars VAM.\n

\n

\n Formules, options, franchises : vos utilisateurs ont la possibilité d’ajuster selon leurs besoins et de maîtriser le prix de leur assurance.\n

\n
\n
\n
\n

Des garanties et un service client qui font la différence

\n
\n
\n
\n
\n \"icon \n
\n
\n

Assistance sur la route 24h/24 et 7j/7

\n

Une Panne ? Vos utilisateurs sont dépannés dès la formule au tiers pour leurs trajets à plus de 50 km du domicile. Et même devant chez eux avec la formule Plénitude ou en option.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Remboursements

\n

Le véhicule de vos utilisateurs irréparable ou volé est indemnisé à valeur d’achat jusqu’à ses 4 ans, avec la formule Plénitude.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Intempéries… tous assurés !

\n

Dès la formule au tiers, le véhicule de vos utilisateurs est pris en charge en cas de grêle, tempête, inondation.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Service Client

\n

Trouver des solutions, indemniser, c’est notre métier. Depuis 19 ans, MAIF est numéro 1 de la Relation Client1.

\n
\n
\n
\n
\n

En cas de sinistre, comment ça se passe ?

\n
\n
\n
\n
\n \"icon\n
\n
\n

Assistance et suivi de la dépanneuse

\n

Vos utilisateurs peuvent contacter l’assistance MAIF 24h/24, 7j/7 puis suivre le trajet de la dépanneuse sur leur smartphone avec l’application MAIF.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Constat et déclaration en ligne

\n

Un accident ? Vos utilisateurs peuvent réaliser un (e)constat et déclarer leur sinistre en ligne, sans attendre.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Réparer sans gaspiller

\n

Depuis 2013, MAIF propose des réparations avec pièces recyclées, avec l’accord de vos utilisateurs. La réparation est garantie à vie2.

\n
\n
\n
\n
\n
\n

1.

\n

N°1 du Podium de la Relation Client. Enquête réalisée par BearingPoint et Kantar fin 2022 auprès de 4000 personnes.

\n
\n
\n

2.

\n

Dans la limite du vieillissement des pièces dû au soleil, à l’usure normale, aux intempéries ou au défaut d’entretien.

\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520722284}} +{"type":"cmspages","payload":{"_id":"659e60a72a0000359b5c5705","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/parcours-api\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Souscription

\n
\n
\n

\n Parcours en développement par les équipes MAIF.\n

\n
\n \"parcours \n
\n
\n
\n
\n

Partenaires Cibles

\n
\n
\n
\n Notre parcours Souscription est adapté aux partenaires avec un besoin d’intégration complet de l’assurance dans le parcours d’achat du bien ou du service proposé à leurs utilisateurs utilisateur.\n
\n \"parcours\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}","name":"parcours-souscription","path":"/parcours-souscription","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/parcours-api\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Souscription

\n
\n
\n

\n Parcours en développement par les équipes MAIF.\n

\n
\n \"parcours \n
\n
\n
\n
\n

Partenaires Cibles

\n
\n
\n
\n Notre parcours Souscription est adapté aux partenaires avec un besoin d’intégration complet de l’assurance dans le parcours d’achat du bien ou du service proposé à leurs utilisateurs utilisateur.\n
\n \"parcours\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520773400}} +{"type":"cmspages","payload":{"_id":"654b545f2100008ec5dca38d","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-mobilite\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre CORPORELLE (PACS)

\n
\n
\n

\n Vos utilisateurs sont des utilisateurs de solutions de mobilité douce ou partagée ?\n

\n

\n En cas d'accident, l’assurance corporelle PACS garantit une indemnisation de vos utilisateurs dans leurs mobilités à l’usage mais aussi en covoiturage et transports en commun.\n

\n
\n \"offre \n
\n
\n
\n
\n \"offre \n

Quels trajets sont couverts par notre assurance PACS ?

\n
\n
\n
\n \"offre\n
    \n
  • En train, métro, tram, bus, car
  • \n
  • En covoiturage, VTC, taxi
  • \n
  • Dans ma voiture de fonction, une voiture empruntée à un tiers
  • \n
  • Dans une location courte durée, libre-service : vélo …
  • \n
\n
\n
\n

Le contrat PACS couvrira vos trajets en véhicule de fonction, covoiturage, transports en commun, location en libre-service…

\n

Vous bénéficiez d’une prise en charge immédiate et d’un accompagnement dans vos démarches vis-à-vis des opérateurs de mobilité (vélo libre-service, train, tram, métro…)

\n
\n
\n
\n
\n

… mais également dans leurs véhicules assurés MAIF

\n
    \n
  • En voiture, en Camping-cars
  • \n
  • En moto, en scooter, en cyclomoteur
  • \n
  • En trottinette électrique
  • \n
  • En vélo 100% électrique (vitesse allant jusqu’à 45km/h)
  • \n
\n
\n
\n \"offre \n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","name":"offre-corporelle","path":"/offre-corporelle","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-mobilite\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre CORPORELLE (PACS)

\n
\n
\n

\n Vos utilisateurs sont des utilisateurs de solutions de mobilité douce ou partagée ?\n

\n

\n En cas d'accident, l’assurance corporelle PACS garantit une indemnisation de vos utilisateurs dans leurs mobilités à l’usage mais aussi en covoiturage et transports en commun.\n

\n
\n \"offre \n
\n
\n
\n
\n \"offre \n

Quels trajets sont couverts par notre assurance PACS ?

\n
\n
\n
\n \"offre\n
    \n
  • En train, métro, tram, bus, car
  • \n
  • En covoiturage, VTC, taxi
  • \n
  • Dans ma voiture de fonction, une voiture empruntée à un tiers
  • \n
  • Dans une location courte durée, libre-service : vélo …
  • \n
\n
\n
\n

Le contrat PACS couvrira vos trajets en véhicule de fonction, covoiturage, transports en commun, location en libre-service…

\n

Vous bénéficiez d’une prise en charge immédiate et d’un accompagnement dans vos démarches vis-à-vis des opérateurs de mobilité (vélo libre-service, train, tram, métro…)

\n
\n
\n
\n
\n

… mais également dans leurs véhicules assurés MAIF

\n
    \n
  • En voiture, en Camping-cars
  • \n
  • En moto, en scooter, en cyclomoteur
  • \n
  • En trottinette électrique
  • \n
  • En vélo 100% électrique (vitesse allant jusqu’à 45km/h)
  • \n
\n
\n
\n \"offre \n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520736622}} +{"type":"cmspages","payload":{"_id":"62457958430000127e85c5cf","body":"*,\n*::after,\n*::before {\n margin: 0;\n padding: 0;\n box-sizing: inherit;\n}\n\nul{\n list-style-type: none;\n}\n\n.maif-color-text{\n color:#fc0d1a;\n}\n\nheader {\n text-align: center;\n background-color: #F2F2F2\n}\n\nheader a {\n color: black;\n text-decoration: none;\n}\n\nheader a:hover{\n color: black;\n text-decoration: none;\n font-weight: 600;\n}\n\n.header-left img {\n height: auto;\n max-width: 59px;\n}\n\n.header-right .non-connecte a, header .header-right .non-connecte button {\n color: #2e404f;\n cursor: pointer;\n padding: 6px 12px;\n align-items: center;\n text-align: center;\n font-size: 1.5rem;\n}\n\n.header-right .non-connecte a .icon, header .header-right .non-connecte button .icon {\n width: 38px;\n border-radius: 50%;\n}\n\n.header-right img{\n max-width: 30px;\n border-radius: 50%;\n}\n\nheader-left {\n text-align: left;\n}\n\n.header-right {\n display: flex;\n justify-content: start\n}\n\n.header-right .buttons {\n display: flex;\n flex-direction: column;\n}\n\n.header-right .buttons a {\n justify-content: start;\n display: flex;\n padding: 0;\n color: #212529;\n}\n\n.container-burger-menu {\n justify-content: right;\n margin-top: 0.5em;\n}\n\n.sidenav {\n height: 100%;\n width: 290px;\n position: fixed;\n z-index: 1;\n top: 0;\n right: -290px;\n background-color: #e8e8e8;\n padding-top: 60px;\n transition: right 0.5s ease;\n}\n\n/* Sidenav menu links */\n.sidenav a {\n padding: 8px 8px 8px 32px;\n text-decoration: none;\n font-size: 25px;\n color: #818181;\n display: block;\n transition: 0.3s;\n}\n\n.sidenav a:hover {\n color: #111;\n}\n\n.sidenav ul {\n text-align: left;\n list-style-type: none;\n padding: 0;\n margin: 0;\n}\n\n/* Active class */\n.sidenav.active {\n right: 0;\n}\n\n/* Close btn */\n.sidenav .close {\n position: absolute;\n top: 0;\n right: 25px;\n font-size: 36px;\n}\n\n/* Icône burger */\n.burger-icon span {\n display: block;\n width: 35px;\n height: 5px;\n background-color: black;\n margin: 6px 0;\n}\n\nbody {\n font-family: Custom, Raleway, Helvetica, sans-serif;\n margin: 0px;\n min-height: 100vh;\n display: flex;\n flex-direction: column\n}\n\n.offre-title {\n display: flex;\n flex-direction: row;\n align-items: center;\n border-bottom: solid #fc0d1a 5px;\n}\n\n.offre-title-double {\n display: flex;\n flex-direction: column;\n}\n\n.offre-title-double img:not(:last-child) {\n margin-bottom: -20px;\n}\n\n.offre-title-double img {\n padding-bottom: 0 !important;\n}\n\n.offre-title img {\n padding-bottom: 0.7em;\n padding-right: 3em;\n}\n\n.offre-title-detail {\n font-size: 12px;\n}\n\n.offre-title-2 {\n font-size: 18px;\n}\n\n.offre-text {\n font-size: 20px;\n}\n\n.offre-bandeau {\n display: flex;\n flex-direction: row;\n justify-content: center;\n align-items: center;\n background-color: #FBEBE9;\n padding: 10px;\n min-height: 120px;\n text-align: center;\n}\n\n.offre-bandeau img {\n padding-right: 5em;\n}\n\n.offre-bandeau-reverse img {\n -webkit-transform: scaleX(-1);\n padding-left: 5em;\n}\n\n.offre-description-text {\n display: flex;\n flex-direction: column;\n}\n\n.offre-description-text-margin {\n margin-top: 3em;\n}\n\n.offre-img-padding {\n align-items: end;\n}\n\n.offre-img-padding img{\n width: -webkit-fill-available;\n}\n\n.offre-ul {\n > li {\n font-style: italic;\n }\n}\n\n.offre-li-detail {\n font-size: 10px;\n}\n\n.img-reduction img {\n width: 70%;\n height: 80%;\n}\n\n.container {\n flex: 1; \n}\n\n.home-section .home-section-header {\n display: flex;\n width: 100%;\n flex-direction: column;\n}\n\n.home-section-body > div {\n margin-bottom: 3rem;\n}\n\n.img-inherit img {\n width: inherit;\n}\n\n.home-img img {\n width: 485px; \n}\n\n.home-section-side { \n padding-top: 4em;\n}\n\n.home-section-side img {\n margin-top: 1em;\n max-width: 130px;\n}\n\n.icon-menu {\n border: solid 1px grey;\n border-radius: 0.75em;\n margin-bottom: 1em;\n align-items: center;\n display: flex;\n flex-direction: column;\n width: 140px;\n height: 140px;\n text-align: center;\n}\n\n.icon-menu-disabled {\n filter: grayscale(1);\n}\n\n.icon-menu img {\n height: 80px;\n width: 80px;\n}\n\n.icon-menu a {\n color: black;\n text-decoration: none\n}\n\n.icon-menu a:hover{\n color: black;\n text-decoration: none;\n font-weight: 600;\n}\n\n.icon-menu-text {\n margin-bottom: 0 !important\n}\n\n.fa-check:before {\n content: \"\\f00c\";\n font-family: 'FontAwesome';\n color: #fc0d1a;\n padding-right: 0.3em;\n}\n\n.fa-close:before {\n content: \"\\f00c\";\n font-family: 'FontAwesome';\n color: #fc0d1a;\n padding-right: 0.3em;\n}\n\n.parcours-span {\n text-align: center;\n img {\n max-width:130px\n }\n margin-bottom: 1.5em;\n}\n\n.parcours-span-block {\n align-items: center;\n}\n\n.offre-parcours {\n padding-top: 3em;\n}\n\n.offre-parcours-li:before {\n content: '- '\n}\n\n.schema-bloc {\n width:50%;\n}\n\n.schema-marge {\n margin-right: 0.5em;\n width: auto !important\n}\n\n.schema-title {\n border: 1px solid;\n height: 30px;\n align-items: center;\n display: flex;\n justify-content: center;\n border-radius: 5px;\n width: 100%;\n}\n\n.schema-text {\n background-color: #F2F2F2;\n justify-content: center;\n display: flex;\n align-items: center;\n padding: 0.5em 0.25em 0.5em 0.25em;\n border-radius: 5px;\n font-size: 12px;\n height: 70px;\n align-items: center;\n text-align: center;\n margin-bottom: 0.25em;\n width: 100%;\n}\n\n.schema-text-title {\n font-size: 18px;\n color: #fc0d1a;\n font-weight: bold;\n}\n\n.axterisque-block {\n margin-bottom: 0 !important;\n}\n\n.axterisque {\n display: flex;\n flex-direction: row;\n font-size: 10px;\n}\n\n.axterisque p {\n margin-bottom: 0 !important\n}\n\n.axterisque p:not(:last-child) {\n padding-right: 3em;\n}\n\n.contactez-nous {\n display: flex;\n flex-direction: row;\n align-items: center;\n justify-content: right;\n margin-right: 2em;\n}\n\n.contactez-nous button {\n display: flex;\n flex-direction: row;\n align-items: center;\n cursor: pointer;\n border: 0;\n font-family: inherit;\n margin: 0;\n text-decoration: none;\n background-color: transparent;\n}\n\n.contactez-nous button:focus {\n outline: none;\n}\n\n.contactez-nous button > p {\n font-size: 12px;\n font-weight: bold;\n padding-top: 1.1em;\n}\n\nfooter .navbar {\n display: flex;\n justify-content: space-between;\n/* min-height: 60px; */\n font-size: 14px;\n background-image: linear-gradient(to left, #684a52, #595667);\n color: #fff;\n}\n\nfooter .navbar .liens-utiles {\n padding: 0 0 0 1.5rem;\n}\n\nfooter .navbar .liens-utiles button, footer .navbar .liens-utiles a {\n cursor: pointer;\n color: #fff;\n background-color: transparent;\n border: 0;\n font-family: inherit;\n margin: 0;\n text-decoration: none;\n}\n\nfooter .navbar .liens-utiles button:after, footer .navbar .liens-utiles a:after {\n content: \"|\";\n margin: 0 0.5rem;\n}\n\nfooter .reseaux-sociaux {\n padding: 0 0 0 1.5rem;\n}\n\nfooter .reseaux-sociaux button, footer .reseaux-sociaux a {\n cursor: pointer;\n color: #fff;\n background-color: transparent;\n border: 0;\n font-family: inherit;\n margin: 0;\n}\n\n@media screen and (min-width: 250px) {\n .offre-block-img {\n width: 70px;\n } \n .offre-block-img img {\n width: 69px;\n height: 69px;\n } \n .offre-block-text {\n min-width: 180px;\n }\n}\n\n@media screen and (max-width: 250px) {\n .offre-block-img {\n display: none;\n }\n .offre-block-text {\n width: inherit;\n } \n}\n\n@media screen and (min-width: 298px) {\n .icon-menu {\n margin-right: 1em;\n }\n}\n\n@media screen and (max-width: 330px) {\n .menu-title {\n text-align: center;\n }\n}\n\n@media screen and (max-width: 400px) {\n .offre-title img {\n display: none;\n }\n .offre-bandeau img {\n display: none;\n }\n .schema-text-title {\n font-size: 12px !important;\n }\n .schema-text {\n font-size: 12px !important;\n }\n}\n\n@media screen and (min-width: 768px) {\n .fa-arrow-right:before {\n content: \"\\f061c\";\n font-family: 'FontAwesome';\n color: #fc0d1a;\n font-size:2.5em;\n margin-right: -15px;\n }\n .fa-arrow-down:before {\n display:none\n }\n .arrow {\n margin-left: -30px;\n height: auto;\n display: flex;\n align-items: end;\n } \n .border-dotted {\n border-right: dotted 3px red;\n }\n .schema {\n margin-right: 1em !important;\n }\n}\n@media screen and (max-width: 768px) {\n .fa-arrow-right:before {\n display:none\n }\n .fa-arrow-down:before {\n content: \"\\f061c\";\n font-family: 'FontAwesome';\n color: #fc0d1a;\n font-size:2.5em;\n }\n}\n@media screen and (min-width: 992px) {\n .home-section .home-section-left span {\n text-wrap: nowrap;\n z-index: 1;\n }\n .home-section .home-section-left {\n margin-top: 5em;\n }\n .home-section .home-section-left .en-tete {\n margin-top: 3em;\n text-align: end;\n }\n .en-tete {\n font-size: 23px;\n line-height: 2rem;\n }\n .offre-parcours-img {\n display: inline-flex!important;\n align-items: end;\n }\n}\n\n\n@media screen and (max-width: 992px) {\n .offre-parcours-img {\n display: none;\n }\n}\n\n.modal {\n display: none;\n /* Hidden by default */\n position: fixed;\n z-index: 9999;\n padding-top: 20px;\n left: 0;\n top: 0;\n width: 100%;\n height: 100%;\n overflow: auto;\n background-color: rgb(0, 0, 0);\n /* Fallback color */\n background-color: rgba(0, 0, 0, 0.4);\n /* Black w/ opacity */\n}\n.modal .modal-content {\n background-color: #fefefe;\n margin: auto;\n padding: 20px;\n max-width: 800px;\n border: 1px solid #888;\n border-radius: 5px;\n width: 80%;\n}\n\n.modal-content-scroll {\n overflow-y: scroll;\n height: 100%;\n}\n\n.modal .modal-content .modal-header {\n border-bottom: 1px solid #dee2e6;\n display: flex;\n justify-content: space-between;\n padding: 1rem;\n}\n.modal .modal-content .modal-header span {\n cursor: pointer;\n}\n.modal .modal-content .modal-footer {\n border-top: 1px solid #dee2e6;\n display: flex;\n justify-content: flex-end;\n padding: 1rem 1rem 0rem 1rem;\n}\n.modal .modal-content .close {\n color: #aaaaaa;\n float: right;\n font-size: 28px;\n font-weight: bold;\n}\n.modal .modal-content .close:hover,\n.modal .modal-content .close:focus {\n color: #000;\n text-decoration: none;\n cursor: pointer;\n}\n.modal .modal-content h1 {\n line-height: 1.5;\n margin-top: 0;\n margin-bottom: 0;\n font-size: 1.25rem;\n color: rgba(0, 0, 0, 0.85);\n font-weight: 500;\n}\n.modal .modal-content h2 {\n line-height: 1.2;\n margin-top: 0.5rem;\n margin-bottom: 0.5rem;\n font-size: 1.5rem;\n font-weight: 500;\n}\n.modal .modal-content h3 {\n color: #c62222;\n line-height: 1.2;\n margin-top: 0;\n margin-bottom: 0.5rem;\n font-size: 1.25rem;\n font-weight: 500;\n}\n.modal .modal-content p {\n margin-bottom: 1rem;\n text-align: justify;\n}\n.modal .modal-content ul {\n margin-bottom: 1em;\n margin-top: 0;\n}\n.modal .modal-content ul li {\n list-style-type: none;\n padding-left: 2rem;\n}\n.modal .modal-content form {\n margin: 1rem 0;\n font-weight: 500;\n}\n.modal .modal-content form .form-group {\n margin-bottom: 1rem;\n padding-bottom: 10px;\n}\n.modal .modal-content form .form-group label {\n display: inline-block;\n margin-bottom: 0.5rem;\n}\n.modal .modal-content form .form-group .form-control {\n background-clip: padding-box;\n background-color: #fff;\n border: 1px solid #ced4da;\n border-radius: 0.25rem;\n color: #495057;\n display: block;\n font-size: 1rem;\n font-weight: 400;\n height: calc(1.5em + 0.75rem + 2px);\n line-height: 1.5;\n padding: 0.375rem 2%;\n transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n width: 96%;\n}\n.modal .modal-content form .form-group select.form-control[multiple], .modal .modal-content form .form-group select.form-control[size], .modal .modal-content form .form-group textarea.form-control {\n height: auto;\n}\n.modal .modal-content form button {\n background-color: transparent;\n border: 1px solid transparent;\n border-radius: 0.25rem;\n color: #212529;\n display: inline-block;\n font-size: 1rem;\n font-weight: 400;\n line-height: 1.5;\n padding: 0.375rem 0.75rem;\n text-align: center;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n vertical-align: middle;\n border-color: #28a745;\n color: #28a745;\n}\n.modal .modal-content form textarea {\n font-family: inherit;\n} */","name":"IAAS-BASIC-css","path":"/62457958430000127e85c5d0","tags":[],"draft":"*,\n*::after,\n*::before {\n margin: 0;\n padding: 0;\n box-sizing: inherit;\n}\n\nul{\n list-style-type: none;\n}\n\n.maif-color-text{\n color:#fc0d1a;\n}\n\nheader {\n text-align: center;\n background-color: #F2F2F2\n}\n\nheader a {\n color: black;\n text-decoration: none;\n}\n\nheader a:hover{\n color: black;\n text-decoration: none;\n font-weight: 600;\n}\n\n.header-left img {\n height: auto;\n max-width: 59px;\n}\n\n.header-right .non-connecte a, header .header-right .non-connecte button {\n color: #2e404f;\n cursor: pointer;\n padding: 6px 12px;\n align-items: center;\n text-align: center;\n font-size: 1.5rem;\n}\n\n.header-right .non-connecte a .icon, header .header-right .non-connecte button .icon {\n width: 38px;\n border-radius: 50%;\n}\n\n.header-right img{\n max-width: 30px;\n border-radius: 50%;\n}\n\nheader-left {\n text-align: left;\n}\n\n.header-right {\n display: flex;\n justify-content: start\n}\n\n.header-right .buttons {\n display: flex;\n flex-direction: column;\n}\n\n.header-right .buttons a {\n justify-content: start;\n display: flex;\n padding: 0;\n color: #212529;\n}\n\n.container-burger-menu {\n justify-content: right;\n margin-top: 0.5em;\n}\n\n.sidenav {\n height: 100%;\n width: 290px;\n position: fixed;\n z-index: 1;\n top: 0;\n right: -290px;\n background-color: #e8e8e8;\n padding-top: 60px;\n transition: right 0.5s ease;\n}\n\n/* Sidenav menu links */\n.sidenav a {\n padding: 8px 8px 8px 32px;\n text-decoration: none;\n font-size: 25px;\n color: #818181;\n display: block;\n transition: 0.3s;\n}\n\n.sidenav a:hover {\n color: #111;\n}\n\n.sidenav ul {\n text-align: left;\n list-style-type: none;\n padding: 0;\n margin: 0;\n}\n\n/* Active class */\n.sidenav.active {\n right: 0;\n}\n\n/* Close btn */\n.sidenav .close {\n position: absolute;\n top: 0;\n right: 25px;\n font-size: 36px;\n}\n\n/* Icône burger */\n.burger-icon span {\n display: block;\n width: 35px;\n height: 5px;\n background-color: black;\n margin: 6px 0;\n}\n\nbody {\n font-family: Custom, Raleway, Helvetica, sans-serif;\n margin: 0px;\n min-height: 100vh;\n display: flex;\n flex-direction: column\n}\n\n.offre-title {\n display: flex;\n flex-direction: row;\n align-items: center;\n border-bottom: solid #fc0d1a 5px;\n}\n\n.offre-title-double {\n display: flex;\n flex-direction: column;\n}\n\n.offre-title-double img:not(:last-child) {\n margin-bottom: -20px;\n}\n\n.offre-title-double img {\n padding-bottom: 0 !important;\n}\n\n.offre-title img {\n padding-bottom: 0.7em;\n padding-right: 3em;\n}\n\n.offre-title-detail {\n font-size: 12px;\n}\n\n.offre-title-2 {\n font-size: 18px;\n}\n\n.offre-text {\n font-size: 20px;\n}\n\n.offre-bandeau {\n display: flex;\n flex-direction: row;\n justify-content: center;\n align-items: center;\n background-color: #FBEBE9;\n padding: 10px;\n min-height: 120px;\n text-align: center;\n}\n\n.offre-bandeau img {\n padding-right: 5em;\n}\n\n.offre-bandeau-reverse img {\n -webkit-transform: scaleX(-1);\n padding-left: 5em;\n}\n\n.offre-description-text {\n display: flex;\n flex-direction: column;\n}\n\n.offre-description-text-margin {\n margin-top: 3em;\n}\n\n.offre-img-padding {\n align-items: end;\n}\n\n.offre-img-padding img{\n width: -webkit-fill-available;\n}\n\n.offre-ul {\n > li {\n font-style: italic;\n }\n}\n\n.offre-li-detail {\n font-size: 10px;\n}\n\n.img-reduction img {\n width: 70%;\n height: 80%;\n}\n\n.container {\n flex: 1; \n}\n\n.home-section .home-section-header {\n display: flex;\n width: 100%;\n flex-direction: column;\n}\n\n.home-section-body > div {\n margin-bottom: 3rem;\n}\n\n.img-inherit img {\n width: inherit;\n}\n\n.home-img img {\n width: 485px; \n}\n\n.home-section-side { \n padding-top: 4em;\n}\n\n.home-section-side img {\n margin-top: 1em;\n max-width: 130px;\n}\n\n.icon-menu {\n border: solid 1px grey;\n border-radius: 0.75em;\n margin-bottom: 1em;\n align-items: center;\n display: flex;\n flex-direction: column;\n width: 140px;\n height: 140px;\n text-align: center;\n}\n\n.icon-menu-disabled {\n filter: grayscale(1);\n}\n\n.icon-menu img {\n height: 80px;\n width: 80px;\n}\n\n.icon-menu a {\n color: black;\n text-decoration: none\n}\n\n.icon-menu a:hover{\n color: black;\n text-decoration: none;\n font-weight: 600;\n}\n\n.icon-menu-text {\n margin-bottom: 0 !important\n}\n\n.fa-check:before {\n content: \"\\f00c\";\n font-family: 'FontAwesome';\n color: #fc0d1a;\n padding-right: 0.3em;\n}\n\n.fa-close:before {\n content: \"\\f00c\";\n font-family: 'FontAwesome';\n color: #fc0d1a;\n padding-right: 0.3em;\n}\n\n.parcours-span {\n text-align: center;\n img {\n max-width:130px\n }\n margin-bottom: 1.5em;\n}\n\n.parcours-span-block {\n align-items: center;\n}\n\n.offre-parcours {\n padding-top: 3em;\n}\n\n.offre-parcours-li:before {\n content: '- '\n}\n\n.schema-bloc {\n width:50%;\n}\n\n.schema-marge {\n margin-right: 0.5em;\n width: auto !important\n}\n\n.schema-title {\n border: 1px solid;\n height: 30px;\n align-items: center;\n display: flex;\n justify-content: center;\n border-radius: 5px;\n width: 100%;\n}\n\n.schema-text {\n background-color: #F2F2F2;\n justify-content: center;\n display: flex;\n align-items: center;\n padding: 0.5em 0.25em 0.5em 0.25em;\n border-radius: 5px;\n font-size: 12px;\n height: 70px;\n align-items: center;\n text-align: center;\n margin-bottom: 0.25em;\n width: 100%;\n}\n\n.schema-text-title {\n font-size: 18px;\n color: #fc0d1a;\n font-weight: bold;\n}\n\n.axterisque-block {\n margin-bottom: 0 !important;\n}\n\n.axterisque {\n display: flex;\n flex-direction: row;\n font-size: 10px;\n}\n\n.axterisque p {\n margin-bottom: 0 !important\n}\n\n.axterisque p:not(:last-child) {\n padding-right: 3em;\n}\n\n.contactez-nous {\n display: flex;\n flex-direction: row;\n align-items: center;\n justify-content: right;\n margin-right: 2em;\n}\n\n.contactez-nous button {\n display: flex;\n flex-direction: row;\n align-items: center;\n cursor: pointer;\n border: 0;\n font-family: inherit;\n margin: 0;\n text-decoration: none;\n background-color: transparent;\n}\n\n.contactez-nous button:focus {\n outline: none;\n}\n\n.contactez-nous button > p {\n font-size: 12px;\n font-weight: bold;\n padding-top: 1.1em;\n}\n\nfooter .navbar {\n display: flex;\n justify-content: space-between;\n/* min-height: 60px; */\n font-size: 14px;\n background-image: linear-gradient(to left, #684a52, #595667);\n color: #fff;\n}\n\nfooter .navbar .liens-utiles {\n padding: 0 0 0 1.5rem;\n}\n\nfooter .navbar .liens-utiles button, footer .navbar .liens-utiles a {\n cursor: pointer;\n color: #fff;\n background-color: transparent;\n border: 0;\n font-family: inherit;\n margin: 0;\n text-decoration: none;\n}\n\nfooter .navbar .liens-utiles button:after, footer .navbar .liens-utiles a:after {\n content: \"|\";\n margin: 0 0.5rem;\n}\n\nfooter .reseaux-sociaux {\n padding: 0 0 0 1.5rem;\n}\n\nfooter .reseaux-sociaux button, footer .reseaux-sociaux a {\n cursor: pointer;\n color: #fff;\n background-color: transparent;\n border: 0;\n font-family: inherit;\n margin: 0;\n}\n\n@media screen and (min-width: 250px) {\n .offre-block-img {\n width: 70px;\n } \n .offre-block-img img {\n width: 69px;\n height: 69px;\n } \n .offre-block-text {\n min-width: 180px;\n }\n}\n\n@media screen and (max-width: 250px) {\n .offre-block-img {\n display: none;\n }\n .offre-block-text {\n width: inherit;\n } \n}\n\n@media screen and (min-width: 298px) {\n .icon-menu {\n margin-right: 1em;\n }\n}\n\n@media screen and (max-width: 330px) {\n .menu-title {\n text-align: center;\n }\n}\n\n@media screen and (max-width: 400px) {\n .offre-title img {\n display: none;\n }\n .offre-bandeau img {\n display: none;\n }\n .schema-text-title {\n font-size: 12px !important;\n }\n .schema-text {\n font-size: 12px !important;\n }\n}\n\n@media screen and (min-width: 768px) {\n .fa-arrow-right:before {\n content: \"\\f061c\";\n font-family: 'FontAwesome';\n color: #fc0d1a;\n font-size:2.5em;\n margin-right: -15px;\n }\n .fa-arrow-down:before {\n display:none\n }\n .arrow {\n margin-left: -30px;\n height: auto;\n display: flex;\n align-items: end;\n } \n .border-dotted {\n border-right: dotted 3px red;\n }\n .schema {\n margin-right: 1em !important;\n }\n}\n@media screen and (max-width: 768px) {\n .fa-arrow-right:before {\n display:none\n }\n .fa-arrow-down:before {\n content: \"\\f061c\";\n font-family: 'FontAwesome';\n color: #fc0d1a;\n font-size:2.5em;\n }\n}\n@media screen and (min-width: 992px) {\n .home-section .home-section-left span {\n text-wrap: nowrap;\n z-index: 1;\n }\n .home-section .home-section-left {\n margin-top: 5em;\n }\n .home-section .home-section-left .en-tete {\n margin-top: 3em;\n text-align: end;\n }\n .en-tete {\n font-size: 23px;\n line-height: 2rem;\n }\n .offre-parcours-img {\n display: inline-flex!important;\n align-items: end;\n }\n}\n\n\n@media screen and (max-width: 992px) {\n .offre-parcours-img {\n display: none;\n }\n}\n\n.modal {\n display: none;\n /* Hidden by default */\n position: fixed;\n z-index: 9999;\n padding-top: 20px;\n left: 0;\n top: 0;\n width: 100%;\n height: 100%;\n overflow: auto;\n background-color: rgb(0, 0, 0);\n /* Fallback color */\n background-color: rgba(0, 0, 0, 0.4);\n /* Black w/ opacity */\n}\n.modal .modal-content {\n background-color: #fefefe;\n margin: auto;\n padding: 20px;\n max-width: 800px;\n border: 1px solid #888;\n border-radius: 5px;\n width: 80%;\n}\n\n.modal-content-scroll {\n overflow-y: scroll;\n height: 100%;\n}\n\n.modal .modal-content .modal-header {\n border-bottom: 1px solid #dee2e6;\n display: flex;\n justify-content: space-between;\n padding: 1rem;\n}\n.modal .modal-content .modal-header span {\n cursor: pointer;\n}\n.modal .modal-content .modal-footer {\n border-top: 1px solid #dee2e6;\n display: flex;\n justify-content: flex-end;\n padding: 1rem 1rem 0rem 1rem;\n}\n.modal .modal-content .close {\n color: #aaaaaa;\n float: right;\n font-size: 28px;\n font-weight: bold;\n}\n.modal .modal-content .close:hover,\n.modal .modal-content .close:focus {\n color: #000;\n text-decoration: none;\n cursor: pointer;\n}\n.modal .modal-content h1 {\n line-height: 1.5;\n margin-top: 0;\n margin-bottom: 0;\n font-size: 1.25rem;\n color: rgba(0, 0, 0, 0.85);\n font-weight: 500;\n}\n.modal .modal-content h2 {\n line-height: 1.2;\n margin-top: 0.5rem;\n margin-bottom: 0.5rem;\n font-size: 1.5rem;\n font-weight: 500;\n}\n.modal .modal-content h3 {\n color: #c62222;\n line-height: 1.2;\n margin-top: 0;\n margin-bottom: 0.5rem;\n font-size: 1.25rem;\n font-weight: 500;\n}\n.modal .modal-content p {\n margin-bottom: 1rem;\n text-align: justify;\n}\n.modal .modal-content ul {\n margin-bottom: 1em;\n margin-top: 0;\n}\n.modal .modal-content ul li {\n list-style-type: none;\n padding-left: 2rem;\n}\n.modal .modal-content form {\n margin: 1rem 0;\n font-weight: 500;\n}\n.modal .modal-content form .form-group {\n margin-bottom: 1rem;\n padding-bottom: 10px;\n}\n.modal .modal-content form .form-group label {\n display: inline-block;\n margin-bottom: 0.5rem;\n}\n.modal .modal-content form .form-group .form-control {\n background-clip: padding-box;\n background-color: #fff;\n border: 1px solid #ced4da;\n border-radius: 0.25rem;\n color: #495057;\n display: block;\n font-size: 1rem;\n font-weight: 400;\n height: calc(1.5em + 0.75rem + 2px);\n line-height: 1.5;\n padding: 0.375rem 2%;\n transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n width: 96%;\n}\n.modal .modal-content form .form-group select.form-control[multiple], .modal .modal-content form .form-group select.form-control[size], .modal .modal-content form .form-group textarea.form-control {\n height: auto;\n}\n.modal .modal-content form button {\n background-color: transparent;\n border: 1px solid transparent;\n border-radius: 0.25rem;\n color: #212529;\n display: inline-block;\n font-size: 1rem;\n font-weight: 400;\n line-height: 1.5;\n padding: 0.375rem 0.75rem;\n text-align: center;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n vertical-align: middle;\n border-color: #28a745;\n color: #28a745;\n}\n.modal .modal-content form textarea {\n font-family: inherit;\n} */","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/css","authenticated":false,"lastPublishedDate":1707811449114}} +{"type":"cmspages","payload":{"_id":"652fa89c4400000d82fcea04","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-mobilite\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre VELO

\n
\n
\n

\n Vol, accident, dommages, assistance, vos utilisateurs sont-ils bien protégés ?\n

\n

\n Notre assurance propose des garanties complètes pour le vélo mais aussi pour le cycliste avec une assistance et une protection corporelle.\n

\n
\n \"offre \n
\n
\n
\n
\n

Penser à bien s’assurer avant de prendre le guidon

\n
\n \"offre \n
\n
\n
\n
\n
\n \"icon \n
\n
\n

Vol, casse… vos utilisateurs couverts partout, quoiqu’il arrive

\n

Une protection pour leur vélo neuf ou d’occasion, et pour ses accessoires. Même la tentative de vol est couverte !

\n
\n
\n
\n
\n \"icon \n
\n
\n

Crevaison panne : Assistance 24h/24

\n

MAIF Assistance dépanne vos utilisateurs et les amènent à leur domicile ou à destination. Intervention sur la route autour de chez eux (de 1 km à 50 km, sauf batterie déchargée).

\n
\n
\n
\n
\n \"icon \n
\n
\n

Chute, accident : vos utilisateurs sont pris en charge

\n

En cas de blessures, MAIF aide vos utilisateurs à se remettre en selle : aide financière, services à domicile, perte de revenus. Et leur enfant passager est couvert.

\n
\n
\n
\n
\n
\n \"offre \n
\n

Garanties & Indemnisations

\n
\n
\n
\n

Des garanties essentielles pour le vélo…

\n
    \n
  • Dommages accident
  • \n
  • Vol
  • \n
  • Accessoires et équipements de protection (y compris la batterie)
  • \n
  • Indemnisation en valeur d’achat 1 an…
  • \n
  • Vélo de remplacement​
  • \n
\n
\n
\n

… mais également pour le cycliste

\n
    \n
  • Assistance (panne & accident) jusqu’à 50km du domicile
  • \n
  • Retour au domicile
  • \n
  • Couverture corporelle
  • \n
  • Prêt de guidon gratuit
  • \n
  • Enfant passager couvert
  • \n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","name":"offre-velo","path":"/offre-velo","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-mobilite\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre VELO

\n
\n
\n

\n Vol, accident, dommages, assistance, vos utilisateurs sont-ils bien protégés ?\n

\n

\n Notre assurance propose des garanties complètes pour le vélo mais aussi pour le cycliste avec une assistance et une protection corporelle.\n

\n
\n \"offre \n
\n
\n
\n
\n

Penser à bien s’assurer avant de prendre le guidon

\n
\n \"offre \n
\n
\n
\n
\n
\n \"icon \n
\n
\n

Vol, casse… vos utilisateurs couverts partout, quoiqu’il arrive

\n

Une protection pour leur vélo neuf ou d’occasion, et pour ses accessoires. Même la tentative de vol est couverte !

\n
\n
\n
\n
\n \"icon \n
\n
\n

Crevaison panne : Assistance 24h/24

\n

MAIF Assistance dépanne vos utilisateurs et les amènent à leur domicile ou à destination. Intervention sur la route autour de chez eux (de 1 km à 50 km, sauf batterie déchargée).

\n
\n
\n
\n
\n \"icon \n
\n
\n

Chute, accident : vos utilisateurs sont pris en charge

\n

En cas de blessures, MAIF aide vos utilisateurs à se remettre en selle : aide financière, services à domicile, perte de revenus. Et leur enfant passager est couvert.

\n
\n
\n
\n
\n
\n \"offre \n
\n

Garanties & Indemnisations

\n
\n
\n
\n

Des garanties essentielles pour le vélo…

\n
    \n
  • Dommages accident
  • \n
  • Vol
  • \n
  • Accessoires et équipements de protection (y compris la batterie)
  • \n
  • Indemnisation en valeur d’achat 1 an…
  • \n
  • Vélo de remplacement​
  • \n
\n
\n
\n

… mais également pour le cycliste

\n
    \n
  • Assistance (panne & accident) jusqu’à 50km du domicile
  • \n
  • Retour au domicile
  • \n
  • Couverture corporelle
  • \n
  • Prêt de guidon gratuit
  • \n
  • Enfant passager couvert
  • \n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520614419}} +{"type":"cmspages","payload":{"_id":"6582b0212a0000359b5c3187","body":"
\n

Portail API MAIF

\n Le site dédié aux partenaires de distribution digitale des assurances MAIF\n \"icon\n
","name":"bloc-offre-side","path":null,"tags":[],"draft":"
\n

Portail API MAIF

\n Le site dédié aux partenaires de distribution digitale des assurances MAIF\n \"icon\n
","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520642480}} +{"type":"cmspages","payload":{"_id":"623c7943350000e106e28f89","body":"\n\n\n \n \n \n \n \n \n \n \n \n\n\n\n {{daikoku-include-block \"623c6fe5350000e106e28d1b\"}}\n \n {{children}}\n\n {{daikoku-include-block \"623c79b8350000e106e28fb4\"}}\n\n\n","name":"IAAS-BASIC-template","path":null,"tags":[],"draft":"\n\n\n \n \n \n \n \n \n \n \n \n\n\n\n {{daikoku-include-block \"623c6fe5350000e106e28d1b\"}}\n \n {{children}}\n\n {{daikoku-include-block \"623c79b8350000e106e28fb4\"}}\n\n\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520677032}} +{"type":"cmspages","payload":{"_id":"623c79b8350000e106e28fb4","body":"
\n
\n \n
\n
\n
\n \n maif.fr\n \n \n {{#unless connected}}\n Créer un compte\n {{/unless}}\n
\n
\n \n \n \n
\n
\n\n \n {{daikoku-include-block \"62a8424a440000921d1d3f06\"}}\n \n \n {{daikoku-include-block \"62aae19a3600001707b32ec4\"}}\n\n \n {{daikoku-include-block \"629f15aa440000921d1d2372\"}}\n\n \n {{daikoku-include-block \"629f4106440000921d1d278f\"}}\n\n\n \n\n
","name":"IAAS-BASIC-footer","path":null,"tags":[],"draft":"
\n
\n \n
\n
\n
\n \n maif.fr\n \n \n {{#unless connected}}\n Créer un compte\n {{/unless}}\n
\n
\n \n \n \n
\n
\n\n \n {{daikoku-include-block \"62a8424a440000921d1d3f06\"}}\n \n \n {{daikoku-include-block \"62aae19a3600001707b32ec4\"}}\n\n \n {{daikoku-include-block \"629f15aa440000921d1d2372\"}}\n\n \n {{daikoku-include-block \"629f4106440000921d1d278f\"}}\n\n\n \n\n
","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520760583}} +{"type":"cmspages","payload":{"_id":"V80UfyIoYE6YTagwCGr3rHlD2QB0AAaE","body":"un super header","name":"page.html","path":"/apis/un-test-pour-el-cli/header","tags":[],"draft":"un super header","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{"from":"cli","_name":"page.html","_path":"/apis/un-test-pour-el-cli/header","_content_type":"text/html"},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":null}} +{"type":"cmspages","payload":{"_id":"62aae19a3600001707b32ec4","body":"
\n \n
\n
\n

Demande de contact

\n ×\n
\n

\n
\n

Le message a bien été envoyé.

\n
\n
\n
\n","name":"MODAL-success","path":null,"tags":[],"draft":"
\n \n
\n
\n

Demande de contact

\n ×\n
\n

\n
\n

Le message a bien été envoyé.

\n
\n
\n
\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1677069681802}} +{"type":"cmspages","payload":{"_id":"6514440c3d00001b85d2dc8a","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/parcours-api\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Devis

\n
\n
\n
\n

\n Le parcours Devis est la solution optimale pour intégrer une proposition d’assurance dans votre parcours utilisateur.\n

\n
\n Offres disponibles sur ce parcours\n
    \n
  • Offres AUTO, VÉLO et EDPM
  • \n
  • Offre MRH jeune
  • \n
  • Offre Travailleur Indépendant
  • \n
\n
\n
\n
\n \"parcours \n
\n
\n
\n
\n

Intégrez le devis d’assurance dans votre parcours utilisateur

\n
\n
\n
\n
\n \"icon\n Le parcours Devis vous permet d’intégrer sur votre environnement (app mobile ou site internet) la réalisation du devis d’assurance. Vos utilisateurs bénéficient d’un parcours sans couture.\n
\n
\n
\n
\n

Votre Site / App

\n

Votre utilisateur réalise de manière transparente son devis au sein de votre parcours, sans rupture.

\n

DEVIS

\n
\n
\n
\n
\n

MAIF.fr

\n

Votre utilisateur réalise sa souscription d’assurance sur MAIF.fr (via le lien reçu par email avec son devis)

\n

SOUSCRIPTION

\n
\n
\n
\n
\n

Partenaires Cibles

\n
\n
\n
\n Notre Parcours Devis est adapté pour les partenaires avec expertise assurance, avec un besoin d’intégration de l’assurance dans le parcours utilisateur.\n
\n \"parcours\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}","name":"parcours-devis","path":"/parcours-devis","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/parcours-api\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Devis

\n
\n
\n
\n

\n Le parcours Devis est la solution optimale pour intégrer une proposition d’assurance dans votre parcours utilisateur.\n

\n
\n Offres disponibles sur ce parcours\n
    \n
  • Offres AUTO, VÉLO et EDPM
  • \n
  • Offre MRH jeune
  • \n
  • Offre Travailleur Indépendant
  • \n
\n
\n
\n
\n \"parcours \n
\n
\n
\n
\n

Intégrez le devis d’assurance dans votre parcours utilisateur

\n
\n
\n
\n
\n \"icon\n Le parcours Devis vous permet d’intégrer sur votre environnement (app mobile ou site internet) la réalisation du devis d’assurance. Vos utilisateurs bénéficient d’un parcours sans couture.\n
\n
\n
\n
\n

Votre Site / App

\n

Votre utilisateur réalise de manière transparente son devis au sein de votre parcours, sans rupture.

\n

DEVIS

\n
\n
\n
\n
\n

MAIF.fr

\n

Votre utilisateur réalise sa souscription d’assurance sur MAIF.fr (via le lien reçu par email avec son devis)

\n

SOUSCRIPTION

\n
\n
\n
\n
\n

Partenaires Cibles

\n
\n
\n
\n Notre Parcours Devis est adapté pour les partenaires avec expertise assurance, avec un besoin d’intégration de l’assurance dans le parcours utilisateur.\n
\n \"parcours\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520456539}} +{"type":"cmspages","payload":{"_id":"655dcd5615000001e6aed967","body":"var sidenav = document.getElementById(\"mySidenav\");\nvar openBtn = document.getElementById(\"openBtn\");\nvar closeBtn = document.getElementById(\"closeBtn\");\n\nopenBtn.onclick = openNav;\ncloseBtn.onclick = closeNav;\n\n/* Set the width of the side navigation to 250px */\nfunction openNav() {\n sidenav.classList.add(\"active\");\n}\n\n/* Set the width of the side navigation to 0 */\nfunction closeNav() {\n sidenav.classList.remove(\"active\");\n}","name":"navbar-js","path":"/655dcd5615000001e6aed968","tags":[],"draft":"var sidenav = document.getElementById(\"mySidenav\");\nvar openBtn = document.getElementById(\"openBtn\");\nvar closeBtn = document.getElementById(\"closeBtn\");\n\nopenBtn.onclick = openNav;\ncloseBtn.onclick = closeNav;\n\n/* Set the width of the side navigation to 250px */\nfunction openNav() {\n sidenav.classList.add(\"active\");\n}\n\n/* Set the width of the side navigation to 0 */\nfunction closeNav() {\n sidenav.classList.remove(\"active\");\n}","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/javascript","authenticated":false,"lastPublishedDate":1700647278213}} +{"type":"cmspages","payload":{"_id":"654b95412100008ec5dcc05e","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-mobilite\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre MOTO

\n
\n
\n

\n Notre assurance moto et scooter s’ajuste aux besoins et aux budgets de vos utilisateurs : ils peuvent choisir parmi 4 formules au tiers ou tous risques, avec des garanties étendues et des options.\n

\n
\n
\n
\n

Des garanties solides adaptées aux motards

\n
\n
\n
\n
\n \"icon \n
\n
\n

Assistance sur la route 24h/24 et 7j/7

\n

Vos utilisateurs sont dépannés lors de leurs déplacements à plus de 50 km de chez eux. Et géolocalisés, avec l’application MAIF. Leur passager est également pris en charge et rapatrié.​

\n
\n
\n
\n
\n \"icon \n
\n
\n

Vol, incendie : même assuré au tiers

\n

La moto de vos utilisateurs est couverte dès la formule Tiers enrichie (Essentiel). Leur équipement de protection motard est pris en charge : casque, gants, gilet airbag, antivol.

\n
\n
\n
\n
\n \"icon \n
\n
\n

Protection du motard

\n

Un premier niveau de protection corporelle est inclus dans toutes nos formules, pour aider vos utilisateurs et leurs proches, en cas de blessure ou de décès.

\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","name":"offre-moto","path":"/offre-moto","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-mobilite\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre MOTO

\n
\n
\n

\n Notre assurance moto et scooter s’ajuste aux besoins et aux budgets de vos utilisateurs : ils peuvent choisir parmi 4 formules au tiers ou tous risques, avec des garanties étendues et des options.\n

\n
\n
\n
\n

Des garanties solides adaptées aux motards

\n
\n
\n
\n
\n \"icon \n
\n
\n

Assistance sur la route 24h/24 et 7j/7

\n

Vos utilisateurs sont dépannés lors de leurs déplacements à plus de 50 km de chez eux. Et géolocalisés, avec l’application MAIF. Leur passager est également pris en charge et rapatrié.​

\n
\n
\n
\n
\n \"icon \n
\n
\n

Vol, incendie : même assuré au tiers

\n

La moto de vos utilisateurs est couverte dès la formule Tiers enrichie (Essentiel). Leur équipement de protection motard est pris en charge : casque, gants, gilet airbag, antivol.

\n
\n
\n
\n
\n \"icon \n
\n
\n

Protection du motard

\n

Un premier niveau de protection corporelle est inclus dans toutes nos formules, pour aider vos utilisateurs et leurs proches, en cas de blessure ou de décès.

\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520379884}} +{"type":"cmspages","payload":{"_id":"656750ea2a00008e0f55a812","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n

UNIVERS Vie Quotidienne & Pro

\n
\n
\n
\n
\n \n \n
\n \"icon\n

Garanties Accidents de la Vie

\n
\n \n
\n \"icon\n

Propriétaire Bailleur

\n
\n
\n \"icon\n

Assurance Vie

\n
\n
\n
\n
\n \"icon\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","name":"univers-vie-quotidienne-pro","path":"/univers-vie-quotidienne-pro","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n

UNIVERS Vie Quotidienne & Pro

\n
\n
\n
\n
\n \n \n
\n \"icon\n

Garanties Accidents de la Vie

\n
\n \n
\n \"icon\n

Propriétaire Bailleur

\n
\n
\n \"icon\n

Assurance Vie

\n
\n
\n
\n
\n \"icon\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520479461}} +{"type":"cmspages","payload":{"_id":"65708cd52b0000c5d720a237","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-vie-quotidienne-pro\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre HABITATION JEUNES

\n
\n
\n

\n Pour les locataires ou colocataires de moins de 30 ans et sans enfant.\n

\n

\n S'assurer est une obligation légale pour tous les locataires.\n

\n

\n Notre Assurance Habitation Jeunes répond à ce devoir et va bien au-delà pour vos utilisateurs.\n

\n
\n \"offre \n
\n
\n
\n
\n

Garanties de l’offre habitation jeunes

\n
\n
\n
\n

Des garanties essentielles dans le logement…

\n
    \n
  • Incendie, explosion et dégâts des eaux
  • \n
  • Bris de vitre sur l’immobilier
  • \n
  • Evénement climatique
  • \n
  • Cambriolage…
  • \n
\n
\n
\n \"offre \n
\n
\n

… mais également en dehors

\n
    \n
  • Responsabilité Civile (dommages causés à un tiers)
  • \n
  • Vol des biens pendant le déménagement
  • \n
  • Assistance voyage rapatriement
  • \n
  • Extension Biens Nomades (assure partout les biens que vous transportez avec vous : Smartphone, PC portable, instrument de musique, matériel de sport…)
  • \n
\n
\n
\n
\n

Une assurance responsable adaptée à votre quotidien

\n
\n
\n
\n
\n \"icon \n
\n
\n

Prêter, emprunter, louer

\n

MAIF encourage le partage plutôt que l’achat. On assure les biens que vos utilisateurs louent, prêtent ou empruntent.

\n
\n
\n
\n
\n \"icon \n
\n
\n

Réparer plutôt que remplacer

\n

A chaque fois que cela est possible, en cas de sinistre, nous privilégions la réparation des biens plutôt que leur remplacement. Un petit geste pour la planète.

\n
\n
\n
\n
\n \"icon \n
\n
\n

Limiter vos frais en colocation

\n

Les colocataires sont tous assurés de la même manière sur le même contrat.

\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","name":"offre-mrh-jeunes","path":"/offre-mrh-jeunes","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-vie-quotidienne-pro\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre HABITATION JEUNES

\n
\n
\n

\n Pour les locataires ou colocataires de moins de 30 ans et sans enfant.\n

\n

\n S'assurer est une obligation légale pour tous les locataires.\n

\n

\n Notre Assurance Habitation Jeunes répond à ce devoir et va bien au-delà pour vos utilisateurs.\n

\n
\n \"offre \n
\n
\n
\n
\n

Garanties de l’offre habitation jeunes

\n
\n
\n
\n

Des garanties essentielles dans le logement…

\n
    \n
  • Incendie, explosion et dégâts des eaux
  • \n
  • Bris de vitre sur l’immobilier
  • \n
  • Evénement climatique
  • \n
  • Cambriolage…
  • \n
\n
\n
\n \"offre \n
\n
\n

… mais également en dehors

\n
    \n
  • Responsabilité Civile (dommages causés à un tiers)
  • \n
  • Vol des biens pendant le déménagement
  • \n
  • Assistance voyage rapatriement
  • \n
  • Extension Biens Nomades (assure partout les biens que vous transportez avec vous : Smartphone, PC portable, instrument de musique, matériel de sport…)
  • \n
\n
\n
\n
\n

Une assurance responsable adaptée à votre quotidien

\n
\n
\n
\n
\n \"icon \n
\n
\n

Prêter, emprunter, louer

\n

MAIF encourage le partage plutôt que l’achat. On assure les biens que vos utilisateurs louent, prêtent ou empruntent.

\n
\n
\n
\n
\n \"icon \n
\n
\n

Réparer plutôt que remplacer

\n

A chaque fois que cela est possible, en cas de sinistre, nous privilégions la réparation des biens plutôt que leur remplacement. Un petit geste pour la planète.

\n
\n
\n
\n
\n \"icon \n
\n
\n

Limiter vos frais en colocation

\n

Les colocataires sont tous assurés de la même manière sur le même contrat.

\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520526380}} +{"type":"cmspages","payload":{"_id":"623c6fe5350000e106e28d1b","body":"
\n
\n
\n
\n \n \n \n
\n \n
\n \n {{#if connected}}\n
\n \n \n \n \n \n
\n {{/if}}\n \n \n {{#unless connected}}\n \n {{/unless}} \n
\n \n
\n \n \n \n \n \n \n \n
\n ×\n \n
\n
\n
\n \n \n
\n
","name":"IAAS-BASIC-navbar","path":null,"tags":[],"draft":"
\n
\n
\n
\n \n \n \n
\n \n
\n \n {{#if connected}}\n
\n \n \n \n \n \n
\n {{/if}}\n \n \n {{#unless connected}}\n \n {{/unless}} \n
\n \n
\n \n \n \n \n \n \n \n
\n ×\n \n
\n
\n
\n \n \n
\n
","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520280358}} +{"type":"cmspages","payload":{"_id":"629f44d0440000921d1d297f","body":"// Modal Contact\nfunction closeModalContact(){\n document.getElementById(\"contact\").style.display = \"none\";\n}\n\nfunction closeModalSuccess(){\n document.getElementById(\"contact-success\").style.display = \"none\";\n}\n\nfunction openContact(){\n document.getElementById(\"contact\").style.display = \"block\";\n}\n\n// Modal Mentions Légales\nfunction openMentionsLegales() {\n document.getElementById(\"mentions-legales\").style.display = \"block\";\n}\n\nfunction closeMentionsLegales() {\n document.getElementById(\"mentions-legales\").style.display = \"none\";\n}\n\n// Modal Données personnelles\nfunction openDonneesPersonnelles() {\n document.getElementById(\"donnees-personnelles\").style.display = \"block\";\n}\n\nfunction closeDonneesPersonnelles() {\n document.getElementById(\"donnees-personnelles\").style.display = \"none\";\n}\n\n// Modal Commun\nwindow.onclick = function(event) {\n if (event.target == modalContact || event.target == modalContactSuccess || event.target == modalMentionsLegales || event.target == modalDonneesPersonnelles) {\n modalContact.style.display = \"none\";\n modalContactSuccess.style.display = \"none\";\n modalMentionsLegales.style.display = \"none\";\n modalDonneesPersonnelles.style.display = \"none\";\n }\n}","name":"MODAL-js","path":"/629f44d0440000921d1d2980","tags":[],"draft":"// Modal Contact\nfunction closeModalContact(){\n document.getElementById(\"contact\").style.display = \"none\";\n}\n\nfunction closeModalSuccess(){\n document.getElementById(\"contact-success\").style.display = \"none\";\n}\n\nfunction openContact(){\n document.getElementById(\"contact\").style.display = \"block\";\n}\n\n// Modal Mentions Légales\nfunction openMentionsLegales() {\n document.getElementById(\"mentions-legales\").style.display = \"block\";\n}\n\nfunction closeMentionsLegales() {\n document.getElementById(\"mentions-legales\").style.display = \"none\";\n}\n\n// Modal Données personnelles\nfunction openDonneesPersonnelles() {\n document.getElementById(\"donnees-personnelles\").style.display = \"block\";\n}\n\nfunction closeDonneesPersonnelles() {\n document.getElementById(\"donnees-personnelles\").style.display = \"none\";\n}\n\n// Modal Commun\nwindow.onclick = function(event) {\n if (event.target == modalContact || event.target == modalContactSuccess || event.target == modalMentionsLegales || event.target == modalDonneesPersonnelles) {\n modalContact.style.display = \"none\";\n modalContactSuccess.style.display = \"none\";\n modalMentionsLegales.style.display = \"none\";\n modalDonneesPersonnelles.style.display = \"none\";\n }\n}","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/javascript","authenticated":false,"lastPublishedDate":1705571631920}} +{"type":"cmspages","payload":{"_id":"62a8424a440000921d1d3f06","body":"
\n \n
\n
\n

Demande de contact

\n ×\n
\n

\n
\n
\n \n {{#if connected}}\n \n {{/if}}\n {{#unless connected}}\n \n {{/unless}}\n
\n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n \n \n \n
\n \n
\n
\n
\n
\n\n\n","name":"MODAL-formulaire-contact","path":null,"tags":[],"draft":"
\n \n
\n
\n

Demande de contact

\n ×\n
\n

\n
\n
\n \n {{#if connected}}\n \n {{/if}}\n {{#unless connected}}\n \n {{/unless}}\n
\n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n \n \n \n
\n \n
\n
\n
\n
\n\n\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1677069434665}} +{"type":"cmspages","payload":{"_id":"629f4106440000921d1d278f","body":"
\n \n
\n
\n

Mentions légales

\n ×\n
\n

\n
\n

MAIF - Société d’assurance mutuelle à cotisations variables – CS 90000 - 79038 Niort cedex 9.

\n
\n
\n

Entreprises régies par le Code des assurances.

\n
\n
\n
\n","name":"MODAL-mentions-legales","path":null,"tags":[],"draft":"
\n \n
\n
\n

Mentions légales

\n ×\n
\n

\n
\n

MAIF - Société d’assurance mutuelle à cotisations variables – CS 90000 - 79038 Niort cedex 9.

\n
\n
\n

Entreprises régies par le Code des assurances.

\n
\n
\n
\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1705570212987}} +{"type":"cmspages","payload":{"_id":"657076982b0000c5d7209a94","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-mobilite\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre EDPM (Engins de Déplacement Personnels Motorisés)

\n
\n
\n

\n Vos utilisateurs ont choisi de se déplacer autrement ?
Nous les accompagnons dans cette nouvelle mobilité plus respectueuse de l'environnement.​\n

\n

\n Notre offre assure tous les engins de déplacement personnels motorisés (EDPM).\n

\n
\n \"offre \n
\n
\n
\n
\n

3 raisons de choisir l’assurance MAIF

\n
\n
\n
\n
\n

1

\n

Les garanties

\n

Avec l’assurance EDPM la protection des personnes reste une priorité. Même assuré au tiers, vous bénéficiez de garanties étendues.

\n
\n
\n
\n
\n

2

\n

Assurance au tiers ou tous risques : à chacun sa formule

\n

Le meilleur contrat d’assurance EDPM est celui qui correspond à vos besoins.

\n
\n
\n
\n
\n

3

\n

L’option biens transportés accessible dans les deux formules

\n

Les effets personnels que vous emportez (smartphone, …) sont couverts en cas d’accident.

\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","name":"offre-edpm","path":"/offre-edpm","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-mobilite\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre EDPM (Engins de Déplacement Personnels Motorisés)

\n
\n
\n

\n Vos utilisateurs ont choisi de se déplacer autrement ?
Nous les accompagnons dans cette nouvelle mobilité plus respectueuse de l'environnement.​\n

\n

\n Notre offre assure tous les engins de déplacement personnels motorisés (EDPM).\n

\n
\n \"offre \n
\n
\n
\n
\n

3 raisons de choisir l’assurance MAIF

\n
\n
\n
\n
\n

1

\n

Les garanties

\n

Avec l’assurance EDPM la protection des personnes reste une priorité. Même assuré au tiers, vous bénéficiez de garanties étendues.

\n
\n
\n
\n
\n

2

\n

Assurance au tiers ou tous risques : à chacun sa formule

\n

Le meilleur contrat d’assurance EDPM est celui qui correspond à vos besoins.

\n
\n
\n
\n
\n

3

\n

L’option biens transportés accessible dans les deux formules

\n

Les effets personnels que vous emportez (smartphone, …) sont couverts en cas d’accident.

\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520582665}} +{"type":"cmspages","payload":{"_id":"623c6eac350000e106e28caa","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\"}}\n \n
\n
\n
\n
\n

Portail API MAIF

\n Le site dédié aux partenaires de distribution digitale des assurances MAIF\n
\n
\n

\n Proposez à vos utilisateurs des \n solutions d'assurance intégrées \n dans vos parcours digitaux

\n
\n
\n
\n \"image\n
\n
\n
\n{{/daikoku-template-wrapper}}","name":"home","path":"/","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\"}}\n \n
\n
\n
\n
\n

Portail API MAIF

\n Le site dédié aux partenaires de distribution digitale des assurances MAIF\n
\n
\n

\n Proposez à vos utilisateurs des \n solutions d'assurance intégrées \n dans vos parcours digitaux

\n
\n
\n
\n \"image\n
\n
\n
\n{{/daikoku-template-wrapper}}","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520690725}} +{"type":"cmspages","payload":{"_id":"6526b0273d00001b85d31feb","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n

UNIVERS MOBILITE

\n
\n
\n
\n \n
\n
\n \"icon\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","name":"univers-mobilite","path":"/univers-mobilite","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n

UNIVERS MOBILITE

\n
\n
\n
\n \n
\n
\n \"icon\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520659265}} +{"type":"cmspages","payload":{"_id":"651443f53d00001b85d2dc82","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/parcours-api\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Simulation

\n
\n
\n
\n

\n Le parcours Simulation est la solution idéale pour intégrer un premier niveau d’assurance dans votre parcours utilisateur, d’une manière intuitive et sans effort.\n

\n
\n Offres disponibles sur ce parcours\n
    \n
  • Offres AUTO, VÉLO et EDPM
  • \n
  • Offre MRH jeune
  • \n
  • Offre Travailleur Indépendant
  • \n
\n
\n
\n
\n \"parcours \n
\n
\n
\n
\n

Proposez une estimation tarifaire instantanée et personnalisée à vos utilisateurs

\n
\n
\n
\n
\n \"icon\n Intégrer la simulation à vos parcours permet de délivrer à vos utilisateurs, au regard des informations dont vous disposez, un premier niveau d'informations pour les guider dans leur choix d'assurance.\n
\n
\n
\n
\n

Votre Site / App

\n

Vous affichez à votre utilisateur une estimation tarifaire et l'invitez à réaliser un devis sur MAIF.fr

\n

SIMULATION

\n
\n
\n
\n
\n

MAIF.fr

\n

Votre utilisateur réalise son devis et sa souscription d’assurance sur MAIF.fr.

\n
\n
\n

DEVIS

\n
\n
\n

SOUSCRIPTION

\n
\n
\n
\n
\n
\n
\n

Partenaires Cibles

\n
\n
\n
\n Notre parcours Simulation est adapté pour les partenaires sans expertise assurance, mais dont les services ou produits proposés sont en lien direct ou indirect avec l’offre d’assurance.\n
\n \"parcours\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}","name":"parcours-simulation","path":"/parcours-simulation","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/parcours-api\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Simulation

\n
\n
\n
\n

\n Le parcours Simulation est la solution idéale pour intégrer un premier niveau d’assurance dans votre parcours utilisateur, d’une manière intuitive et sans effort.\n

\n
\n Offres disponibles sur ce parcours\n
    \n
  • Offres AUTO, VÉLO et EDPM
  • \n
  • Offre MRH jeune
  • \n
  • Offre Travailleur Indépendant
  • \n
\n
\n
\n
\n \"parcours \n
\n
\n
\n
\n

Proposez une estimation tarifaire instantanée et personnalisée à vos utilisateurs

\n
\n
\n
\n
\n \"icon\n Intégrer la simulation à vos parcours permet de délivrer à vos utilisateurs, au regard des informations dont vous disposez, un premier niveau d'informations pour les guider dans leur choix d'assurance.\n
\n
\n
\n
\n

Votre Site / App

\n

Vous affichez à votre utilisateur une estimation tarifaire et l'invitez à réaliser un devis sur MAIF.fr

\n

SIMULATION

\n
\n
\n
\n
\n

MAIF.fr

\n

Votre utilisateur réalise son devis et sa souscription d’assurance sur MAIF.fr.

\n
\n
\n

DEVIS

\n
\n
\n

SOUSCRIPTION

\n
\n
\n
\n
\n
\n
\n

Partenaires Cibles

\n
\n
\n
\n Notre parcours Simulation est adapté pour les partenaires sans expertise assurance, mais dont les services ou produits proposés sont en lien direct ou indirect avec l’offre d’assurance.\n
\n \"parcours\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520546449}} +{"type":"cmspages","payload":{"_id":"6579ca012a0000359b5c1e27","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n

NOS PARCOURS API

\n
\n
\n \n
\n \"icon\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","name":"parcours-api","path":"/parcours-api","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n

NOS PARCOURS API

\n
\n
\n \n
\n \"icon\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520703310}} +{"type":"cmspages","payload":{"_id":"65709cd72b0000c5d720aa98","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-vie-quotidienne-pro\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre TRAVAILLEUR INDÉPENDANT

\n
\n
\n

\n Une assurance qui couvre la responsabilité civile professionnelle (RC Pro) et les biens (PC, téléphone, locaux...) des auto-entrepreneurs, freelances, travailleurs indépendants.\n

\n
\n \"offre \n
\n
\n
\n
\n

Choisir MAIF pour que vos utilisateurs assurent leur(s) activité(s)

\n
\n
\n
\n
\n \"icon \n
\n
\n

Des garanties indispensables

\n

En plus des risques liés à l’activité de vos utilisateurs, leurs biens, leur local et même leur stock sont assurés.

\n
\n
\n
\n
\n \"icon \n
\n
\n

Le prêt d’objet couvert

\n

Tout bien que l’on prête à vos utilisateurs ou qu’ils prêtent dans le cadre de leur activité est couvert.

\n
\n
\n
\n
\n \"icon \n
\n
\n

Le + relationnel

\n

Des conseillers spécialisés pour guider vos utilisateurs dans leur activité.

\n
\n
\n
\n
\n

Les activités assurées par l’assurance MAIF

\n
\n
\n
\n

Les Activités éligibles

\n
    \n
  • Prestations intellectuelles
  • \n
  • Bien-être et Thérapie
  • \n
  • Petits Artisanats
  • \n
  • Culture, Loisirs, Art et Musique
  • \n
  • Sport
  • \n
  • Social & Services
  • \n
\n
\n
\n \"icon \n
\n
\n

Les Activités exclues

\n
    \n
  • BTP / Agriculture / Grossiste
  • \n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","name":"offre-travailleur-independant","path":"/offre-travailleur-independant","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-vie-quotidienne-pro\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre TRAVAILLEUR INDÉPENDANT

\n
\n
\n

\n Une assurance qui couvre la responsabilité civile professionnelle (RC Pro) et les biens (PC, téléphone, locaux...) des auto-entrepreneurs, freelances, travailleurs indépendants.\n

\n
\n \"offre \n
\n
\n
\n
\n

Choisir MAIF pour que vos utilisateurs assurent leur(s) activité(s)

\n
\n
\n
\n
\n \"icon \n
\n
\n

Des garanties indispensables

\n

En plus des risques liés à l’activité de vos utilisateurs, leurs biens, leur local et même leur stock sont assurés.

\n
\n
\n
\n
\n \"icon \n
\n
\n

Le prêt d’objet couvert

\n

Tout bien que l’on prête à vos utilisateurs ou qu’ils prêtent dans le cadre de leur activité est couvert.

\n
\n
\n
\n
\n \"icon \n
\n
\n

Le + relationnel

\n

Des conseillers spécialisés pour guider vos utilisateurs dans leur activité.

\n
\n
\n
\n
\n

Les activités assurées par l’assurance MAIF

\n
\n
\n
\n

Les Activités éligibles

\n
    \n
  • Prestations intellectuelles
  • \n
  • Bien-être et Thérapie
  • \n
  • Petits Artisanats
  • \n
  • Culture, Loisirs, Art et Musique
  • \n
  • Sport
  • \n
  • Social & Services
  • \n
\n
\n
\n \"icon \n
\n
\n

Les Activités exclues

\n
    \n
  • BTP / Agriculture / Grossiste
  • \n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520631296}} +{"type":"cmspages","payload":{"_id":"651443d43d00001b85d2dc7a","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/parcours-api\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Affiliation Digitale

\n
\n
\n
\n

\n Le parcours Affiliation Digitale est la solution la plus simple pour recommander les offres d’assurance MAIF auprès de vos utilisateurs.\n

\n
\n Offres disponibles sur ce parcours\n
    \n
  • Offres MOBILITÉ
  • \n
  • Offres MRH et MRH jeune
  • \n
  • Offre Travailleur Indépendant
  • \n
\n
\n
\n
\n \"parcours \n
\n
\n
\n
\n

Redirigez vos utilisateurs vers le parcours d’assurance dédié sur MAIF.fr

\n
\n
\n
\n
\n \"icon\n Un simple lien de redirection depuis votre site ou application mobile vers le parcours d’assurance dédié sur MAIF.fr.\n
\n
\n
\n
\n

Votre Site / App

\n

Votre utilisateur est informé du partenariat MAIF et invité à réaliser un devis en cliquant sur le lien tracké

\n

LIEN TRACKE

\n
\n
\n
\n
\n

MAIF.fr

\n

Votre utilisateur réalise l’intégralité de son expérience assurance sur MAIF.fr.

\n
\n
\n

DEVIS

\n
\n
\n

SOUSCRIPTION

\n
\n
\n
\n
\n
\n
\n

Partenaires Cibles

\n
\n
\n
\n Notre parcours Affiliation Digitale est adapté pour les partenaires sans expertise assurance, dont les services ou produits proposés ne sont pas en lien direct avec l’offre d’assurance.\n
\n \"parcours\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","name":"parcours-affiliation-digitale","path":"/parcours-affiliation-digitale","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/parcours-api\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Affiliation Digitale

\n
\n
\n
\n

\n Le parcours Affiliation Digitale est la solution la plus simple pour recommander les offres d’assurance MAIF auprès de vos utilisateurs.\n

\n
\n Offres disponibles sur ce parcours\n
    \n
  • Offres MOBILITÉ
  • \n
  • Offres MRH et MRH jeune
  • \n
  • Offre Travailleur Indépendant
  • \n
\n
\n
\n
\n \"parcours \n
\n
\n
\n
\n

Redirigez vos utilisateurs vers le parcours d’assurance dédié sur MAIF.fr

\n
\n
\n
\n
\n \"icon\n Un simple lien de redirection depuis votre site ou application mobile vers le parcours d’assurance dédié sur MAIF.fr.\n
\n
\n
\n
\n

Votre Site / App

\n

Votre utilisateur est informé du partenariat MAIF et invité à réaliser un devis en cliquant sur le lien tracké

\n

LIEN TRACKE

\n
\n
\n
\n
\n

MAIF.fr

\n

Votre utilisateur réalise l’intégralité de son expérience assurance sur MAIF.fr.

\n
\n
\n

DEVIS

\n
\n
\n

SOUSCRIPTION

\n
\n
\n
\n
\n
\n
\n

Partenaires Cibles

\n
\n
\n
\n Notre parcours Affiliation Digitale est adapté pour les partenaires sans expertise assurance, dont les services ou produits proposés ne sont pas en lien direct avec l’offre d’assurance.\n
\n \"parcours\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520418595}} +{"type":"cmspages","payload":{"_id":"659ec57d2a0000359b5ca6fc","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-vie-quotidienne-pro\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre HABITATION

\n
\n
\n

\n Propriétaire, locataire : le contrat d'Assurance Habitation MAIF protège votre logement et vos biens avec de solides garanties en cas de sinistre.\n

\n
\n \"offre \n
\n
\n
\n
\n

Bien plus qu’une assurance habitation

\n
\n
\n
\n
\n \"icon \n
\n
\n

Intempéries

\n

En cas d’inondation, même sans arrêté de catastrophes naturelles, vous êtes indemnisé.

\n \"offre \n
\n
\n
\n
\n \"icon\n
\n
\n

Assurance Scolaire

\n

Incluse pour chaque enfant sans surcoût.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Vélo à assistance électrique

\n

Assuré en formule 3 contre le vol et le bris en tous lieux (en option en formule 2).

\n
\n
\n
\n
\n

Choisir MAIF pour assurer votre mode de vie responsable

\n
\n
\n
\n
\n \"icon\n
\n
\n

Réparer plutôt que jeter

\n

Pour lutter contre le gaspillage, MAIF inclut une garantie panne pour vos appareils en formule 3.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Vos équipements écologiques sont couverts

\n

Sans supplément dès la formule de base.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Les biens que vous prêtez sont assurés

\n

Comme s’ils étaient chez vous.

\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","name":"offre-mrh","path":"/offre-mrh","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-vie-quotidienne-pro\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre HABITATION

\n
\n
\n

\n Propriétaire, locataire : le contrat d'Assurance Habitation MAIF protège votre logement et vos biens avec de solides garanties en cas de sinistre.\n

\n
\n \"offre \n
\n
\n
\n
\n

Bien plus qu’une assurance habitation

\n
\n
\n
\n
\n \"icon \n
\n
\n

Intempéries

\n

En cas d’inondation, même sans arrêté de catastrophes naturelles, vous êtes indemnisé.

\n \"offre \n
\n
\n
\n
\n \"icon\n
\n
\n

Assurance Scolaire

\n

Incluse pour chaque enfant sans surcoût.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Vélo à assistance électrique

\n

Assuré en formule 3 contre le vol et le bris en tous lieux (en option en formule 2).

\n
\n
\n
\n
\n

Choisir MAIF pour assurer votre mode de vie responsable

\n
\n
\n
\n
\n \"icon\n
\n
\n

Réparer plutôt que jeter

\n

Pour lutter contre le gaspillage, MAIF inclut une garantie panne pour vos appareils en formule 3.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Vos équipements écologiques sont couverts

\n

Sans supplément dès la formule de base.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Les biens que vous prêtez sont assurés

\n

Comme s’ils étaient chez vous.

\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520506557}} +{"type":"cmspages","payload":{"_id":"629f15aa440000921d1d2372","body":"
\n \n
\n
\n

Données personnnelles

\n ×\n
\n

Politique de confidentialité du Portail API MAIF

\n
\n

Données personnelles :

\n

Par donnée à caractère personnel, il y a lieu d’entendre toutes les informations qui permettent de vous identifier directement ou indirectement (croisement d’un ensemble de données) telles que noms, prénoms, adresse postale et de courrier électronique, numéro de téléphone… Lors de votre accès au portail de l’API MAIF, nous sommes amenés à collecter des données à caractère personnel, notamment dans le cadre de la création de votre compte ou lorsque vous nous contacter.

\n
\n\n
\n

Qui sont les responsables de traitements ?

\n

MAIF Société d’Assurance Mutuelle à cotisations variables, entreprise régie par le code des assurances. Adresse postale : 200 avenue Salvador Allende - CS 90000 - 79038 NIORT cedex 9.

\n
\n
\n

Comment utilisons-nous vos données ?

\n

Vos données personnelles sont utilisées dans le cadre de notre relation pour répondre à plusieurs finalités pour l’utilisation du portail :

\n
    \n
  • La création de votre compte utilisateur et votre authentification
  • \n
  • La prise en charge de vos demandes via le formulaire de contact
  • \n
\n
\n
\n

Qui sont les Destinataires des données ?

\n

Les données que nous collectons vous concernant, sont destinées aux entreprises du groupe MAIF et à nos prestataires de service, agissant en qualité de sous-traitants lorsqu’ils concourent à la réalisation des finalités pour lesquelles vos données sont collectées. Pour assurer la confidentialité de vos données personnelles, nous nous prévoyons dans les contrats des obligations de confidentialité à la charge des sous-traitants.

\n
\n
\n

Quels sont vos droits ?

\n

Le droit d’accès et de rectification : Vous pouvez demander l’accès et la rectification des données inexactes vous concernant dont nous disposons sur vous.

\n

Le droit à la portabilité : Vous pouvez demander à récupérer les données que vous avez fourni dans un format informatique pour un usage personnel ou pour les transmettre à un tiers de votre choix.

\n

Le droit à l’oubli : Il s’agit du droit d'obtenir la suppression des informations vous concernant détenues par la MAIF dans la mesure où elles ne sont plus nécessaires un contrat ou un service.

\n

Le droit d'opposition : Vous pouvez demander l’opposition au traitement de vos données. Pour se faire, vous devez exposer des raisons tenant à votre situation particulière.

\n

Le droit de limitation du traitement : Vous pouvez nous demander la limitation de certaines de vos données lorsqu’elles ne sont plus nécessaires ou ne sont plus nécessaires dans notre relation contractuelle.

\n

Le droit de définir le sort de vos données post mortem : Vous pouvez définir des directives relatives à la conservation, à l'effacement et à la communication de vos données après votre décès.

\n
\n
\n

Combien de temps conservons-nous vos données et quelle est leur localisation ?

\n

La durée de conservation de vos données personnelles ne dépassera pas 6 mois après la suppression de votre compte. Vos données sont traitées et conservées sur le territoire de l’Union Européenne. Toutefois, dans de très rares hypothèses, elles peuvent faire l’objet d’un transfert hors de ce territoire. Lorsque c’est le cas, vous bénéficiez d’une information spécifique lors de la collecte et nous mettons en œuvre des mesures pour assurer que la législation européenne est respectée afin de vous apporter une sécurité plus importante.

\n
\n
\n

Vos contacts au sein du groupe MAIF :

\n

Pour exercer vos droits, vous pouvez contacter directement : claire.du.mesnildot@maif.fr MAIF a désigné un délégué à la protection des données personnelles dont la mission est de vous aider pour exercer vos droits. Vous pouvez le contacter :

\n
    \n
  • Par courrier postal en écrivant à : Délégué à la protection des données, 200 avenue Salvador Allende - CS 90000 - 79038 NIORT cedex 9
  • \n
  • Par courrier électronique en écrivant à l’adresse de courriel : vosdonnees@maif.fr
  • \n
\n
\n
\n
","name":"MODAL-donnees-personnelles","path":null,"tags":[],"draft":"
\n \n
\n
\n

Données personnnelles

\n ×\n
\n

Politique de confidentialité du Portail API MAIF

\n
\n

Données personnelles :

\n

Par donnée à caractère personnel, il y a lieu d’entendre toutes les informations qui permettent de vous identifier directement ou indirectement (croisement d’un ensemble de données) telles que noms, prénoms, adresse postale et de courrier électronique, numéro de téléphone… Lors de votre accès au portail de l’API MAIF, nous sommes amenés à collecter des données à caractère personnel, notamment dans le cadre de la création de votre compte ou lorsque vous nous contacter.

\n
\n\n
\n

Qui sont les responsables de traitements ?

\n

MAIF Société d’Assurance Mutuelle à cotisations variables, entreprise régie par le code des assurances. Adresse postale : 200 avenue Salvador Allende - CS 90000 - 79038 NIORT cedex 9.

\n
\n
\n

Comment utilisons-nous vos données ?

\n

Vos données personnelles sont utilisées dans le cadre de notre relation pour répondre à plusieurs finalités pour l’utilisation du portail :

\n
    \n
  • La création de votre compte utilisateur et votre authentification
  • \n
  • La prise en charge de vos demandes via le formulaire de contact
  • \n
\n
\n
\n

Qui sont les Destinataires des données ?

\n

Les données que nous collectons vous concernant, sont destinées aux entreprises du groupe MAIF et à nos prestataires de service, agissant en qualité de sous-traitants lorsqu’ils concourent à la réalisation des finalités pour lesquelles vos données sont collectées. Pour assurer la confidentialité de vos données personnelles, nous nous prévoyons dans les contrats des obligations de confidentialité à la charge des sous-traitants.

\n
\n
\n

Quels sont vos droits ?

\n

Le droit d’accès et de rectification : Vous pouvez demander l’accès et la rectification des données inexactes vous concernant dont nous disposons sur vous.

\n

Le droit à la portabilité : Vous pouvez demander à récupérer les données que vous avez fourni dans un format informatique pour un usage personnel ou pour les transmettre à un tiers de votre choix.

\n

Le droit à l’oubli : Il s’agit du droit d'obtenir la suppression des informations vous concernant détenues par la MAIF dans la mesure où elles ne sont plus nécessaires un contrat ou un service.

\n

Le droit d'opposition : Vous pouvez demander l’opposition au traitement de vos données. Pour se faire, vous devez exposer des raisons tenant à votre situation particulière.

\n

Le droit de limitation du traitement : Vous pouvez nous demander la limitation de certaines de vos données lorsqu’elles ne sont plus nécessaires ou ne sont plus nécessaires dans notre relation contractuelle.

\n

Le droit de définir le sort de vos données post mortem : Vous pouvez définir des directives relatives à la conservation, à l'effacement et à la communication de vos données après votre décès.

\n
\n
\n

Combien de temps conservons-nous vos données et quelle est leur localisation ?

\n

La durée de conservation de vos données personnelles ne dépassera pas 6 mois après la suppression de votre compte. Vos données sont traitées et conservées sur le territoire de l’Union Européenne. Toutefois, dans de très rares hypothèses, elles peuvent faire l’objet d’un transfert hors de ce territoire. Lorsque c’est le cas, vous bénéficiez d’une information spécifique lors de la collecte et nous mettons en œuvre des mesures pour assurer que la législation européenne est respectée afin de vous apporter une sécurité plus importante.

\n
\n
\n

Vos contacts au sein du groupe MAIF :

\n

Pour exercer vos droits, vous pouvez contacter directement : claire.du.mesnildot@maif.fr MAIF a désigné un délégué à la protection des données personnelles dont la mission est de vous aider pour exercer vos droits. Vous pouvez le contacter :

\n
    \n
  • Par courrier postal en écrivant à : Délégué à la protection des données, 200 avenue Salvador Allende - CS 90000 - 79038 NIORT cedex 9
  • \n
  • Par courrier électronique en écrivant à l’adresse de courriel : vosdonnees@maif.fr
  • \n
\n
\n
\n
","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1705571459321}} +{"type":"cmspages","payload":{"_id":"659e65a42a0000359b5c58bc","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-mobilite\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n
\n \"icon \n \"icon \n
\n

Offre AUTO & Camping-Cars

\n
\n
\n

\n Au tiers ou tous risques, vos utilisateurs peuvent compter sur les garanties de notre assurance auto/camping-cars VAM.\n

\n

\n Formules, options, franchises : vos utilisateurs ont la possibilité d’ajuster selon leurs besoins et de maîtriser le prix de leur assurance.\n

\n
\n
\n
\n

Des garanties et un service client qui font la différence

\n
\n
\n
\n
\n \"icon \n
\n
\n

Assistance sur la route 24h/24 et 7j/7

\n

Une Panne ? Vos utilisateurs sont dépannés dès la formule au tiers pour leurs trajets à plus de 50 km du domicile. Et même devant chez eux avec la formule Plénitude ou en option.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Remboursements

\n

Le véhicule de vos utilisateurs irréparable ou volé est indemnisé à valeur d’achat jusqu’à ses 4 ans, avec la formule Plénitude.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Intempéries… tous assurés !

\n

Dès la formule au tiers, le véhicule de vos utilisateurs est pris en charge en cas de grêle, tempête, inondation.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Service Client

\n

Trouver des solutions, indemniser, c’est notre métier. Depuis 19 ans, MAIF est numéro 1 de la Relation Client1.

\n
\n
\n
\n
\n

En cas de sinistre, comment ça se passe ?

\n
\n
\n
\n
\n \"icon\n
\n
\n

Assistance et suivi de la dépanneuse

\n

Vos utilisateurs peuvent contacter l’assistance MAIF 24h/24, 7j/7 puis suivre le trajet de la dépanneuse sur leur smartphone avec l’application MAIF.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Constat et déclaration en ligne

\n

Un accident ? Vos utilisateurs peuvent réaliser un (e)constat et déclarer leur sinistre en ligne, sans attendre.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Réparer sans gaspiller

\n

Depuis 2013, MAIF propose des réparations avec pièces recyclées, avec l’accord de vos utilisateurs. La réparation est garantie à vie2.

\n
\n
\n
\n
\n
\n

1.

\n

N°1 du Podium de la Relation Client. Enquête réalisée par BearingPoint et Kantar fin 2022 auprès de 4000 personnes.

\n
\n
\n

2.

\n

Dans la limite du vieillissement des pièces dû au soleil, à l’usure normale, aux intempéries ou au défaut d’entretien.

\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","name":"offre-auto-cc","path":"/offre-auto-cc","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-mobilite\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n
\n \"icon \n \"icon \n
\n

Offre AUTO & Camping-Cars

\n
\n
\n

\n Au tiers ou tous risques, vos utilisateurs peuvent compter sur les garanties de notre assurance auto/camping-cars VAM.\n

\n

\n Formules, options, franchises : vos utilisateurs ont la possibilité d’ajuster selon leurs besoins et de maîtriser le prix de leur assurance.\n

\n
\n
\n
\n

Des garanties et un service client qui font la différence

\n
\n
\n
\n
\n \"icon \n
\n
\n

Assistance sur la route 24h/24 et 7j/7

\n

Une Panne ? Vos utilisateurs sont dépannés dès la formule au tiers pour leurs trajets à plus de 50 km du domicile. Et même devant chez eux avec la formule Plénitude ou en option.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Remboursements

\n

Le véhicule de vos utilisateurs irréparable ou volé est indemnisé à valeur d’achat jusqu’à ses 4 ans, avec la formule Plénitude.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Intempéries… tous assurés !

\n

Dès la formule au tiers, le véhicule de vos utilisateurs est pris en charge en cas de grêle, tempête, inondation.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Service Client

\n

Trouver des solutions, indemniser, c’est notre métier. Depuis 19 ans, MAIF est numéro 1 de la Relation Client1.

\n
\n
\n
\n
\n

En cas de sinistre, comment ça se passe ?

\n
\n
\n
\n
\n \"icon\n
\n
\n

Assistance et suivi de la dépanneuse

\n

Vos utilisateurs peuvent contacter l’assistance MAIF 24h/24, 7j/7 puis suivre le trajet de la dépanneuse sur leur smartphone avec l’application MAIF.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Constat et déclaration en ligne

\n

Un accident ? Vos utilisateurs peuvent réaliser un (e)constat et déclarer leur sinistre en ligne, sans attendre.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Réparer sans gaspiller

\n

Depuis 2013, MAIF propose des réparations avec pièces recyclées, avec l’accord de vos utilisateurs. La réparation est garantie à vie2.

\n
\n
\n
\n
\n
\n

1.

\n

N°1 du Podium de la Relation Client. Enquête réalisée par BearingPoint et Kantar fin 2022 auprès de 4000 personnes.

\n
\n
\n

2.

\n

Dans la limite du vieillissement des pièces dû au soleil, à l’usure normale, aux intempéries ou au défaut d’entretien.

\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520722284}} +{"type":"cmspages","payload":{"_id":"659e60a72a0000359b5c5705","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/parcours-api\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Souscription

\n
\n
\n

\n Parcours en développement par les équipes MAIF.\n

\n
\n \"parcours \n
\n
\n
\n
\n

Partenaires Cibles

\n
\n
\n
\n Notre parcours Souscription est adapté aux partenaires avec un besoin d’intégration complet de l’assurance dans le parcours d’achat du bien ou du service proposé à leurs utilisateurs utilisateur.\n
\n \"parcours\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}","name":"parcours-souscription","path":"/parcours-souscription","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/parcours-api\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Souscription

\n
\n
\n

\n Parcours en développement par les équipes MAIF.\n

\n
\n \"parcours \n
\n
\n
\n
\n

Partenaires Cibles

\n
\n
\n
\n Notre parcours Souscription est adapté aux partenaires avec un besoin d’intégration complet de l’assurance dans le parcours d’achat du bien ou du service proposé à leurs utilisateurs utilisateur.\n
\n \"parcours\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520773400}} +{"type":"cmspages","payload":{"_id":"654b545f2100008ec5dca38d","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-mobilite\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre CORPORELLE (PACS)

\n
\n
\n

\n Vos utilisateurs sont des utilisateurs de solutions de mobilité douce ou partagée ?\n

\n

\n En cas d'accident, l’assurance corporelle PACS garantit une indemnisation de vos utilisateurs dans leurs mobilités à l’usage mais aussi en covoiturage et transports en commun.\n

\n
\n \"offre \n
\n
\n
\n
\n \"offre \n

Quels trajets sont couverts par notre assurance PACS ?

\n
\n
\n
\n \"offre\n
    \n
  • En train, métro, tram, bus, car
  • \n
  • En covoiturage, VTC, taxi
  • \n
  • Dans ma voiture de fonction, une voiture empruntée à un tiers
  • \n
  • Dans une location courte durée, libre-service : vélo …
  • \n
\n
\n
\n

Le contrat PACS couvrira vos trajets en véhicule de fonction, covoiturage, transports en commun, location en libre-service…

\n

Vous bénéficiez d’une prise en charge immédiate et d’un accompagnement dans vos démarches vis-à-vis des opérateurs de mobilité (vélo libre-service, train, tram, métro…)

\n
\n
\n
\n
\n

… mais également dans leurs véhicules assurés MAIF

\n
    \n
  • En voiture, en Camping-cars
  • \n
  • En moto, en scooter, en cyclomoteur
  • \n
  • En trottinette électrique
  • \n
  • En vélo 100% électrique (vitesse allant jusqu’à 45km/h)
  • \n
\n
\n
\n \"offre \n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","name":"offre-corporelle","path":"/offre-corporelle","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-mobilite\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre CORPORELLE (PACS)

\n
\n
\n

\n Vos utilisateurs sont des utilisateurs de solutions de mobilité douce ou partagée ?\n

\n

\n En cas d'accident, l’assurance corporelle PACS garantit une indemnisation de vos utilisateurs dans leurs mobilités à l’usage mais aussi en covoiturage et transports en commun.\n

\n
\n \"offre \n
\n
\n
\n
\n \"offre \n

Quels trajets sont couverts par notre assurance PACS ?

\n
\n
\n
\n \"offre\n
    \n
  • En train, métro, tram, bus, car
  • \n
  • En covoiturage, VTC, taxi
  • \n
  • Dans ma voiture de fonction, une voiture empruntée à un tiers
  • \n
  • Dans une location courte durée, libre-service : vélo …
  • \n
\n
\n
\n

Le contrat PACS couvrira vos trajets en véhicule de fonction, covoiturage, transports en commun, location en libre-service…

\n

Vous bénéficiez d’une prise en charge immédiate et d’un accompagnement dans vos démarches vis-à-vis des opérateurs de mobilité (vélo libre-service, train, tram, métro…)

\n
\n
\n
\n
\n

… mais également dans leurs véhicules assurés MAIF

\n
    \n
  • En voiture, en Camping-cars
  • \n
  • En moto, en scooter, en cyclomoteur
  • \n
  • En trottinette électrique
  • \n
  • En vélo 100% électrique (vitesse allant jusqu’à 45km/h)
  • \n
\n
\n
\n \"offre \n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520736622}} +{"type":"cmspages","payload":{"_id":"62457958430000127e85c5cf","body":"*,\n*::after,\n*::before {\n margin: 0;\n padding: 0;\n box-sizing: inherit;\n}\n\nul{\n list-style-type: none;\n}\n\n.maif-color-text{\n color:#fc0d1a;\n}\n\nheader {\n text-align: center;\n background-color: #F2F2F2\n}\n\nheader a {\n color: black;\n text-decoration: none;\n}\n\nheader a:hover{\n color: black;\n text-decoration: none;\n font-weight: 600;\n}\n\n.header-left img {\n height: auto;\n max-width: 59px;\n}\n\n.header-right .non-connecte a, header .header-right .non-connecte button {\n color: #2e404f;\n cursor: pointer;\n padding: 6px 12px;\n align-items: center;\n text-align: center;\n font-size: 1.5rem;\n}\n\n.header-right .non-connecte a .icon, header .header-right .non-connecte button .icon {\n width: 38px;\n border-radius: 50%;\n}\n\n.header-right img{\n max-width: 30px;\n border-radius: 50%;\n}\n\nheader-left {\n text-align: left;\n}\n\n.header-right {\n display: flex;\n justify-content: start\n}\n\n.header-right .buttons {\n display: flex;\n flex-direction: column;\n}\n\n.header-right .buttons a {\n justify-content: start;\n display: flex;\n padding: 0;\n color: #212529;\n}\n\n.container-burger-menu {\n justify-content: right;\n margin-top: 0.5em;\n}\n\n.sidenav {\n height: 100%;\n width: 290px;\n position: fixed;\n z-index: 1;\n top: 0;\n right: -290px;\n background-color: #e8e8e8;\n padding-top: 60px;\n transition: right 0.5s ease;\n}\n\n/* Sidenav menu links */\n.sidenav a {\n padding: 8px 8px 8px 32px;\n text-decoration: none;\n font-size: 25px;\n color: #818181;\n display: block;\n transition: 0.3s;\n}\n\n.sidenav a:hover {\n color: #111;\n}\n\n.sidenav ul {\n text-align: left;\n list-style-type: none;\n padding: 0;\n margin: 0;\n}\n\n/* Active class */\n.sidenav.active {\n right: 0;\n}\n\n/* Close btn */\n.sidenav .close {\n position: absolute;\n top: 0;\n right: 25px;\n font-size: 36px;\n}\n\n/* Icône burger */\n.burger-icon span {\n display: block;\n width: 35px;\n height: 5px;\n background-color: black;\n margin: 6px 0;\n}\n\nbody {\n font-family: Custom, Raleway, Helvetica, sans-serif;\n margin: 0px;\n min-height: 100vh;\n display: flex;\n flex-direction: column\n}\n\n.offre-title {\n display: flex;\n flex-direction: row;\n align-items: center;\n border-bottom: solid #fc0d1a 5px;\n}\n\n.offre-title-double {\n display: flex;\n flex-direction: column;\n}\n\n.offre-title-double img:not(:last-child) {\n margin-bottom: -20px;\n}\n\n.offre-title-double img {\n padding-bottom: 0 !important;\n}\n\n.offre-title img {\n padding-bottom: 0.7em;\n padding-right: 3em;\n}\n\n.offre-title-detail {\n font-size: 12px;\n}\n\n.offre-title-2 {\n font-size: 18px;\n}\n\n.offre-text {\n font-size: 20px;\n}\n\n.offre-bandeau {\n display: flex;\n flex-direction: row;\n justify-content: center;\n align-items: center;\n background-color: #FBEBE9;\n padding: 10px;\n min-height: 120px;\n text-align: center;\n}\n\n.offre-bandeau img {\n padding-right: 5em;\n}\n\n.offre-bandeau-reverse img {\n -webkit-transform: scaleX(-1);\n padding-left: 5em;\n}\n\n.offre-description-text {\n display: flex;\n flex-direction: column;\n}\n\n.offre-description-text-margin {\n margin-top: 3em;\n}\n\n.offre-img-padding {\n align-items: end;\n}\n\n.offre-img-padding img{\n width: -webkit-fill-available;\n}\n\n.offre-ul {\n > li {\n font-style: italic;\n }\n}\n\n.offre-li-detail {\n font-size: 10px;\n}\n\n.img-reduction img {\n width: 70%;\n height: 80%;\n}\n\n.container {\n flex: 1; \n}\n\n.home-section .home-section-header {\n display: flex;\n width: 100%;\n flex-direction: column;\n}\n\n.home-section-body > div {\n margin-bottom: 3rem;\n}\n\n.img-inherit img {\n width: inherit;\n}\n\n.home-img img {\n width: 485px; \n}\n\n.home-section-side { \n padding-top: 4em;\n}\n\n.home-section-side img {\n margin-top: 1em;\n max-width: 130px;\n}\n\n.icon-menu {\n border: solid 1px grey;\n border-radius: 0.75em;\n margin-bottom: 1em;\n align-items: center;\n display: flex;\n flex-direction: column;\n width: 140px;\n height: 140px;\n text-align: center;\n}\n\n.icon-menu-disabled {\n filter: grayscale(1);\n}\n\n.icon-menu img {\n height: 80px;\n width: 80px;\n}\n\n.icon-menu a {\n color: black;\n text-decoration: none\n}\n\n.icon-menu a:hover{\n color: black;\n text-decoration: none;\n font-weight: 600;\n}\n\n.icon-menu-text {\n margin-bottom: 0 !important\n}\n\n.fa-check:before {\n content: \"\\f00c\";\n font-family: 'FontAwesome';\n color: #fc0d1a;\n padding-right: 0.3em;\n}\n\n.fa-close:before {\n content: \"\\f00c\";\n font-family: 'FontAwesome';\n color: #fc0d1a;\n padding-right: 0.3em;\n}\n\n.parcours-span {\n text-align: center;\n img {\n max-width:130px\n }\n margin-bottom: 1.5em;\n}\n\n.parcours-span-block {\n align-items: center;\n}\n\n.offre-parcours {\n padding-top: 3em;\n}\n\n.offre-parcours-li:before {\n content: '- '\n}\n\n.schema-bloc {\n width:50%;\n}\n\n.schema-marge {\n margin-right: 0.5em;\n width: auto !important\n}\n\n.schema-title {\n border: 1px solid;\n height: 30px;\n align-items: center;\n display: flex;\n justify-content: center;\n border-radius: 5px;\n width: 100%;\n}\n\n.schema-text {\n background-color: #F2F2F2;\n justify-content: center;\n display: flex;\n align-items: center;\n padding: 0.5em 0.25em 0.5em 0.25em;\n border-radius: 5px;\n font-size: 12px;\n height: 70px;\n align-items: center;\n text-align: center;\n margin-bottom: 0.25em;\n width: 100%;\n}\n\n.schema-text-title {\n font-size: 18px;\n color: #fc0d1a;\n font-weight: bold;\n}\n\n.axterisque-block {\n margin-bottom: 0 !important;\n}\n\n.axterisque {\n display: flex;\n flex-direction: row;\n font-size: 10px;\n}\n\n.axterisque p {\n margin-bottom: 0 !important\n}\n\n.axterisque p:not(:last-child) {\n padding-right: 3em;\n}\n\n.contactez-nous {\n display: flex;\n flex-direction: row;\n align-items: center;\n justify-content: right;\n margin-right: 2em;\n}\n\n.contactez-nous button {\n display: flex;\n flex-direction: row;\n align-items: center;\n cursor: pointer;\n border: 0;\n font-family: inherit;\n margin: 0;\n text-decoration: none;\n background-color: transparent;\n}\n\n.contactez-nous button:focus {\n outline: none;\n}\n\n.contactez-nous button > p {\n font-size: 12px;\n font-weight: bold;\n padding-top: 1.1em;\n}\n\nfooter .navbar {\n display: flex;\n justify-content: space-between;\n/* min-height: 60px; */\n font-size: 14px;\n background-image: linear-gradient(to left, #684a52, #595667);\n color: #fff;\n}\n\nfooter .navbar .liens-utiles {\n padding: 0 0 0 1.5rem;\n}\n\nfooter .navbar .liens-utiles button, footer .navbar .liens-utiles a {\n cursor: pointer;\n color: #fff;\n background-color: transparent;\n border: 0;\n font-family: inherit;\n margin: 0;\n text-decoration: none;\n}\n\nfooter .navbar .liens-utiles button:after, footer .navbar .liens-utiles a:after {\n content: \"|\";\n margin: 0 0.5rem;\n}\n\nfooter .reseaux-sociaux {\n padding: 0 0 0 1.5rem;\n}\n\nfooter .reseaux-sociaux button, footer .reseaux-sociaux a {\n cursor: pointer;\n color: #fff;\n background-color: transparent;\n border: 0;\n font-family: inherit;\n margin: 0;\n}\n\n@media screen and (min-width: 250px) {\n .offre-block-img {\n width: 70px;\n } \n .offre-block-img img {\n width: 69px;\n height: 69px;\n } \n .offre-block-text {\n min-width: 180px;\n }\n}\n\n@media screen and (max-width: 250px) {\n .offre-block-img {\n display: none;\n }\n .offre-block-text {\n width: inherit;\n } \n}\n\n@media screen and (min-width: 298px) {\n .icon-menu {\n margin-right: 1em;\n }\n}\n\n@media screen and (max-width: 330px) {\n .menu-title {\n text-align: center;\n }\n}\n\n@media screen and (max-width: 400px) {\n .offre-title img {\n display: none;\n }\n .offre-bandeau img {\n display: none;\n }\n .schema-text-title {\n font-size: 12px !important;\n }\n .schema-text {\n font-size: 12px !important;\n }\n}\n\n@media screen and (min-width: 768px) {\n .fa-arrow-right:before {\n content: \"\\f061c\";\n font-family: 'FontAwesome';\n color: #fc0d1a;\n font-size:2.5em;\n margin-right: -15px;\n }\n .fa-arrow-down:before {\n display:none\n }\n .arrow {\n margin-left: -30px;\n height: auto;\n display: flex;\n align-items: end;\n } \n .border-dotted {\n border-right: dotted 3px red;\n }\n .schema {\n margin-right: 1em !important;\n }\n}\n@media screen and (max-width: 768px) {\n .fa-arrow-right:before {\n display:none\n }\n .fa-arrow-down:before {\n content: \"\\f061c\";\n font-family: 'FontAwesome';\n color: #fc0d1a;\n font-size:2.5em;\n }\n}\n@media screen and (min-width: 992px) {\n .home-section .home-section-left span {\n text-wrap: nowrap;\n z-index: 1;\n }\n .home-section .home-section-left {\n margin-top: 5em;\n }\n .home-section .home-section-left .en-tete {\n margin-top: 3em;\n text-align: end;\n }\n .en-tete {\n font-size: 23px;\n line-height: 2rem;\n }\n .offre-parcours-img {\n display: inline-flex!important;\n align-items: end;\n }\n}\n\n\n@media screen and (max-width: 992px) {\n .offre-parcours-img {\n display: none;\n }\n}\n\n.modal {\n display: none;\n /* Hidden by default */\n position: fixed;\n z-index: 9999;\n padding-top: 20px;\n left: 0;\n top: 0;\n width: 100%;\n height: 100%;\n overflow: auto;\n background-color: rgb(0, 0, 0);\n /* Fallback color */\n background-color: rgba(0, 0, 0, 0.4);\n /* Black w/ opacity */\n}\n.modal .modal-content {\n background-color: #fefefe;\n margin: auto;\n padding: 20px;\n max-width: 800px;\n border: 1px solid #888;\n border-radius: 5px;\n width: 80%;\n}\n\n.modal-content-scroll {\n overflow-y: scroll;\n height: 100%;\n}\n\n.modal .modal-content .modal-header {\n border-bottom: 1px solid #dee2e6;\n display: flex;\n justify-content: space-between;\n padding: 1rem;\n}\n.modal .modal-content .modal-header span {\n cursor: pointer;\n}\n.modal .modal-content .modal-footer {\n border-top: 1px solid #dee2e6;\n display: flex;\n justify-content: flex-end;\n padding: 1rem 1rem 0rem 1rem;\n}\n.modal .modal-content .close {\n color: #aaaaaa;\n float: right;\n font-size: 28px;\n font-weight: bold;\n}\n.modal .modal-content .close:hover,\n.modal .modal-content .close:focus {\n color: #000;\n text-decoration: none;\n cursor: pointer;\n}\n.modal .modal-content h1 {\n line-height: 1.5;\n margin-top: 0;\n margin-bottom: 0;\n font-size: 1.25rem;\n color: rgba(0, 0, 0, 0.85);\n font-weight: 500;\n}\n.modal .modal-content h2 {\n line-height: 1.2;\n margin-top: 0.5rem;\n margin-bottom: 0.5rem;\n font-size: 1.5rem;\n font-weight: 500;\n}\n.modal .modal-content h3 {\n color: #c62222;\n line-height: 1.2;\n margin-top: 0;\n margin-bottom: 0.5rem;\n font-size: 1.25rem;\n font-weight: 500;\n}\n.modal .modal-content p {\n margin-bottom: 1rem;\n text-align: justify;\n}\n.modal .modal-content ul {\n margin-bottom: 1em;\n margin-top: 0;\n}\n.modal .modal-content ul li {\n list-style-type: none;\n padding-left: 2rem;\n}\n.modal .modal-content form {\n margin: 1rem 0;\n font-weight: 500;\n}\n.modal .modal-content form .form-group {\n margin-bottom: 1rem;\n padding-bottom: 10px;\n}\n.modal .modal-content form .form-group label {\n display: inline-block;\n margin-bottom: 0.5rem;\n}\n.modal .modal-content form .form-group .form-control {\n background-clip: padding-box;\n background-color: #fff;\n border: 1px solid #ced4da;\n border-radius: 0.25rem;\n color: #495057;\n display: block;\n font-size: 1rem;\n font-weight: 400;\n height: calc(1.5em + 0.75rem + 2px);\n line-height: 1.5;\n padding: 0.375rem 2%;\n transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n width: 96%;\n}\n.modal .modal-content form .form-group select.form-control[multiple], .modal .modal-content form .form-group select.form-control[size], .modal .modal-content form .form-group textarea.form-control {\n height: auto;\n}\n.modal .modal-content form button {\n background-color: transparent;\n border: 1px solid transparent;\n border-radius: 0.25rem;\n color: #212529;\n display: inline-block;\n font-size: 1rem;\n font-weight: 400;\n line-height: 1.5;\n padding: 0.375rem 0.75rem;\n text-align: center;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n vertical-align: middle;\n border-color: #28a745;\n color: #28a745;\n}\n.modal .modal-content form textarea {\n font-family: inherit;\n} */","name":"IAAS-BASIC-css","path":"/62457958430000127e85c5d0","tags":[],"draft":"*,\n*::after,\n*::before {\n margin: 0;\n padding: 0;\n box-sizing: inherit;\n}\n\nul{\n list-style-type: none;\n}\n\n.maif-color-text{\n color:#fc0d1a;\n}\n\nheader {\n text-align: center;\n background-color: #F2F2F2\n}\n\nheader a {\n color: black;\n text-decoration: none;\n}\n\nheader a:hover{\n color: black;\n text-decoration: none;\n font-weight: 600;\n}\n\n.header-left img {\n height: auto;\n max-width: 59px;\n}\n\n.header-right .non-connecte a, header .header-right .non-connecte button {\n color: #2e404f;\n cursor: pointer;\n padding: 6px 12px;\n align-items: center;\n text-align: center;\n font-size: 1.5rem;\n}\n\n.header-right .non-connecte a .icon, header .header-right .non-connecte button .icon {\n width: 38px;\n border-radius: 50%;\n}\n\n.header-right img{\n max-width: 30px;\n border-radius: 50%;\n}\n\nheader-left {\n text-align: left;\n}\n\n.header-right {\n display: flex;\n justify-content: start\n}\n\n.header-right .buttons {\n display: flex;\n flex-direction: column;\n}\n\n.header-right .buttons a {\n justify-content: start;\n display: flex;\n padding: 0;\n color: #212529;\n}\n\n.container-burger-menu {\n justify-content: right;\n margin-top: 0.5em;\n}\n\n.sidenav {\n height: 100%;\n width: 290px;\n position: fixed;\n z-index: 1;\n top: 0;\n right: -290px;\n background-color: #e8e8e8;\n padding-top: 60px;\n transition: right 0.5s ease;\n}\n\n/* Sidenav menu links */\n.sidenav a {\n padding: 8px 8px 8px 32px;\n text-decoration: none;\n font-size: 25px;\n color: #818181;\n display: block;\n transition: 0.3s;\n}\n\n.sidenav a:hover {\n color: #111;\n}\n\n.sidenav ul {\n text-align: left;\n list-style-type: none;\n padding: 0;\n margin: 0;\n}\n\n/* Active class */\n.sidenav.active {\n right: 0;\n}\n\n/* Close btn */\n.sidenav .close {\n position: absolute;\n top: 0;\n right: 25px;\n font-size: 36px;\n}\n\n/* Icône burger */\n.burger-icon span {\n display: block;\n width: 35px;\n height: 5px;\n background-color: black;\n margin: 6px 0;\n}\n\nbody {\n font-family: Custom, Raleway, Helvetica, sans-serif;\n margin: 0px;\n min-height: 100vh;\n display: flex;\n flex-direction: column\n}\n\n.offre-title {\n display: flex;\n flex-direction: row;\n align-items: center;\n border-bottom: solid #fc0d1a 5px;\n}\n\n.offre-title-double {\n display: flex;\n flex-direction: column;\n}\n\n.offre-title-double img:not(:last-child) {\n margin-bottom: -20px;\n}\n\n.offre-title-double img {\n padding-bottom: 0 !important;\n}\n\n.offre-title img {\n padding-bottom: 0.7em;\n padding-right: 3em;\n}\n\n.offre-title-detail {\n font-size: 12px;\n}\n\n.offre-title-2 {\n font-size: 18px;\n}\n\n.offre-text {\n font-size: 20px;\n}\n\n.offre-bandeau {\n display: flex;\n flex-direction: row;\n justify-content: center;\n align-items: center;\n background-color: #FBEBE9;\n padding: 10px;\n min-height: 120px;\n text-align: center;\n}\n\n.offre-bandeau img {\n padding-right: 5em;\n}\n\n.offre-bandeau-reverse img {\n -webkit-transform: scaleX(-1);\n padding-left: 5em;\n}\n\n.offre-description-text {\n display: flex;\n flex-direction: column;\n}\n\n.offre-description-text-margin {\n margin-top: 3em;\n}\n\n.offre-img-padding {\n align-items: end;\n}\n\n.offre-img-padding img{\n width: -webkit-fill-available;\n}\n\n.offre-ul {\n > li {\n font-style: italic;\n }\n}\n\n.offre-li-detail {\n font-size: 10px;\n}\n\n.img-reduction img {\n width: 70%;\n height: 80%;\n}\n\n.container {\n flex: 1; \n}\n\n.home-section .home-section-header {\n display: flex;\n width: 100%;\n flex-direction: column;\n}\n\n.home-section-body > div {\n margin-bottom: 3rem;\n}\n\n.img-inherit img {\n width: inherit;\n}\n\n.home-img img {\n width: 485px; \n}\n\n.home-section-side { \n padding-top: 4em;\n}\n\n.home-section-side img {\n margin-top: 1em;\n max-width: 130px;\n}\n\n.icon-menu {\n border: solid 1px grey;\n border-radius: 0.75em;\n margin-bottom: 1em;\n align-items: center;\n display: flex;\n flex-direction: column;\n width: 140px;\n height: 140px;\n text-align: center;\n}\n\n.icon-menu-disabled {\n filter: grayscale(1);\n}\n\n.icon-menu img {\n height: 80px;\n width: 80px;\n}\n\n.icon-menu a {\n color: black;\n text-decoration: none\n}\n\n.icon-menu a:hover{\n color: black;\n text-decoration: none;\n font-weight: 600;\n}\n\n.icon-menu-text {\n margin-bottom: 0 !important\n}\n\n.fa-check:before {\n content: \"\\f00c\";\n font-family: 'FontAwesome';\n color: #fc0d1a;\n padding-right: 0.3em;\n}\n\n.fa-close:before {\n content: \"\\f00c\";\n font-family: 'FontAwesome';\n color: #fc0d1a;\n padding-right: 0.3em;\n}\n\n.parcours-span {\n text-align: center;\n img {\n max-width:130px\n }\n margin-bottom: 1.5em;\n}\n\n.parcours-span-block {\n align-items: center;\n}\n\n.offre-parcours {\n padding-top: 3em;\n}\n\n.offre-parcours-li:before {\n content: '- '\n}\n\n.schema-bloc {\n width:50%;\n}\n\n.schema-marge {\n margin-right: 0.5em;\n width: auto !important\n}\n\n.schema-title {\n border: 1px solid;\n height: 30px;\n align-items: center;\n display: flex;\n justify-content: center;\n border-radius: 5px;\n width: 100%;\n}\n\n.schema-text {\n background-color: #F2F2F2;\n justify-content: center;\n display: flex;\n align-items: center;\n padding: 0.5em 0.25em 0.5em 0.25em;\n border-radius: 5px;\n font-size: 12px;\n height: 70px;\n align-items: center;\n text-align: center;\n margin-bottom: 0.25em;\n width: 100%;\n}\n\n.schema-text-title {\n font-size: 18px;\n color: #fc0d1a;\n font-weight: bold;\n}\n\n.axterisque-block {\n margin-bottom: 0 !important;\n}\n\n.axterisque {\n display: flex;\n flex-direction: row;\n font-size: 10px;\n}\n\n.axterisque p {\n margin-bottom: 0 !important\n}\n\n.axterisque p:not(:last-child) {\n padding-right: 3em;\n}\n\n.contactez-nous {\n display: flex;\n flex-direction: row;\n align-items: center;\n justify-content: right;\n margin-right: 2em;\n}\n\n.contactez-nous button {\n display: flex;\n flex-direction: row;\n align-items: center;\n cursor: pointer;\n border: 0;\n font-family: inherit;\n margin: 0;\n text-decoration: none;\n background-color: transparent;\n}\n\n.contactez-nous button:focus {\n outline: none;\n}\n\n.contactez-nous button > p {\n font-size: 12px;\n font-weight: bold;\n padding-top: 1.1em;\n}\n\nfooter .navbar {\n display: flex;\n justify-content: space-between;\n/* min-height: 60px; */\n font-size: 14px;\n background-image: linear-gradient(to left, #684a52, #595667);\n color: #fff;\n}\n\nfooter .navbar .liens-utiles {\n padding: 0 0 0 1.5rem;\n}\n\nfooter .navbar .liens-utiles button, footer .navbar .liens-utiles a {\n cursor: pointer;\n color: #fff;\n background-color: transparent;\n border: 0;\n font-family: inherit;\n margin: 0;\n text-decoration: none;\n}\n\nfooter .navbar .liens-utiles button:after, footer .navbar .liens-utiles a:after {\n content: \"|\";\n margin: 0 0.5rem;\n}\n\nfooter .reseaux-sociaux {\n padding: 0 0 0 1.5rem;\n}\n\nfooter .reseaux-sociaux button, footer .reseaux-sociaux a {\n cursor: pointer;\n color: #fff;\n background-color: transparent;\n border: 0;\n font-family: inherit;\n margin: 0;\n}\n\n@media screen and (min-width: 250px) {\n .offre-block-img {\n width: 70px;\n } \n .offre-block-img img {\n width: 69px;\n height: 69px;\n } \n .offre-block-text {\n min-width: 180px;\n }\n}\n\n@media screen and (max-width: 250px) {\n .offre-block-img {\n display: none;\n }\n .offre-block-text {\n width: inherit;\n } \n}\n\n@media screen and (min-width: 298px) {\n .icon-menu {\n margin-right: 1em;\n }\n}\n\n@media screen and (max-width: 330px) {\n .menu-title {\n text-align: center;\n }\n}\n\n@media screen and (max-width: 400px) {\n .offre-title img {\n display: none;\n }\n .offre-bandeau img {\n display: none;\n }\n .schema-text-title {\n font-size: 12px !important;\n }\n .schema-text {\n font-size: 12px !important;\n }\n}\n\n@media screen and (min-width: 768px) {\n .fa-arrow-right:before {\n content: \"\\f061c\";\n font-family: 'FontAwesome';\n color: #fc0d1a;\n font-size:2.5em;\n margin-right: -15px;\n }\n .fa-arrow-down:before {\n display:none\n }\n .arrow {\n margin-left: -30px;\n height: auto;\n display: flex;\n align-items: end;\n } \n .border-dotted {\n border-right: dotted 3px red;\n }\n .schema {\n margin-right: 1em !important;\n }\n}\n@media screen and (max-width: 768px) {\n .fa-arrow-right:before {\n display:none\n }\n .fa-arrow-down:before {\n content: \"\\f061c\";\n font-family: 'FontAwesome';\n color: #fc0d1a;\n font-size:2.5em;\n }\n}\n@media screen and (min-width: 992px) {\n .home-section .home-section-left span {\n text-wrap: nowrap;\n z-index: 1;\n }\n .home-section .home-section-left {\n margin-top: 5em;\n }\n .home-section .home-section-left .en-tete {\n margin-top: 3em;\n text-align: end;\n }\n .en-tete {\n font-size: 23px;\n line-height: 2rem;\n }\n .offre-parcours-img {\n display: inline-flex!important;\n align-items: end;\n }\n}\n\n\n@media screen and (max-width: 992px) {\n .offre-parcours-img {\n display: none;\n }\n}\n\n.modal {\n display: none;\n /* Hidden by default */\n position: fixed;\n z-index: 9999;\n padding-top: 20px;\n left: 0;\n top: 0;\n width: 100%;\n height: 100%;\n overflow: auto;\n background-color: rgb(0, 0, 0);\n /* Fallback color */\n background-color: rgba(0, 0, 0, 0.4);\n /* Black w/ opacity */\n}\n.modal .modal-content {\n background-color: #fefefe;\n margin: auto;\n padding: 20px;\n max-width: 800px;\n border: 1px solid #888;\n border-radius: 5px;\n width: 80%;\n}\n\n.modal-content-scroll {\n overflow-y: scroll;\n height: 100%;\n}\n\n.modal .modal-content .modal-header {\n border-bottom: 1px solid #dee2e6;\n display: flex;\n justify-content: space-between;\n padding: 1rem;\n}\n.modal .modal-content .modal-header span {\n cursor: pointer;\n}\n.modal .modal-content .modal-footer {\n border-top: 1px solid #dee2e6;\n display: flex;\n justify-content: flex-end;\n padding: 1rem 1rem 0rem 1rem;\n}\n.modal .modal-content .close {\n color: #aaaaaa;\n float: right;\n font-size: 28px;\n font-weight: bold;\n}\n.modal .modal-content .close:hover,\n.modal .modal-content .close:focus {\n color: #000;\n text-decoration: none;\n cursor: pointer;\n}\n.modal .modal-content h1 {\n line-height: 1.5;\n margin-top: 0;\n margin-bottom: 0;\n font-size: 1.25rem;\n color: rgba(0, 0, 0, 0.85);\n font-weight: 500;\n}\n.modal .modal-content h2 {\n line-height: 1.2;\n margin-top: 0.5rem;\n margin-bottom: 0.5rem;\n font-size: 1.5rem;\n font-weight: 500;\n}\n.modal .modal-content h3 {\n color: #c62222;\n line-height: 1.2;\n margin-top: 0;\n margin-bottom: 0.5rem;\n font-size: 1.25rem;\n font-weight: 500;\n}\n.modal .modal-content p {\n margin-bottom: 1rem;\n text-align: justify;\n}\n.modal .modal-content ul {\n margin-bottom: 1em;\n margin-top: 0;\n}\n.modal .modal-content ul li {\n list-style-type: none;\n padding-left: 2rem;\n}\n.modal .modal-content form {\n margin: 1rem 0;\n font-weight: 500;\n}\n.modal .modal-content form .form-group {\n margin-bottom: 1rem;\n padding-bottom: 10px;\n}\n.modal .modal-content form .form-group label {\n display: inline-block;\n margin-bottom: 0.5rem;\n}\n.modal .modal-content form .form-group .form-control {\n background-clip: padding-box;\n background-color: #fff;\n border: 1px solid #ced4da;\n border-radius: 0.25rem;\n color: #495057;\n display: block;\n font-size: 1rem;\n font-weight: 400;\n height: calc(1.5em + 0.75rem + 2px);\n line-height: 1.5;\n padding: 0.375rem 2%;\n transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n width: 96%;\n}\n.modal .modal-content form .form-group select.form-control[multiple], .modal .modal-content form .form-group select.form-control[size], .modal .modal-content form .form-group textarea.form-control {\n height: auto;\n}\n.modal .modal-content form button {\n background-color: transparent;\n border: 1px solid transparent;\n border-radius: 0.25rem;\n color: #212529;\n display: inline-block;\n font-size: 1rem;\n font-weight: 400;\n line-height: 1.5;\n padding: 0.375rem 0.75rem;\n text-align: center;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n vertical-align: middle;\n border-color: #28a745;\n color: #28a745;\n}\n.modal .modal-content form textarea {\n font-family: inherit;\n} */","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/css","authenticated":false,"lastPublishedDate":1707811449114}} +{"type":"cmspages","payload":{"_id":"652fa89c4400000d82fcea04","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-mobilite\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre VELO

\n
\n
\n

\n Vol, accident, dommages, assistance, vos utilisateurs sont-ils bien protégés ?\n

\n

\n Notre assurance propose des garanties complètes pour le vélo mais aussi pour le cycliste avec une assistance et une protection corporelle.\n

\n
\n \"offre \n
\n
\n
\n
\n

Penser à bien s’assurer avant de prendre le guidon

\n
\n \"offre \n
\n
\n
\n
\n
\n \"icon \n
\n
\n

Vol, casse… vos utilisateurs couverts partout, quoiqu’il arrive

\n

Une protection pour leur vélo neuf ou d’occasion, et pour ses accessoires. Même la tentative de vol est couverte !

\n
\n
\n
\n
\n \"icon \n
\n
\n

Crevaison panne : Assistance 24h/24

\n

MAIF Assistance dépanne vos utilisateurs et les amènent à leur domicile ou à destination. Intervention sur la route autour de chez eux (de 1 km à 50 km, sauf batterie déchargée).

\n
\n
\n
\n
\n \"icon \n
\n
\n

Chute, accident : vos utilisateurs sont pris en charge

\n

En cas de blessures, MAIF aide vos utilisateurs à se remettre en selle : aide financière, services à domicile, perte de revenus. Et leur enfant passager est couvert.

\n
\n
\n
\n
\n
\n \"offre \n
\n

Garanties & Indemnisations

\n
\n
\n
\n

Des garanties essentielles pour le vélo…

\n
    \n
  • Dommages accident
  • \n
  • Vol
  • \n
  • Accessoires et équipements de protection (y compris la batterie)
  • \n
  • Indemnisation en valeur d’achat 1 an…
  • \n
  • Vélo de remplacement​
  • \n
\n
\n
\n

… mais également pour le cycliste

\n
    \n
  • Assistance (panne & accident) jusqu’à 50km du domicile
  • \n
  • Retour au domicile
  • \n
  • Couverture corporelle
  • \n
  • Prêt de guidon gratuit
  • \n
  • Enfant passager couvert
  • \n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","name":"offre-velo","path":"/offre-velo","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-mobilite\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre VELO

\n
\n
\n

\n Vol, accident, dommages, assistance, vos utilisateurs sont-ils bien protégés ?\n

\n

\n Notre assurance propose des garanties complètes pour le vélo mais aussi pour le cycliste avec une assistance et une protection corporelle.\n

\n
\n \"offre \n
\n
\n
\n
\n

Penser à bien s’assurer avant de prendre le guidon

\n
\n \"offre \n
\n
\n
\n
\n
\n \"icon \n
\n
\n

Vol, casse… vos utilisateurs couverts partout, quoiqu’il arrive

\n

Une protection pour leur vélo neuf ou d’occasion, et pour ses accessoires. Même la tentative de vol est couverte !

\n
\n
\n
\n
\n \"icon \n
\n
\n

Crevaison panne : Assistance 24h/24

\n

MAIF Assistance dépanne vos utilisateurs et les amènent à leur domicile ou à destination. Intervention sur la route autour de chez eux (de 1 km à 50 km, sauf batterie déchargée).

\n
\n
\n
\n
\n \"icon \n
\n
\n

Chute, accident : vos utilisateurs sont pris en charge

\n

En cas de blessures, MAIF aide vos utilisateurs à se remettre en selle : aide financière, services à domicile, perte de revenus. Et leur enfant passager est couvert.

\n
\n
\n
\n
\n
\n \"offre \n
\n

Garanties & Indemnisations

\n
\n
\n
\n

Des garanties essentielles pour le vélo…

\n
    \n
  • Dommages accident
  • \n
  • Vol
  • \n
  • Accessoires et équipements de protection (y compris la batterie)
  • \n
  • Indemnisation en valeur d’achat 1 an…
  • \n
  • Vélo de remplacement​
  • \n
\n
\n
\n

… mais également pour le cycliste

\n
    \n
  • Assistance (panne & accident) jusqu’à 50km du domicile
  • \n
  • Retour au domicile
  • \n
  • Couverture corporelle
  • \n
  • Prêt de guidon gratuit
  • \n
  • Enfant passager couvert
  • \n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520614419}} +{"type":"cmspages","payload":{"_id":"6582b0212a0000359b5c3187","body":"
\n

Portail API MAIF

\n Le site dédié aux partenaires de distribution digitale des assurances MAIF\n \"icon\n
","name":"bloc-offre-side","path":null,"tags":[],"draft":"
\n

Portail API MAIF

\n Le site dédié aux partenaires de distribution digitale des assurances MAIF\n \"icon\n
","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520642480}} +{"type":"cmspages","payload":{"_id":"623c7943350000e106e28f89","body":"\n\n\n \n \n \n \n \n \n \n \n \n\n\n\n {{daikoku-include-block \"623c6fe5350000e106e28d1b\"}}\n \n {{children}}\n\n {{daikoku-include-block \"623c79b8350000e106e28fb4\"}}\n\n\n","name":"IAAS-BASIC-template","path":null,"tags":[],"draft":"\n\n\n \n \n \n \n \n \n \n \n \n\n\n\n {{daikoku-include-block \"623c6fe5350000e106e28d1b\"}}\n \n {{children}}\n\n {{daikoku-include-block \"623c79b8350000e106e28fb4\"}}\n\n\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520677032}} +{"type":"cmspages","payload":{"_id":"623c79b8350000e106e28fb4","body":"
\n
\n \n
\n
\n
\n \n maif.fr\n \n \n {{#unless connected}}\n Créer un compte\n {{/unless}}\n
\n
\n \n \n \n
\n
\n\n \n {{daikoku-include-block \"62a8424a440000921d1d3f06\"}}\n \n \n {{daikoku-include-block \"62aae19a3600001707b32ec4\"}}\n\n \n {{daikoku-include-block \"629f15aa440000921d1d2372\"}}\n\n \n {{daikoku-include-block \"629f4106440000921d1d278f\"}}\n\n\n \n\n
","name":"IAAS-BASIC-footer","path":null,"tags":[],"draft":"
\n
\n \n
\n
\n
\n \n maif.fr\n \n \n {{#unless connected}}\n Créer un compte\n {{/unless}}\n
\n
\n \n \n \n
\n
\n\n \n {{daikoku-include-block \"62a8424a440000921d1d3f06\"}}\n \n \n {{daikoku-include-block \"62aae19a3600001707b32ec4\"}}\n\n \n {{daikoku-include-block \"629f15aa440000921d1d2372\"}}\n\n \n {{daikoku-include-block \"629f4106440000921d1d278f\"}}\n\n\n \n\n
","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520760583}} +{"type":"usage_plans","payload":{"_id":"7Mv0NwlWXXKSdpiLspZOCWYcPw0XTVFnTdBDXPJh0KgmZHoOflAoxqfdmAwkxtWm","type":"FreeWithoutQuotas","_tenant":"default","swagger":null,"testing":null,"_deleted":false,"currency":{"code":"EUR"},"customName":"admin","visibility":"Public","autoRotation":false,"documentation":null,"otoroshiTarget":null,"authorizedTeams":[],"billingDuration":{"unit":"Month","value":1},"allowMultipleKeys":true,"customDescription":null,"integrationProcess":"ApiKey","subscriptionProcess":[],"aggregationApiKeysSecurity":false}} +{"type":"usage_plans","payload":{"_id":"9r3Eh6FN0CtRkqRhHeFs0QZgsF44lx6i","type":"FreeWithQuotas","_tenant":"default","swagger":null,"testing":null,"_deleted":false,"currency":{"code":"EUR"},"maxPerDay":1,"customName":null,"visibility":"Public","maxPerMonth":1,"autoRotation":false,"maxPerSecond":1,"documentation":null,"otoroshiTarget":null,"authorizedTeams":[],"billingDuration":{"unit":"Month","value":1},"allowMultipleKeys":false,"customDescription":"Free plan with limited number of calls per day and per month","integrationProcess":"ApiKey","subscriptionProcess":[],"aggregationApiKeysSecurity":false}} +{"type":"usage_plans","payload":{"_id":"7p9AW4w7ZmGMl4hWzAp7YCLxAD9TvOiw","type":"FreeWithoutQuotas","_tenant":"default","swagger":null,"testing":null,"_deleted":false,"currency":{"code":"EUR"},"customName":null,"visibility":"Public","autoRotation":false,"documentation":null,"otoroshiTarget":null,"authorizedTeams":[],"billingDuration":{"unit":"Month","value":1},"allowMultipleKeys":false,"customDescription":"Free plan with unlimited number of calls per day and per month","integrationProcess":"ApiKey","subscriptionProcess":[],"aggregationApiKeysSecurity":false}} diff --git a/cli/tests/resources/daikoku-state.ndjson b/cli/tests/resources/daikoku-state.ndjson new file mode 100644 index 000000000..6e2a69be1 --- /dev/null +++ b/cli/tests/resources/daikoku-state.ndjson @@ -0,0 +1,99 @@ +{"type":"tenants","payload":{"_id":"default","name":"Daikoku Default Tenant","style":{"js":"","css":"","logo":"/assets/images/daikoku.svg","jsUrl":null,"title":"Daikoku Default Tenant","cssUrl":null,"footer":null,"cacheTTL":60000,"colorTheme":":root {\n --error-color: #ff6347;\n --error-color: #ffa494;\n --success-color: #4F8A10;\n --success-color: #76cf18;\n\n --link-color: #7f96af;\n --link--hover-color: #8fa6bf;\n\n --body-bg-color: #fff;\n --body-text-color: #212529;\n --navbar-bg-color: #7f96af;\n --navbar-brand-color: #fff;\n --menu-bg-color: #fff;\n --menu-text-color: #212529;\n --menu-text-hover-bg-color: #9bb0c5;\n --menu-text-hover-color: #fff;\n --section-bg-color: #f8f9fa;\n --section-text-color: #6c757d;\n --section-bottom-color: #eee;\n --addContent-bg-color: #e9ecef;\n --addContent-text-color: #000;\n --sidebar-bg-color: #f8f9fa;\n\n --btn-bg-color: #fff;\n --btn-text-color: #495057;\n --btn-border-color: #97b0c7;\n\n --badge-tags-bg-color: #ffc107;\n --badge-tags-bg-color: #ffe1a7;\n --badge-tags-text-color: #212529;\n\n --pagination-text-color: #586069;\n --pagination-border-color: #586069;\n\n --table-bg-color: #f8f9fa;\n\n --apicard-visibility-color: #586069;\n --apicard-visibility-border-color: rgba(27,31,35,.15);\n --modal-selection-bg-color: rgba(27,31,35,.15);\n}","faviconUrl":null,"description":"A new organization to host very fine APIs","homeCmsPage":"aaWYws62IbbyHGqHKDMMnko0yk66LUTq","unloggedHome":"","fontFamilyUrl":null,"homePageVisible":true,"notFoundCmsPage":"aaWYws62IbbyHGqHKDMMnko0yk66LUTq","cmsHistoryLength":10,"authenticatedCmsPage":"aaWYws62IbbyHGqHKDMMnko0yk66LUTq"},"domain":"localhost","contact":"contact@foo.bar","display":"default","enabled":true,"_deleted":false,"adminApi":"admin-api-tenant-default","robotTxt":null,"isPrivate":false,"tenantMode":"Default","authProvider":"Local","environments":[],"bucketSettings":{"access":"J11Q131JBRSOXFEOIHR8","bucket":"demo-daikoku","region":"eu-west-1","secret":"jSwxKcaFLTLUvpoRdE80wOl7dbwRIWxpDtomrAph","v4auth":true,"endpoint":"https://demo-daikoku.cellar-c2.services.clever-cloud.com","chunkSize":8388608},"defaultMessage":null,"mailerSettings":{"type":"console","template":"Bienvenue !\n{{email}}"},"defaultLanguage":"En","_humanReadableId":"daikoku-default-tenant","auditTrailConfig":{"kafkaConfig":null,"alertsEmails":[],"auditWebhooks":[],"elasticConfigs":null},"creationSecurity":false,"otoroshiSettings":[],"adminSubscriptions":["wKmlBKvIpD6RMF4ahGtwdNgwVVnX8feZ","cIYrbHDhwvhwDpO0A4dx9w6FhBlaydBb"],"authProviderSettings":{"sessionMaxAge":86400},"subscriptionSecurity":false,"apiReferenceHideForGuest":true,"thirdPartyPaymentSettings":[],"aggregationApiKeysSecurity":false}} +{"type":"users","payload":{"_id":"oxrtSfdDVXe7b0jEYFaziUzATuFuWsAX","name":"Daikoku admin","email":"admin@daikoku.io","isGuest":false,"origins":["Otoroshi"],"picture":"https://www.gravatar.com/avatar/35486a5583d99e3b2773814f359e4631?size=128&d=robohash","tenants":["default"],"_deleted":false,"metadata":{},"password":"$2a$10$RIZnvDZNZbmKBVkIJU1toOjjhfyW6d1OePboAcksYaB7A2EDNYOWe","invitation":null,"lastTenant":"default","starredApis":[],"personalToken":"2pAuww5Onlfn8po79jBCdsyB3lqWYRu8","isDaikokuAdmin":true,"defaultLanguage":null,"_humanReadableId":"admin-daikoku.io","pictureFromProvider":true,"twoFactorAuthentication":null,"hardwareKeyRegistrations":[]}} +{"type":"user_sessions","payload":{"_id":"KDF7uHaE2csoj4k4XPmqqIpQ4s47KU0W","ttl":86400000,"userId":"oxrtSfdDVXe7b0jEYFaziUzATuFuWsAX","created":1722521844604,"expires":1726821444735,"userName":"Daikoku admin","sessionId":"aWTVLkEg9jPwsK5hqDdxXR78wepinfe8kLbxsaE81gjKK7qY6ipOSNYSsMubrMFe","userEmail":"admin@daikoku.io","impersonatorId":null,"impersonatorName":null,"impersonatorEmail":null,"impersonatorSessionId":null}} +{"type":"evolutions","payload":{"_id":"JLJNk2wsTjTty1rHM4obDrPfSqO2J9Iq","date":1718877359581,"applied":true,"version":"1.0.2"}} +{"type":"evolutions","payload":{"_id":"XdHwjuH0J7V1GYcT6QhYVDnv4rNfWSSQ","date":1718877359672,"applied":true,"version":"1.5.0"}} +{"type":"evolutions","payload":{"_id":"JaU5exr5jgVgCdHuqJlODdFYvFjOG8KZ","date":1718877359824,"applied":true,"version":"1.5.1"}} +{"type":"evolutions","payload":{"_id":"bXjq9ctGAm2sVWs62eGZddYrTwaCMjW9","date":1718877359943,"applied":true,"version":"1.5.5"}} +{"type":"evolutions","payload":{"_id":"8mHk5z9cFPb4MzzXvjBZZHUueznmefAM","date":1718877359960,"applied":true,"version":"1.5.7"}} +{"type":"evolutions","payload":{"_id":"WH6GZbFMyYCeiuTxXskLrVps33vPGjvp","date":1718877359996,"applied":true,"version":"1.5.7_b"}} +{"type":"evolutions","payload":{"_id":"TI8pshde6PWcEpqydZj64AINWIlKvOig","date":1718877360020,"applied":true,"version":"1.5.7_c"}} +{"type":"evolutions","payload":{"_id":"uS9ImhWnuXuHq29H2XWVOHMg6GFYKppy","date":1718877360049,"applied":true,"version":"16.1.2_a"}} +{"type":"evolutions","payload":{"_id":"OlLZeOhBoq4WOh6meoPleuAPlqYWNnzw","date":1718877360068,"applied":true,"version":"16.1.2_b"}} +{"type":"evolutions","payload":{"_id":"pTP0F8ahxLmHlT4IfZ5tfWzS1B9gf5zI","date":1722502534886,"applied":true,"version":"16.1.2_c"}} +{"type":"evolutions","payload":{"_id":"n7FBhRDFC74F5shFrHqZ4LVFLr0HGmGm","date":1722502534892,"applied":true,"version":"16.1.3"}} +{"type":"evolutions","payload":{"_id":"Ijn8BILDTpb55IOEed4e31F4AskgAjuu","date":1722502534894,"applied":true,"version":"16.1.3_b"}} +{"type":"evolutions","payload":{"_id":"WPLQDypKW5FMiUx08iNHIVcwJfP1mGQ0","date":1722502534898,"applied":true,"version":"16.3.0"}} +{"type":"evolutions","payload":{"_id":"j1xCrUA1S6j3tEZkAM7Lc4afo1vbtHBM","date":1722502534901,"applied":true,"version":"16.3.4"}} +{"type":"evolutions","payload":{"_id":"3w4QDIT2uF85dqd4B7XQl9cQwGoHQ7lu","date":1726646024172,"applied":true,"version":"17.5.0"}} +{"type":"teams","payload":{"_id":"Z336LuOwnaqYRslEfVMDLokjL75WndtlSZFh3rg5i1EZv2HRqpFg6YnFunkWowoS","name":"daikoku-default-tenant-admin-team","type":"Admin","users":[{"userId":"oxrtSfdDVXe7b0jEYFaziUzATuFuWsAX","teamPermission":"Administrator"}],"avatar":"/assets/images/daikoku.svg","_tenant":"default","contact":"contact@foo.bar","_deleted":false,"metadata":{},"verified":true,"description":"The admin team for the default tenant","_humanReadableId":"daikoku-default-tenant-admin-team","apiKeyVisibility":"User","apisCreationPermission":null,"authorizedOtoroshiEntities":null}} +{"type":"teams","payload":{"_id":"FnagTV9CDvPgJNPY7wCga03hdjyuE2HI","name":"Daikoku admin","type":"Personal","users":[{"userId":"oxrtSfdDVXe7b0jEYFaziUzATuFuWsAX","teamPermission":"Administrator"}],"avatar":"/assets/images/daikoku.svg","_tenant":"default","contact":"admin@daikoku.io","_deleted":false,"metadata":{},"verified":true,"description":"Daikoku admin's team","_humanReadableId":"daikoku-admin","apiKeyVisibility":"User","apisCreationPermission":null,"authorizedOtoroshiEntities":null}} +{"type":"teams","payload":{"_id":"8jHt9Kysthbt32Lt4VsdMh3S2q3bgwhs","name":"New Team","type":"Organization","users":[{"userId":"oxrtSfdDVXe7b0jEYFaziUzATuFuWsAX","teamPermission":"Administrator"}],"avatar":"/assets/images/daikoku.svg","_tenant":"default","contact":"contact@foo.bar","_deleted":false,"metadata":{},"verified":false,"description":"A new team","_humanReadableId":"new-team","apiKeyVisibility":"Administrator","apisCreationPermission":null,"authorizedOtoroshiEntities":null}} +{"type":"apis","payload":{"_id":"cms-api-tenant-default","apis":null,"name":"cms-api-tenant-default","tags":[],"team":"Z336LuOwnaqYRslEfVMDLokjL75WndtlSZFh3rg5i1EZv2HRqpFg6YnFunkWowoS","image":null,"posts":[],"stars":0,"state":"published","header":null,"issues":[],"parent":null,"_tenant":"default","swagger":{"url":"/cms-api/swagger.json","content":null,"headers":{},"additionalConf":null,"specificationType":"openapi"},"testing":null,"_deleted":false,"isDefault":true,"categories":[],"issuesTags":[],"lastUpdate":1726646024114,"visibility":"AdminOnly","description":"cms api","documentation":{"_id":"1ykF7i8ZOLXNh46Tq0AkzBFB26xntC9b","pages":[],"_tenant":"default","lastModificationAt":1726646024114},"currentVersion":"1.0.0","authorizedTeams":[],"_humanReadableId":"cms-api-tenant-default","defaultUsagePlan":"7Mv0NwlWXXKSdpiLspZOCWYcPw0XTVFnTdBDXPJh0KgmZHoOflAoxqfdmAwkxtWm","smallDescription":"cms api","supportedVersions":["1.0.0"],"descriptionCmsPage":null,"possibleUsagePlans":["7Mv0NwlWXXKSdpiLspZOCWYcPw0XTVFnTdBDXPJh0KgmZHoOflAoxqfdmAwkxtWm"],"customHeaderCmsPage":null}} +{"type":"apis","payload":{"_id":"y33XIGECvL0EhiRuosRTRyyE429hj3Vj","apis":null,"name":"CMS API","tags":[],"team":"FnagTV9CDvPgJNPY7wCga03hdjyuE2HI","image":null,"posts":["q7w5eLC6Ap0YdxQMR8rko6fCAChSlqwx"],"stars":0,"state":"created","header":"

Un header spécialisé

","issues":["yLyiSRBJlSuoNmgwCdNvQylPSXTz5Dou","BUq7LkJJ8xrvRfi3JqDguTGAexSFgGcv"],"parent":null,"_tenant":"default","swagger":{"url":"https://petstore3.swagger.io/api/v3/openapi.json","content":null,"headers":{},"additionalConf":null,"specificationType":"openapi"},"testing":null,"_deleted":true,"isDefault":true,"categories":[],"issuesTags":[],"lastUpdate":1722410834016,"visibility":"Public","description":"

\n Description perso auss\n

","documentation":{"_id":"YhtgkCN3jjgyKes0bAoTu5SHk3VwVOnf","pages":[{"id":"p3v0gupO5SI9y_P-epX0pLmLVZA8oZZe","title":"Une page","children":[]},{"id":"TJ90oJSKcSUz4i1LAKLp7RKAXb8nqf8Q","title":"une second epage","children":[]}],"_tenant":"default","lastModificationAt":1722410834016},"currentVersion":"1.0.0","authorizedTeams":[],"_humanReadableId":"cms-api","defaultUsagePlan":null,"smallDescription":"CMS API Desc","supportedVersions":[],"descriptionCmsPage":"mails-mail-create-post-title-fr","possibleUsagePlans":["9r3Eh6FN0CtRkqRhHeFs0QZgsF44lx6i"],"customHeaderCmsPage":"mails-mail-api-access-rejection-body-en"}} +{"type":"apis","payload":{"_id":"V80UfyIoYE6YTagwCGr3rHlD2QB0AAaE","apis":null,"name":"un test pour el CLI","tags":[],"team":"FnagTV9CDvPgJNPY7wCga03hdjyuE2HI","image":null,"posts":[],"stars":0,"state":"published","header":null,"issues":[],"parent":null,"_tenant":"default","swagger":null,"testing":null,"_deleted":false,"isDefault":true,"categories":[],"issuesTags":[],"lastUpdate":1726647292775,"visibility":"Public","description":"A new API","documentation":{"_id":"6THbGX08Lal1hzqsi8Ta5F7QPGmHe8jb","pages":[],"_tenant":"default","lastModificationAt":1726647292775},"currentVersion":"1.0.0","authorizedTeams":[],"_humanReadableId":"un-test-pour-el-cli","defaultUsagePlan":null,"smallDescription":"A new API","supportedVersions":["1.0.0"],"descriptionCmsPage":null,"possibleUsagePlans":[],"customHeaderCmsPage":null}} +{"type":"apis","payload":{"_id":"admin-api-tenant-default","apis":null,"name":"admin-api-tenant-default","tags":[],"team":"Z336LuOwnaqYRslEfVMDLokjL75WndtlSZFh3rg5i1EZv2HRqpFg6YnFunkWowoS","image":null,"posts":[],"stars":0,"state":"published","header":null,"issues":[],"parent":null,"_tenant":"default","swagger":{"url":"/admin-api/swagger.json","content":null,"headers":{},"additionalConf":null,"specificationType":"openapi"},"testing":{"auth":"Basic","name":null,"config":null,"enabled":false,"password":null,"username":null},"_deleted":false,"isDefault":true,"categories":[],"issuesTags":[],"lastUpdate":1718877357724,"visibility":"AdminOnly","description":"admin api","documentation":{"_id":"A92iE2DXAtfNPsvzH93NdlS4i5PAGLKE","pages":[],"_tenant":"default","lastModificationAt":1718877357799},"currentVersion":"1.0.0","authorizedTeams":[],"_humanReadableId":"admin-api-tenant-default","defaultUsagePlan":null,"smallDescription":"admin api","supportedVersions":["1.0.0"],"descriptionCmsPage":null,"possibleUsagePlans":["7p9AW4w7ZmGMl4hWzAp7YCLxAD9TvOiw"],"customHeaderCmsPage":null}} +{"type":"api_subscriptions","payload":{"by":"oxrtSfdDVXe7b0jEYFaziUzATuFuWsAX","_id":"wKmlBKvIpD6RMF4ahGtwdNgwVVnX8feZ","api":"cms-api-tenant-default","plan":"7Mv0NwlWXXKSdpiLspZOCWYcPw0XTVFnTdBDXPJh0KgmZHoOflAoxqfdmAwkxtWm","tags":[],"team":"Z336LuOwnaqYRslEfVMDLokjL75WndtlSZFh3rg5i1EZv2HRqpFg6YnFunkWowoS","apiKey":{"clientId":"jbuQkDajYe8VU54kdcUmhZ8V3B5CCfWR","clientName":"daikoku-api-key-cms-api-tenant-default-admin-daikoku-default-tenant-admin-team-1726646839925","clientSecret":"y2W6kXWmrG0qvo1SjlJ7XSS4dA9pg9t9Ygnp19H9tyqBZdNMJFCBdIQuJCxZ1y8U"},"parent":null,"_tenant":"default","enabled":true,"_deleted":false,"metadata":null,"rotation":{"enabled":true,"gracePeriod":168,"rotationEvery":744,"pendingRotation":false},"createdAt":1726646839925,"customName":null,"customMetadata":null,"customReadOnly":null,"adminCustomName":null,"customMaxPerDay":null,"integrationToken":"ZViMD9IfYLlVn72SHFRzyqadDEaC4pMxxBnLiHJRTPdjrxPJomtJi7gAg7TuX3Y0","customMaxPerMonth":null,"customMaxPerSecond":null,"thirdPartySubscriptionInformations":null}} +{"type":"api_subscriptions","payload":{"by":"oxrtSfdDVXe7b0jEYFaziUzATuFuWsAX","_id":"cIYrbHDhwvhwDpO0A4dx9w6FhBlaydBb","api":"admin-api-tenant-default","plan":"7p9AW4w7ZmGMl4hWzAp7YCLxAD9TvOiw","tags":[],"team":"Z336LuOwnaqYRslEfVMDLokjL75WndtlSZFh3rg5i1EZv2HRqpFg6YnFunkWowoS","apiKey":{"clientId":"MN63Hd1lehqqiE1iUwCeM35hVRllw76g","clientName":"daikoku-api-key-admin-api-tenant-default-freewithoutquotas-daikoku-default-tenant-admin-team-1726736293074","clientSecret":"ycD9zAAD3G0h14tWK14oOaIw7eGdEPBnMX29zRgKapSz2WE1itLs9eiJkv9aHuVz"},"parent":null,"_tenant":"default","enabled":true,"_deleted":false,"metadata":null,"rotation":{"enabled":true,"gracePeriod":168,"rotationEvery":744,"pendingRotation":false},"createdAt":1726736293074,"customName":null,"customMetadata":null,"customReadOnly":null,"adminCustomName":null,"customMaxPerDay":null,"integrationToken":"yuHAieGA8eaykMxtCEL1CrJXhAQMjEX3i99gl8LEzRoDnzmdpbTdI9lfo5dBCbn1","customMaxPerMonth":null,"customMaxPerSecond":null,"thirdPartySubscriptionInformations":null}} +{"type":"api_documentation_pages","payload":{"_id":"p3v0gupO5SI9y_P-epX0pLmLVZA8oZZe","title":"Une page","_tenant":"default","content":"# New page\n\nA new page","_deleted":false,"contentType":"text/markdown","_humanReadableId":"p3v0gupO5SI9y_P-epX0pLmLVZA8oZZe","remoteContentUrl":null,"lastModificationAt":1722410950203,"remoteContentEnabled":false,"remoteContentHeaders":{}}} +{"type":"api_documentation_pages","payload":{"_id":"TJ90oJSKcSUz4i1LAKLp7RKAXb8nqf8Q","title":"une second epage","_tenant":"default","cmsPage":"apis-cms-api-documentations-iard-sinistres-v4","content":"# New page\n\nA new page","_deleted":false,"contentType":"cms/page","_humanReadableId":"TJ90oJSKcSUz4i1LAKLp7RKAXb8nqf8Q","remoteContentUrl":null,"lastModificationAt":1722434664842,"remoteContentEnabled":false,"remoteContentHeaders":{}}} +{"type":"api_posts","payload":{"_id":"q7w5eLC6Ap0YdxQMR8rko6fCAChSlqwx","title":"Une news","_tenant":"default","content":"Une news description\n","_deleted":false,"_humanReadableId":"une-news","lastModificationAt":1722410969178}} +{"type":"api_issues","payload":{"by":"oxrtSfdDVXe7b0jEYFaziUzATuFuWsAX","_id":"yLyiSRBJlSuoNmgwCdNvQylPSXTz5Dou","open":false,"tags":[],"seqId":0,"title":"New issue","_tenant":"default","_deleted":false,"closedAt":null,"comments":[{"by":"oxrtSfdDVXe7b0jEYFaziUzATuFuWsAX","content":"description","createdAt":1722411029113,"lastModificationAt":1722411029113}],"createdAt":1722411015499,"apiVersion":"1.0.0","_humanReadableId":"0","lastModificationAt":1722411015499}} +{"type":"api_issues","payload":{"by":"oxrtSfdDVXe7b0jEYFaziUzATuFuWsAX","_id":"BUq7LkJJ8xrvRfi3JqDguTGAexSFgGcv","open":true,"tags":[],"seqId":1,"title":"sadasd","_tenant":"default","_deleted":false,"closedAt":null,"comments":[{"by":"oxrtSfdDVXe7b0jEYFaziUzATuFuWsAX","content":"asdasd","createdAt":1722411079010,"lastModificationAt":1722411079010}],"createdAt":1722411066705,"apiVersion":"1.0.0","_humanReadableId":"1","lastModificationAt":1722411066705}} +{"type":"notifications","payload":{"_id":"gWTPLN13NpdQptlXYLeEzAplPLfAQktn","date":1722411071780,"team":"FnagTV9CDvPgJNPY7wCga03hdjyuE2HI","action":{"type":"NewIssueOpen","linkTo":"/daikoku-admin/cms-api/1.0.0/issues/BUq7LkJJ8xrvRfi3JqDguTGAexSFgGcv","teamId":"FnagTV9CDvPgJNPY7wCga03hdjyuE2HI","apiName":"CMS API"},"sender":{"id":"oxrtSfdDVXe7b0jEYFaziUzATuFuWsAX","name":"Daikoku admin","email":"admin@daikoku.io"},"status":{"date":1722501444667,"status":"Accepted"},"_tenant":"default","_deleted":false,"notificationType":"AcceptOnly"}} +{"type":"notifications","payload":{"_id":"lIegg9eQnLbzUd0fm98ZNhOTLPKu5pVz","date":1722411029150,"team":"FnagTV9CDvPgJNPY7wCga03hdjyuE2HI","action":{"type":"NewIssueOpen","linkTo":"/daikoku-admin/cms-api/1.0.0/issues/yLyiSRBJlSuoNmgwCdNvQylPSXTz5Dou","teamId":"FnagTV9CDvPgJNPY7wCga03hdjyuE2HI","apiName":"CMS API"},"sender":{"id":"oxrtSfdDVXe7b0jEYFaziUzATuFuWsAX","name":"Daikoku admin","email":"admin@daikoku.io"},"status":{"date":1722501445331,"status":"Accepted"},"_tenant":"default","_deleted":false,"notificationType":"AcceptOnly"}} +{"type":"translations","payload":{"_id":"o2Wlf8078xzPPk29TwLBAfbiJa6KxyiF","key":"tenant.mail.template","value":"En francais je change le template par default\n[email]","_tenant":"default","language":"fr","lastModificationAt":1722515549597}} +{"type":"translations","payload":{"_id":"Xz31HG0cdkIWoUUaMPTaE46ZcW3ofsah","key":"mail.subscription.validation.body","value":"Hello, \n
\n[user] for the team [team] wants to subscribe your API,
[body]
Accept or Reject\n\n\n\n\nmail.subscription.validation.body je change aussi ici\n","_tenant":"default","language":"en","lastModificationAt":1722515570243}} +{"type":"translations","payload":{"_id":"PgEc6JKI6XOpWheo9GbjtKEDh9EPhGHV","key":"mail.subscription.validation.body","value":"Bonjour,
[user] pour l'équipe [team] voudrait souscrire a l'API [apiName],
[body]
Accept or Reject\n\n\nEt un bout du french aussi!","_tenant":"default","language":"fr","lastModificationAt":1722515581470}} +{"type":"translations","payload":{"_id":"KVMJHXnReOBmXQgPPLgpTan3FMnUxyCg","key":"tenant.mail.template","value":"En anglais je change le template par default asdasd\n[email]","_tenant":"default","language":"en","lastModificationAt":1722517991730}} +{"type":"translations","payload":{"_id":"K9qzedRJjY3o9xBTG9ccC2OzuCrZyxSx","key":"mail.acceptation.title","value":"Your Daikoku request has been accepted encore.","_tenant":"default","language":"en","lastModificationAt":1724417135045}} +{"type":"email_verifications","payload":{"_id":"tCJbgru2ckMBrTKhqzEDTB7xKDeRxy5j","teamId":"8jHt9Kysthbt32Lt4VsdMh3S2q3bgwhs","_tenant":"default","_deleted":false,"randomId":"egPP1YfGppPeBaDwjorUN2hObhOIyIiDDa6dMECKZUAVe9KcO9R3CG2HUZ9X7GDd","validUntil":1726737601618,"creationDate":1726736701618}} +{"type":"cmspages","payload":{"_id":"V80UfyIoYE6YTagwCGr3rHlD2QB0AAaE","body":"un super header","name":"page.html","path":"/apis/un-test-pour-el-cli/header","tags":[],"draft":"un super header","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{"from":"cli","_name":"page.html","_path":"/apis/un-test-pour-el-cli/header","_content_type":"text/html"},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":null}} +{"type":"cmspages","payload":{"_id":"62aae19a3600001707b32ec4","body":"
\n \n
\n
\n

Demande de contact

\n ×\n
\n

\n
\n

Le message a bien été envoyé.

\n
\n
\n
\n","name":"MODAL-success","path":null,"tags":[],"draft":"
\n \n
\n
\n

Demande de contact

\n ×\n
\n

\n
\n

Le message a bien été envoyé.

\n
\n
\n
\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1677069681802}} +{"type":"cmspages","payload":{"_id":"6514440c3d00001b85d2dc8a","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/parcours-api\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Devis

\n
\n
\n
\n

\n Le parcours Devis est la solution optimale pour intégrer une proposition d’assurance dans votre parcours utilisateur.\n

\n
\n Offres disponibles sur ce parcours\n
    \n
  • Offres AUTO, VÉLO et EDPM
  • \n
  • Offre MRH jeune
  • \n
  • Offre Travailleur Indépendant
  • \n
\n
\n
\n
\n \"parcours \n
\n
\n
\n
\n

Intégrez le devis d’assurance dans votre parcours utilisateur

\n
\n
\n
\n
\n \"icon\n Le parcours Devis vous permet d’intégrer sur votre environnement (app mobile ou site internet) la réalisation du devis d’assurance. Vos utilisateurs bénéficient d’un parcours sans couture.\n
\n
\n
\n
\n

Votre Site / App

\n

Votre utilisateur réalise de manière transparente son devis au sein de votre parcours, sans rupture.

\n

DEVIS

\n
\n
\n
\n
\n

MAIF.fr

\n

Votre utilisateur réalise sa souscription d’assurance sur MAIF.fr (via le lien reçu par email avec son devis)

\n

SOUSCRIPTION

\n
\n
\n
\n
\n

Partenaires Cibles

\n
\n
\n
\n Notre Parcours Devis est adapté pour les partenaires avec expertise assurance, avec un besoin d’intégration de l’assurance dans le parcours utilisateur.\n
\n \"parcours\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}","name":"parcours-devis","path":"/parcours-devis","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/parcours-api\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Devis

\n
\n
\n
\n

\n Le parcours Devis est la solution optimale pour intégrer une proposition d’assurance dans votre parcours utilisateur.\n

\n
\n Offres disponibles sur ce parcours\n
    \n
  • Offres AUTO, VÉLO et EDPM
  • \n
  • Offre MRH jeune
  • \n
  • Offre Travailleur Indépendant
  • \n
\n
\n
\n
\n \"parcours \n
\n
\n
\n
\n

Intégrez le devis d’assurance dans votre parcours utilisateur

\n
\n
\n
\n
\n \"icon\n Le parcours Devis vous permet d’intégrer sur votre environnement (app mobile ou site internet) la réalisation du devis d’assurance. Vos utilisateurs bénéficient d’un parcours sans couture.\n
\n
\n
\n
\n

Votre Site / App

\n

Votre utilisateur réalise de manière transparente son devis au sein de votre parcours, sans rupture.

\n

DEVIS

\n
\n
\n
\n
\n

MAIF.fr

\n

Votre utilisateur réalise sa souscription d’assurance sur MAIF.fr (via le lien reçu par email avec son devis)

\n

SOUSCRIPTION

\n
\n
\n
\n
\n

Partenaires Cibles

\n
\n
\n
\n Notre Parcours Devis est adapté pour les partenaires avec expertise assurance, avec un besoin d’intégration de l’assurance dans le parcours utilisateur.\n
\n \"parcours\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520456539}} +{"type":"cmspages","payload":{"_id":"655dcd5615000001e6aed967","body":"var sidenav = document.getElementById(\"mySidenav\");\nvar openBtn = document.getElementById(\"openBtn\");\nvar closeBtn = document.getElementById(\"closeBtn\");\n\nopenBtn.onclick = openNav;\ncloseBtn.onclick = closeNav;\n\n/* Set the width of the side navigation to 250px */\nfunction openNav() {\n sidenav.classList.add(\"active\");\n}\n\n/* Set the width of the side navigation to 0 */\nfunction closeNav() {\n sidenav.classList.remove(\"active\");\n}","name":"navbar-js","path":"/655dcd5615000001e6aed968","tags":[],"draft":"var sidenav = document.getElementById(\"mySidenav\");\nvar openBtn = document.getElementById(\"openBtn\");\nvar closeBtn = document.getElementById(\"closeBtn\");\n\nopenBtn.onclick = openNav;\ncloseBtn.onclick = closeNav;\n\n/* Set the width of the side navigation to 250px */\nfunction openNav() {\n sidenav.classList.add(\"active\");\n}\n\n/* Set the width of the side navigation to 0 */\nfunction closeNav() {\n sidenav.classList.remove(\"active\");\n}","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/javascript","authenticated":false,"lastPublishedDate":1700647278213}} +{"type":"cmspages","payload":{"_id":"654b95412100008ec5dcc05e","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-mobilite\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre MOTO

\n
\n
\n

\n Notre assurance moto et scooter s’ajuste aux besoins et aux budgets de vos utilisateurs : ils peuvent choisir parmi 4 formules au tiers ou tous risques, avec des garanties étendues et des options.\n

\n
\n
\n
\n

Des garanties solides adaptées aux motards

\n
\n
\n
\n
\n \"icon \n
\n
\n

Assistance sur la route 24h/24 et 7j/7

\n

Vos utilisateurs sont dépannés lors de leurs déplacements à plus de 50 km de chez eux. Et géolocalisés, avec l’application MAIF. Leur passager est également pris en charge et rapatrié.​

\n
\n
\n
\n
\n \"icon \n
\n
\n

Vol, incendie : même assuré au tiers

\n

La moto de vos utilisateurs est couverte dès la formule Tiers enrichie (Essentiel). Leur équipement de protection motard est pris en charge : casque, gants, gilet airbag, antivol.

\n
\n
\n
\n
\n \"icon \n
\n
\n

Protection du motard

\n

Un premier niveau de protection corporelle est inclus dans toutes nos formules, pour aider vos utilisateurs et leurs proches, en cas de blessure ou de décès.

\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","name":"offre-moto","path":"/offre-moto","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-mobilite\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre MOTO

\n
\n
\n

\n Notre assurance moto et scooter s’ajuste aux besoins et aux budgets de vos utilisateurs : ils peuvent choisir parmi 4 formules au tiers ou tous risques, avec des garanties étendues et des options.\n

\n
\n
\n
\n

Des garanties solides adaptées aux motards

\n
\n
\n
\n
\n \"icon \n
\n
\n

Assistance sur la route 24h/24 et 7j/7

\n

Vos utilisateurs sont dépannés lors de leurs déplacements à plus de 50 km de chez eux. Et géolocalisés, avec l’application MAIF. Leur passager est également pris en charge et rapatrié.​

\n
\n
\n
\n
\n \"icon \n
\n
\n

Vol, incendie : même assuré au tiers

\n

La moto de vos utilisateurs est couverte dès la formule Tiers enrichie (Essentiel). Leur équipement de protection motard est pris en charge : casque, gants, gilet airbag, antivol.

\n
\n
\n
\n
\n \"icon \n
\n
\n

Protection du motard

\n

Un premier niveau de protection corporelle est inclus dans toutes nos formules, pour aider vos utilisateurs et leurs proches, en cas de blessure ou de décès.

\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520379884}} +{"type":"cmspages","payload":{"_id":"656750ea2a00008e0f55a812","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n

UNIVERS Vie Quotidienne & Pro

\n
\n
\n
\n
\n \n \n
\n \"icon\n

Garanties Accidents de la Vie

\n
\n \n
\n \"icon\n

Propriétaire Bailleur

\n
\n
\n \"icon\n

Assurance Vie

\n
\n
\n
\n
\n \"icon\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","name":"univers-vie-quotidienne-pro","path":"/univers-vie-quotidienne-pro","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n

UNIVERS Vie Quotidienne & Pro

\n
\n
\n
\n
\n \n \n
\n \"icon\n

Garanties Accidents de la Vie

\n
\n \n
\n \"icon\n

Propriétaire Bailleur

\n
\n
\n \"icon\n

Assurance Vie

\n
\n
\n
\n
\n \"icon\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520479461}} +{"type":"cmspages","payload":{"_id":"65708cd52b0000c5d720a237","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-vie-quotidienne-pro\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre HABITATION JEUNES

\n
\n
\n

\n Pour les locataires ou colocataires de moins de 30 ans et sans enfant.\n

\n

\n S'assurer est une obligation légale pour tous les locataires.\n

\n

\n Notre Assurance Habitation Jeunes répond à ce devoir et va bien au-delà pour vos utilisateurs.\n

\n
\n \"offre \n
\n
\n
\n
\n

Garanties de l’offre habitation jeunes

\n
\n
\n
\n

Des garanties essentielles dans le logement…

\n
    \n
  • Incendie, explosion et dégâts des eaux
  • \n
  • Bris de vitre sur l’immobilier
  • \n
  • Evénement climatique
  • \n
  • Cambriolage…
  • \n
\n
\n
\n \"offre \n
\n
\n

… mais également en dehors

\n
    \n
  • Responsabilité Civile (dommages causés à un tiers)
  • \n
  • Vol des biens pendant le déménagement
  • \n
  • Assistance voyage rapatriement
  • \n
  • Extension Biens Nomades (assure partout les biens que vous transportez avec vous : Smartphone, PC portable, instrument de musique, matériel de sport…)
  • \n
\n
\n
\n
\n

Une assurance responsable adaptée à votre quotidien

\n
\n
\n
\n
\n \"icon \n
\n
\n

Prêter, emprunter, louer

\n

MAIF encourage le partage plutôt que l’achat. On assure les biens que vos utilisateurs louent, prêtent ou empruntent.

\n
\n
\n
\n
\n \"icon \n
\n
\n

Réparer plutôt que remplacer

\n

A chaque fois que cela est possible, en cas de sinistre, nous privilégions la réparation des biens plutôt que leur remplacement. Un petit geste pour la planète.

\n
\n
\n
\n
\n \"icon \n
\n
\n

Limiter vos frais en colocation

\n

Les colocataires sont tous assurés de la même manière sur le même contrat.

\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","name":"offre-mrh-jeunes","path":"/offre-mrh-jeunes","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-vie-quotidienne-pro\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre HABITATION JEUNES

\n
\n
\n

\n Pour les locataires ou colocataires de moins de 30 ans et sans enfant.\n

\n

\n S'assurer est une obligation légale pour tous les locataires.\n

\n

\n Notre Assurance Habitation Jeunes répond à ce devoir et va bien au-delà pour vos utilisateurs.\n

\n
\n \"offre \n
\n
\n
\n
\n

Garanties de l’offre habitation jeunes

\n
\n
\n
\n

Des garanties essentielles dans le logement…

\n
    \n
  • Incendie, explosion et dégâts des eaux
  • \n
  • Bris de vitre sur l’immobilier
  • \n
  • Evénement climatique
  • \n
  • Cambriolage…
  • \n
\n
\n
\n \"offre \n
\n
\n

… mais également en dehors

\n
    \n
  • Responsabilité Civile (dommages causés à un tiers)
  • \n
  • Vol des biens pendant le déménagement
  • \n
  • Assistance voyage rapatriement
  • \n
  • Extension Biens Nomades (assure partout les biens que vous transportez avec vous : Smartphone, PC portable, instrument de musique, matériel de sport…)
  • \n
\n
\n
\n
\n

Une assurance responsable adaptée à votre quotidien

\n
\n
\n
\n
\n \"icon \n
\n
\n

Prêter, emprunter, louer

\n

MAIF encourage le partage plutôt que l’achat. On assure les biens que vos utilisateurs louent, prêtent ou empruntent.

\n
\n
\n
\n
\n \"icon \n
\n
\n

Réparer plutôt que remplacer

\n

A chaque fois que cela est possible, en cas de sinistre, nous privilégions la réparation des biens plutôt que leur remplacement. Un petit geste pour la planète.

\n
\n
\n
\n
\n \"icon \n
\n
\n

Limiter vos frais en colocation

\n

Les colocataires sont tous assurés de la même manière sur le même contrat.

\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520526380}} +{"type":"cmspages","payload":{"_id":"623c6fe5350000e106e28d1b","body":"
\n
\n
\n
\n \n \n \n
\n \n
\n \n {{#if connected}}\n
\n \n \n \n \n \n
\n {{/if}}\n \n \n {{#unless connected}}\n \n {{/unless}} \n
\n \n
\n \n \n \n \n \n \n \n
\n ×\n \n
\n
\n
\n \n \n
\n
","name":"IAAS-BASIC-navbar","path":null,"tags":[],"draft":"
\n
\n
\n
\n \n \n \n
\n \n
\n \n {{#if connected}}\n
\n \n \n \n \n \n
\n {{/if}}\n \n \n {{#unless connected}}\n \n {{/unless}} \n
\n \n
\n \n \n \n \n \n \n \n
\n ×\n \n
\n
\n
\n \n \n
\n
","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520280358}} +{"type":"cmspages","payload":{"_id":"629f44d0440000921d1d297f","body":"// Modal Contact\nfunction closeModalContact(){\n document.getElementById(\"contact\").style.display = \"none\";\n}\n\nfunction closeModalSuccess(){\n document.getElementById(\"contact-success\").style.display = \"none\";\n}\n\nfunction openContact(){\n document.getElementById(\"contact\").style.display = \"block\";\n}\n\n// Modal Mentions Légales\nfunction openMentionsLegales() {\n document.getElementById(\"mentions-legales\").style.display = \"block\";\n}\n\nfunction closeMentionsLegales() {\n document.getElementById(\"mentions-legales\").style.display = \"none\";\n}\n\n// Modal Données personnelles\nfunction openDonneesPersonnelles() {\n document.getElementById(\"donnees-personnelles\").style.display = \"block\";\n}\n\nfunction closeDonneesPersonnelles() {\n document.getElementById(\"donnees-personnelles\").style.display = \"none\";\n}\n\n// Modal Commun\nwindow.onclick = function(event) {\n if (event.target == modalContact || event.target == modalContactSuccess || event.target == modalMentionsLegales || event.target == modalDonneesPersonnelles) {\n modalContact.style.display = \"none\";\n modalContactSuccess.style.display = \"none\";\n modalMentionsLegales.style.display = \"none\";\n modalDonneesPersonnelles.style.display = \"none\";\n }\n}","name":"MODAL-js","path":"/629f44d0440000921d1d2980","tags":[],"draft":"// Modal Contact\nfunction closeModalContact(){\n document.getElementById(\"contact\").style.display = \"none\";\n}\n\nfunction closeModalSuccess(){\n document.getElementById(\"contact-success\").style.display = \"none\";\n}\n\nfunction openContact(){\n document.getElementById(\"contact\").style.display = \"block\";\n}\n\n// Modal Mentions Légales\nfunction openMentionsLegales() {\n document.getElementById(\"mentions-legales\").style.display = \"block\";\n}\n\nfunction closeMentionsLegales() {\n document.getElementById(\"mentions-legales\").style.display = \"none\";\n}\n\n// Modal Données personnelles\nfunction openDonneesPersonnelles() {\n document.getElementById(\"donnees-personnelles\").style.display = \"block\";\n}\n\nfunction closeDonneesPersonnelles() {\n document.getElementById(\"donnees-personnelles\").style.display = \"none\";\n}\n\n// Modal Commun\nwindow.onclick = function(event) {\n if (event.target == modalContact || event.target == modalContactSuccess || event.target == modalMentionsLegales || event.target == modalDonneesPersonnelles) {\n modalContact.style.display = \"none\";\n modalContactSuccess.style.display = \"none\";\n modalMentionsLegales.style.display = \"none\";\n modalDonneesPersonnelles.style.display = \"none\";\n }\n}","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/javascript","authenticated":false,"lastPublishedDate":1705571631920}} +{"type":"cmspages","payload":{"_id":"62a8424a440000921d1d3f06","body":"
\n \n
\n
\n

Demande de contact

\n ×\n
\n

\n
\n
\n \n {{#if connected}}\n \n {{/if}}\n {{#unless connected}}\n \n {{/unless}}\n
\n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n \n \n \n
\n \n
\n
\n
\n
\n\n\n","name":"MODAL-formulaire-contact","path":null,"tags":[],"draft":"
\n \n
\n
\n

Demande de contact

\n ×\n
\n

\n
\n
\n \n {{#if connected}}\n \n {{/if}}\n {{#unless connected}}\n \n {{/unless}}\n
\n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n \n \n \n
\n \n
\n
\n
\n
\n\n\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1677069434665}} +{"type":"cmspages","payload":{"_id":"629f4106440000921d1d278f","body":"
\n \n
\n
\n

Mentions légales

\n ×\n
\n

\n
\n

MAIF - Société d’assurance mutuelle à cotisations variables – CS 90000 - 79038 Niort cedex 9.

\n
\n
\n

Entreprises régies par le Code des assurances.

\n
\n
\n
\n","name":"MODAL-mentions-legales","path":null,"tags":[],"draft":"
\n \n
\n
\n

Mentions légales

\n ×\n
\n

\n
\n

MAIF - Société d’assurance mutuelle à cotisations variables – CS 90000 - 79038 Niort cedex 9.

\n
\n
\n

Entreprises régies par le Code des assurances.

\n
\n
\n
\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1705570212987}} +{"type":"cmspages","payload":{"_id":"657076982b0000c5d7209a94","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-mobilite\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre EDPM (Engins de Déplacement Personnels Motorisés)

\n
\n
\n

\n Vos utilisateurs ont choisi de se déplacer autrement ?
Nous les accompagnons dans cette nouvelle mobilité plus respectueuse de l'environnement.​\n

\n

\n Notre offre assure tous les engins de déplacement personnels motorisés (EDPM).\n

\n
\n \"offre \n
\n
\n
\n
\n

3 raisons de choisir l’assurance MAIF

\n
\n
\n
\n
\n

1

\n

Les garanties

\n

Avec l’assurance EDPM la protection des personnes reste une priorité. Même assuré au tiers, vous bénéficiez de garanties étendues.

\n
\n
\n
\n
\n

2

\n

Assurance au tiers ou tous risques : à chacun sa formule

\n

Le meilleur contrat d’assurance EDPM est celui qui correspond à vos besoins.

\n
\n
\n
\n
\n

3

\n

L’option biens transportés accessible dans les deux formules

\n

Les effets personnels que vous emportez (smartphone, …) sont couverts en cas d’accident.

\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","name":"offre-edpm","path":"/offre-edpm","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-mobilite\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre EDPM (Engins de Déplacement Personnels Motorisés)

\n
\n
\n

\n Vos utilisateurs ont choisi de se déplacer autrement ?
Nous les accompagnons dans cette nouvelle mobilité plus respectueuse de l'environnement.​\n

\n

\n Notre offre assure tous les engins de déplacement personnels motorisés (EDPM).\n

\n
\n \"offre \n
\n
\n
\n
\n

3 raisons de choisir l’assurance MAIF

\n
\n
\n
\n
\n

1

\n

Les garanties

\n

Avec l’assurance EDPM la protection des personnes reste une priorité. Même assuré au tiers, vous bénéficiez de garanties étendues.

\n
\n
\n
\n
\n

2

\n

Assurance au tiers ou tous risques : à chacun sa formule

\n

Le meilleur contrat d’assurance EDPM est celui qui correspond à vos besoins.

\n
\n
\n
\n
\n

3

\n

L’option biens transportés accessible dans les deux formules

\n

Les effets personnels que vous emportez (smartphone, …) sont couverts en cas d’accident.

\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520582665}} +{"type":"cmspages","payload":{"_id":"623c6eac350000e106e28caa","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\"}}\n \n
\n
\n
\n
\n

Portail API MAIF

\n Le site dédié aux partenaires de distribution digitale des assurances MAIF\n
\n
\n

\n Proposez à vos utilisateurs des \n solutions d'assurance intégrées \n dans vos parcours digitaux

\n
\n
\n
\n \"image\n
\n
\n
\n{{/daikoku-template-wrapper}}","name":"home","path":"/","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\"}}\n \n
\n
\n
\n
\n

Portail API MAIF

\n Le site dédié aux partenaires de distribution digitale des assurances MAIF\n
\n
\n

\n Proposez à vos utilisateurs des \n solutions d'assurance intégrées \n dans vos parcours digitaux

\n
\n
\n
\n \"image\n
\n
\n
\n{{/daikoku-template-wrapper}}","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520690725}} +{"type":"cmspages","payload":{"_id":"6526b0273d00001b85d31feb","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n

UNIVERS MOBILITE

\n
\n
\n
\n \n
\n
\n \"icon\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","name":"univers-mobilite","path":"/univers-mobilite","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n

UNIVERS MOBILITE

\n
\n
\n
\n \n
\n
\n \"icon\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520659265}} +{"type":"cmspages","payload":{"_id":"651443f53d00001b85d2dc82","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/parcours-api\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Simulation

\n
\n
\n
\n

\n Le parcours Simulation est la solution idéale pour intégrer un premier niveau d’assurance dans votre parcours utilisateur, d’une manière intuitive et sans effort.\n

\n
\n Offres disponibles sur ce parcours\n
    \n
  • Offres AUTO, VÉLO et EDPM
  • \n
  • Offre MRH jeune
  • \n
  • Offre Travailleur Indépendant
  • \n
\n
\n
\n
\n \"parcours \n
\n
\n
\n
\n

Proposez une estimation tarifaire instantanée et personnalisée à vos utilisateurs

\n
\n
\n
\n
\n \"icon\n Intégrer la simulation à vos parcours permet de délivrer à vos utilisateurs, au regard des informations dont vous disposez, un premier niveau d'informations pour les guider dans leur choix d'assurance.\n
\n
\n
\n
\n

Votre Site / App

\n

Vous affichez à votre utilisateur une estimation tarifaire et l'invitez à réaliser un devis sur MAIF.fr

\n

SIMULATION

\n
\n
\n
\n
\n

MAIF.fr

\n

Votre utilisateur réalise son devis et sa souscription d’assurance sur MAIF.fr.

\n
\n
\n

DEVIS

\n
\n
\n

SOUSCRIPTION

\n
\n
\n
\n
\n
\n
\n

Partenaires Cibles

\n
\n
\n
\n Notre parcours Simulation est adapté pour les partenaires sans expertise assurance, mais dont les services ou produits proposés sont en lien direct ou indirect avec l’offre d’assurance.\n
\n \"parcours\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}","name":"parcours-simulation","path":"/parcours-simulation","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/parcours-api\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Simulation

\n
\n
\n
\n

\n Le parcours Simulation est la solution idéale pour intégrer un premier niveau d’assurance dans votre parcours utilisateur, d’une manière intuitive et sans effort.\n

\n
\n Offres disponibles sur ce parcours\n
    \n
  • Offres AUTO, VÉLO et EDPM
  • \n
  • Offre MRH jeune
  • \n
  • Offre Travailleur Indépendant
  • \n
\n
\n
\n
\n \"parcours \n
\n
\n
\n
\n

Proposez une estimation tarifaire instantanée et personnalisée à vos utilisateurs

\n
\n
\n
\n
\n \"icon\n Intégrer la simulation à vos parcours permet de délivrer à vos utilisateurs, au regard des informations dont vous disposez, un premier niveau d'informations pour les guider dans leur choix d'assurance.\n
\n
\n
\n
\n

Votre Site / App

\n

Vous affichez à votre utilisateur une estimation tarifaire et l'invitez à réaliser un devis sur MAIF.fr

\n

SIMULATION

\n
\n
\n
\n
\n

MAIF.fr

\n

Votre utilisateur réalise son devis et sa souscription d’assurance sur MAIF.fr.

\n
\n
\n

DEVIS

\n
\n
\n

SOUSCRIPTION

\n
\n
\n
\n
\n
\n
\n

Partenaires Cibles

\n
\n
\n
\n Notre parcours Simulation est adapté pour les partenaires sans expertise assurance, mais dont les services ou produits proposés sont en lien direct ou indirect avec l’offre d’assurance.\n
\n \"parcours\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520546449}} +{"type":"cmspages","payload":{"_id":"6579ca012a0000359b5c1e27","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n

NOS PARCOURS API

\n
\n
\n \n
\n \"icon\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","name":"parcours-api","path":"/parcours-api","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n

NOS PARCOURS API

\n
\n
\n \n
\n \"icon\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520703310}} +{"type":"cmspages","payload":{"_id":"65709cd72b0000c5d720aa98","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-vie-quotidienne-pro\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre TRAVAILLEUR INDÉPENDANT

\n
\n
\n

\n Une assurance qui couvre la responsabilité civile professionnelle (RC Pro) et les biens (PC, téléphone, locaux...) des auto-entrepreneurs, freelances, travailleurs indépendants.\n

\n
\n \"offre \n
\n
\n
\n
\n

Choisir MAIF pour que vos utilisateurs assurent leur(s) activité(s)

\n
\n
\n
\n
\n \"icon \n
\n
\n

Des garanties indispensables

\n

En plus des risques liés à l’activité de vos utilisateurs, leurs biens, leur local et même leur stock sont assurés.

\n
\n
\n
\n
\n \"icon \n
\n
\n

Le prêt d’objet couvert

\n

Tout bien que l’on prête à vos utilisateurs ou qu’ils prêtent dans le cadre de leur activité est couvert.

\n
\n
\n
\n
\n \"icon \n
\n
\n

Le + relationnel

\n

Des conseillers spécialisés pour guider vos utilisateurs dans leur activité.

\n
\n
\n
\n
\n

Les activités assurées par l’assurance MAIF

\n
\n
\n
\n

Les Activités éligibles

\n
    \n
  • Prestations intellectuelles
  • \n
  • Bien-être et Thérapie
  • \n
  • Petits Artisanats
  • \n
  • Culture, Loisirs, Art et Musique
  • \n
  • Sport
  • \n
  • Social & Services
  • \n
\n
\n
\n \"icon \n
\n
\n

Les Activités exclues

\n
    \n
  • BTP / Agriculture / Grossiste
  • \n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","name":"offre-travailleur-independant","path":"/offre-travailleur-independant","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-vie-quotidienne-pro\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre TRAVAILLEUR INDÉPENDANT

\n
\n
\n

\n Une assurance qui couvre la responsabilité civile professionnelle (RC Pro) et les biens (PC, téléphone, locaux...) des auto-entrepreneurs, freelances, travailleurs indépendants.\n

\n
\n \"offre \n
\n
\n
\n
\n

Choisir MAIF pour que vos utilisateurs assurent leur(s) activité(s)

\n
\n
\n
\n
\n \"icon \n
\n
\n

Des garanties indispensables

\n

En plus des risques liés à l’activité de vos utilisateurs, leurs biens, leur local et même leur stock sont assurés.

\n
\n
\n
\n
\n \"icon \n
\n
\n

Le prêt d’objet couvert

\n

Tout bien que l’on prête à vos utilisateurs ou qu’ils prêtent dans le cadre de leur activité est couvert.

\n
\n
\n
\n
\n \"icon \n
\n
\n

Le + relationnel

\n

Des conseillers spécialisés pour guider vos utilisateurs dans leur activité.

\n
\n
\n
\n
\n

Les activités assurées par l’assurance MAIF

\n
\n
\n
\n

Les Activités éligibles

\n
    \n
  • Prestations intellectuelles
  • \n
  • Bien-être et Thérapie
  • \n
  • Petits Artisanats
  • \n
  • Culture, Loisirs, Art et Musique
  • \n
  • Sport
  • \n
  • Social & Services
  • \n
\n
\n
\n \"icon \n
\n
\n

Les Activités exclues

\n
    \n
  • BTP / Agriculture / Grossiste
  • \n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520631296}} +{"type":"cmspages","payload":{"_id":"651443d43d00001b85d2dc7a","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/parcours-api\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Affiliation Digitale

\n
\n
\n
\n

\n Le parcours Affiliation Digitale est la solution la plus simple pour recommander les offres d’assurance MAIF auprès de vos utilisateurs.\n

\n
\n Offres disponibles sur ce parcours\n
    \n
  • Offres MOBILITÉ
  • \n
  • Offres MRH et MRH jeune
  • \n
  • Offre Travailleur Indépendant
  • \n
\n
\n
\n
\n \"parcours \n
\n
\n
\n
\n

Redirigez vos utilisateurs vers le parcours d’assurance dédié sur MAIF.fr

\n
\n
\n
\n
\n \"icon\n Un simple lien de redirection depuis votre site ou application mobile vers le parcours d’assurance dédié sur MAIF.fr.\n
\n
\n
\n
\n

Votre Site / App

\n

Votre utilisateur est informé du partenariat MAIF et invité à réaliser un devis en cliquant sur le lien tracké

\n

LIEN TRACKE

\n
\n
\n
\n
\n

MAIF.fr

\n

Votre utilisateur réalise l’intégralité de son expérience assurance sur MAIF.fr.

\n
\n
\n

DEVIS

\n
\n
\n

SOUSCRIPTION

\n
\n
\n
\n
\n
\n
\n

Partenaires Cibles

\n
\n
\n
\n Notre parcours Affiliation Digitale est adapté pour les partenaires sans expertise assurance, dont les services ou produits proposés ne sont pas en lien direct avec l’offre d’assurance.\n
\n \"parcours\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","name":"parcours-affiliation-digitale","path":"/parcours-affiliation-digitale","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/parcours-api\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Affiliation Digitale

\n
\n
\n
\n

\n Le parcours Affiliation Digitale est la solution la plus simple pour recommander les offres d’assurance MAIF auprès de vos utilisateurs.\n

\n
\n Offres disponibles sur ce parcours\n
    \n
  • Offres MOBILITÉ
  • \n
  • Offres MRH et MRH jeune
  • \n
  • Offre Travailleur Indépendant
  • \n
\n
\n
\n
\n \"parcours \n
\n
\n
\n
\n

Redirigez vos utilisateurs vers le parcours d’assurance dédié sur MAIF.fr

\n
\n
\n
\n
\n \"icon\n Un simple lien de redirection depuis votre site ou application mobile vers le parcours d’assurance dédié sur MAIF.fr.\n
\n
\n
\n
\n

Votre Site / App

\n

Votre utilisateur est informé du partenariat MAIF et invité à réaliser un devis en cliquant sur le lien tracké

\n

LIEN TRACKE

\n
\n
\n
\n
\n

MAIF.fr

\n

Votre utilisateur réalise l’intégralité de son expérience assurance sur MAIF.fr.

\n
\n
\n

DEVIS

\n
\n
\n

SOUSCRIPTION

\n
\n
\n
\n
\n
\n
\n

Partenaires Cibles

\n
\n
\n
\n Notre parcours Affiliation Digitale est adapté pour les partenaires sans expertise assurance, dont les services ou produits proposés ne sont pas en lien direct avec l’offre d’assurance.\n
\n \"parcours\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520418595}} +{"type":"cmspages","payload":{"_id":"659ec57d2a0000359b5ca6fc","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-vie-quotidienne-pro\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre HABITATION

\n
\n
\n

\n Propriétaire, locataire : le contrat d'Assurance Habitation MAIF protège votre logement et vos biens avec de solides garanties en cas de sinistre.\n

\n
\n \"offre \n
\n
\n
\n
\n

Bien plus qu’une assurance habitation

\n
\n
\n
\n
\n \"icon \n
\n
\n

Intempéries

\n

En cas d’inondation, même sans arrêté de catastrophes naturelles, vous êtes indemnisé.

\n \"offre \n
\n
\n
\n
\n \"icon\n
\n
\n

Assurance Scolaire

\n

Incluse pour chaque enfant sans surcoût.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Vélo à assistance électrique

\n

Assuré en formule 3 contre le vol et le bris en tous lieux (en option en formule 2).

\n
\n
\n
\n
\n

Choisir MAIF pour assurer votre mode de vie responsable

\n
\n
\n
\n
\n \"icon\n
\n
\n

Réparer plutôt que jeter

\n

Pour lutter contre le gaspillage, MAIF inclut une garantie panne pour vos appareils en formule 3.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Vos équipements écologiques sont couverts

\n

Sans supplément dès la formule de base.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Les biens que vous prêtez sont assurés

\n

Comme s’ils étaient chez vous.

\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","name":"offre-mrh","path":"/offre-mrh","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-vie-quotidienne-pro\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre HABITATION

\n
\n
\n

\n Propriétaire, locataire : le contrat d'Assurance Habitation MAIF protège votre logement et vos biens avec de solides garanties en cas de sinistre.\n

\n
\n \"offre \n
\n
\n
\n
\n

Bien plus qu’une assurance habitation

\n
\n
\n
\n
\n \"icon \n
\n
\n

Intempéries

\n

En cas d’inondation, même sans arrêté de catastrophes naturelles, vous êtes indemnisé.

\n \"offre \n
\n
\n
\n
\n \"icon\n
\n
\n

Assurance Scolaire

\n

Incluse pour chaque enfant sans surcoût.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Vélo à assistance électrique

\n

Assuré en formule 3 contre le vol et le bris en tous lieux (en option en formule 2).

\n
\n
\n
\n
\n

Choisir MAIF pour assurer votre mode de vie responsable

\n
\n
\n
\n
\n \"icon\n
\n
\n

Réparer plutôt que jeter

\n

Pour lutter contre le gaspillage, MAIF inclut une garantie panne pour vos appareils en formule 3.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Vos équipements écologiques sont couverts

\n

Sans supplément dès la formule de base.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Les biens que vous prêtez sont assurés

\n

Comme s’ils étaient chez vous.

\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520506557}} +{"type":"cmspages","payload":{"_id":"629f15aa440000921d1d2372","body":"
\n \n
\n
\n

Données personnnelles

\n ×\n
\n

Politique de confidentialité du Portail API MAIF

\n
\n

Données personnelles :

\n

Par donnée à caractère personnel, il y a lieu d’entendre toutes les informations qui permettent de vous identifier directement ou indirectement (croisement d’un ensemble de données) telles que noms, prénoms, adresse postale et de courrier électronique, numéro de téléphone… Lors de votre accès au portail de l’API MAIF, nous sommes amenés à collecter des données à caractère personnel, notamment dans le cadre de la création de votre compte ou lorsque vous nous contacter.

\n
\n\n
\n

Qui sont les responsables de traitements ?

\n

MAIF Société d’Assurance Mutuelle à cotisations variables, entreprise régie par le code des assurances. Adresse postale : 200 avenue Salvador Allende - CS 90000 - 79038 NIORT cedex 9.

\n
\n
\n

Comment utilisons-nous vos données ?

\n

Vos données personnelles sont utilisées dans le cadre de notre relation pour répondre à plusieurs finalités pour l’utilisation du portail :

\n
    \n
  • La création de votre compte utilisateur et votre authentification
  • \n
  • La prise en charge de vos demandes via le formulaire de contact
  • \n
\n
\n
\n

Qui sont les Destinataires des données ?

\n

Les données que nous collectons vous concernant, sont destinées aux entreprises du groupe MAIF et à nos prestataires de service, agissant en qualité de sous-traitants lorsqu’ils concourent à la réalisation des finalités pour lesquelles vos données sont collectées. Pour assurer la confidentialité de vos données personnelles, nous nous prévoyons dans les contrats des obligations de confidentialité à la charge des sous-traitants.

\n
\n
\n

Quels sont vos droits ?

\n

Le droit d’accès et de rectification : Vous pouvez demander l’accès et la rectification des données inexactes vous concernant dont nous disposons sur vous.

\n

Le droit à la portabilité : Vous pouvez demander à récupérer les données que vous avez fourni dans un format informatique pour un usage personnel ou pour les transmettre à un tiers de votre choix.

\n

Le droit à l’oubli : Il s’agit du droit d'obtenir la suppression des informations vous concernant détenues par la MAIF dans la mesure où elles ne sont plus nécessaires un contrat ou un service.

\n

Le droit d'opposition : Vous pouvez demander l’opposition au traitement de vos données. Pour se faire, vous devez exposer des raisons tenant à votre situation particulière.

\n

Le droit de limitation du traitement : Vous pouvez nous demander la limitation de certaines de vos données lorsqu’elles ne sont plus nécessaires ou ne sont plus nécessaires dans notre relation contractuelle.

\n

Le droit de définir le sort de vos données post mortem : Vous pouvez définir des directives relatives à la conservation, à l'effacement et à la communication de vos données après votre décès.

\n
\n
\n

Combien de temps conservons-nous vos données et quelle est leur localisation ?

\n

La durée de conservation de vos données personnelles ne dépassera pas 6 mois après la suppression de votre compte. Vos données sont traitées et conservées sur le territoire de l’Union Européenne. Toutefois, dans de très rares hypothèses, elles peuvent faire l’objet d’un transfert hors de ce territoire. Lorsque c’est le cas, vous bénéficiez d’une information spécifique lors de la collecte et nous mettons en œuvre des mesures pour assurer que la législation européenne est respectée afin de vous apporter une sécurité plus importante.

\n
\n
\n

Vos contacts au sein du groupe MAIF :

\n

Pour exercer vos droits, vous pouvez contacter directement : claire.du.mesnildot@maif.fr MAIF a désigné un délégué à la protection des données personnelles dont la mission est de vous aider pour exercer vos droits. Vous pouvez le contacter :

\n
    \n
  • Par courrier postal en écrivant à : Délégué à la protection des données, 200 avenue Salvador Allende - CS 90000 - 79038 NIORT cedex 9
  • \n
  • Par courrier électronique en écrivant à l’adresse de courriel : vosdonnees@maif.fr
  • \n
\n
\n
\n
","name":"MODAL-donnees-personnelles","path":null,"tags":[],"draft":"
\n \n
\n
\n

Données personnnelles

\n ×\n
\n

Politique de confidentialité du Portail API MAIF

\n
\n

Données personnelles :

\n

Par donnée à caractère personnel, il y a lieu d’entendre toutes les informations qui permettent de vous identifier directement ou indirectement (croisement d’un ensemble de données) telles que noms, prénoms, adresse postale et de courrier électronique, numéro de téléphone… Lors de votre accès au portail de l’API MAIF, nous sommes amenés à collecter des données à caractère personnel, notamment dans le cadre de la création de votre compte ou lorsque vous nous contacter.

\n
\n\n
\n

Qui sont les responsables de traitements ?

\n

MAIF Société d’Assurance Mutuelle à cotisations variables, entreprise régie par le code des assurances. Adresse postale : 200 avenue Salvador Allende - CS 90000 - 79038 NIORT cedex 9.

\n
\n
\n

Comment utilisons-nous vos données ?

\n

Vos données personnelles sont utilisées dans le cadre de notre relation pour répondre à plusieurs finalités pour l’utilisation du portail :

\n
    \n
  • La création de votre compte utilisateur et votre authentification
  • \n
  • La prise en charge de vos demandes via le formulaire de contact
  • \n
\n
\n
\n

Qui sont les Destinataires des données ?

\n

Les données que nous collectons vous concernant, sont destinées aux entreprises du groupe MAIF et à nos prestataires de service, agissant en qualité de sous-traitants lorsqu’ils concourent à la réalisation des finalités pour lesquelles vos données sont collectées. Pour assurer la confidentialité de vos données personnelles, nous nous prévoyons dans les contrats des obligations de confidentialité à la charge des sous-traitants.

\n
\n
\n

Quels sont vos droits ?

\n

Le droit d’accès et de rectification : Vous pouvez demander l’accès et la rectification des données inexactes vous concernant dont nous disposons sur vous.

\n

Le droit à la portabilité : Vous pouvez demander à récupérer les données que vous avez fourni dans un format informatique pour un usage personnel ou pour les transmettre à un tiers de votre choix.

\n

Le droit à l’oubli : Il s’agit du droit d'obtenir la suppression des informations vous concernant détenues par la MAIF dans la mesure où elles ne sont plus nécessaires un contrat ou un service.

\n

Le droit d'opposition : Vous pouvez demander l’opposition au traitement de vos données. Pour se faire, vous devez exposer des raisons tenant à votre situation particulière.

\n

Le droit de limitation du traitement : Vous pouvez nous demander la limitation de certaines de vos données lorsqu’elles ne sont plus nécessaires ou ne sont plus nécessaires dans notre relation contractuelle.

\n

Le droit de définir le sort de vos données post mortem : Vous pouvez définir des directives relatives à la conservation, à l'effacement et à la communication de vos données après votre décès.

\n
\n
\n

Combien de temps conservons-nous vos données et quelle est leur localisation ?

\n

La durée de conservation de vos données personnelles ne dépassera pas 6 mois après la suppression de votre compte. Vos données sont traitées et conservées sur le territoire de l’Union Européenne. Toutefois, dans de très rares hypothèses, elles peuvent faire l’objet d’un transfert hors de ce territoire. Lorsque c’est le cas, vous bénéficiez d’une information spécifique lors de la collecte et nous mettons en œuvre des mesures pour assurer que la législation européenne est respectée afin de vous apporter une sécurité plus importante.

\n
\n
\n

Vos contacts au sein du groupe MAIF :

\n

Pour exercer vos droits, vous pouvez contacter directement : claire.du.mesnildot@maif.fr MAIF a désigné un délégué à la protection des données personnelles dont la mission est de vous aider pour exercer vos droits. Vous pouvez le contacter :

\n
    \n
  • Par courrier postal en écrivant à : Délégué à la protection des données, 200 avenue Salvador Allende - CS 90000 - 79038 NIORT cedex 9
  • \n
  • Par courrier électronique en écrivant à l’adresse de courriel : vosdonnees@maif.fr
  • \n
\n
\n
\n
","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1705571459321}} +{"type":"cmspages","payload":{"_id":"659e65a42a0000359b5c58bc","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-mobilite\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n
\n \"icon \n \"icon \n
\n

Offre AUTO & Camping-Cars

\n
\n
\n

\n Au tiers ou tous risques, vos utilisateurs peuvent compter sur les garanties de notre assurance auto/camping-cars VAM.\n

\n

\n Formules, options, franchises : vos utilisateurs ont la possibilité d’ajuster selon leurs besoins et de maîtriser le prix de leur assurance.\n

\n
\n
\n
\n

Des garanties et un service client qui font la différence

\n
\n
\n
\n
\n \"icon \n
\n
\n

Assistance sur la route 24h/24 et 7j/7

\n

Une Panne ? Vos utilisateurs sont dépannés dès la formule au tiers pour leurs trajets à plus de 50 km du domicile. Et même devant chez eux avec la formule Plénitude ou en option.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Remboursements

\n

Le véhicule de vos utilisateurs irréparable ou volé est indemnisé à valeur d’achat jusqu’à ses 4 ans, avec la formule Plénitude.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Intempéries… tous assurés !

\n

Dès la formule au tiers, le véhicule de vos utilisateurs est pris en charge en cas de grêle, tempête, inondation.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Service Client

\n

Trouver des solutions, indemniser, c’est notre métier. Depuis 19 ans, MAIF est numéro 1 de la Relation Client1.

\n
\n
\n
\n
\n

En cas de sinistre, comment ça se passe ?

\n
\n
\n
\n
\n \"icon\n
\n
\n

Assistance et suivi de la dépanneuse

\n

Vos utilisateurs peuvent contacter l’assistance MAIF 24h/24, 7j/7 puis suivre le trajet de la dépanneuse sur leur smartphone avec l’application MAIF.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Constat et déclaration en ligne

\n

Un accident ? Vos utilisateurs peuvent réaliser un (e)constat et déclarer leur sinistre en ligne, sans attendre.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Réparer sans gaspiller

\n

Depuis 2013, MAIF propose des réparations avec pièces recyclées, avec l’accord de vos utilisateurs. La réparation est garantie à vie2.

\n
\n
\n
\n
\n
\n

1.

\n

N°1 du Podium de la Relation Client. Enquête réalisée par BearingPoint et Kantar fin 2022 auprès de 4000 personnes.

\n
\n
\n

2.

\n

Dans la limite du vieillissement des pièces dû au soleil, à l’usure normale, aux intempéries ou au défaut d’entretien.

\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","name":"offre-auto-cc","path":"/offre-auto-cc","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-mobilite\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n
\n \"icon \n \"icon \n
\n

Offre AUTO & Camping-Cars

\n
\n
\n

\n Au tiers ou tous risques, vos utilisateurs peuvent compter sur les garanties de notre assurance auto/camping-cars VAM.\n

\n

\n Formules, options, franchises : vos utilisateurs ont la possibilité d’ajuster selon leurs besoins et de maîtriser le prix de leur assurance.\n

\n
\n
\n
\n

Des garanties et un service client qui font la différence

\n
\n
\n
\n
\n \"icon \n
\n
\n

Assistance sur la route 24h/24 et 7j/7

\n

Une Panne ? Vos utilisateurs sont dépannés dès la formule au tiers pour leurs trajets à plus de 50 km du domicile. Et même devant chez eux avec la formule Plénitude ou en option.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Remboursements

\n

Le véhicule de vos utilisateurs irréparable ou volé est indemnisé à valeur d’achat jusqu’à ses 4 ans, avec la formule Plénitude.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Intempéries… tous assurés !

\n

Dès la formule au tiers, le véhicule de vos utilisateurs est pris en charge en cas de grêle, tempête, inondation.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Service Client

\n

Trouver des solutions, indemniser, c’est notre métier. Depuis 19 ans, MAIF est numéro 1 de la Relation Client1.

\n
\n
\n
\n
\n

En cas de sinistre, comment ça se passe ?

\n
\n
\n
\n
\n \"icon\n
\n
\n

Assistance et suivi de la dépanneuse

\n

Vos utilisateurs peuvent contacter l’assistance MAIF 24h/24, 7j/7 puis suivre le trajet de la dépanneuse sur leur smartphone avec l’application MAIF.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Constat et déclaration en ligne

\n

Un accident ? Vos utilisateurs peuvent réaliser un (e)constat et déclarer leur sinistre en ligne, sans attendre.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Réparer sans gaspiller

\n

Depuis 2013, MAIF propose des réparations avec pièces recyclées, avec l’accord de vos utilisateurs. La réparation est garantie à vie2.

\n
\n
\n
\n
\n
\n

1.

\n

N°1 du Podium de la Relation Client. Enquête réalisée par BearingPoint et Kantar fin 2022 auprès de 4000 personnes.

\n
\n
\n

2.

\n

Dans la limite du vieillissement des pièces dû au soleil, à l’usure normale, aux intempéries ou au défaut d’entretien.

\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520722284}} +{"type":"cmspages","payload":{"_id":"659e60a72a0000359b5c5705","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/parcours-api\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Souscription

\n
\n
\n

\n Parcours en développement par les équipes MAIF.\n

\n
\n \"parcours \n
\n
\n
\n
\n

Partenaires Cibles

\n
\n
\n
\n Notre parcours Souscription est adapté aux partenaires avec un besoin d’intégration complet de l’assurance dans le parcours d’achat du bien ou du service proposé à leurs utilisateurs utilisateur.\n
\n \"parcours\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}","name":"parcours-souscription","path":"/parcours-souscription","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/parcours-api\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Souscription

\n
\n
\n

\n Parcours en développement par les équipes MAIF.\n

\n
\n \"parcours \n
\n
\n
\n
\n

Partenaires Cibles

\n
\n
\n
\n Notre parcours Souscription est adapté aux partenaires avec un besoin d’intégration complet de l’assurance dans le parcours d’achat du bien ou du service proposé à leurs utilisateurs utilisateur.\n
\n \"parcours\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520773400}} +{"type":"cmspages","payload":{"_id":"654b545f2100008ec5dca38d","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-mobilite\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre CORPORELLE (PACS)

\n
\n
\n

\n Vos utilisateurs sont des utilisateurs de solutions de mobilité douce ou partagée ?\n

\n

\n En cas d'accident, l’assurance corporelle PACS garantit une indemnisation de vos utilisateurs dans leurs mobilités à l’usage mais aussi en covoiturage et transports en commun.\n

\n
\n \"offre \n
\n
\n
\n
\n \"offre \n

Quels trajets sont couverts par notre assurance PACS ?

\n
\n
\n
\n \"offre\n
    \n
  • En train, métro, tram, bus, car
  • \n
  • En covoiturage, VTC, taxi
  • \n
  • Dans ma voiture de fonction, une voiture empruntée à un tiers
  • \n
  • Dans une location courte durée, libre-service : vélo …
  • \n
\n
\n
\n

Le contrat PACS couvrira vos trajets en véhicule de fonction, covoiturage, transports en commun, location en libre-service…

\n

Vous bénéficiez d’une prise en charge immédiate et d’un accompagnement dans vos démarches vis-à-vis des opérateurs de mobilité (vélo libre-service, train, tram, métro…)

\n
\n
\n
\n
\n

… mais également dans leurs véhicules assurés MAIF

\n
    \n
  • En voiture, en Camping-cars
  • \n
  • En moto, en scooter, en cyclomoteur
  • \n
  • En trottinette électrique
  • \n
  • En vélo 100% électrique (vitesse allant jusqu’à 45km/h)
  • \n
\n
\n
\n \"offre \n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","name":"offre-corporelle","path":"/offre-corporelle","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-mobilite\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre CORPORELLE (PACS)

\n
\n
\n

\n Vos utilisateurs sont des utilisateurs de solutions de mobilité douce ou partagée ?\n

\n

\n En cas d'accident, l’assurance corporelle PACS garantit une indemnisation de vos utilisateurs dans leurs mobilités à l’usage mais aussi en covoiturage et transports en commun.\n

\n
\n \"offre \n
\n
\n
\n
\n \"offre \n

Quels trajets sont couverts par notre assurance PACS ?

\n
\n
\n
\n \"offre\n
    \n
  • En train, métro, tram, bus, car
  • \n
  • En covoiturage, VTC, taxi
  • \n
  • Dans ma voiture de fonction, une voiture empruntée à un tiers
  • \n
  • Dans une location courte durée, libre-service : vélo …
  • \n
\n
\n
\n

Le contrat PACS couvrira vos trajets en véhicule de fonction, covoiturage, transports en commun, location en libre-service…

\n

Vous bénéficiez d’une prise en charge immédiate et d’un accompagnement dans vos démarches vis-à-vis des opérateurs de mobilité (vélo libre-service, train, tram, métro…)

\n
\n
\n
\n
\n

… mais également dans leurs véhicules assurés MAIF

\n
    \n
  • En voiture, en Camping-cars
  • \n
  • En moto, en scooter, en cyclomoteur
  • \n
  • En trottinette électrique
  • \n
  • En vélo 100% électrique (vitesse allant jusqu’à 45km/h)
  • \n
\n
\n
\n \"offre \n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520736622}} +{"type":"cmspages","payload":{"_id":"62457958430000127e85c5cf","body":"*,\n*::after,\n*::before {\n margin: 0;\n padding: 0;\n box-sizing: inherit;\n}\n\nul{\n list-style-type: none;\n}\n\n.maif-color-text{\n color:#fc0d1a;\n}\n\nheader {\n text-align: center;\n background-color: #F2F2F2\n}\n\nheader a {\n color: black;\n text-decoration: none;\n}\n\nheader a:hover{\n color: black;\n text-decoration: none;\n font-weight: 600;\n}\n\n.header-left img {\n height: auto;\n max-width: 59px;\n}\n\n.header-right .non-connecte a, header .header-right .non-connecte button {\n color: #2e404f;\n cursor: pointer;\n padding: 6px 12px;\n align-items: center;\n text-align: center;\n font-size: 1.5rem;\n}\n\n.header-right .non-connecte a .icon, header .header-right .non-connecte button .icon {\n width: 38px;\n border-radius: 50%;\n}\n\n.header-right img{\n max-width: 30px;\n border-radius: 50%;\n}\n\nheader-left {\n text-align: left;\n}\n\n.header-right {\n display: flex;\n justify-content: start\n}\n\n.header-right .buttons {\n display: flex;\n flex-direction: column;\n}\n\n.header-right .buttons a {\n justify-content: start;\n display: flex;\n padding: 0;\n color: #212529;\n}\n\n.container-burger-menu {\n justify-content: right;\n margin-top: 0.5em;\n}\n\n.sidenav {\n height: 100%;\n width: 290px;\n position: fixed;\n z-index: 1;\n top: 0;\n right: -290px;\n background-color: #e8e8e8;\n padding-top: 60px;\n transition: right 0.5s ease;\n}\n\n/* Sidenav menu links */\n.sidenav a {\n padding: 8px 8px 8px 32px;\n text-decoration: none;\n font-size: 25px;\n color: #818181;\n display: block;\n transition: 0.3s;\n}\n\n.sidenav a:hover {\n color: #111;\n}\n\n.sidenav ul {\n text-align: left;\n list-style-type: none;\n padding: 0;\n margin: 0;\n}\n\n/* Active class */\n.sidenav.active {\n right: 0;\n}\n\n/* Close btn */\n.sidenav .close {\n position: absolute;\n top: 0;\n right: 25px;\n font-size: 36px;\n}\n\n/* Icône burger */\n.burger-icon span {\n display: block;\n width: 35px;\n height: 5px;\n background-color: black;\n margin: 6px 0;\n}\n\nbody {\n font-family: Custom, Raleway, Helvetica, sans-serif;\n margin: 0px;\n min-height: 100vh;\n display: flex;\n flex-direction: column\n}\n\n.offre-title {\n display: flex;\n flex-direction: row;\n align-items: center;\n border-bottom: solid #fc0d1a 5px;\n}\n\n.offre-title-double {\n display: flex;\n flex-direction: column;\n}\n\n.offre-title-double img:not(:last-child) {\n margin-bottom: -20px;\n}\n\n.offre-title-double img {\n padding-bottom: 0 !important;\n}\n\n.offre-title img {\n padding-bottom: 0.7em;\n padding-right: 3em;\n}\n\n.offre-title-detail {\n font-size: 12px;\n}\n\n.offre-title-2 {\n font-size: 18px;\n}\n\n.offre-text {\n font-size: 20px;\n}\n\n.offre-bandeau {\n display: flex;\n flex-direction: row;\n justify-content: center;\n align-items: center;\n background-color: #FBEBE9;\n padding: 10px;\n min-height: 120px;\n text-align: center;\n}\n\n.offre-bandeau img {\n padding-right: 5em;\n}\n\n.offre-bandeau-reverse img {\n -webkit-transform: scaleX(-1);\n padding-left: 5em;\n}\n\n.offre-description-text {\n display: flex;\n flex-direction: column;\n}\n\n.offre-description-text-margin {\n margin-top: 3em;\n}\n\n.offre-img-padding {\n align-items: end;\n}\n\n.offre-img-padding img{\n width: -webkit-fill-available;\n}\n\n.offre-ul {\n > li {\n font-style: italic;\n }\n}\n\n.offre-li-detail {\n font-size: 10px;\n}\n\n.img-reduction img {\n width: 70%;\n height: 80%;\n}\n\n.container {\n flex: 1; \n}\n\n.home-section .home-section-header {\n display: flex;\n width: 100%;\n flex-direction: column;\n}\n\n.home-section-body > div {\n margin-bottom: 3rem;\n}\n\n.img-inherit img {\n width: inherit;\n}\n\n.home-img img {\n width: 485px; \n}\n\n.home-section-side { \n padding-top: 4em;\n}\n\n.home-section-side img {\n margin-top: 1em;\n max-width: 130px;\n}\n\n.icon-menu {\n border: solid 1px grey;\n border-radius: 0.75em;\n margin-bottom: 1em;\n align-items: center;\n display: flex;\n flex-direction: column;\n width: 140px;\n height: 140px;\n text-align: center;\n}\n\n.icon-menu-disabled {\n filter: grayscale(1);\n}\n\n.icon-menu img {\n height: 80px;\n width: 80px;\n}\n\n.icon-menu a {\n color: black;\n text-decoration: none\n}\n\n.icon-menu a:hover{\n color: black;\n text-decoration: none;\n font-weight: 600;\n}\n\n.icon-menu-text {\n margin-bottom: 0 !important\n}\n\n.fa-check:before {\n content: \"\\f00c\";\n font-family: 'FontAwesome';\n color: #fc0d1a;\n padding-right: 0.3em;\n}\n\n.fa-close:before {\n content: \"\\f00c\";\n font-family: 'FontAwesome';\n color: #fc0d1a;\n padding-right: 0.3em;\n}\n\n.parcours-span {\n text-align: center;\n img {\n max-width:130px\n }\n margin-bottom: 1.5em;\n}\n\n.parcours-span-block {\n align-items: center;\n}\n\n.offre-parcours {\n padding-top: 3em;\n}\n\n.offre-parcours-li:before {\n content: '- '\n}\n\n.schema-bloc {\n width:50%;\n}\n\n.schema-marge {\n margin-right: 0.5em;\n width: auto !important\n}\n\n.schema-title {\n border: 1px solid;\n height: 30px;\n align-items: center;\n display: flex;\n justify-content: center;\n border-radius: 5px;\n width: 100%;\n}\n\n.schema-text {\n background-color: #F2F2F2;\n justify-content: center;\n display: flex;\n align-items: center;\n padding: 0.5em 0.25em 0.5em 0.25em;\n border-radius: 5px;\n font-size: 12px;\n height: 70px;\n align-items: center;\n text-align: center;\n margin-bottom: 0.25em;\n width: 100%;\n}\n\n.schema-text-title {\n font-size: 18px;\n color: #fc0d1a;\n font-weight: bold;\n}\n\n.axterisque-block {\n margin-bottom: 0 !important;\n}\n\n.axterisque {\n display: flex;\n flex-direction: row;\n font-size: 10px;\n}\n\n.axterisque p {\n margin-bottom: 0 !important\n}\n\n.axterisque p:not(:last-child) {\n padding-right: 3em;\n}\n\n.contactez-nous {\n display: flex;\n flex-direction: row;\n align-items: center;\n justify-content: right;\n margin-right: 2em;\n}\n\n.contactez-nous button {\n display: flex;\n flex-direction: row;\n align-items: center;\n cursor: pointer;\n border: 0;\n font-family: inherit;\n margin: 0;\n text-decoration: none;\n background-color: transparent;\n}\n\n.contactez-nous button:focus {\n outline: none;\n}\n\n.contactez-nous button > p {\n font-size: 12px;\n font-weight: bold;\n padding-top: 1.1em;\n}\n\nfooter .navbar {\n display: flex;\n justify-content: space-between;\n/* min-height: 60px; */\n font-size: 14px;\n background-image: linear-gradient(to left, #684a52, #595667);\n color: #fff;\n}\n\nfooter .navbar .liens-utiles {\n padding: 0 0 0 1.5rem;\n}\n\nfooter .navbar .liens-utiles button, footer .navbar .liens-utiles a {\n cursor: pointer;\n color: #fff;\n background-color: transparent;\n border: 0;\n font-family: inherit;\n margin: 0;\n text-decoration: none;\n}\n\nfooter .navbar .liens-utiles button:after, footer .navbar .liens-utiles a:after {\n content: \"|\";\n margin: 0 0.5rem;\n}\n\nfooter .reseaux-sociaux {\n padding: 0 0 0 1.5rem;\n}\n\nfooter .reseaux-sociaux button, footer .reseaux-sociaux a {\n cursor: pointer;\n color: #fff;\n background-color: transparent;\n border: 0;\n font-family: inherit;\n margin: 0;\n}\n\n@media screen and (min-width: 250px) {\n .offre-block-img {\n width: 70px;\n } \n .offre-block-img img {\n width: 69px;\n height: 69px;\n } \n .offre-block-text {\n min-width: 180px;\n }\n}\n\n@media screen and (max-width: 250px) {\n .offre-block-img {\n display: none;\n }\n .offre-block-text {\n width: inherit;\n } \n}\n\n@media screen and (min-width: 298px) {\n .icon-menu {\n margin-right: 1em;\n }\n}\n\n@media screen and (max-width: 330px) {\n .menu-title {\n text-align: center;\n }\n}\n\n@media screen and (max-width: 400px) {\n .offre-title img {\n display: none;\n }\n .offre-bandeau img {\n display: none;\n }\n .schema-text-title {\n font-size: 12px !important;\n }\n .schema-text {\n font-size: 12px !important;\n }\n}\n\n@media screen and (min-width: 768px) {\n .fa-arrow-right:before {\n content: \"\\f061c\";\n font-family: 'FontAwesome';\n color: #fc0d1a;\n font-size:2.5em;\n margin-right: -15px;\n }\n .fa-arrow-down:before {\n display:none\n }\n .arrow {\n margin-left: -30px;\n height: auto;\n display: flex;\n align-items: end;\n } \n .border-dotted {\n border-right: dotted 3px red;\n }\n .schema {\n margin-right: 1em !important;\n }\n}\n@media screen and (max-width: 768px) {\n .fa-arrow-right:before {\n display:none\n }\n .fa-arrow-down:before {\n content: \"\\f061c\";\n font-family: 'FontAwesome';\n color: #fc0d1a;\n font-size:2.5em;\n }\n}\n@media screen and (min-width: 992px) {\n .home-section .home-section-left span {\n text-wrap: nowrap;\n z-index: 1;\n }\n .home-section .home-section-left {\n margin-top: 5em;\n }\n .home-section .home-section-left .en-tete {\n margin-top: 3em;\n text-align: end;\n }\n .en-tete {\n font-size: 23px;\n line-height: 2rem;\n }\n .offre-parcours-img {\n display: inline-flex!important;\n align-items: end;\n }\n}\n\n\n@media screen and (max-width: 992px) {\n .offre-parcours-img {\n display: none;\n }\n}\n\n.modal {\n display: none;\n /* Hidden by default */\n position: fixed;\n z-index: 9999;\n padding-top: 20px;\n left: 0;\n top: 0;\n width: 100%;\n height: 100%;\n overflow: auto;\n background-color: rgb(0, 0, 0);\n /* Fallback color */\n background-color: rgba(0, 0, 0, 0.4);\n /* Black w/ opacity */\n}\n.modal .modal-content {\n background-color: #fefefe;\n margin: auto;\n padding: 20px;\n max-width: 800px;\n border: 1px solid #888;\n border-radius: 5px;\n width: 80%;\n}\n\n.modal-content-scroll {\n overflow-y: scroll;\n height: 100%;\n}\n\n.modal .modal-content .modal-header {\n border-bottom: 1px solid #dee2e6;\n display: flex;\n justify-content: space-between;\n padding: 1rem;\n}\n.modal .modal-content .modal-header span {\n cursor: pointer;\n}\n.modal .modal-content .modal-footer {\n border-top: 1px solid #dee2e6;\n display: flex;\n justify-content: flex-end;\n padding: 1rem 1rem 0rem 1rem;\n}\n.modal .modal-content .close {\n color: #aaaaaa;\n float: right;\n font-size: 28px;\n font-weight: bold;\n}\n.modal .modal-content .close:hover,\n.modal .modal-content .close:focus {\n color: #000;\n text-decoration: none;\n cursor: pointer;\n}\n.modal .modal-content h1 {\n line-height: 1.5;\n margin-top: 0;\n margin-bottom: 0;\n font-size: 1.25rem;\n color: rgba(0, 0, 0, 0.85);\n font-weight: 500;\n}\n.modal .modal-content h2 {\n line-height: 1.2;\n margin-top: 0.5rem;\n margin-bottom: 0.5rem;\n font-size: 1.5rem;\n font-weight: 500;\n}\n.modal .modal-content h3 {\n color: #c62222;\n line-height: 1.2;\n margin-top: 0;\n margin-bottom: 0.5rem;\n font-size: 1.25rem;\n font-weight: 500;\n}\n.modal .modal-content p {\n margin-bottom: 1rem;\n text-align: justify;\n}\n.modal .modal-content ul {\n margin-bottom: 1em;\n margin-top: 0;\n}\n.modal .modal-content ul li {\n list-style-type: none;\n padding-left: 2rem;\n}\n.modal .modal-content form {\n margin: 1rem 0;\n font-weight: 500;\n}\n.modal .modal-content form .form-group {\n margin-bottom: 1rem;\n padding-bottom: 10px;\n}\n.modal .modal-content form .form-group label {\n display: inline-block;\n margin-bottom: 0.5rem;\n}\n.modal .modal-content form .form-group .form-control {\n background-clip: padding-box;\n background-color: #fff;\n border: 1px solid #ced4da;\n border-radius: 0.25rem;\n color: #495057;\n display: block;\n font-size: 1rem;\n font-weight: 400;\n height: calc(1.5em + 0.75rem + 2px);\n line-height: 1.5;\n padding: 0.375rem 2%;\n transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n width: 96%;\n}\n.modal .modal-content form .form-group select.form-control[multiple], .modal .modal-content form .form-group select.form-control[size], .modal .modal-content form .form-group textarea.form-control {\n height: auto;\n}\n.modal .modal-content form button {\n background-color: transparent;\n border: 1px solid transparent;\n border-radius: 0.25rem;\n color: #212529;\n display: inline-block;\n font-size: 1rem;\n font-weight: 400;\n line-height: 1.5;\n padding: 0.375rem 0.75rem;\n text-align: center;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n vertical-align: middle;\n border-color: #28a745;\n color: #28a745;\n}\n.modal .modal-content form textarea {\n font-family: inherit;\n} */","name":"IAAS-BASIC-css","path":"/62457958430000127e85c5d0","tags":[],"draft":"*,\n*::after,\n*::before {\n margin: 0;\n padding: 0;\n box-sizing: inherit;\n}\n\nul{\n list-style-type: none;\n}\n\n.maif-color-text{\n color:#fc0d1a;\n}\n\nheader {\n text-align: center;\n background-color: #F2F2F2\n}\n\nheader a {\n color: black;\n text-decoration: none;\n}\n\nheader a:hover{\n color: black;\n text-decoration: none;\n font-weight: 600;\n}\n\n.header-left img {\n height: auto;\n max-width: 59px;\n}\n\n.header-right .non-connecte a, header .header-right .non-connecte button {\n color: #2e404f;\n cursor: pointer;\n padding: 6px 12px;\n align-items: center;\n text-align: center;\n font-size: 1.5rem;\n}\n\n.header-right .non-connecte a .icon, header .header-right .non-connecte button .icon {\n width: 38px;\n border-radius: 50%;\n}\n\n.header-right img{\n max-width: 30px;\n border-radius: 50%;\n}\n\nheader-left {\n text-align: left;\n}\n\n.header-right {\n display: flex;\n justify-content: start\n}\n\n.header-right .buttons {\n display: flex;\n flex-direction: column;\n}\n\n.header-right .buttons a {\n justify-content: start;\n display: flex;\n padding: 0;\n color: #212529;\n}\n\n.container-burger-menu {\n justify-content: right;\n margin-top: 0.5em;\n}\n\n.sidenav {\n height: 100%;\n width: 290px;\n position: fixed;\n z-index: 1;\n top: 0;\n right: -290px;\n background-color: #e8e8e8;\n padding-top: 60px;\n transition: right 0.5s ease;\n}\n\n/* Sidenav menu links */\n.sidenav a {\n padding: 8px 8px 8px 32px;\n text-decoration: none;\n font-size: 25px;\n color: #818181;\n display: block;\n transition: 0.3s;\n}\n\n.sidenav a:hover {\n color: #111;\n}\n\n.sidenav ul {\n text-align: left;\n list-style-type: none;\n padding: 0;\n margin: 0;\n}\n\n/* Active class */\n.sidenav.active {\n right: 0;\n}\n\n/* Close btn */\n.sidenav .close {\n position: absolute;\n top: 0;\n right: 25px;\n font-size: 36px;\n}\n\n/* Icône burger */\n.burger-icon span {\n display: block;\n width: 35px;\n height: 5px;\n background-color: black;\n margin: 6px 0;\n}\n\nbody {\n font-family: Custom, Raleway, Helvetica, sans-serif;\n margin: 0px;\n min-height: 100vh;\n display: flex;\n flex-direction: column\n}\n\n.offre-title {\n display: flex;\n flex-direction: row;\n align-items: center;\n border-bottom: solid #fc0d1a 5px;\n}\n\n.offre-title-double {\n display: flex;\n flex-direction: column;\n}\n\n.offre-title-double img:not(:last-child) {\n margin-bottom: -20px;\n}\n\n.offre-title-double img {\n padding-bottom: 0 !important;\n}\n\n.offre-title img {\n padding-bottom: 0.7em;\n padding-right: 3em;\n}\n\n.offre-title-detail {\n font-size: 12px;\n}\n\n.offre-title-2 {\n font-size: 18px;\n}\n\n.offre-text {\n font-size: 20px;\n}\n\n.offre-bandeau {\n display: flex;\n flex-direction: row;\n justify-content: center;\n align-items: center;\n background-color: #FBEBE9;\n padding: 10px;\n min-height: 120px;\n text-align: center;\n}\n\n.offre-bandeau img {\n padding-right: 5em;\n}\n\n.offre-bandeau-reverse img {\n -webkit-transform: scaleX(-1);\n padding-left: 5em;\n}\n\n.offre-description-text {\n display: flex;\n flex-direction: column;\n}\n\n.offre-description-text-margin {\n margin-top: 3em;\n}\n\n.offre-img-padding {\n align-items: end;\n}\n\n.offre-img-padding img{\n width: -webkit-fill-available;\n}\n\n.offre-ul {\n > li {\n font-style: italic;\n }\n}\n\n.offre-li-detail {\n font-size: 10px;\n}\n\n.img-reduction img {\n width: 70%;\n height: 80%;\n}\n\n.container {\n flex: 1; \n}\n\n.home-section .home-section-header {\n display: flex;\n width: 100%;\n flex-direction: column;\n}\n\n.home-section-body > div {\n margin-bottom: 3rem;\n}\n\n.img-inherit img {\n width: inherit;\n}\n\n.home-img img {\n width: 485px; \n}\n\n.home-section-side { \n padding-top: 4em;\n}\n\n.home-section-side img {\n margin-top: 1em;\n max-width: 130px;\n}\n\n.icon-menu {\n border: solid 1px grey;\n border-radius: 0.75em;\n margin-bottom: 1em;\n align-items: center;\n display: flex;\n flex-direction: column;\n width: 140px;\n height: 140px;\n text-align: center;\n}\n\n.icon-menu-disabled {\n filter: grayscale(1);\n}\n\n.icon-menu img {\n height: 80px;\n width: 80px;\n}\n\n.icon-menu a {\n color: black;\n text-decoration: none\n}\n\n.icon-menu a:hover{\n color: black;\n text-decoration: none;\n font-weight: 600;\n}\n\n.icon-menu-text {\n margin-bottom: 0 !important\n}\n\n.fa-check:before {\n content: \"\\f00c\";\n font-family: 'FontAwesome';\n color: #fc0d1a;\n padding-right: 0.3em;\n}\n\n.fa-close:before {\n content: \"\\f00c\";\n font-family: 'FontAwesome';\n color: #fc0d1a;\n padding-right: 0.3em;\n}\n\n.parcours-span {\n text-align: center;\n img {\n max-width:130px\n }\n margin-bottom: 1.5em;\n}\n\n.parcours-span-block {\n align-items: center;\n}\n\n.offre-parcours {\n padding-top: 3em;\n}\n\n.offre-parcours-li:before {\n content: '- '\n}\n\n.schema-bloc {\n width:50%;\n}\n\n.schema-marge {\n margin-right: 0.5em;\n width: auto !important\n}\n\n.schema-title {\n border: 1px solid;\n height: 30px;\n align-items: center;\n display: flex;\n justify-content: center;\n border-radius: 5px;\n width: 100%;\n}\n\n.schema-text {\n background-color: #F2F2F2;\n justify-content: center;\n display: flex;\n align-items: center;\n padding: 0.5em 0.25em 0.5em 0.25em;\n border-radius: 5px;\n font-size: 12px;\n height: 70px;\n align-items: center;\n text-align: center;\n margin-bottom: 0.25em;\n width: 100%;\n}\n\n.schema-text-title {\n font-size: 18px;\n color: #fc0d1a;\n font-weight: bold;\n}\n\n.axterisque-block {\n margin-bottom: 0 !important;\n}\n\n.axterisque {\n display: flex;\n flex-direction: row;\n font-size: 10px;\n}\n\n.axterisque p {\n margin-bottom: 0 !important\n}\n\n.axterisque p:not(:last-child) {\n padding-right: 3em;\n}\n\n.contactez-nous {\n display: flex;\n flex-direction: row;\n align-items: center;\n justify-content: right;\n margin-right: 2em;\n}\n\n.contactez-nous button {\n display: flex;\n flex-direction: row;\n align-items: center;\n cursor: pointer;\n border: 0;\n font-family: inherit;\n margin: 0;\n text-decoration: none;\n background-color: transparent;\n}\n\n.contactez-nous button:focus {\n outline: none;\n}\n\n.contactez-nous button > p {\n font-size: 12px;\n font-weight: bold;\n padding-top: 1.1em;\n}\n\nfooter .navbar {\n display: flex;\n justify-content: space-between;\n/* min-height: 60px; */\n font-size: 14px;\n background-image: linear-gradient(to left, #684a52, #595667);\n color: #fff;\n}\n\nfooter .navbar .liens-utiles {\n padding: 0 0 0 1.5rem;\n}\n\nfooter .navbar .liens-utiles button, footer .navbar .liens-utiles a {\n cursor: pointer;\n color: #fff;\n background-color: transparent;\n border: 0;\n font-family: inherit;\n margin: 0;\n text-decoration: none;\n}\n\nfooter .navbar .liens-utiles button:after, footer .navbar .liens-utiles a:after {\n content: \"|\";\n margin: 0 0.5rem;\n}\n\nfooter .reseaux-sociaux {\n padding: 0 0 0 1.5rem;\n}\n\nfooter .reseaux-sociaux button, footer .reseaux-sociaux a {\n cursor: pointer;\n color: #fff;\n background-color: transparent;\n border: 0;\n font-family: inherit;\n margin: 0;\n}\n\n@media screen and (min-width: 250px) {\n .offre-block-img {\n width: 70px;\n } \n .offre-block-img img {\n width: 69px;\n height: 69px;\n } \n .offre-block-text {\n min-width: 180px;\n }\n}\n\n@media screen and (max-width: 250px) {\n .offre-block-img {\n display: none;\n }\n .offre-block-text {\n width: inherit;\n } \n}\n\n@media screen and (min-width: 298px) {\n .icon-menu {\n margin-right: 1em;\n }\n}\n\n@media screen and (max-width: 330px) {\n .menu-title {\n text-align: center;\n }\n}\n\n@media screen and (max-width: 400px) {\n .offre-title img {\n display: none;\n }\n .offre-bandeau img {\n display: none;\n }\n .schema-text-title {\n font-size: 12px !important;\n }\n .schema-text {\n font-size: 12px !important;\n }\n}\n\n@media screen and (min-width: 768px) {\n .fa-arrow-right:before {\n content: \"\\f061c\";\n font-family: 'FontAwesome';\n color: #fc0d1a;\n font-size:2.5em;\n margin-right: -15px;\n }\n .fa-arrow-down:before {\n display:none\n }\n .arrow {\n margin-left: -30px;\n height: auto;\n display: flex;\n align-items: end;\n } \n .border-dotted {\n border-right: dotted 3px red;\n }\n .schema {\n margin-right: 1em !important;\n }\n}\n@media screen and (max-width: 768px) {\n .fa-arrow-right:before {\n display:none\n }\n .fa-arrow-down:before {\n content: \"\\f061c\";\n font-family: 'FontAwesome';\n color: #fc0d1a;\n font-size:2.5em;\n }\n}\n@media screen and (min-width: 992px) {\n .home-section .home-section-left span {\n text-wrap: nowrap;\n z-index: 1;\n }\n .home-section .home-section-left {\n margin-top: 5em;\n }\n .home-section .home-section-left .en-tete {\n margin-top: 3em;\n text-align: end;\n }\n .en-tete {\n font-size: 23px;\n line-height: 2rem;\n }\n .offre-parcours-img {\n display: inline-flex!important;\n align-items: end;\n }\n}\n\n\n@media screen and (max-width: 992px) {\n .offre-parcours-img {\n display: none;\n }\n}\n\n.modal {\n display: none;\n /* Hidden by default */\n position: fixed;\n z-index: 9999;\n padding-top: 20px;\n left: 0;\n top: 0;\n width: 100%;\n height: 100%;\n overflow: auto;\n background-color: rgb(0, 0, 0);\n /* Fallback color */\n background-color: rgba(0, 0, 0, 0.4);\n /* Black w/ opacity */\n}\n.modal .modal-content {\n background-color: #fefefe;\n margin: auto;\n padding: 20px;\n max-width: 800px;\n border: 1px solid #888;\n border-radius: 5px;\n width: 80%;\n}\n\n.modal-content-scroll {\n overflow-y: scroll;\n height: 100%;\n}\n\n.modal .modal-content .modal-header {\n border-bottom: 1px solid #dee2e6;\n display: flex;\n justify-content: space-between;\n padding: 1rem;\n}\n.modal .modal-content .modal-header span {\n cursor: pointer;\n}\n.modal .modal-content .modal-footer {\n border-top: 1px solid #dee2e6;\n display: flex;\n justify-content: flex-end;\n padding: 1rem 1rem 0rem 1rem;\n}\n.modal .modal-content .close {\n color: #aaaaaa;\n float: right;\n font-size: 28px;\n font-weight: bold;\n}\n.modal .modal-content .close:hover,\n.modal .modal-content .close:focus {\n color: #000;\n text-decoration: none;\n cursor: pointer;\n}\n.modal .modal-content h1 {\n line-height: 1.5;\n margin-top: 0;\n margin-bottom: 0;\n font-size: 1.25rem;\n color: rgba(0, 0, 0, 0.85);\n font-weight: 500;\n}\n.modal .modal-content h2 {\n line-height: 1.2;\n margin-top: 0.5rem;\n margin-bottom: 0.5rem;\n font-size: 1.5rem;\n font-weight: 500;\n}\n.modal .modal-content h3 {\n color: #c62222;\n line-height: 1.2;\n margin-top: 0;\n margin-bottom: 0.5rem;\n font-size: 1.25rem;\n font-weight: 500;\n}\n.modal .modal-content p {\n margin-bottom: 1rem;\n text-align: justify;\n}\n.modal .modal-content ul {\n margin-bottom: 1em;\n margin-top: 0;\n}\n.modal .modal-content ul li {\n list-style-type: none;\n padding-left: 2rem;\n}\n.modal .modal-content form {\n margin: 1rem 0;\n font-weight: 500;\n}\n.modal .modal-content form .form-group {\n margin-bottom: 1rem;\n padding-bottom: 10px;\n}\n.modal .modal-content form .form-group label {\n display: inline-block;\n margin-bottom: 0.5rem;\n}\n.modal .modal-content form .form-group .form-control {\n background-clip: padding-box;\n background-color: #fff;\n border: 1px solid #ced4da;\n border-radius: 0.25rem;\n color: #495057;\n display: block;\n font-size: 1rem;\n font-weight: 400;\n height: calc(1.5em + 0.75rem + 2px);\n line-height: 1.5;\n padding: 0.375rem 2%;\n transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n width: 96%;\n}\n.modal .modal-content form .form-group select.form-control[multiple], .modal .modal-content form .form-group select.form-control[size], .modal .modal-content form .form-group textarea.form-control {\n height: auto;\n}\n.modal .modal-content form button {\n background-color: transparent;\n border: 1px solid transparent;\n border-radius: 0.25rem;\n color: #212529;\n display: inline-block;\n font-size: 1rem;\n font-weight: 400;\n line-height: 1.5;\n padding: 0.375rem 0.75rem;\n text-align: center;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n vertical-align: middle;\n border-color: #28a745;\n color: #28a745;\n}\n.modal .modal-content form textarea {\n font-family: inherit;\n} */","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/css","authenticated":false,"lastPublishedDate":1707811449114}} +{"type":"cmspages","payload":{"_id":"652fa89c4400000d82fcea04","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-mobilite\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre VELO

\n
\n
\n

\n Vol, accident, dommages, assistance, vos utilisateurs sont-ils bien protégés ?\n

\n

\n Notre assurance propose des garanties complètes pour le vélo mais aussi pour le cycliste avec une assistance et une protection corporelle.\n

\n
\n \"offre \n
\n
\n
\n
\n

Penser à bien s’assurer avant de prendre le guidon

\n
\n \"offre \n
\n
\n
\n
\n
\n \"icon \n
\n
\n

Vol, casse… vos utilisateurs couverts partout, quoiqu’il arrive

\n

Une protection pour leur vélo neuf ou d’occasion, et pour ses accessoires. Même la tentative de vol est couverte !

\n
\n
\n
\n
\n \"icon \n
\n
\n

Crevaison panne : Assistance 24h/24

\n

MAIF Assistance dépanne vos utilisateurs et les amènent à leur domicile ou à destination. Intervention sur la route autour de chez eux (de 1 km à 50 km, sauf batterie déchargée).

\n
\n
\n
\n
\n \"icon \n
\n
\n

Chute, accident : vos utilisateurs sont pris en charge

\n

En cas de blessures, MAIF aide vos utilisateurs à se remettre en selle : aide financière, services à domicile, perte de revenus. Et leur enfant passager est couvert.

\n
\n
\n
\n
\n
\n \"offre \n
\n

Garanties & Indemnisations

\n
\n
\n
\n

Des garanties essentielles pour le vélo…

\n
    \n
  • Dommages accident
  • \n
  • Vol
  • \n
  • Accessoires et équipements de protection (y compris la batterie)
  • \n
  • Indemnisation en valeur d’achat 1 an…
  • \n
  • Vélo de remplacement​
  • \n
\n
\n
\n

… mais également pour le cycliste

\n
    \n
  • Assistance (panne & accident) jusqu’à 50km du domicile
  • \n
  • Retour au domicile
  • \n
  • Couverture corporelle
  • \n
  • Prêt de guidon gratuit
  • \n
  • Enfant passager couvert
  • \n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","name":"offre-velo","path":"/offre-velo","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-mobilite\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre VELO

\n
\n
\n

\n Vol, accident, dommages, assistance, vos utilisateurs sont-ils bien protégés ?\n

\n

\n Notre assurance propose des garanties complètes pour le vélo mais aussi pour le cycliste avec une assistance et une protection corporelle.\n

\n
\n \"offre \n
\n
\n
\n
\n

Penser à bien s’assurer avant de prendre le guidon

\n
\n \"offre \n
\n
\n
\n
\n
\n \"icon \n
\n
\n

Vol, casse… vos utilisateurs couverts partout, quoiqu’il arrive

\n

Une protection pour leur vélo neuf ou d’occasion, et pour ses accessoires. Même la tentative de vol est couverte !

\n
\n
\n
\n
\n \"icon \n
\n
\n

Crevaison panne : Assistance 24h/24

\n

MAIF Assistance dépanne vos utilisateurs et les amènent à leur domicile ou à destination. Intervention sur la route autour de chez eux (de 1 km à 50 km, sauf batterie déchargée).

\n
\n
\n
\n
\n \"icon \n
\n
\n

Chute, accident : vos utilisateurs sont pris en charge

\n

En cas de blessures, MAIF aide vos utilisateurs à se remettre en selle : aide financière, services à domicile, perte de revenus. Et leur enfant passager est couvert.

\n
\n
\n
\n
\n
\n \"offre \n
\n

Garanties & Indemnisations

\n
\n
\n
\n

Des garanties essentielles pour le vélo…

\n
    \n
  • Dommages accident
  • \n
  • Vol
  • \n
  • Accessoires et équipements de protection (y compris la batterie)
  • \n
  • Indemnisation en valeur d’achat 1 an…
  • \n
  • Vélo de remplacement​
  • \n
\n
\n
\n

… mais également pour le cycliste

\n
    \n
  • Assistance (panne & accident) jusqu’à 50km du domicile
  • \n
  • Retour au domicile
  • \n
  • Couverture corporelle
  • \n
  • Prêt de guidon gratuit
  • \n
  • Enfant passager couvert
  • \n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520614419}} +{"type":"cmspages","payload":{"_id":"6582b0212a0000359b5c3187","body":"
\n

Portail API MAIF

\n Le site dédié aux partenaires de distribution digitale des assurances MAIF\n \"icon\n
","name":"bloc-offre-side","path":null,"tags":[],"draft":"
\n

Portail API MAIF

\n Le site dédié aux partenaires de distribution digitale des assurances MAIF\n \"icon\n
","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520642480}} +{"type":"cmspages","payload":{"_id":"623c7943350000e106e28f89","body":"\n\n\n \n \n \n \n \n \n \n \n \n\n\n\n {{daikoku-include-block \"623c6fe5350000e106e28d1b\"}}\n \n {{children}}\n\n {{daikoku-include-block \"623c79b8350000e106e28fb4\"}}\n\n\n","name":"IAAS-BASIC-template","path":null,"tags":[],"draft":"\n\n\n \n \n \n \n \n \n \n \n \n\n\n\n {{daikoku-include-block \"623c6fe5350000e106e28d1b\"}}\n \n {{children}}\n\n {{daikoku-include-block \"623c79b8350000e106e28fb4\"}}\n\n\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520677032}} +{"type":"cmspages","payload":{"_id":"623c79b8350000e106e28fb4","body":"
\n
\n \n
\n
\n
\n \n maif.fr\n \n \n {{#unless connected}}\n Créer un compte\n {{/unless}}\n
\n
\n \n \n \n
\n
\n\n \n {{daikoku-include-block \"62a8424a440000921d1d3f06\"}}\n \n \n {{daikoku-include-block \"62aae19a3600001707b32ec4\"}}\n\n \n {{daikoku-include-block \"629f15aa440000921d1d2372\"}}\n\n \n {{daikoku-include-block \"629f4106440000921d1d278f\"}}\n\n\n \n\n
","name":"IAAS-BASIC-footer","path":null,"tags":[],"draft":"
\n
\n \n
\n
\n
\n \n maif.fr\n \n \n {{#unless connected}}\n Créer un compte\n {{/unless}}\n
\n
\n \n \n \n
\n
\n\n \n {{daikoku-include-block \"62a8424a440000921d1d3f06\"}}\n \n \n {{daikoku-include-block \"62aae19a3600001707b32ec4\"}}\n\n \n {{daikoku-include-block \"629f15aa440000921d1d2372\"}}\n\n \n {{daikoku-include-block \"629f4106440000921d1d278f\"}}\n\n\n \n\n
","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520760583}} +{"type":"cmspages","payload":{"_id":"V80UfyIoYE6YTagwCGr3rHlD2QB0AAaE","body":"un super header","name":"page.html","path":"/apis/un-test-pour-el-cli/header","tags":[],"draft":"un super header","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{"from":"cli","_name":"page.html","_path":"/apis/un-test-pour-el-cli/header","_content_type":"text/html"},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":null}} +{"type":"cmspages","payload":{"_id":"62aae19a3600001707b32ec4","body":"
\n \n
\n
\n

Demande de contact

\n ×\n
\n

\n
\n

Le message a bien été envoyé.

\n
\n
\n
\n","name":"MODAL-success","path":null,"tags":[],"draft":"
\n \n
\n
\n

Demande de contact

\n ×\n
\n

\n
\n

Le message a bien été envoyé.

\n
\n
\n
\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1677069681802}} +{"type":"cmspages","payload":{"_id":"6514440c3d00001b85d2dc8a","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/parcours-api\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Devis

\n
\n
\n
\n

\n Le parcours Devis est la solution optimale pour intégrer une proposition d’assurance dans votre parcours utilisateur.\n

\n
\n Offres disponibles sur ce parcours\n
    \n
  • Offres AUTO, VÉLO et EDPM
  • \n
  • Offre MRH jeune
  • \n
  • Offre Travailleur Indépendant
  • \n
\n
\n
\n
\n \"parcours \n
\n
\n
\n
\n

Intégrez le devis d’assurance dans votre parcours utilisateur

\n
\n
\n
\n
\n \"icon\n Le parcours Devis vous permet d’intégrer sur votre environnement (app mobile ou site internet) la réalisation du devis d’assurance. Vos utilisateurs bénéficient d’un parcours sans couture.\n
\n
\n
\n
\n

Votre Site / App

\n

Votre utilisateur réalise de manière transparente son devis au sein de votre parcours, sans rupture.

\n

DEVIS

\n
\n
\n
\n
\n

MAIF.fr

\n

Votre utilisateur réalise sa souscription d’assurance sur MAIF.fr (via le lien reçu par email avec son devis)

\n

SOUSCRIPTION

\n
\n
\n
\n
\n

Partenaires Cibles

\n
\n
\n
\n Notre Parcours Devis est adapté pour les partenaires avec expertise assurance, avec un besoin d’intégration de l’assurance dans le parcours utilisateur.\n
\n \"parcours\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}","name":"parcours-devis","path":"/parcours-devis","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/parcours-api\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Devis

\n
\n
\n
\n

\n Le parcours Devis est la solution optimale pour intégrer une proposition d’assurance dans votre parcours utilisateur.\n

\n
\n Offres disponibles sur ce parcours\n
    \n
  • Offres AUTO, VÉLO et EDPM
  • \n
  • Offre MRH jeune
  • \n
  • Offre Travailleur Indépendant
  • \n
\n
\n
\n
\n \"parcours \n
\n
\n
\n
\n

Intégrez le devis d’assurance dans votre parcours utilisateur

\n
\n
\n
\n
\n \"icon\n Le parcours Devis vous permet d’intégrer sur votre environnement (app mobile ou site internet) la réalisation du devis d’assurance. Vos utilisateurs bénéficient d’un parcours sans couture.\n
\n
\n
\n
\n

Votre Site / App

\n

Votre utilisateur réalise de manière transparente son devis au sein de votre parcours, sans rupture.

\n

DEVIS

\n
\n
\n
\n
\n

MAIF.fr

\n

Votre utilisateur réalise sa souscription d’assurance sur MAIF.fr (via le lien reçu par email avec son devis)

\n

SOUSCRIPTION

\n
\n
\n
\n
\n

Partenaires Cibles

\n
\n
\n
\n Notre Parcours Devis est adapté pour les partenaires avec expertise assurance, avec un besoin d’intégration de l’assurance dans le parcours utilisateur.\n
\n \"parcours\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520456539}} +{"type":"cmspages","payload":{"_id":"655dcd5615000001e6aed967","body":"var sidenav = document.getElementById(\"mySidenav\");\nvar openBtn = document.getElementById(\"openBtn\");\nvar closeBtn = document.getElementById(\"closeBtn\");\n\nopenBtn.onclick = openNav;\ncloseBtn.onclick = closeNav;\n\n/* Set the width of the side navigation to 250px */\nfunction openNav() {\n sidenav.classList.add(\"active\");\n}\n\n/* Set the width of the side navigation to 0 */\nfunction closeNav() {\n sidenav.classList.remove(\"active\");\n}","name":"navbar-js","path":"/655dcd5615000001e6aed968","tags":[],"draft":"var sidenav = document.getElementById(\"mySidenav\");\nvar openBtn = document.getElementById(\"openBtn\");\nvar closeBtn = document.getElementById(\"closeBtn\");\n\nopenBtn.onclick = openNav;\ncloseBtn.onclick = closeNav;\n\n/* Set the width of the side navigation to 250px */\nfunction openNav() {\n sidenav.classList.add(\"active\");\n}\n\n/* Set the width of the side navigation to 0 */\nfunction closeNav() {\n sidenav.classList.remove(\"active\");\n}","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/javascript","authenticated":false,"lastPublishedDate":1700647278213}} +{"type":"cmspages","payload":{"_id":"654b95412100008ec5dcc05e","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-mobilite\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre MOTO

\n
\n
\n

\n Notre assurance moto et scooter s’ajuste aux besoins et aux budgets de vos utilisateurs : ils peuvent choisir parmi 4 formules au tiers ou tous risques, avec des garanties étendues et des options.\n

\n
\n
\n
\n

Des garanties solides adaptées aux motards

\n
\n
\n
\n
\n \"icon \n
\n
\n

Assistance sur la route 24h/24 et 7j/7

\n

Vos utilisateurs sont dépannés lors de leurs déplacements à plus de 50 km de chez eux. Et géolocalisés, avec l’application MAIF. Leur passager est également pris en charge et rapatrié.​

\n
\n
\n
\n
\n \"icon \n
\n
\n

Vol, incendie : même assuré au tiers

\n

La moto de vos utilisateurs est couverte dès la formule Tiers enrichie (Essentiel). Leur équipement de protection motard est pris en charge : casque, gants, gilet airbag, antivol.

\n
\n
\n
\n
\n \"icon \n
\n
\n

Protection du motard

\n

Un premier niveau de protection corporelle est inclus dans toutes nos formules, pour aider vos utilisateurs et leurs proches, en cas de blessure ou de décès.

\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","name":"offre-moto","path":"/offre-moto","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-mobilite\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre MOTO

\n
\n
\n

\n Notre assurance moto et scooter s’ajuste aux besoins et aux budgets de vos utilisateurs : ils peuvent choisir parmi 4 formules au tiers ou tous risques, avec des garanties étendues et des options.\n

\n
\n
\n
\n

Des garanties solides adaptées aux motards

\n
\n
\n
\n
\n \"icon \n
\n
\n

Assistance sur la route 24h/24 et 7j/7

\n

Vos utilisateurs sont dépannés lors de leurs déplacements à plus de 50 km de chez eux. Et géolocalisés, avec l’application MAIF. Leur passager est également pris en charge et rapatrié.​

\n
\n
\n
\n
\n \"icon \n
\n
\n

Vol, incendie : même assuré au tiers

\n

La moto de vos utilisateurs est couverte dès la formule Tiers enrichie (Essentiel). Leur équipement de protection motard est pris en charge : casque, gants, gilet airbag, antivol.

\n
\n
\n
\n
\n \"icon \n
\n
\n

Protection du motard

\n

Un premier niveau de protection corporelle est inclus dans toutes nos formules, pour aider vos utilisateurs et leurs proches, en cas de blessure ou de décès.

\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520379884}} +{"type":"cmspages","payload":{"_id":"656750ea2a00008e0f55a812","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n

UNIVERS Vie Quotidienne & Pro

\n
\n
\n
\n
\n \n \n
\n \"icon\n

Garanties Accidents de la Vie

\n
\n \n
\n \"icon\n

Propriétaire Bailleur

\n
\n
\n \"icon\n

Assurance Vie

\n
\n
\n
\n
\n \"icon\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","name":"univers-vie-quotidienne-pro","path":"/univers-vie-quotidienne-pro","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n

UNIVERS Vie Quotidienne & Pro

\n
\n
\n
\n
\n \n \n
\n \"icon\n

Garanties Accidents de la Vie

\n
\n \n
\n \"icon\n

Propriétaire Bailleur

\n
\n
\n \"icon\n

Assurance Vie

\n
\n
\n
\n
\n \"icon\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520479461}} +{"type":"cmspages","payload":{"_id":"65708cd52b0000c5d720a237","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-vie-quotidienne-pro\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre HABITATION JEUNES

\n
\n
\n

\n Pour les locataires ou colocataires de moins de 30 ans et sans enfant.\n

\n

\n S'assurer est une obligation légale pour tous les locataires.\n

\n

\n Notre Assurance Habitation Jeunes répond à ce devoir et va bien au-delà pour vos utilisateurs.\n

\n
\n \"offre \n
\n
\n
\n
\n

Garanties de l’offre habitation jeunes

\n
\n
\n
\n

Des garanties essentielles dans le logement…

\n
    \n
  • Incendie, explosion et dégâts des eaux
  • \n
  • Bris de vitre sur l’immobilier
  • \n
  • Evénement climatique
  • \n
  • Cambriolage…
  • \n
\n
\n
\n \"offre \n
\n
\n

… mais également en dehors

\n
    \n
  • Responsabilité Civile (dommages causés à un tiers)
  • \n
  • Vol des biens pendant le déménagement
  • \n
  • Assistance voyage rapatriement
  • \n
  • Extension Biens Nomades (assure partout les biens que vous transportez avec vous : Smartphone, PC portable, instrument de musique, matériel de sport…)
  • \n
\n
\n
\n
\n

Une assurance responsable adaptée à votre quotidien

\n
\n
\n
\n
\n \"icon \n
\n
\n

Prêter, emprunter, louer

\n

MAIF encourage le partage plutôt que l’achat. On assure les biens que vos utilisateurs louent, prêtent ou empruntent.

\n
\n
\n
\n
\n \"icon \n
\n
\n

Réparer plutôt que remplacer

\n

A chaque fois que cela est possible, en cas de sinistre, nous privilégions la réparation des biens plutôt que leur remplacement. Un petit geste pour la planète.

\n
\n
\n
\n
\n \"icon \n
\n
\n

Limiter vos frais en colocation

\n

Les colocataires sont tous assurés de la même manière sur le même contrat.

\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","name":"offre-mrh-jeunes","path":"/offre-mrh-jeunes","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-vie-quotidienne-pro\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre HABITATION JEUNES

\n
\n
\n

\n Pour les locataires ou colocataires de moins de 30 ans et sans enfant.\n

\n

\n S'assurer est une obligation légale pour tous les locataires.\n

\n

\n Notre Assurance Habitation Jeunes répond à ce devoir et va bien au-delà pour vos utilisateurs.\n

\n
\n \"offre \n
\n
\n
\n
\n

Garanties de l’offre habitation jeunes

\n
\n
\n
\n

Des garanties essentielles dans le logement…

\n
    \n
  • Incendie, explosion et dégâts des eaux
  • \n
  • Bris de vitre sur l’immobilier
  • \n
  • Evénement climatique
  • \n
  • Cambriolage…
  • \n
\n
\n
\n \"offre \n
\n
\n

… mais également en dehors

\n
    \n
  • Responsabilité Civile (dommages causés à un tiers)
  • \n
  • Vol des biens pendant le déménagement
  • \n
  • Assistance voyage rapatriement
  • \n
  • Extension Biens Nomades (assure partout les biens que vous transportez avec vous : Smartphone, PC portable, instrument de musique, matériel de sport…)
  • \n
\n
\n
\n
\n

Une assurance responsable adaptée à votre quotidien

\n
\n
\n
\n
\n \"icon \n
\n
\n

Prêter, emprunter, louer

\n

MAIF encourage le partage plutôt que l’achat. On assure les biens que vos utilisateurs louent, prêtent ou empruntent.

\n
\n
\n
\n
\n \"icon \n
\n
\n

Réparer plutôt que remplacer

\n

A chaque fois que cela est possible, en cas de sinistre, nous privilégions la réparation des biens plutôt que leur remplacement. Un petit geste pour la planète.

\n
\n
\n
\n
\n \"icon \n
\n
\n

Limiter vos frais en colocation

\n

Les colocataires sont tous assurés de la même manière sur le même contrat.

\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520526380}} +{"type":"cmspages","payload":{"_id":"623c6fe5350000e106e28d1b","body":"
\n
\n
\n
\n \n \n \n
\n \n
\n \n {{#if connected}}\n
\n \n \n \n \n \n
\n {{/if}}\n \n \n {{#unless connected}}\n \n {{/unless}} \n
\n \n
\n \n \n \n \n \n \n \n
\n ×\n \n
\n
\n
\n \n \n
\n
","name":"IAAS-BASIC-navbar","path":null,"tags":[],"draft":"
\n
\n
\n
\n \n \n \n
\n \n
\n \n {{#if connected}}\n
\n \n \n \n \n \n
\n {{/if}}\n \n \n {{#unless connected}}\n \n {{/unless}} \n
\n \n
\n \n \n \n \n \n \n \n
\n ×\n \n
\n
\n
\n \n \n
\n
","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520280358}} +{"type":"cmspages","payload":{"_id":"629f44d0440000921d1d297f","body":"// Modal Contact\nfunction closeModalContact(){\n document.getElementById(\"contact\").style.display = \"none\";\n}\n\nfunction closeModalSuccess(){\n document.getElementById(\"contact-success\").style.display = \"none\";\n}\n\nfunction openContact(){\n document.getElementById(\"contact\").style.display = \"block\";\n}\n\n// Modal Mentions Légales\nfunction openMentionsLegales() {\n document.getElementById(\"mentions-legales\").style.display = \"block\";\n}\n\nfunction closeMentionsLegales() {\n document.getElementById(\"mentions-legales\").style.display = \"none\";\n}\n\n// Modal Données personnelles\nfunction openDonneesPersonnelles() {\n document.getElementById(\"donnees-personnelles\").style.display = \"block\";\n}\n\nfunction closeDonneesPersonnelles() {\n document.getElementById(\"donnees-personnelles\").style.display = \"none\";\n}\n\n// Modal Commun\nwindow.onclick = function(event) {\n if (event.target == modalContact || event.target == modalContactSuccess || event.target == modalMentionsLegales || event.target == modalDonneesPersonnelles) {\n modalContact.style.display = \"none\";\n modalContactSuccess.style.display = \"none\";\n modalMentionsLegales.style.display = \"none\";\n modalDonneesPersonnelles.style.display = \"none\";\n }\n}","name":"MODAL-js","path":"/629f44d0440000921d1d2980","tags":[],"draft":"// Modal Contact\nfunction closeModalContact(){\n document.getElementById(\"contact\").style.display = \"none\";\n}\n\nfunction closeModalSuccess(){\n document.getElementById(\"contact-success\").style.display = \"none\";\n}\n\nfunction openContact(){\n document.getElementById(\"contact\").style.display = \"block\";\n}\n\n// Modal Mentions Légales\nfunction openMentionsLegales() {\n document.getElementById(\"mentions-legales\").style.display = \"block\";\n}\n\nfunction closeMentionsLegales() {\n document.getElementById(\"mentions-legales\").style.display = \"none\";\n}\n\n// Modal Données personnelles\nfunction openDonneesPersonnelles() {\n document.getElementById(\"donnees-personnelles\").style.display = \"block\";\n}\n\nfunction closeDonneesPersonnelles() {\n document.getElementById(\"donnees-personnelles\").style.display = \"none\";\n}\n\n// Modal Commun\nwindow.onclick = function(event) {\n if (event.target == modalContact || event.target == modalContactSuccess || event.target == modalMentionsLegales || event.target == modalDonneesPersonnelles) {\n modalContact.style.display = \"none\";\n modalContactSuccess.style.display = \"none\";\n modalMentionsLegales.style.display = \"none\";\n modalDonneesPersonnelles.style.display = \"none\";\n }\n}","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/javascript","authenticated":false,"lastPublishedDate":1705571631920}} +{"type":"cmspages","payload":{"_id":"62a8424a440000921d1d3f06","body":"
\n \n
\n
\n

Demande de contact

\n ×\n
\n

\n
\n
\n \n {{#if connected}}\n \n {{/if}}\n {{#unless connected}}\n \n {{/unless}}\n
\n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n \n \n \n
\n \n
\n
\n
\n
\n\n\n","name":"MODAL-formulaire-contact","path":null,"tags":[],"draft":"
\n \n
\n
\n

Demande de contact

\n ×\n
\n

\n
\n
\n \n {{#if connected}}\n \n {{/if}}\n {{#unless connected}}\n \n {{/unless}}\n
\n
\n \n \n
\n
\n \n \n
\n
\n \n \n
\n \n \n \n
\n \n
\n
\n
\n
\n\n\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1677069434665}} +{"type":"cmspages","payload":{"_id":"629f4106440000921d1d278f","body":"
\n \n
\n
\n

Mentions légales

\n ×\n
\n

\n
\n

MAIF - Société d’assurance mutuelle à cotisations variables – CS 90000 - 79038 Niort cedex 9.

\n
\n
\n

Entreprises régies par le Code des assurances.

\n
\n
\n
\n","name":"MODAL-mentions-legales","path":null,"tags":[],"draft":"
\n \n
\n
\n

Mentions légales

\n ×\n
\n

\n
\n

MAIF - Société d’assurance mutuelle à cotisations variables – CS 90000 - 79038 Niort cedex 9.

\n
\n
\n

Entreprises régies par le Code des assurances.

\n
\n
\n
\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1705570212987}} +{"type":"cmspages","payload":{"_id":"657076982b0000c5d7209a94","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-mobilite\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre EDPM (Engins de Déplacement Personnels Motorisés)

\n
\n
\n

\n Vos utilisateurs ont choisi de se déplacer autrement ?
Nous les accompagnons dans cette nouvelle mobilité plus respectueuse de l'environnement.​\n

\n

\n Notre offre assure tous les engins de déplacement personnels motorisés (EDPM).\n

\n
\n \"offre \n
\n
\n
\n
\n

3 raisons de choisir l’assurance MAIF

\n
\n
\n
\n
\n

1

\n

Les garanties

\n

Avec l’assurance EDPM la protection des personnes reste une priorité. Même assuré au tiers, vous bénéficiez de garanties étendues.

\n
\n
\n
\n
\n

2

\n

Assurance au tiers ou tous risques : à chacun sa formule

\n

Le meilleur contrat d’assurance EDPM est celui qui correspond à vos besoins.

\n
\n
\n
\n
\n

3

\n

L’option biens transportés accessible dans les deux formules

\n

Les effets personnels que vous emportez (smartphone, …) sont couverts en cas d’accident.

\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","name":"offre-edpm","path":"/offre-edpm","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-mobilite\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre EDPM (Engins de Déplacement Personnels Motorisés)

\n
\n
\n

\n Vos utilisateurs ont choisi de se déplacer autrement ?
Nous les accompagnons dans cette nouvelle mobilité plus respectueuse de l'environnement.​\n

\n

\n Notre offre assure tous les engins de déplacement personnels motorisés (EDPM).\n

\n
\n \"offre \n
\n
\n
\n
\n

3 raisons de choisir l’assurance MAIF

\n
\n
\n
\n
\n

1

\n

Les garanties

\n

Avec l’assurance EDPM la protection des personnes reste une priorité. Même assuré au tiers, vous bénéficiez de garanties étendues.

\n
\n
\n
\n
\n

2

\n

Assurance au tiers ou tous risques : à chacun sa formule

\n

Le meilleur contrat d’assurance EDPM est celui qui correspond à vos besoins.

\n
\n
\n
\n
\n

3

\n

L’option biens transportés accessible dans les deux formules

\n

Les effets personnels que vous emportez (smartphone, …) sont couverts en cas d’accident.

\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520582665}} +{"type":"cmspages","payload":{"_id":"623c6eac350000e106e28caa","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\"}}\n \n
\n
\n
\n
\n

Portail API MAIF

\n Le site dédié aux partenaires de distribution digitale des assurances MAIF\n
\n
\n

\n Proposez à vos utilisateurs des \n solutions d'assurance intégrées \n dans vos parcours digitaux

\n
\n
\n
\n \"image\n
\n
\n
\n{{/daikoku-template-wrapper}}","name":"home","path":"/","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\"}}\n \n
\n
\n
\n
\n

Portail API MAIF

\n Le site dédié aux partenaires de distribution digitale des assurances MAIF\n
\n
\n

\n Proposez à vos utilisateurs des \n solutions d'assurance intégrées \n dans vos parcours digitaux

\n
\n
\n
\n \"image\n
\n
\n
\n{{/daikoku-template-wrapper}}","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520690725}} +{"type":"cmspages","payload":{"_id":"6526b0273d00001b85d31feb","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n

UNIVERS MOBILITE

\n
\n
\n
\n \n
\n
\n \"icon\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","name":"univers-mobilite","path":"/univers-mobilite","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n

UNIVERS MOBILITE

\n
\n
\n
\n \n
\n
\n \"icon\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520659265}} +{"type":"cmspages","payload":{"_id":"651443f53d00001b85d2dc82","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/parcours-api\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Simulation

\n
\n
\n
\n

\n Le parcours Simulation est la solution idéale pour intégrer un premier niveau d’assurance dans votre parcours utilisateur, d’une manière intuitive et sans effort.\n

\n
\n Offres disponibles sur ce parcours\n
    \n
  • Offres AUTO, VÉLO et EDPM
  • \n
  • Offre MRH jeune
  • \n
  • Offre Travailleur Indépendant
  • \n
\n
\n
\n
\n \"parcours \n
\n
\n
\n
\n

Proposez une estimation tarifaire instantanée et personnalisée à vos utilisateurs

\n
\n
\n
\n
\n \"icon\n Intégrer la simulation à vos parcours permet de délivrer à vos utilisateurs, au regard des informations dont vous disposez, un premier niveau d'informations pour les guider dans leur choix d'assurance.\n
\n
\n
\n
\n

Votre Site / App

\n

Vous affichez à votre utilisateur une estimation tarifaire et l'invitez à réaliser un devis sur MAIF.fr

\n

SIMULATION

\n
\n
\n
\n
\n

MAIF.fr

\n

Votre utilisateur réalise son devis et sa souscription d’assurance sur MAIF.fr.

\n
\n
\n

DEVIS

\n
\n
\n

SOUSCRIPTION

\n
\n
\n
\n
\n
\n
\n

Partenaires Cibles

\n
\n
\n
\n Notre parcours Simulation est adapté pour les partenaires sans expertise assurance, mais dont les services ou produits proposés sont en lien direct ou indirect avec l’offre d’assurance.\n
\n \"parcours\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}","name":"parcours-simulation","path":"/parcours-simulation","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/parcours-api\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Simulation

\n
\n
\n
\n

\n Le parcours Simulation est la solution idéale pour intégrer un premier niveau d’assurance dans votre parcours utilisateur, d’une manière intuitive et sans effort.\n

\n
\n Offres disponibles sur ce parcours\n
    \n
  • Offres AUTO, VÉLO et EDPM
  • \n
  • Offre MRH jeune
  • \n
  • Offre Travailleur Indépendant
  • \n
\n
\n
\n
\n \"parcours \n
\n
\n
\n
\n

Proposez une estimation tarifaire instantanée et personnalisée à vos utilisateurs

\n
\n
\n
\n
\n \"icon\n Intégrer la simulation à vos parcours permet de délivrer à vos utilisateurs, au regard des informations dont vous disposez, un premier niveau d'informations pour les guider dans leur choix d'assurance.\n
\n
\n
\n
\n

Votre Site / App

\n

Vous affichez à votre utilisateur une estimation tarifaire et l'invitez à réaliser un devis sur MAIF.fr

\n

SIMULATION

\n
\n
\n
\n
\n

MAIF.fr

\n

Votre utilisateur réalise son devis et sa souscription d’assurance sur MAIF.fr.

\n
\n
\n

DEVIS

\n
\n
\n

SOUSCRIPTION

\n
\n
\n
\n
\n
\n
\n

Partenaires Cibles

\n
\n
\n
\n Notre parcours Simulation est adapté pour les partenaires sans expertise assurance, mais dont les services ou produits proposés sont en lien direct ou indirect avec l’offre d’assurance.\n
\n \"parcours\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520546449}} +{"type":"cmspages","payload":{"_id":"6579ca012a0000359b5c1e27","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n

NOS PARCOURS API

\n
\n
\n \n
\n \"icon\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","name":"parcours-api","path":"/parcours-api","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n

NOS PARCOURS API

\n
\n
\n \n
\n \"icon\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520703310}} +{"type":"cmspages","payload":{"_id":"65709cd72b0000c5d720aa98","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-vie-quotidienne-pro\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre TRAVAILLEUR INDÉPENDANT

\n
\n
\n

\n Une assurance qui couvre la responsabilité civile professionnelle (RC Pro) et les biens (PC, téléphone, locaux...) des auto-entrepreneurs, freelances, travailleurs indépendants.\n

\n
\n \"offre \n
\n
\n
\n
\n

Choisir MAIF pour que vos utilisateurs assurent leur(s) activité(s)

\n
\n
\n
\n
\n \"icon \n
\n
\n

Des garanties indispensables

\n

En plus des risques liés à l’activité de vos utilisateurs, leurs biens, leur local et même leur stock sont assurés.

\n
\n
\n
\n
\n \"icon \n
\n
\n

Le prêt d’objet couvert

\n

Tout bien que l’on prête à vos utilisateurs ou qu’ils prêtent dans le cadre de leur activité est couvert.

\n
\n
\n
\n
\n \"icon \n
\n
\n

Le + relationnel

\n

Des conseillers spécialisés pour guider vos utilisateurs dans leur activité.

\n
\n
\n
\n
\n

Les activités assurées par l’assurance MAIF

\n
\n
\n
\n

Les Activités éligibles

\n
    \n
  • Prestations intellectuelles
  • \n
  • Bien-être et Thérapie
  • \n
  • Petits Artisanats
  • \n
  • Culture, Loisirs, Art et Musique
  • \n
  • Sport
  • \n
  • Social & Services
  • \n
\n
\n
\n \"icon \n
\n
\n

Les Activités exclues

\n
    \n
  • BTP / Agriculture / Grossiste
  • \n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","name":"offre-travailleur-independant","path":"/offre-travailleur-independant","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-vie-quotidienne-pro\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre TRAVAILLEUR INDÉPENDANT

\n
\n
\n

\n Une assurance qui couvre la responsabilité civile professionnelle (RC Pro) et les biens (PC, téléphone, locaux...) des auto-entrepreneurs, freelances, travailleurs indépendants.\n

\n
\n \"offre \n
\n
\n
\n
\n

Choisir MAIF pour que vos utilisateurs assurent leur(s) activité(s)

\n
\n
\n
\n
\n \"icon \n
\n
\n

Des garanties indispensables

\n

En plus des risques liés à l’activité de vos utilisateurs, leurs biens, leur local et même leur stock sont assurés.

\n
\n
\n
\n
\n \"icon \n
\n
\n

Le prêt d’objet couvert

\n

Tout bien que l’on prête à vos utilisateurs ou qu’ils prêtent dans le cadre de leur activité est couvert.

\n
\n
\n
\n
\n \"icon \n
\n
\n

Le + relationnel

\n

Des conseillers spécialisés pour guider vos utilisateurs dans leur activité.

\n
\n
\n
\n
\n

Les activités assurées par l’assurance MAIF

\n
\n
\n
\n

Les Activités éligibles

\n
    \n
  • Prestations intellectuelles
  • \n
  • Bien-être et Thérapie
  • \n
  • Petits Artisanats
  • \n
  • Culture, Loisirs, Art et Musique
  • \n
  • Sport
  • \n
  • Social & Services
  • \n
\n
\n
\n \"icon \n
\n
\n

Les Activités exclues

\n
    \n
  • BTP / Agriculture / Grossiste
  • \n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520631296}} +{"type":"cmspages","payload":{"_id":"651443d43d00001b85d2dc7a","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/parcours-api\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Affiliation Digitale

\n
\n
\n
\n

\n Le parcours Affiliation Digitale est la solution la plus simple pour recommander les offres d’assurance MAIF auprès de vos utilisateurs.\n

\n
\n Offres disponibles sur ce parcours\n
    \n
  • Offres MOBILITÉ
  • \n
  • Offres MRH et MRH jeune
  • \n
  • Offre Travailleur Indépendant
  • \n
\n
\n
\n
\n \"parcours \n
\n
\n
\n
\n

Redirigez vos utilisateurs vers le parcours d’assurance dédié sur MAIF.fr

\n
\n
\n
\n
\n \"icon\n Un simple lien de redirection depuis votre site ou application mobile vers le parcours d’assurance dédié sur MAIF.fr.\n
\n
\n
\n
\n

Votre Site / App

\n

Votre utilisateur est informé du partenariat MAIF et invité à réaliser un devis en cliquant sur le lien tracké

\n

LIEN TRACKE

\n
\n
\n
\n
\n

MAIF.fr

\n

Votre utilisateur réalise l’intégralité de son expérience assurance sur MAIF.fr.

\n
\n
\n

DEVIS

\n
\n
\n

SOUSCRIPTION

\n
\n
\n
\n
\n
\n
\n

Partenaires Cibles

\n
\n
\n
\n Notre parcours Affiliation Digitale est adapté pour les partenaires sans expertise assurance, dont les services ou produits proposés ne sont pas en lien direct avec l’offre d’assurance.\n
\n \"parcours\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","name":"parcours-affiliation-digitale","path":"/parcours-affiliation-digitale","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/parcours-api\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Affiliation Digitale

\n
\n
\n
\n

\n Le parcours Affiliation Digitale est la solution la plus simple pour recommander les offres d’assurance MAIF auprès de vos utilisateurs.\n

\n
\n Offres disponibles sur ce parcours\n
    \n
  • Offres MOBILITÉ
  • \n
  • Offres MRH et MRH jeune
  • \n
  • Offre Travailleur Indépendant
  • \n
\n
\n
\n
\n \"parcours \n
\n
\n
\n
\n

Redirigez vos utilisateurs vers le parcours d’assurance dédié sur MAIF.fr

\n
\n
\n
\n
\n \"icon\n Un simple lien de redirection depuis votre site ou application mobile vers le parcours d’assurance dédié sur MAIF.fr.\n
\n
\n
\n
\n

Votre Site / App

\n

Votre utilisateur est informé du partenariat MAIF et invité à réaliser un devis en cliquant sur le lien tracké

\n

LIEN TRACKE

\n
\n
\n
\n
\n

MAIF.fr

\n

Votre utilisateur réalise l’intégralité de son expérience assurance sur MAIF.fr.

\n
\n
\n

DEVIS

\n
\n
\n

SOUSCRIPTION

\n
\n
\n
\n
\n
\n
\n

Partenaires Cibles

\n
\n
\n
\n Notre parcours Affiliation Digitale est adapté pour les partenaires sans expertise assurance, dont les services ou produits proposés ne sont pas en lien direct avec l’offre d’assurance.\n
\n \"parcours\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520418595}} +{"type":"cmspages","payload":{"_id":"659ec57d2a0000359b5ca6fc","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-vie-quotidienne-pro\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre HABITATION

\n
\n
\n

\n Propriétaire, locataire : le contrat d'Assurance Habitation MAIF protège votre logement et vos biens avec de solides garanties en cas de sinistre.\n

\n
\n \"offre \n
\n
\n
\n
\n

Bien plus qu’une assurance habitation

\n
\n
\n
\n
\n \"icon \n
\n
\n

Intempéries

\n

En cas d’inondation, même sans arrêté de catastrophes naturelles, vous êtes indemnisé.

\n \"offre \n
\n
\n
\n
\n \"icon\n
\n
\n

Assurance Scolaire

\n

Incluse pour chaque enfant sans surcoût.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Vélo à assistance électrique

\n

Assuré en formule 3 contre le vol et le bris en tous lieux (en option en formule 2).

\n
\n
\n
\n
\n

Choisir MAIF pour assurer votre mode de vie responsable

\n
\n
\n
\n
\n \"icon\n
\n
\n

Réparer plutôt que jeter

\n

Pour lutter contre le gaspillage, MAIF inclut une garantie panne pour vos appareils en formule 3.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Vos équipements écologiques sont couverts

\n

Sans supplément dès la formule de base.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Les biens que vous prêtez sont assurés

\n

Comme s’ils étaient chez vous.

\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","name":"offre-mrh","path":"/offre-mrh","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-vie-quotidienne-pro\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre HABITATION

\n
\n
\n

\n Propriétaire, locataire : le contrat d'Assurance Habitation MAIF protège votre logement et vos biens avec de solides garanties en cas de sinistre.\n

\n
\n \"offre \n
\n
\n
\n
\n

Bien plus qu’une assurance habitation

\n
\n
\n
\n
\n \"icon \n
\n
\n

Intempéries

\n

En cas d’inondation, même sans arrêté de catastrophes naturelles, vous êtes indemnisé.

\n \"offre \n
\n
\n
\n
\n \"icon\n
\n
\n

Assurance Scolaire

\n

Incluse pour chaque enfant sans surcoût.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Vélo à assistance électrique

\n

Assuré en formule 3 contre le vol et le bris en tous lieux (en option en formule 2).

\n
\n
\n
\n
\n

Choisir MAIF pour assurer votre mode de vie responsable

\n
\n
\n
\n
\n \"icon\n
\n
\n

Réparer plutôt que jeter

\n

Pour lutter contre le gaspillage, MAIF inclut une garantie panne pour vos appareils en formule 3.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Vos équipements écologiques sont couverts

\n

Sans supplément dès la formule de base.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Les biens que vous prêtez sont assurés

\n

Comme s’ils étaient chez vous.

\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520506557}} +{"type":"cmspages","payload":{"_id":"629f15aa440000921d1d2372","body":"
\n \n
\n
\n

Données personnnelles

\n ×\n
\n

Politique de confidentialité du Portail API MAIF

\n
\n

Données personnelles :

\n

Par donnée à caractère personnel, il y a lieu d’entendre toutes les informations qui permettent de vous identifier directement ou indirectement (croisement d’un ensemble de données) telles que noms, prénoms, adresse postale et de courrier électronique, numéro de téléphone… Lors de votre accès au portail de l’API MAIF, nous sommes amenés à collecter des données à caractère personnel, notamment dans le cadre de la création de votre compte ou lorsque vous nous contacter.

\n
\n\n
\n

Qui sont les responsables de traitements ?

\n

MAIF Société d’Assurance Mutuelle à cotisations variables, entreprise régie par le code des assurances. Adresse postale : 200 avenue Salvador Allende - CS 90000 - 79038 NIORT cedex 9.

\n
\n
\n

Comment utilisons-nous vos données ?

\n

Vos données personnelles sont utilisées dans le cadre de notre relation pour répondre à plusieurs finalités pour l’utilisation du portail :

\n
    \n
  • La création de votre compte utilisateur et votre authentification
  • \n
  • La prise en charge de vos demandes via le formulaire de contact
  • \n
\n
\n
\n

Qui sont les Destinataires des données ?

\n

Les données que nous collectons vous concernant, sont destinées aux entreprises du groupe MAIF et à nos prestataires de service, agissant en qualité de sous-traitants lorsqu’ils concourent à la réalisation des finalités pour lesquelles vos données sont collectées. Pour assurer la confidentialité de vos données personnelles, nous nous prévoyons dans les contrats des obligations de confidentialité à la charge des sous-traitants.

\n
\n
\n

Quels sont vos droits ?

\n

Le droit d’accès et de rectification : Vous pouvez demander l’accès et la rectification des données inexactes vous concernant dont nous disposons sur vous.

\n

Le droit à la portabilité : Vous pouvez demander à récupérer les données que vous avez fourni dans un format informatique pour un usage personnel ou pour les transmettre à un tiers de votre choix.

\n

Le droit à l’oubli : Il s’agit du droit d'obtenir la suppression des informations vous concernant détenues par la MAIF dans la mesure où elles ne sont plus nécessaires un contrat ou un service.

\n

Le droit d'opposition : Vous pouvez demander l’opposition au traitement de vos données. Pour se faire, vous devez exposer des raisons tenant à votre situation particulière.

\n

Le droit de limitation du traitement : Vous pouvez nous demander la limitation de certaines de vos données lorsqu’elles ne sont plus nécessaires ou ne sont plus nécessaires dans notre relation contractuelle.

\n

Le droit de définir le sort de vos données post mortem : Vous pouvez définir des directives relatives à la conservation, à l'effacement et à la communication de vos données après votre décès.

\n
\n
\n

Combien de temps conservons-nous vos données et quelle est leur localisation ?

\n

La durée de conservation de vos données personnelles ne dépassera pas 6 mois après la suppression de votre compte. Vos données sont traitées et conservées sur le territoire de l’Union Européenne. Toutefois, dans de très rares hypothèses, elles peuvent faire l’objet d’un transfert hors de ce territoire. Lorsque c’est le cas, vous bénéficiez d’une information spécifique lors de la collecte et nous mettons en œuvre des mesures pour assurer que la législation européenne est respectée afin de vous apporter une sécurité plus importante.

\n
\n
\n

Vos contacts au sein du groupe MAIF :

\n

Pour exercer vos droits, vous pouvez contacter directement : claire.du.mesnildot@maif.fr MAIF a désigné un délégué à la protection des données personnelles dont la mission est de vous aider pour exercer vos droits. Vous pouvez le contacter :

\n
    \n
  • Par courrier postal en écrivant à : Délégué à la protection des données, 200 avenue Salvador Allende - CS 90000 - 79038 NIORT cedex 9
  • \n
  • Par courrier électronique en écrivant à l’adresse de courriel : vosdonnees@maif.fr
  • \n
\n
\n
\n
","name":"MODAL-donnees-personnelles","path":null,"tags":[],"draft":"
\n \n
\n
\n

Données personnnelles

\n ×\n
\n

Politique de confidentialité du Portail API MAIF

\n
\n

Données personnelles :

\n

Par donnée à caractère personnel, il y a lieu d’entendre toutes les informations qui permettent de vous identifier directement ou indirectement (croisement d’un ensemble de données) telles que noms, prénoms, adresse postale et de courrier électronique, numéro de téléphone… Lors de votre accès au portail de l’API MAIF, nous sommes amenés à collecter des données à caractère personnel, notamment dans le cadre de la création de votre compte ou lorsque vous nous contacter.

\n
\n\n
\n

Qui sont les responsables de traitements ?

\n

MAIF Société d’Assurance Mutuelle à cotisations variables, entreprise régie par le code des assurances. Adresse postale : 200 avenue Salvador Allende - CS 90000 - 79038 NIORT cedex 9.

\n
\n
\n

Comment utilisons-nous vos données ?

\n

Vos données personnelles sont utilisées dans le cadre de notre relation pour répondre à plusieurs finalités pour l’utilisation du portail :

\n
    \n
  • La création de votre compte utilisateur et votre authentification
  • \n
  • La prise en charge de vos demandes via le formulaire de contact
  • \n
\n
\n
\n

Qui sont les Destinataires des données ?

\n

Les données que nous collectons vous concernant, sont destinées aux entreprises du groupe MAIF et à nos prestataires de service, agissant en qualité de sous-traitants lorsqu’ils concourent à la réalisation des finalités pour lesquelles vos données sont collectées. Pour assurer la confidentialité de vos données personnelles, nous nous prévoyons dans les contrats des obligations de confidentialité à la charge des sous-traitants.

\n
\n
\n

Quels sont vos droits ?

\n

Le droit d’accès et de rectification : Vous pouvez demander l’accès et la rectification des données inexactes vous concernant dont nous disposons sur vous.

\n

Le droit à la portabilité : Vous pouvez demander à récupérer les données que vous avez fourni dans un format informatique pour un usage personnel ou pour les transmettre à un tiers de votre choix.

\n

Le droit à l’oubli : Il s’agit du droit d'obtenir la suppression des informations vous concernant détenues par la MAIF dans la mesure où elles ne sont plus nécessaires un contrat ou un service.

\n

Le droit d'opposition : Vous pouvez demander l’opposition au traitement de vos données. Pour se faire, vous devez exposer des raisons tenant à votre situation particulière.

\n

Le droit de limitation du traitement : Vous pouvez nous demander la limitation de certaines de vos données lorsqu’elles ne sont plus nécessaires ou ne sont plus nécessaires dans notre relation contractuelle.

\n

Le droit de définir le sort de vos données post mortem : Vous pouvez définir des directives relatives à la conservation, à l'effacement et à la communication de vos données après votre décès.

\n
\n
\n

Combien de temps conservons-nous vos données et quelle est leur localisation ?

\n

La durée de conservation de vos données personnelles ne dépassera pas 6 mois après la suppression de votre compte. Vos données sont traitées et conservées sur le territoire de l’Union Européenne. Toutefois, dans de très rares hypothèses, elles peuvent faire l’objet d’un transfert hors de ce territoire. Lorsque c’est le cas, vous bénéficiez d’une information spécifique lors de la collecte et nous mettons en œuvre des mesures pour assurer que la législation européenne est respectée afin de vous apporter une sécurité plus importante.

\n
\n
\n

Vos contacts au sein du groupe MAIF :

\n

Pour exercer vos droits, vous pouvez contacter directement : claire.du.mesnildot@maif.fr MAIF a désigné un délégué à la protection des données personnelles dont la mission est de vous aider pour exercer vos droits. Vous pouvez le contacter :

\n
    \n
  • Par courrier postal en écrivant à : Délégué à la protection des données, 200 avenue Salvador Allende - CS 90000 - 79038 NIORT cedex 9
  • \n
  • Par courrier électronique en écrivant à l’adresse de courriel : vosdonnees@maif.fr
  • \n
\n
\n
\n
","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1705571459321}} +{"type":"cmspages","payload":{"_id":"659e65a42a0000359b5c58bc","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-mobilite\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n
\n \"icon \n \"icon \n
\n

Offre AUTO & Camping-Cars

\n
\n
\n

\n Au tiers ou tous risques, vos utilisateurs peuvent compter sur les garanties de notre assurance auto/camping-cars VAM.\n

\n

\n Formules, options, franchises : vos utilisateurs ont la possibilité d’ajuster selon leurs besoins et de maîtriser le prix de leur assurance.\n

\n
\n
\n
\n

Des garanties et un service client qui font la différence

\n
\n
\n
\n
\n \"icon \n
\n
\n

Assistance sur la route 24h/24 et 7j/7

\n

Une Panne ? Vos utilisateurs sont dépannés dès la formule au tiers pour leurs trajets à plus de 50 km du domicile. Et même devant chez eux avec la formule Plénitude ou en option.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Remboursements

\n

Le véhicule de vos utilisateurs irréparable ou volé est indemnisé à valeur d’achat jusqu’à ses 4 ans, avec la formule Plénitude.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Intempéries… tous assurés !

\n

Dès la formule au tiers, le véhicule de vos utilisateurs est pris en charge en cas de grêle, tempête, inondation.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Service Client

\n

Trouver des solutions, indemniser, c’est notre métier. Depuis 19 ans, MAIF est numéro 1 de la Relation Client1.

\n
\n
\n
\n
\n

En cas de sinistre, comment ça se passe ?

\n
\n
\n
\n
\n \"icon\n
\n
\n

Assistance et suivi de la dépanneuse

\n

Vos utilisateurs peuvent contacter l’assistance MAIF 24h/24, 7j/7 puis suivre le trajet de la dépanneuse sur leur smartphone avec l’application MAIF.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Constat et déclaration en ligne

\n

Un accident ? Vos utilisateurs peuvent réaliser un (e)constat et déclarer leur sinistre en ligne, sans attendre.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Réparer sans gaspiller

\n

Depuis 2013, MAIF propose des réparations avec pièces recyclées, avec l’accord de vos utilisateurs. La réparation est garantie à vie2.

\n
\n
\n
\n
\n
\n

1.

\n

N°1 du Podium de la Relation Client. Enquête réalisée par BearingPoint et Kantar fin 2022 auprès de 4000 personnes.

\n
\n
\n

2.

\n

Dans la limite du vieillissement des pièces dû au soleil, à l’usure normale, aux intempéries ou au défaut d’entretien.

\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","name":"offre-auto-cc","path":"/offre-auto-cc","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-mobilite\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n
\n \"icon \n \"icon \n
\n

Offre AUTO & Camping-Cars

\n
\n
\n

\n Au tiers ou tous risques, vos utilisateurs peuvent compter sur les garanties de notre assurance auto/camping-cars VAM.\n

\n

\n Formules, options, franchises : vos utilisateurs ont la possibilité d’ajuster selon leurs besoins et de maîtriser le prix de leur assurance.\n

\n
\n
\n
\n

Des garanties et un service client qui font la différence

\n
\n
\n
\n
\n \"icon \n
\n
\n

Assistance sur la route 24h/24 et 7j/7

\n

Une Panne ? Vos utilisateurs sont dépannés dès la formule au tiers pour leurs trajets à plus de 50 km du domicile. Et même devant chez eux avec la formule Plénitude ou en option.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Remboursements

\n

Le véhicule de vos utilisateurs irréparable ou volé est indemnisé à valeur d’achat jusqu’à ses 4 ans, avec la formule Plénitude.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Intempéries… tous assurés !

\n

Dès la formule au tiers, le véhicule de vos utilisateurs est pris en charge en cas de grêle, tempête, inondation.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Service Client

\n

Trouver des solutions, indemniser, c’est notre métier. Depuis 19 ans, MAIF est numéro 1 de la Relation Client1.

\n
\n
\n
\n
\n

En cas de sinistre, comment ça se passe ?

\n
\n
\n
\n
\n \"icon\n
\n
\n

Assistance et suivi de la dépanneuse

\n

Vos utilisateurs peuvent contacter l’assistance MAIF 24h/24, 7j/7 puis suivre le trajet de la dépanneuse sur leur smartphone avec l’application MAIF.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Constat et déclaration en ligne

\n

Un accident ? Vos utilisateurs peuvent réaliser un (e)constat et déclarer leur sinistre en ligne, sans attendre.

\n
\n
\n
\n
\n \"icon\n
\n
\n

Réparer sans gaspiller

\n

Depuis 2013, MAIF propose des réparations avec pièces recyclées, avec l’accord de vos utilisateurs. La réparation est garantie à vie2.

\n
\n
\n
\n
\n
\n

1.

\n

N°1 du Podium de la Relation Client. Enquête réalisée par BearingPoint et Kantar fin 2022 auprès de 4000 personnes.

\n
\n
\n

2.

\n

Dans la limite du vieillissement des pièces dû au soleil, à l’usure normale, aux intempéries ou au défaut d’entretien.

\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520722284}} +{"type":"cmspages","payload":{"_id":"659e60a72a0000359b5c5705","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/parcours-api\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Souscription

\n
\n
\n

\n Parcours en développement par les équipes MAIF.\n

\n
\n \"parcours \n
\n
\n
\n
\n

Partenaires Cibles

\n
\n
\n
\n Notre parcours Souscription est adapté aux partenaires avec un besoin d’intégration complet de l’assurance dans le parcours d’achat du bien ou du service proposé à leurs utilisateurs utilisateur.\n
\n \"parcours\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}","name":"parcours-souscription","path":"/parcours-souscription","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/parcours-api\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Souscription

\n
\n
\n

\n Parcours en développement par les équipes MAIF.\n

\n
\n \"parcours \n
\n
\n
\n
\n

Partenaires Cibles

\n
\n
\n
\n Notre parcours Souscription est adapté aux partenaires avec un besoin d’intégration complet de l’assurance dans le parcours d’achat du bien ou du service proposé à leurs utilisateurs utilisateur.\n
\n \"parcours\n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520773400}} +{"type":"cmspages","payload":{"_id":"654b545f2100008ec5dca38d","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-mobilite\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre CORPORELLE (PACS)

\n
\n
\n

\n Vos utilisateurs sont des utilisateurs de solutions de mobilité douce ou partagée ?\n

\n

\n En cas d'accident, l’assurance corporelle PACS garantit une indemnisation de vos utilisateurs dans leurs mobilités à l’usage mais aussi en covoiturage et transports en commun.\n

\n
\n \"offre \n
\n
\n
\n
\n \"offre \n

Quels trajets sont couverts par notre assurance PACS ?

\n
\n
\n
\n \"offre\n
    \n
  • En train, métro, tram, bus, car
  • \n
  • En covoiturage, VTC, taxi
  • \n
  • Dans ma voiture de fonction, une voiture empruntée à un tiers
  • \n
  • Dans une location courte durée, libre-service : vélo …
  • \n
\n
\n
\n

Le contrat PACS couvrira vos trajets en véhicule de fonction, covoiturage, transports en commun, location en libre-service…

\n

Vous bénéficiez d’une prise en charge immédiate et d’un accompagnement dans vos démarches vis-à-vis des opérateurs de mobilité (vélo libre-service, train, tram, métro…)

\n
\n
\n
\n
\n

… mais également dans leurs véhicules assurés MAIF

\n
    \n
  • En voiture, en Camping-cars
  • \n
  • En moto, en scooter, en cyclomoteur
  • \n
  • En trottinette électrique
  • \n
  • En vélo 100% électrique (vitesse allant jusqu’à 45km/h)
  • \n
\n
\n
\n \"offre \n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","name":"offre-corporelle","path":"/offre-corporelle","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-mobilite\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre CORPORELLE (PACS)

\n
\n
\n

\n Vos utilisateurs sont des utilisateurs de solutions de mobilité douce ou partagée ?\n

\n

\n En cas d'accident, l’assurance corporelle PACS garantit une indemnisation de vos utilisateurs dans leurs mobilités à l’usage mais aussi en covoiturage et transports en commun.\n

\n
\n \"offre \n
\n
\n
\n
\n \"offre \n

Quels trajets sont couverts par notre assurance PACS ?

\n
\n
\n
\n \"offre\n
    \n
  • En train, métro, tram, bus, car
  • \n
  • En covoiturage, VTC, taxi
  • \n
  • Dans ma voiture de fonction, une voiture empruntée à un tiers
  • \n
  • Dans une location courte durée, libre-service : vélo …
  • \n
\n
\n
\n

Le contrat PACS couvrira vos trajets en véhicule de fonction, covoiturage, transports en commun, location en libre-service…

\n

Vous bénéficiez d’une prise en charge immédiate et d’un accompagnement dans vos démarches vis-à-vis des opérateurs de mobilité (vélo libre-service, train, tram, métro…)

\n
\n
\n
\n
\n

… mais également dans leurs véhicules assurés MAIF

\n
    \n
  • En voiture, en Camping-cars
  • \n
  • En moto, en scooter, en cyclomoteur
  • \n
  • En trottinette électrique
  • \n
  • En vélo 100% électrique (vitesse allant jusqu’à 45km/h)
  • \n
\n
\n
\n \"offre \n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520736622}} +{"type":"cmspages","payload":{"_id":"62457958430000127e85c5cf","body":"*,\n*::after,\n*::before {\n margin: 0;\n padding: 0;\n box-sizing: inherit;\n}\n\nul{\n list-style-type: none;\n}\n\n.maif-color-text{\n color:#fc0d1a;\n}\n\nheader {\n text-align: center;\n background-color: #F2F2F2\n}\n\nheader a {\n color: black;\n text-decoration: none;\n}\n\nheader a:hover{\n color: black;\n text-decoration: none;\n font-weight: 600;\n}\n\n.header-left img {\n height: auto;\n max-width: 59px;\n}\n\n.header-right .non-connecte a, header .header-right .non-connecte button {\n color: #2e404f;\n cursor: pointer;\n padding: 6px 12px;\n align-items: center;\n text-align: center;\n font-size: 1.5rem;\n}\n\n.header-right .non-connecte a .icon, header .header-right .non-connecte button .icon {\n width: 38px;\n border-radius: 50%;\n}\n\n.header-right img{\n max-width: 30px;\n border-radius: 50%;\n}\n\nheader-left {\n text-align: left;\n}\n\n.header-right {\n display: flex;\n justify-content: start\n}\n\n.header-right .buttons {\n display: flex;\n flex-direction: column;\n}\n\n.header-right .buttons a {\n justify-content: start;\n display: flex;\n padding: 0;\n color: #212529;\n}\n\n.container-burger-menu {\n justify-content: right;\n margin-top: 0.5em;\n}\n\n.sidenav {\n height: 100%;\n width: 290px;\n position: fixed;\n z-index: 1;\n top: 0;\n right: -290px;\n background-color: #e8e8e8;\n padding-top: 60px;\n transition: right 0.5s ease;\n}\n\n/* Sidenav menu links */\n.sidenav a {\n padding: 8px 8px 8px 32px;\n text-decoration: none;\n font-size: 25px;\n color: #818181;\n display: block;\n transition: 0.3s;\n}\n\n.sidenav a:hover {\n color: #111;\n}\n\n.sidenav ul {\n text-align: left;\n list-style-type: none;\n padding: 0;\n margin: 0;\n}\n\n/* Active class */\n.sidenav.active {\n right: 0;\n}\n\n/* Close btn */\n.sidenav .close {\n position: absolute;\n top: 0;\n right: 25px;\n font-size: 36px;\n}\n\n/* Icône burger */\n.burger-icon span {\n display: block;\n width: 35px;\n height: 5px;\n background-color: black;\n margin: 6px 0;\n}\n\nbody {\n font-family: Custom, Raleway, Helvetica, sans-serif;\n margin: 0px;\n min-height: 100vh;\n display: flex;\n flex-direction: column\n}\n\n.offre-title {\n display: flex;\n flex-direction: row;\n align-items: center;\n border-bottom: solid #fc0d1a 5px;\n}\n\n.offre-title-double {\n display: flex;\n flex-direction: column;\n}\n\n.offre-title-double img:not(:last-child) {\n margin-bottom: -20px;\n}\n\n.offre-title-double img {\n padding-bottom: 0 !important;\n}\n\n.offre-title img {\n padding-bottom: 0.7em;\n padding-right: 3em;\n}\n\n.offre-title-detail {\n font-size: 12px;\n}\n\n.offre-title-2 {\n font-size: 18px;\n}\n\n.offre-text {\n font-size: 20px;\n}\n\n.offre-bandeau {\n display: flex;\n flex-direction: row;\n justify-content: center;\n align-items: center;\n background-color: #FBEBE9;\n padding: 10px;\n min-height: 120px;\n text-align: center;\n}\n\n.offre-bandeau img {\n padding-right: 5em;\n}\n\n.offre-bandeau-reverse img {\n -webkit-transform: scaleX(-1);\n padding-left: 5em;\n}\n\n.offre-description-text {\n display: flex;\n flex-direction: column;\n}\n\n.offre-description-text-margin {\n margin-top: 3em;\n}\n\n.offre-img-padding {\n align-items: end;\n}\n\n.offre-img-padding img{\n width: -webkit-fill-available;\n}\n\n.offre-ul {\n > li {\n font-style: italic;\n }\n}\n\n.offre-li-detail {\n font-size: 10px;\n}\n\n.img-reduction img {\n width: 70%;\n height: 80%;\n}\n\n.container {\n flex: 1; \n}\n\n.home-section .home-section-header {\n display: flex;\n width: 100%;\n flex-direction: column;\n}\n\n.home-section-body > div {\n margin-bottom: 3rem;\n}\n\n.img-inherit img {\n width: inherit;\n}\n\n.home-img img {\n width: 485px; \n}\n\n.home-section-side { \n padding-top: 4em;\n}\n\n.home-section-side img {\n margin-top: 1em;\n max-width: 130px;\n}\n\n.icon-menu {\n border: solid 1px grey;\n border-radius: 0.75em;\n margin-bottom: 1em;\n align-items: center;\n display: flex;\n flex-direction: column;\n width: 140px;\n height: 140px;\n text-align: center;\n}\n\n.icon-menu-disabled {\n filter: grayscale(1);\n}\n\n.icon-menu img {\n height: 80px;\n width: 80px;\n}\n\n.icon-menu a {\n color: black;\n text-decoration: none\n}\n\n.icon-menu a:hover{\n color: black;\n text-decoration: none;\n font-weight: 600;\n}\n\n.icon-menu-text {\n margin-bottom: 0 !important\n}\n\n.fa-check:before {\n content: \"\\f00c\";\n font-family: 'FontAwesome';\n color: #fc0d1a;\n padding-right: 0.3em;\n}\n\n.fa-close:before {\n content: \"\\f00c\";\n font-family: 'FontAwesome';\n color: #fc0d1a;\n padding-right: 0.3em;\n}\n\n.parcours-span {\n text-align: center;\n img {\n max-width:130px\n }\n margin-bottom: 1.5em;\n}\n\n.parcours-span-block {\n align-items: center;\n}\n\n.offre-parcours {\n padding-top: 3em;\n}\n\n.offre-parcours-li:before {\n content: '- '\n}\n\n.schema-bloc {\n width:50%;\n}\n\n.schema-marge {\n margin-right: 0.5em;\n width: auto !important\n}\n\n.schema-title {\n border: 1px solid;\n height: 30px;\n align-items: center;\n display: flex;\n justify-content: center;\n border-radius: 5px;\n width: 100%;\n}\n\n.schema-text {\n background-color: #F2F2F2;\n justify-content: center;\n display: flex;\n align-items: center;\n padding: 0.5em 0.25em 0.5em 0.25em;\n border-radius: 5px;\n font-size: 12px;\n height: 70px;\n align-items: center;\n text-align: center;\n margin-bottom: 0.25em;\n width: 100%;\n}\n\n.schema-text-title {\n font-size: 18px;\n color: #fc0d1a;\n font-weight: bold;\n}\n\n.axterisque-block {\n margin-bottom: 0 !important;\n}\n\n.axterisque {\n display: flex;\n flex-direction: row;\n font-size: 10px;\n}\n\n.axterisque p {\n margin-bottom: 0 !important\n}\n\n.axterisque p:not(:last-child) {\n padding-right: 3em;\n}\n\n.contactez-nous {\n display: flex;\n flex-direction: row;\n align-items: center;\n justify-content: right;\n margin-right: 2em;\n}\n\n.contactez-nous button {\n display: flex;\n flex-direction: row;\n align-items: center;\n cursor: pointer;\n border: 0;\n font-family: inherit;\n margin: 0;\n text-decoration: none;\n background-color: transparent;\n}\n\n.contactez-nous button:focus {\n outline: none;\n}\n\n.contactez-nous button > p {\n font-size: 12px;\n font-weight: bold;\n padding-top: 1.1em;\n}\n\nfooter .navbar {\n display: flex;\n justify-content: space-between;\n/* min-height: 60px; */\n font-size: 14px;\n background-image: linear-gradient(to left, #684a52, #595667);\n color: #fff;\n}\n\nfooter .navbar .liens-utiles {\n padding: 0 0 0 1.5rem;\n}\n\nfooter .navbar .liens-utiles button, footer .navbar .liens-utiles a {\n cursor: pointer;\n color: #fff;\n background-color: transparent;\n border: 0;\n font-family: inherit;\n margin: 0;\n text-decoration: none;\n}\n\nfooter .navbar .liens-utiles button:after, footer .navbar .liens-utiles a:after {\n content: \"|\";\n margin: 0 0.5rem;\n}\n\nfooter .reseaux-sociaux {\n padding: 0 0 0 1.5rem;\n}\n\nfooter .reseaux-sociaux button, footer .reseaux-sociaux a {\n cursor: pointer;\n color: #fff;\n background-color: transparent;\n border: 0;\n font-family: inherit;\n margin: 0;\n}\n\n@media screen and (min-width: 250px) {\n .offre-block-img {\n width: 70px;\n } \n .offre-block-img img {\n width: 69px;\n height: 69px;\n } \n .offre-block-text {\n min-width: 180px;\n }\n}\n\n@media screen and (max-width: 250px) {\n .offre-block-img {\n display: none;\n }\n .offre-block-text {\n width: inherit;\n } \n}\n\n@media screen and (min-width: 298px) {\n .icon-menu {\n margin-right: 1em;\n }\n}\n\n@media screen and (max-width: 330px) {\n .menu-title {\n text-align: center;\n }\n}\n\n@media screen and (max-width: 400px) {\n .offre-title img {\n display: none;\n }\n .offre-bandeau img {\n display: none;\n }\n .schema-text-title {\n font-size: 12px !important;\n }\n .schema-text {\n font-size: 12px !important;\n }\n}\n\n@media screen and (min-width: 768px) {\n .fa-arrow-right:before {\n content: \"\\f061c\";\n font-family: 'FontAwesome';\n color: #fc0d1a;\n font-size:2.5em;\n margin-right: -15px;\n }\n .fa-arrow-down:before {\n display:none\n }\n .arrow {\n margin-left: -30px;\n height: auto;\n display: flex;\n align-items: end;\n } \n .border-dotted {\n border-right: dotted 3px red;\n }\n .schema {\n margin-right: 1em !important;\n }\n}\n@media screen and (max-width: 768px) {\n .fa-arrow-right:before {\n display:none\n }\n .fa-arrow-down:before {\n content: \"\\f061c\";\n font-family: 'FontAwesome';\n color: #fc0d1a;\n font-size:2.5em;\n }\n}\n@media screen and (min-width: 992px) {\n .home-section .home-section-left span {\n text-wrap: nowrap;\n z-index: 1;\n }\n .home-section .home-section-left {\n margin-top: 5em;\n }\n .home-section .home-section-left .en-tete {\n margin-top: 3em;\n text-align: end;\n }\n .en-tete {\n font-size: 23px;\n line-height: 2rem;\n }\n .offre-parcours-img {\n display: inline-flex!important;\n align-items: end;\n }\n}\n\n\n@media screen and (max-width: 992px) {\n .offre-parcours-img {\n display: none;\n }\n}\n\n.modal {\n display: none;\n /* Hidden by default */\n position: fixed;\n z-index: 9999;\n padding-top: 20px;\n left: 0;\n top: 0;\n width: 100%;\n height: 100%;\n overflow: auto;\n background-color: rgb(0, 0, 0);\n /* Fallback color */\n background-color: rgba(0, 0, 0, 0.4);\n /* Black w/ opacity */\n}\n.modal .modal-content {\n background-color: #fefefe;\n margin: auto;\n padding: 20px;\n max-width: 800px;\n border: 1px solid #888;\n border-radius: 5px;\n width: 80%;\n}\n\n.modal-content-scroll {\n overflow-y: scroll;\n height: 100%;\n}\n\n.modal .modal-content .modal-header {\n border-bottom: 1px solid #dee2e6;\n display: flex;\n justify-content: space-between;\n padding: 1rem;\n}\n.modal .modal-content .modal-header span {\n cursor: pointer;\n}\n.modal .modal-content .modal-footer {\n border-top: 1px solid #dee2e6;\n display: flex;\n justify-content: flex-end;\n padding: 1rem 1rem 0rem 1rem;\n}\n.modal .modal-content .close {\n color: #aaaaaa;\n float: right;\n font-size: 28px;\n font-weight: bold;\n}\n.modal .modal-content .close:hover,\n.modal .modal-content .close:focus {\n color: #000;\n text-decoration: none;\n cursor: pointer;\n}\n.modal .modal-content h1 {\n line-height: 1.5;\n margin-top: 0;\n margin-bottom: 0;\n font-size: 1.25rem;\n color: rgba(0, 0, 0, 0.85);\n font-weight: 500;\n}\n.modal .modal-content h2 {\n line-height: 1.2;\n margin-top: 0.5rem;\n margin-bottom: 0.5rem;\n font-size: 1.5rem;\n font-weight: 500;\n}\n.modal .modal-content h3 {\n color: #c62222;\n line-height: 1.2;\n margin-top: 0;\n margin-bottom: 0.5rem;\n font-size: 1.25rem;\n font-weight: 500;\n}\n.modal .modal-content p {\n margin-bottom: 1rem;\n text-align: justify;\n}\n.modal .modal-content ul {\n margin-bottom: 1em;\n margin-top: 0;\n}\n.modal .modal-content ul li {\n list-style-type: none;\n padding-left: 2rem;\n}\n.modal .modal-content form {\n margin: 1rem 0;\n font-weight: 500;\n}\n.modal .modal-content form .form-group {\n margin-bottom: 1rem;\n padding-bottom: 10px;\n}\n.modal .modal-content form .form-group label {\n display: inline-block;\n margin-bottom: 0.5rem;\n}\n.modal .modal-content form .form-group .form-control {\n background-clip: padding-box;\n background-color: #fff;\n border: 1px solid #ced4da;\n border-radius: 0.25rem;\n color: #495057;\n display: block;\n font-size: 1rem;\n font-weight: 400;\n height: calc(1.5em + 0.75rem + 2px);\n line-height: 1.5;\n padding: 0.375rem 2%;\n transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n width: 96%;\n}\n.modal .modal-content form .form-group select.form-control[multiple], .modal .modal-content form .form-group select.form-control[size], .modal .modal-content form .form-group textarea.form-control {\n height: auto;\n}\n.modal .modal-content form button {\n background-color: transparent;\n border: 1px solid transparent;\n border-radius: 0.25rem;\n color: #212529;\n display: inline-block;\n font-size: 1rem;\n font-weight: 400;\n line-height: 1.5;\n padding: 0.375rem 0.75rem;\n text-align: center;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n vertical-align: middle;\n border-color: #28a745;\n color: #28a745;\n}\n.modal .modal-content form textarea {\n font-family: inherit;\n} */","name":"IAAS-BASIC-css","path":"/62457958430000127e85c5d0","tags":[],"draft":"*,\n*::after,\n*::before {\n margin: 0;\n padding: 0;\n box-sizing: inherit;\n}\n\nul{\n list-style-type: none;\n}\n\n.maif-color-text{\n color:#fc0d1a;\n}\n\nheader {\n text-align: center;\n background-color: #F2F2F2\n}\n\nheader a {\n color: black;\n text-decoration: none;\n}\n\nheader a:hover{\n color: black;\n text-decoration: none;\n font-weight: 600;\n}\n\n.header-left img {\n height: auto;\n max-width: 59px;\n}\n\n.header-right .non-connecte a, header .header-right .non-connecte button {\n color: #2e404f;\n cursor: pointer;\n padding: 6px 12px;\n align-items: center;\n text-align: center;\n font-size: 1.5rem;\n}\n\n.header-right .non-connecte a .icon, header .header-right .non-connecte button .icon {\n width: 38px;\n border-radius: 50%;\n}\n\n.header-right img{\n max-width: 30px;\n border-radius: 50%;\n}\n\nheader-left {\n text-align: left;\n}\n\n.header-right {\n display: flex;\n justify-content: start\n}\n\n.header-right .buttons {\n display: flex;\n flex-direction: column;\n}\n\n.header-right .buttons a {\n justify-content: start;\n display: flex;\n padding: 0;\n color: #212529;\n}\n\n.container-burger-menu {\n justify-content: right;\n margin-top: 0.5em;\n}\n\n.sidenav {\n height: 100%;\n width: 290px;\n position: fixed;\n z-index: 1;\n top: 0;\n right: -290px;\n background-color: #e8e8e8;\n padding-top: 60px;\n transition: right 0.5s ease;\n}\n\n/* Sidenav menu links */\n.sidenav a {\n padding: 8px 8px 8px 32px;\n text-decoration: none;\n font-size: 25px;\n color: #818181;\n display: block;\n transition: 0.3s;\n}\n\n.sidenav a:hover {\n color: #111;\n}\n\n.sidenav ul {\n text-align: left;\n list-style-type: none;\n padding: 0;\n margin: 0;\n}\n\n/* Active class */\n.sidenav.active {\n right: 0;\n}\n\n/* Close btn */\n.sidenav .close {\n position: absolute;\n top: 0;\n right: 25px;\n font-size: 36px;\n}\n\n/* Icône burger */\n.burger-icon span {\n display: block;\n width: 35px;\n height: 5px;\n background-color: black;\n margin: 6px 0;\n}\n\nbody {\n font-family: Custom, Raleway, Helvetica, sans-serif;\n margin: 0px;\n min-height: 100vh;\n display: flex;\n flex-direction: column\n}\n\n.offre-title {\n display: flex;\n flex-direction: row;\n align-items: center;\n border-bottom: solid #fc0d1a 5px;\n}\n\n.offre-title-double {\n display: flex;\n flex-direction: column;\n}\n\n.offre-title-double img:not(:last-child) {\n margin-bottom: -20px;\n}\n\n.offre-title-double img {\n padding-bottom: 0 !important;\n}\n\n.offre-title img {\n padding-bottom: 0.7em;\n padding-right: 3em;\n}\n\n.offre-title-detail {\n font-size: 12px;\n}\n\n.offre-title-2 {\n font-size: 18px;\n}\n\n.offre-text {\n font-size: 20px;\n}\n\n.offre-bandeau {\n display: flex;\n flex-direction: row;\n justify-content: center;\n align-items: center;\n background-color: #FBEBE9;\n padding: 10px;\n min-height: 120px;\n text-align: center;\n}\n\n.offre-bandeau img {\n padding-right: 5em;\n}\n\n.offre-bandeau-reverse img {\n -webkit-transform: scaleX(-1);\n padding-left: 5em;\n}\n\n.offre-description-text {\n display: flex;\n flex-direction: column;\n}\n\n.offre-description-text-margin {\n margin-top: 3em;\n}\n\n.offre-img-padding {\n align-items: end;\n}\n\n.offre-img-padding img{\n width: -webkit-fill-available;\n}\n\n.offre-ul {\n > li {\n font-style: italic;\n }\n}\n\n.offre-li-detail {\n font-size: 10px;\n}\n\n.img-reduction img {\n width: 70%;\n height: 80%;\n}\n\n.container {\n flex: 1; \n}\n\n.home-section .home-section-header {\n display: flex;\n width: 100%;\n flex-direction: column;\n}\n\n.home-section-body > div {\n margin-bottom: 3rem;\n}\n\n.img-inherit img {\n width: inherit;\n}\n\n.home-img img {\n width: 485px; \n}\n\n.home-section-side { \n padding-top: 4em;\n}\n\n.home-section-side img {\n margin-top: 1em;\n max-width: 130px;\n}\n\n.icon-menu {\n border: solid 1px grey;\n border-radius: 0.75em;\n margin-bottom: 1em;\n align-items: center;\n display: flex;\n flex-direction: column;\n width: 140px;\n height: 140px;\n text-align: center;\n}\n\n.icon-menu-disabled {\n filter: grayscale(1);\n}\n\n.icon-menu img {\n height: 80px;\n width: 80px;\n}\n\n.icon-menu a {\n color: black;\n text-decoration: none\n}\n\n.icon-menu a:hover{\n color: black;\n text-decoration: none;\n font-weight: 600;\n}\n\n.icon-menu-text {\n margin-bottom: 0 !important\n}\n\n.fa-check:before {\n content: \"\\f00c\";\n font-family: 'FontAwesome';\n color: #fc0d1a;\n padding-right: 0.3em;\n}\n\n.fa-close:before {\n content: \"\\f00c\";\n font-family: 'FontAwesome';\n color: #fc0d1a;\n padding-right: 0.3em;\n}\n\n.parcours-span {\n text-align: center;\n img {\n max-width:130px\n }\n margin-bottom: 1.5em;\n}\n\n.parcours-span-block {\n align-items: center;\n}\n\n.offre-parcours {\n padding-top: 3em;\n}\n\n.offre-parcours-li:before {\n content: '- '\n}\n\n.schema-bloc {\n width:50%;\n}\n\n.schema-marge {\n margin-right: 0.5em;\n width: auto !important\n}\n\n.schema-title {\n border: 1px solid;\n height: 30px;\n align-items: center;\n display: flex;\n justify-content: center;\n border-radius: 5px;\n width: 100%;\n}\n\n.schema-text {\n background-color: #F2F2F2;\n justify-content: center;\n display: flex;\n align-items: center;\n padding: 0.5em 0.25em 0.5em 0.25em;\n border-radius: 5px;\n font-size: 12px;\n height: 70px;\n align-items: center;\n text-align: center;\n margin-bottom: 0.25em;\n width: 100%;\n}\n\n.schema-text-title {\n font-size: 18px;\n color: #fc0d1a;\n font-weight: bold;\n}\n\n.axterisque-block {\n margin-bottom: 0 !important;\n}\n\n.axterisque {\n display: flex;\n flex-direction: row;\n font-size: 10px;\n}\n\n.axterisque p {\n margin-bottom: 0 !important\n}\n\n.axterisque p:not(:last-child) {\n padding-right: 3em;\n}\n\n.contactez-nous {\n display: flex;\n flex-direction: row;\n align-items: center;\n justify-content: right;\n margin-right: 2em;\n}\n\n.contactez-nous button {\n display: flex;\n flex-direction: row;\n align-items: center;\n cursor: pointer;\n border: 0;\n font-family: inherit;\n margin: 0;\n text-decoration: none;\n background-color: transparent;\n}\n\n.contactez-nous button:focus {\n outline: none;\n}\n\n.contactez-nous button > p {\n font-size: 12px;\n font-weight: bold;\n padding-top: 1.1em;\n}\n\nfooter .navbar {\n display: flex;\n justify-content: space-between;\n/* min-height: 60px; */\n font-size: 14px;\n background-image: linear-gradient(to left, #684a52, #595667);\n color: #fff;\n}\n\nfooter .navbar .liens-utiles {\n padding: 0 0 0 1.5rem;\n}\n\nfooter .navbar .liens-utiles button, footer .navbar .liens-utiles a {\n cursor: pointer;\n color: #fff;\n background-color: transparent;\n border: 0;\n font-family: inherit;\n margin: 0;\n text-decoration: none;\n}\n\nfooter .navbar .liens-utiles button:after, footer .navbar .liens-utiles a:after {\n content: \"|\";\n margin: 0 0.5rem;\n}\n\nfooter .reseaux-sociaux {\n padding: 0 0 0 1.5rem;\n}\n\nfooter .reseaux-sociaux button, footer .reseaux-sociaux a {\n cursor: pointer;\n color: #fff;\n background-color: transparent;\n border: 0;\n font-family: inherit;\n margin: 0;\n}\n\n@media screen and (min-width: 250px) {\n .offre-block-img {\n width: 70px;\n } \n .offre-block-img img {\n width: 69px;\n height: 69px;\n } \n .offre-block-text {\n min-width: 180px;\n }\n}\n\n@media screen and (max-width: 250px) {\n .offre-block-img {\n display: none;\n }\n .offre-block-text {\n width: inherit;\n } \n}\n\n@media screen and (min-width: 298px) {\n .icon-menu {\n margin-right: 1em;\n }\n}\n\n@media screen and (max-width: 330px) {\n .menu-title {\n text-align: center;\n }\n}\n\n@media screen and (max-width: 400px) {\n .offre-title img {\n display: none;\n }\n .offre-bandeau img {\n display: none;\n }\n .schema-text-title {\n font-size: 12px !important;\n }\n .schema-text {\n font-size: 12px !important;\n }\n}\n\n@media screen and (min-width: 768px) {\n .fa-arrow-right:before {\n content: \"\\f061c\";\n font-family: 'FontAwesome';\n color: #fc0d1a;\n font-size:2.5em;\n margin-right: -15px;\n }\n .fa-arrow-down:before {\n display:none\n }\n .arrow {\n margin-left: -30px;\n height: auto;\n display: flex;\n align-items: end;\n } \n .border-dotted {\n border-right: dotted 3px red;\n }\n .schema {\n margin-right: 1em !important;\n }\n}\n@media screen and (max-width: 768px) {\n .fa-arrow-right:before {\n display:none\n }\n .fa-arrow-down:before {\n content: \"\\f061c\";\n font-family: 'FontAwesome';\n color: #fc0d1a;\n font-size:2.5em;\n }\n}\n@media screen and (min-width: 992px) {\n .home-section .home-section-left span {\n text-wrap: nowrap;\n z-index: 1;\n }\n .home-section .home-section-left {\n margin-top: 5em;\n }\n .home-section .home-section-left .en-tete {\n margin-top: 3em;\n text-align: end;\n }\n .en-tete {\n font-size: 23px;\n line-height: 2rem;\n }\n .offre-parcours-img {\n display: inline-flex!important;\n align-items: end;\n }\n}\n\n\n@media screen and (max-width: 992px) {\n .offre-parcours-img {\n display: none;\n }\n}\n\n.modal {\n display: none;\n /* Hidden by default */\n position: fixed;\n z-index: 9999;\n padding-top: 20px;\n left: 0;\n top: 0;\n width: 100%;\n height: 100%;\n overflow: auto;\n background-color: rgb(0, 0, 0);\n /* Fallback color */\n background-color: rgba(0, 0, 0, 0.4);\n /* Black w/ opacity */\n}\n.modal .modal-content {\n background-color: #fefefe;\n margin: auto;\n padding: 20px;\n max-width: 800px;\n border: 1px solid #888;\n border-radius: 5px;\n width: 80%;\n}\n\n.modal-content-scroll {\n overflow-y: scroll;\n height: 100%;\n}\n\n.modal .modal-content .modal-header {\n border-bottom: 1px solid #dee2e6;\n display: flex;\n justify-content: space-between;\n padding: 1rem;\n}\n.modal .modal-content .modal-header span {\n cursor: pointer;\n}\n.modal .modal-content .modal-footer {\n border-top: 1px solid #dee2e6;\n display: flex;\n justify-content: flex-end;\n padding: 1rem 1rem 0rem 1rem;\n}\n.modal .modal-content .close {\n color: #aaaaaa;\n float: right;\n font-size: 28px;\n font-weight: bold;\n}\n.modal .modal-content .close:hover,\n.modal .modal-content .close:focus {\n color: #000;\n text-decoration: none;\n cursor: pointer;\n}\n.modal .modal-content h1 {\n line-height: 1.5;\n margin-top: 0;\n margin-bottom: 0;\n font-size: 1.25rem;\n color: rgba(0, 0, 0, 0.85);\n font-weight: 500;\n}\n.modal .modal-content h2 {\n line-height: 1.2;\n margin-top: 0.5rem;\n margin-bottom: 0.5rem;\n font-size: 1.5rem;\n font-weight: 500;\n}\n.modal .modal-content h3 {\n color: #c62222;\n line-height: 1.2;\n margin-top: 0;\n margin-bottom: 0.5rem;\n font-size: 1.25rem;\n font-weight: 500;\n}\n.modal .modal-content p {\n margin-bottom: 1rem;\n text-align: justify;\n}\n.modal .modal-content ul {\n margin-bottom: 1em;\n margin-top: 0;\n}\n.modal .modal-content ul li {\n list-style-type: none;\n padding-left: 2rem;\n}\n.modal .modal-content form {\n margin: 1rem 0;\n font-weight: 500;\n}\n.modal .modal-content form .form-group {\n margin-bottom: 1rem;\n padding-bottom: 10px;\n}\n.modal .modal-content form .form-group label {\n display: inline-block;\n margin-bottom: 0.5rem;\n}\n.modal .modal-content form .form-group .form-control {\n background-clip: padding-box;\n background-color: #fff;\n border: 1px solid #ced4da;\n border-radius: 0.25rem;\n color: #495057;\n display: block;\n font-size: 1rem;\n font-weight: 400;\n height: calc(1.5em + 0.75rem + 2px);\n line-height: 1.5;\n padding: 0.375rem 2%;\n transition: border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;\n width: 96%;\n}\n.modal .modal-content form .form-group select.form-control[multiple], .modal .modal-content form .form-group select.form-control[size], .modal .modal-content form .form-group textarea.form-control {\n height: auto;\n}\n.modal .modal-content form button {\n background-color: transparent;\n border: 1px solid transparent;\n border-radius: 0.25rem;\n color: #212529;\n display: inline-block;\n font-size: 1rem;\n font-weight: 400;\n line-height: 1.5;\n padding: 0.375rem 0.75rem;\n text-align: center;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n vertical-align: middle;\n border-color: #28a745;\n color: #28a745;\n}\n.modal .modal-content form textarea {\n font-family: inherit;\n} */","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/css","authenticated":false,"lastPublishedDate":1707811449114}} +{"type":"cmspages","payload":{"_id":"652fa89c4400000d82fcea04","body":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-mobilite\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre VELO

\n
\n
\n

\n Vol, accident, dommages, assistance, vos utilisateurs sont-ils bien protégés ?\n

\n

\n Notre assurance propose des garanties complètes pour le vélo mais aussi pour le cycliste avec une assistance et une protection corporelle.\n

\n
\n \"offre \n
\n
\n
\n
\n

Penser à bien s’assurer avant de prendre le guidon

\n
\n \"offre \n
\n
\n
\n
\n
\n \"icon \n
\n
\n

Vol, casse… vos utilisateurs couverts partout, quoiqu’il arrive

\n

Une protection pour leur vélo neuf ou d’occasion, et pour ses accessoires. Même la tentative de vol est couverte !

\n
\n
\n
\n
\n \"icon \n
\n
\n

Crevaison panne : Assistance 24h/24

\n

MAIF Assistance dépanne vos utilisateurs et les amènent à leur domicile ou à destination. Intervention sur la route autour de chez eux (de 1 km à 50 km, sauf batterie déchargée).

\n
\n
\n
\n
\n \"icon \n
\n
\n

Chute, accident : vos utilisateurs sont pris en charge

\n

En cas de blessures, MAIF aide vos utilisateurs à se remettre en selle : aide financière, services à domicile, perte de revenus. Et leur enfant passager est couvert.

\n
\n
\n
\n
\n
\n \"offre \n
\n

Garanties & Indemnisations

\n
\n
\n
\n

Des garanties essentielles pour le vélo…

\n
    \n
  • Dommages accident
  • \n
  • Vol
  • \n
  • Accessoires et équipements de protection (y compris la batterie)
  • \n
  • Indemnisation en valeur d’achat 1 an…
  • \n
  • Vélo de remplacement​
  • \n
\n
\n
\n

… mais également pour le cycliste

\n
    \n
  • Assistance (panne & accident) jusqu’à 50km du domicile
  • \n
  • Retour au domicile
  • \n
  • Couverture corporelle
  • \n
  • Prêt de guidon gratuit
  • \n
  • Enfant passager couvert
  • \n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","name":"offre-velo","path":"/offre-velo","tags":[],"draft":"{{#daikoku-template-wrapper \"623c7943350000e106e28f89\" url_retour=\"/_/univers-mobilite\"}}\n\n\n\n
\n
\n {{daikoku-include-block \"6582b0212a0000359b5c3187\"}}\n
\n
\n \"icon \n

Offre VELO

\n
\n
\n

\n Vol, accident, dommages, assistance, vos utilisateurs sont-ils bien protégés ?\n

\n

\n Notre assurance propose des garanties complètes pour le vélo mais aussi pour le cycliste avec une assistance et une protection corporelle.\n

\n
\n \"offre \n
\n
\n
\n
\n

Penser à bien s’assurer avant de prendre le guidon

\n
\n \"offre \n
\n
\n
\n
\n
\n \"icon \n
\n
\n

Vol, casse… vos utilisateurs couverts partout, quoiqu’il arrive

\n

Une protection pour leur vélo neuf ou d’occasion, et pour ses accessoires. Même la tentative de vol est couverte !

\n
\n
\n
\n
\n \"icon \n
\n
\n

Crevaison panne : Assistance 24h/24

\n

MAIF Assistance dépanne vos utilisateurs et les amènent à leur domicile ou à destination. Intervention sur la route autour de chez eux (de 1 km à 50 km, sauf batterie déchargée).

\n
\n
\n
\n
\n \"icon \n
\n
\n

Chute, accident : vos utilisateurs sont pris en charge

\n

En cas de blessures, MAIF aide vos utilisateurs à se remettre en selle : aide financière, services à domicile, perte de revenus. Et leur enfant passager est couvert.

\n
\n
\n
\n
\n
\n \"offre \n
\n

Garanties & Indemnisations

\n
\n
\n
\n

Des garanties essentielles pour le vélo…

\n
    \n
  • Dommages accident
  • \n
  • Vol
  • \n
  • Accessoires et équipements de protection (y compris la batterie)
  • \n
  • Indemnisation en valeur d’achat 1 an…
  • \n
  • Vélo de remplacement​
  • \n
\n
\n
\n

… mais également pour le cycliste

\n
    \n
  • Assistance (panne & accident) jusqu’à 50km du domicile
  • \n
  • Retour au domicile
  • \n
  • Couverture corporelle
  • \n
  • Prêt de guidon gratuit
  • \n
  • Enfant passager couvert
  • \n
\n
\n
\n
\n
\n\n{{/daikoku-template-wrapper}}\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520614419}} +{"type":"cmspages","payload":{"_id":"6582b0212a0000359b5c3187","body":"
\n

Portail API MAIF

\n Le site dédié aux partenaires de distribution digitale des assurances MAIF\n \"icon\n
","name":"bloc-offre-side","path":null,"tags":[],"draft":"
\n

Portail API MAIF

\n Le site dédié aux partenaires de distribution digitale des assurances MAIF\n \"icon\n
","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520642480}} +{"type":"cmspages","payload":{"_id":"623c7943350000e106e28f89","body":"\n\n\n \n \n \n \n \n \n \n \n \n\n\n\n {{daikoku-include-block \"623c6fe5350000e106e28d1b\"}}\n \n {{children}}\n\n {{daikoku-include-block \"623c79b8350000e106e28fb4\"}}\n\n\n","name":"IAAS-BASIC-template","path":null,"tags":[],"draft":"\n\n\n \n \n \n \n \n \n \n \n \n\n\n\n {{daikoku-include-block \"623c6fe5350000e106e28d1b\"}}\n \n {{children}}\n\n {{daikoku-include-block \"623c79b8350000e106e28fb4\"}}\n\n\n","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520677032}} +{"type":"cmspages","payload":{"_id":"623c79b8350000e106e28fb4","body":"
\n
\n \n
\n
\n
\n \n maif.fr\n \n \n {{#unless connected}}\n Créer un compte\n {{/unless}}\n
\n
\n \n \n \n
\n
\n\n \n {{daikoku-include-block \"62a8424a440000921d1d3f06\"}}\n \n \n {{daikoku-include-block \"62aae19a3600001707b32ec4\"}}\n\n \n {{daikoku-include-block \"629f15aa440000921d1d2372\"}}\n\n \n {{daikoku-include-block \"629f4106440000921d1d278f\"}}\n\n\n \n\n
","name":"IAAS-BASIC-footer","path":null,"tags":[],"draft":"
\n
\n \n
\n
\n
\n \n maif.fr\n \n \n {{#unless connected}}\n Créer un compte\n {{/unless}}\n
\n
\n \n \n \n
\n
\n\n \n {{daikoku-include-block \"62a8424a440000921d1d3f06\"}}\n \n \n {{daikoku-include-block \"62aae19a3600001707b32ec4\"}}\n\n \n {{daikoku-include-block \"629f15aa440000921d1d2372\"}}\n\n \n {{daikoku-include-block \"629f4106440000921d1d278f\"}}\n\n\n \n\n
","exact":false,"_tenant":"default","picture":null,"visible":true,"_deleted":false,"metadata":{},"forwardRef":null,"contentType":"text/html","authenticated":false,"lastPublishedDate":1706520760583}} +{"type":"usage_plans","payload":{"_id":"7Mv0NwlWXXKSdpiLspZOCWYcPw0XTVFnTdBDXPJh0KgmZHoOflAoxqfdmAwkxtWm","type":"FreeWithoutQuotas","_tenant":"default","swagger":null,"testing":null,"_deleted":false,"currency":{"code":"EUR"},"customName":"admin","visibility":"Public","autoRotation":false,"documentation":null,"otoroshiTarget":null,"authorizedTeams":[],"billingDuration":{"unit":"Month","value":1},"allowMultipleKeys":true,"customDescription":null,"integrationProcess":"ApiKey","subscriptionProcess":[],"aggregationApiKeysSecurity":false}} +{"type":"usage_plans","payload":{"_id":"9r3Eh6FN0CtRkqRhHeFs0QZgsF44lx6i","type":"FreeWithQuotas","_tenant":"default","swagger":null,"testing":null,"_deleted":false,"currency":{"code":"EUR"},"maxPerDay":1,"customName":null,"visibility":"Public","maxPerMonth":1,"autoRotation":false,"maxPerSecond":1,"documentation":null,"otoroshiTarget":null,"authorizedTeams":[],"billingDuration":{"unit":"Month","value":1},"allowMultipleKeys":false,"customDescription":"Free plan with limited number of calls per day and per month","integrationProcess":"ApiKey","subscriptionProcess":[],"aggregationApiKeysSecurity":false}} +{"type":"usage_plans","payload":{"_id":"7p9AW4w7ZmGMl4hWzAp7YCLxAD9TvOiw","type":"FreeWithoutQuotas","_tenant":"default","swagger":null,"testing":null,"_deleted":false,"currency":{"code":"EUR"},"customName":null,"visibility":"Public","autoRotation":false,"documentation":null,"otoroshiTarget":null,"authorizedTeams":[],"billingDuration":{"unit":"Month","value":1},"allowMultipleKeys":false,"customDescription":"Free plan with unlimited number of calls per day and per month","integrationProcess":"ApiKey","subscriptionProcess":[],"aggregationApiKeysSecurity":false}} diff --git a/cli/tests/resources/daikoku.svg b/cli/tests/resources/daikoku.svg new file mode 100644 index 000000000..b63b4b440 --- /dev/null +++ b/cli/tests/resources/daikoku.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/cli/tests/resources/hsperfdata_demiourgos728/1 b/cli/tests/resources/hsperfdata_demiourgos728/1 new file mode 100644 index 000000000..3e82c143f Binary files /dev/null and b/cli/tests/resources/hsperfdata_demiourgos728/1 differ diff --git a/cli/tests/version.rs b/cli/tests/version.rs index 2b6d83cd2..10d8c57c4 100644 --- a/cli/tests/version.rs +++ b/cli/tests/version.rs @@ -1,11 +1,12 @@ -use assert_cmd::prelude::*; -use std::process::Command; +mod cli; -#[test] -fn run() -> Result<(), Box> { - let mut cmd = Command::cargo_bin("daikokucli")?; +use cli::commands::cli::{CustomRun, CLI}; + +use serial_test::serial; +#[tokio::test] +#[serial] +async fn version() -> Result<(), Box> { + CLI::build(["version"]).run_and_expect("daikoku version:"); - cmd.args(["version"]); - cmd.assert().success(); Ok(()) } diff --git a/daikoku/app/actions/actions.scala b/daikoku/app/actions/actions.scala index dd267b3dc..8053fa6d7 100644 --- a/daikoku/app/actions/actions.scala +++ b/daikoku/app/actions/actions.scala @@ -2,9 +2,16 @@ package fr.maif.otoroshi.daikoku.actions import org.apache.pekko.http.scaladsl.util.FastFuture import cats.implicits.catsSyntaxOptionId +import com.auth0.jwt.JWT +import com.google.common.base.Charsets +import fr.maif.otoroshi.daikoku.ctrls.CmsApiActionContext import fr.maif.otoroshi.daikoku.domain.TeamPermission.{Administrator, ApiEditor} import fr.maif.otoroshi.daikoku.domain._ -import fr.maif.otoroshi.daikoku.env.Env +import fr.maif.otoroshi.daikoku.env.{ + Env, + LocalCmsApiConfig, + OtoroshiCmsApiConfig +} import fr.maif.otoroshi.daikoku.login.{IdentityAttrs, TenantHelper} import fr.maif.otoroshi.daikoku.utils.Errors import fr.maif.otoroshi.daikoku.utils.RequestImplicits.EnhancedRequestHeader @@ -12,8 +19,10 @@ import play.api.Logger import play.api.libs.json.{JsString, JsValue, Json} import play.api.mvc._ +import java.util.Base64 import scala.collection.concurrent.TrieMap import scala.concurrent.{ExecutionContext, Future} +import scala.util.{Success, Try} object tenantSecurity { def userCanCreateApi(tenant: Tenant, user: User)(implicit @@ -69,13 +78,15 @@ object tenantSecurity { case class DaikokuTenantActionContext[A]( request: Request[A], tenant: Tenant, - ctx: TrieMap[String, String] = new TrieMap[String, String]() -) { - def setCtxValue(key: String, value: Any): Unit = { - if (value != null) { - ctx.put(key, value.toString) - } - } +// ctx: TrieMap[String, String] = new TrieMap[String, String]() +) extends ApiActionContext[A] { +// def setCtxValue(key: String, value: Any): Unit = { +// if (value != null) { +// ctx.put(key, value.toString) +// } +// } + + override def user: User = null } case class DaikokuActionMaybeWithoutUserContext[A]( @@ -95,16 +106,12 @@ case class DaikokuActionMaybeWithoutUserContext[A]( } } -case class DaikokuActionContext[A]( - request: Request[A], - user: User, - tenant: Tenant, - session: UserSession, - impersonator: Option[User], - isTenantAdmin: Boolean, - apiCreationPermitted: Boolean = false, - ctx: TrieMap[String, String] = new TrieMap[String, String]() -) { +trait ApiActionContext[A] { + def request: Request[A] + def user: User + def tenant: Tenant + def ctx: TrieMap[String, String] = new TrieMap[String, String]() + def setCtxValue(key: String, value: Any): Unit = { if (value != null) { ctx.put(key, value.toString) @@ -112,6 +119,124 @@ case class DaikokuActionContext[A]( } } +case class DaikokuActionContext[A]( + request: Request[A], + user: User, + tenant: Tenant, + session: UserSession, + impersonator: Option[User], + isTenantAdmin: Boolean, + apiCreationPermitted: Boolean = false, + override val ctx: TrieMap[String, String] = new TrieMap[String, String]() + ) extends ApiActionContext[A] { +// def setCtxValue(key: String, value: Any): Unit = { +// if (value != null) { +// ctx.put(key, value.toString) +// } +// } +} + +class CmsApiAction(val parser: BodyParser[AnyContent], env: Env) + extends ActionBuilder[CmsApiActionContext, AnyContent] + with ActionFunction[Request, CmsApiActionContext] { + + implicit lazy val ec: ExecutionContext = env.defaultExecutionContext + + def decodeBase64(encoded: String): String = + new String(Base64.getUrlDecoder.decode(encoded), Charsets.UTF_8) + private def extractUsernamePassword( + header: String + ): Option[(String, String)] = { + val base64 = header.replace("Basic ", "").replace("basic ", "") + Option(base64) + .map(decodeBase64) + .map(_.split(":").toSeq) + .flatMap(a => + a.headOption.flatMap(head => a.lastOption.map(last => (head, last))) + ) + } + + override def invokeBlock[A]( + request: Request[A], + block: CmsApiActionContext[A] => Future[Result] + ): Future[Result] = { + TenantHelper.withTenant(request, env) { tenant => + env.config.cmsApiConfig match { + case OtoroshiCmsApiConfig(headerName, algo) => + request.headers.get(headerName) match { + case Some(value) => + Try(JWT.require(algo).build().verify(value)) match { + case Success(decoded) if !decoded.getClaim("apikey").isNull => + block(CmsApiActionContext[A](request, tenant)) + case _ => + Errors.craftResponseResult( + "No api key provided", + Results.Unauthorized, + request, + None, + env + ) + } + case _ => + Errors.craftResponseResult( + "No api key provided", + Results.Unauthorized, + request, + None, + env + ) + } + case LocalCmsApiConfig(_) => + request.headers.get("Authorization") match { + case Some(auth) if auth.startsWith("Basic ") => + extractUsernamePassword(auth) match { + case None => + Errors.craftResponseResult( + "No api key provided", + Results.Unauthorized, + request, + None, + env + ) + case Some((clientId, clientSecret)) => + env.dataStore.apiSubscriptionRepo + .forTenant(tenant) + .findNotDeleted( + Json.obj( + "apiKey.clientId" -> clientId, + "apiKey.clientSecret" -> clientSecret + ) + ) + .map(_.length == 1) + .flatMap({ + case done if done => + block(CmsApiActionContext[A](request, tenant)) + case _ => + Errors.craftResponseResult( + "No api key provided", + Results.Unauthorized, + request, + None, + env + ) + }) + } + case _ => + Errors.craftResponseResult( + "No api key provided", + Results.Unauthorized, + request, + None, + env + ) + } + } + } + } + + override protected def executionContext: ExecutionContext = ec +} + class DaikokuAction(val parser: BodyParser[AnyContent], env: Env) extends ActionBuilder[DaikokuActionContext, AnyContent] with ActionFunction[Request, DaikokuActionContext] { diff --git a/daikoku/app/controllers/ApiController.scala b/daikoku/app/controllers/ApiController.scala index f9ff5f3a2..04ae52484 100644 --- a/daikoku/app/controllers/ApiController.scala +++ b/daikoku/app/controllers/ApiController.scala @@ -1022,6 +1022,16 @@ class ApiController( } } + def getAllApis() = DaikokuAction.async { ctx => + TenantAdminOnly( + AuditTrailEvent( + s"@{user.name} has fetch all apis" + ) + )(ctx.tenant.id.value, ctx) { (tenant, _) => + apiService.getApis(ctx) + } + } + case class subscriptionData( apiKey: OtoroshiApiKey, plan: UsagePlanId, @@ -4521,7 +4531,7 @@ class ApiController( env.dataStore.apiRepo .findAllVersions(tenant = ctx.tenant, id = apiId) .map { apis => - ctx.setCtxValue("api.name", apis.head.name) + ctx.setCtxValue("api.name", apis.headOption.map(_.name).getOrElse("unknown api name")) ctx.setCtxValue("api.id", apiId) Ok( SeqVersionFormat.writes( diff --git a/daikoku/app/controllers/AssetsController.scala b/daikoku/app/controllers/AssetsController.scala index 98efc929a..077b3373b 100644 --- a/daikoku/app/controllers/AssetsController.scala +++ b/daikoku/app/controllers/AssetsController.scala @@ -14,6 +14,7 @@ import fr.maif.otoroshi.daikoku.ctrls.authorizations.async._ import fr.maif.otoroshi.daikoku.domain.{Asset, AssetId} import fr.maif.otoroshi.daikoku.env.Env import fr.maif.otoroshi.daikoku.logger.AppLogger +import fr.maif.otoroshi.daikoku.services.{AssetsService, NormalizeSupport} import fr.maif.otoroshi.daikoku.utils.IdGenerator import fr.maif.otoroshi.daikoku.utils.StringImplicits.BetterString import org.apache.pekko.stream.connectors.s3.ObjectMetadata @@ -32,30 +33,6 @@ import scala.concurrent.{ExecutionContext, Future} import scala.concurrent.duration.DurationInt import scala.jdk.CollectionConverters._ -trait NormalizeSupport { - - import java.text.Normalizer.{normalize => jnormalize, _} - - def normalize(in: String): String = { - val cleaned = in.trim.toLowerCase - val tuple = cleaned.splitAt(cleaned.lastIndexOf('.')) - - val normalized = jnormalize(tuple._1, Form.NFC) - - val fileNameNormalized = normalized - .replaceAll("'s", "") - .replaceAll("ß", "ss") - .replaceAll("ø", "o") - .replaceAll("[^a-zA-Z0-9-]+", "-") - .replaceAll("-+", "-") - .stripSuffix("-") - - fileNameNormalized + tuple._2 - } -} - -object NormalizeSupport extends NormalizeSupport - class TeamAssetsController( DaikokuAction: DaikokuAction, DaikokuActionMaybeWithGuest: DaikokuActionMaybeWithGuest, @@ -352,7 +329,8 @@ class TenantAssetsController( DaikokuAction: DaikokuAction, DaikokuTenantAction: DaikokuTenantAction, env: Env, - cc: ControllerComponents + cc: ControllerComponents, + assetsService: AssetsService ) extends AbstractController(cc) with NormalizeSupport { @@ -364,82 +342,12 @@ class TenantAssetsController( Accumulator.source[ByteString].map(Right.apply) } - def storeAssets() = + def storeAssets(): Action[Source[ByteString, _]] = DaikokuAction.async(bodyParser) { ctx => TenantAdminOnly( AuditTrailEvent(s"@{user.name} syncs assets") )(ctx.tenant.id.value, ctx) { (_, _) => - ctx.tenant.bucketSettings match { - case None => - FastFuture.successful( - NotFound(Json.obj("error" -> "No bucket config found !")) - ) - case Some(cfg) => - ctx.request.body - .runWith(Sink.reduce[ByteString](_ ++ _))(env.defaultMaterializer) - .map(str => str.utf8String) - .map(Json.parse) - .flatMap(items => - Future.sequence( - items - .as[JsArray] - .value - .map(item => { - val filename = (item \ "filename").as[String] - val assetId = AssetId(IdGenerator.uuid) - val slug = filename.slugify - - env.assetsStore - .storeTenantAsset( - ctx.tenant.id, - assetId, - name = filename, - title = filename, - desc = filename, - contentType = "application/octet-stream", - content = Source - .single((item \ "content").get) - .map(Json.stringify) - .map(ByteString.apply) - )(cfg) - .flatMap { _ => - internalDeleteAsset(slug, ctx) - .map(_ => - env.dataStore.assetRepo - .forTenant(ctx.tenant) - .save( - Asset( - assetId, - tenant = ctx.tenant.id, - slug = slug - ) - ) - ) - .map(_ => - Json.obj( - "done" -> true, - "id" -> assetId.value, - "slug" -> slug - ) - ) - } recover { - case e => - AppLogger.error( - s"Error during tenant asset storage: $filename", - e - ) - Json - .obj("id" -> assetId.value, "error" -> ec.toString) - } - }) - ) - ) - .map(results => Ok(Json.arr(results))) - .recover { - case e: Throwable => - BadRequest(Json.obj("error" -> e.getMessage)) - } - } + assetsService.storeAssets(ctx, ctx.request.body) } } @@ -448,64 +356,7 @@ class TenantAssetsController( TenantAdminOnly( AuditTrailEvent(s"@{user.name} stores asset in team @{team.id}") )(ctx.tenant.id.value, ctx) { (_, _) => - val contentType = ctx.request.headers - .get("Asset-Content-Type") - .orElse(ctx.request.contentType) - .getOrElse("application/octet-stream") - val filename = normalize( - ctx.request - .getQueryString("filename") - .getOrElse(IdGenerator.token(16)) - ) - val title = - normalize(ctx.request.getQueryString("title").getOrElse("--")) - val desc = ctx.request.getQueryString("desc").getOrElse("--") - val querySlug: Option[String] = ctx.request - .getQueryString("slug") - .flatMap(slug => if (slug.isEmpty) None else Some(slug)) - val assetId = AssetId(IdGenerator.uuid) - - ctx.tenant.bucketSettings match { - case None => - FastFuture.successful( - NotFound(Json.obj("error" -> "No bucket config found !")) - ) - case Some(cfg) => - env.assetsStore - .storeTenantAsset( - ctx.tenant.id, - assetId, - filename, - title, - desc, - contentType, - ctx.request.body - )(cfg) - .flatMap { _ => - val slug = querySlug.map(_.slugify).getOrElse(filename.slugify) - - env.dataStore.assetRepo - .forTenant(ctx.tenant) - .save(Asset(assetId, tenant = ctx.tenant.id, slug = slug)) - .map(_ => - Ok( - Json.obj( - "done" -> true, - "id" -> assetId.value, - "slug" -> slug - ) - ) - ) - - } recover { - case e => - AppLogger.error( - s"Error during tenant asset storage: ${filename}", - e - ) - InternalServerError(Json.obj("error" -> ec.toString)) - } - } + assetsService.storeAsset(ctx, ctx.request.body) } } @@ -514,116 +365,13 @@ class TenantAssetsController( TenantAdminOnly( AuditTrailEvent(s"@{user.name} replace asset in team @{team.id}") )(ctx.tenant.id.value, ctx) { (_, _) => - def getMetaHeaderValue( - metadata: ObjectMetadata, - headerName: String - ): Option[String] = { - metadata.headers.asScala - .find(_.name() == s"x-amz-meta-$headerName") - .map(_.value()) - } - - ctx.tenant.bucketSettings match { - case None => - FastFuture.successful( - NotFound(Json.obj("error" -> "No bucket config found !")) - ) - case Some(cfg) => - env.assetsStore - .getTenantAssetMetaHeaders(ctx.tenant.id, AssetId(assetId))(cfg) - .flatMap { - case None => - FastFuture.successful( - NotFound(Json.obj("error" -> "Asset not found")) - ) - case Some(metadata) => - val filename = - getMetaHeaderValue(metadata, "filename").getOrElse("--") - val desc = - getMetaHeaderValue(metadata, "desc").getOrElse("--") - val title = - getMetaHeaderValue(metadata, "title").getOrElse("--") - val contentType = metadata.contentType - .orElse(ctx.request.contentType) - .getOrElse("application/octet-stream") - - env.assetsStore - .storeTenantAsset( - ctx.tenant.id, - AssetId(assetId), - filename, - title, - desc, - contentType, - ctx.request.body - )(cfg) - .flatMap { _ => - val slug = filename.slugify - env.dataStore.assetRepo - .forTenant(ctx.tenant) - .deleteById(assetId) - .flatMap(_ => - env.dataStore.assetRepo - .forTenant(ctx.tenant) - .save( - Asset( - AssetId(assetId), - tenant = ctx.tenant.id, - slug - ) - ) - ) - .map(_ => Ok(Json.obj("done" -> true, "id" -> assetId))) - } recover { - case e => - AppLogger - .error( - s"Error during update tenant asset: $filename", - e - ) - InternalServerError(Json.obj("error" -> ec.toString)) - } - } - } + assetsService.replaceAsset(assetId, ctx) } } def listAssets() = DaikokuAction.async { ctx => - ctx.request.getQueryString("teamId") match { - case Some(teamId) => - TeamAdminOnly( - AuditTrailEvent(s"@{user.name} listed assets of team @{team.id}") - )(teamId, ctx) { _ => - ctx.tenant.bucketSettings match { - case None => - FastFuture.successful( - NotFound(Json.obj("error" -> "No bucket config found !")) - ) - case Some(cfg) => - env.assetsStore.listTenantAssets(ctx.tenant.id)(cfg).map { - res => - Ok(JsArray(res.map(_.asJson))) - } - } - } - case None => - TenantAdminOnly( - AuditTrailEvent(s"@{user.name} listed assets of team @{team.id}") - )(ctx.tenant.id.value, ctx) { (_, _) => - ctx.tenant.bucketSettings match { - case None => - FastFuture.successful( - NotFound(Json.obj("error" -> "No bucket config found !")) - ) - case Some(cfg) => - env.assetsStore.listTenantAssets(ctx.tenant.id)(cfg).map { - res => - Ok(JsArray(res.map(_.asJson))) - } - } - } - } + assetsService.listAssets(ctx) } def slugifiedAssets() = @@ -631,9 +379,7 @@ class TenantAssetsController( TenantAdminOnly( AuditTrailEvent(s"@{user.name} access to slugified assets") )(ctx.tenant.id.value, ctx) { (_, _) => - env.dataStore.assetRepo.forTenant(ctx.tenant).findAllNotDeleted().map { - res => Ok(JsArray(res.map(_.asJson))) - } + assetsService.slugifiedAssets(ctx) } } @@ -642,144 +388,19 @@ class TenantAssetsController( TenantAdminOnly( AuditTrailEvent(s"@{user.name} deleted asset @{assetId} of @{team.id}") )(ctx.tenant.id.value, ctx) { (_, _) => - ctx.setCtxValue("assetId", assetId) - internalDeleteAsset(assetId, ctx) + assetsService.deleteAsset(assetId, ctx) } } - private def internalDeleteAsset[T]( - assetId: String, - ctx: DaikokuActionContext[T] - ) = { - ctx.tenant.bucketSettings match { - case None => - FastFuture.successful( - NotFound(Json.obj("error" -> "No bucket config found !")) - ) - case Some(cfg) => - env.dataStore.assetRepo - .forTenant(ctx.tenant) - .findOne(Json.obj("slug" -> assetId)) - .map(_.map(_.id.value)) - .map { - case None => assetId - case Some(id) => id - } - .flatMap(id => { - env.assetsStore - .deleteTenantAsset(ctx.tenant.id, AssetId(id))(cfg) - .flatMap { _ => - env.dataStore.assetRepo - .forTenant(ctx.tenant) - .deleteById(id) - .map(_ => Ok(Json.obj("done" -> true))) - } - }) - } - } - def doesAssetExists(slug: String) = { DaikokuTenantAction.async { ctx => - ctx.tenant.bucketSettings match { - case None => - FastFuture.successful( - NotFound(Json.obj("error" -> "No bucket config found !")) - ) - case Some(cfg) => - env.dataStore.assetRepo - .forTenant(ctx.tenant) - .findOne(Json.obj("slug" -> slug)) - .map { - case Some(_) => NoContent - case None => NotFound - } - } + assetsService.doesAssetExists(slug, ctx) } } def getAsset(assetId: String) = { DaikokuTenantAction.async { ctx => - ctx.tenant.bucketSettings match { - case None => - FastFuture.successful( - NotFound(Json.obj("error" -> "No bucket config found !")) - ) - case Some(cfg) => - val download = ctx.request.getQueryString("download").contains("true") - val redirect = ctx.request.getQueryString("redirect").contains("true") - - env.dataStore.assetRepo - .forTenant(ctx.tenant) - .findOne(Json.obj("slug" -> assetId)) - .map { - case Some(asset) => - env.assetsStore.getTenantAssetPresignedUrl( - ctx.tenant.id, - asset.id - )(cfg) - case None => - env.assetsStore.getTenantAssetPresignedUrl( - ctx.tenant.id, - AssetId(assetId) - )(cfg) - } - .flatMap { - case None => - FastFuture.successful( - NotFound(Json.obj("error" -> "Asset not found!")) - ) - case Some(url) if redirect => FastFuture.successful(Redirect(url)) - case Some(_) if download => - env.assetsStore - .getTenantAsset(ctx.tenant.id, AssetId(assetId))(cfg) - .map { - case None => - NotFound(Json.obj("error" -> "Asset not found!")) - case Some((source, meta)) => - val filename = meta.metadata - .filter(_.name().startsWith("x-amz-meta-")) - .find(_.name() == "x-amz-meta-filename") - .map(_.value()) - .getOrElse("asset.txt") - - Ok.sendEntity( - HttpEntity.Streamed( - source, - None, - meta.contentType - .map(Some.apply) - .getOrElse(Some("application/octet-stream")) - ) - ) - .withHeaders( - "Content-Disposition" -> s"""attachment; filename="$filename"""" - ) - } - case Some(url) => - env.wsClient - .url(url) - .withRequestTimeout(10.minutes) - .get() - .map(resp => { - resp.status match { - case 200 => - Ok.sendEntity( - HttpEntity.Streamed( - resp.bodyAsSource, - None, - Option(resp.contentType) - ) - ) - case _ => - NotFound(Json.obj("error" -> "Asset not found!")) - } - }) - .recover { - case err => - InternalServerError(Json.obj("error" -> err.getMessage)) - } - } - } + assetsService.getAsset(assetId, ctx) } } } diff --git a/daikoku/app/controllers/CmsApiController.scala b/daikoku/app/controllers/CmsApiController.scala new file mode 100644 index 000000000..f3c8b8b5a --- /dev/null +++ b/daikoku/app/controllers/CmsApiController.scala @@ -0,0 +1,321 @@ +package fr.maif.otoroshi.daikoku.ctrls + +import cats.data.EitherT +import cats.implicits.toBifunctorOps +import controllers.{AppError, Assets} +import fr.maif.otoroshi.daikoku.actions.{ + ApiActionContext, + CmsApiAction, + DaikokuActionMaybeWithoutUser +} +import fr.maif.otoroshi.daikoku.audit.AuditTrailEvent +import fr.maif.otoroshi.daikoku.ctrls.authorizations.async.TenantAdminOnly +import fr.maif.otoroshi.daikoku.domain.json.{CmsFileFormat, CmsPageFormat} +import fr.maif.otoroshi.daikoku.domain.{ + CmsPage, + CmsPageId, + Tenant, + TenantMode, + User, + UserSession +} +import fr.maif.otoroshi.daikoku.env.Env +import fr.maif.otoroshi.daikoku.logger.AppLogger +import fr.maif.otoroshi.daikoku.login.AuthProvider.{OAuth2, Otoroshi} +import fr.maif.otoroshi.daikoku.login.{IdentityAttrs, OAuth2Config} +import fr.maif.otoroshi.daikoku.services.{AssetsService, TranslationsService} +import fr.maif.otoroshi.daikoku.utils.ApiService +import fr.maif.otoroshi.daikoku.utils.Cypher.encrypt +import fr.maif.otoroshi.daikoku.utils.admin.UpdateOrCreate +import fr.maif.otoroshi.daikoku.utils.future.EnhancedObject +import org.apache.pekko.http.scaladsl.util.FastFuture +import org.apache.pekko.stream.scaladsl.Source +import org.apache.pekko.util.ByteString +import play.api.Logger +import play.api.libs.json._ +import play.api.mvc._ +import storage.{DataStore, Repo} + +import scala.collection.concurrent.TrieMap +import scala.concurrent.{ExecutionContext, Future} +import scala.util.{Failure, Success, Using} + +case class CmsApiActionContext[A]( + request: Request[A], + tenant: Tenant, + override val ctx: TrieMap[String, String] = TrieMap.empty[String, String], + override val user: User = null +) extends ApiActionContext[A] + +class CmsApiController( + CmsApiAction: CmsApiAction, + env: Env, + cc: ControllerComponents, + DaikokuActionMaybeWithoutUser: DaikokuActionMaybeWithoutUser, + translationsService: TranslationsService, + apiService: ApiService, + assetsService: AssetsService, + assets: Assets +) extends AbstractController(cc) { + + implicit val ec: ExecutionContext = env.defaultExecutionContext + implicit val ev: Env = env + + val logger: Logger = Logger(s"cms-controller-$entityName") + + private def entityClass = classOf[CmsPage] +// def description: String = entityClass.getName +// def pathRoot: String = "/cms-api" + def entityName: String = "api-cms-page" + def entityStore(tenant: Tenant, ds: DataStore): Repo[CmsPage, CmsPageId] = + ds.cmsRepo.forTenant(tenant) + def toJson(entity: CmsPage): JsValue = entity.asJson + + def fromJson(entity: JsValue): Either[String, CmsPage] = + CmsPageFormat + .reads(entity) + .asEither + .leftMap(_.flatMap(_._2).map(_.message).mkString(", ")) + + def validate( + entity: CmsPage, + updateOrCreate: UpdateOrCreate + ): EitherT[Future, AppError, CmsPage] = EitherT.pure[Future, AppError](entity) + + def getId(entity: CmsPage): CmsPageId = entity.id + + private def bodyToSource[A](body: A): Source[ByteString, _] = { + body match { + case raw: AnyContentAsRaw => + Source.single(raw.raw.asBytes().getOrElse(ByteString.empty)) + case e => + println(e) + throw new IllegalArgumentException("Request body is not raw data") + } + } + + def storeAssets() = + CmsApiAction.async { ctx => + assetsService.storeAssets(ctx, bodyToSource(ctx.request.body)) + } + + def storeAsset() = + CmsApiAction.async { ctx => + assetsService.storeAsset(ctx, bodyToSource(ctx.request.body)) + } + + def listAssets() = + CmsApiAction.async { ctx => + assetsService.listAssets(ctx) + } + + def slugifiedAssets() = + CmsApiAction.async { ctx => + assetsService.slugifiedAssets(ctx) + } + + def deleteAsset(assetId: String) = + CmsApiAction.async { ctx => + assetsService.deleteAsset(assetId, ctx) + } + + def doesAssetExists(slug: String) = { + CmsApiAction.async { ctx => + assetsService.doesAssetExists(slug, ctx) + } + } + + def getAsset(assetId: String) = { + CmsApiAction.async { ctx => + assetsService.getAsset(assetId, ctx) + } + } + + def getCmsPage(id: String) = + CmsApiAction.async { ctx => + env.dataStore.cmsRepo + .forTenant(ctx.tenant) + .findById(id) + .map { + case None => NotFound(Json.obj("error" -> "cms page not found")) + case Some(page) => Ok(page.asJson) + } + } + + def sync() = + CmsApiAction.async(parse.json) { ctx => + for { + _ <- env.dataStore.cmsRepo.forTenant(ctx.tenant).deleteAll() + _ <- Future.sequence( + ctx.request.body + .as(Reads.seq(CmsFileFormat.reads)) + .map(page => { + env.dataStore.cmsRepo + .forTenant(ctx.tenant) + .save(page.toCmsPage(ctx.tenant.id)) + }) + ) + } yield { + NoContent + } + } + +// def sync() = +// CmsApiAction.async(parse.json) { ctx => +// val body = ctx.request.body +// +// Future +// .sequence( +// body +// .as(Reads.seq(CmsFileFormat.reads)) +// .map(page => { +// env.dataStore.cmsRepo +// .forTenant(ctx.tenant) +// .save(page.toCmsPage(ctx.tenant.id)) +// }) +// ) +// .map(_ => NoContent) +// .recover { +// case e: Throwable => +// BadRequest(Json.obj("error" -> e.getMessage)) +// } +// } + + def health() = + CmsApiAction.async { ctx => + ctx.request.headers.get("Otoroshi-Health-Check-Logic-Test") match { + //todo: better health check + case Some(value) => + Ok.withHeaders( + "Otoroshi-Health-Check-Logic-Test-Result" -> (value.toLong + 42L).toString + ) + .future + case None => + Ok( + Json.obj( + "tenantMode" -> ctx.tenant.tenantMode + .getOrElse(TenantMode.Default) + .name + ) + ).future + } + } + + def version() = + CmsApiAction.async { ctx => + entityStore(ctx.tenant, env.dataStore) + .exists(Json.obj("_id" -> "daikoku_metadata")) + .map { + case true => Ok(Json.obj()) + case false => NotFound + } + } + + def defaultTenant() = + CmsApiAction.async { ctx => + env.dataStore.translationRepo + .forTenant(ctx.tenant) + .find(Json.obj("element.id" -> ctx.tenant.id.asJson)) + .map(translations => { + val translationAsJsObject = translations + .groupBy(t => t.language) + .map { + case (k, v) => + Json + .obj(k -> JsObject(v.map(t => t.key -> JsString(t.value)))) + } + .fold(Json.obj())(_ deepMerge _) + val translation = Json.obj("translation" -> translationAsJsObject) + Ok(ctx.tenant.asJsonWithJwt.as[JsObject] ++ translation) + }) + } + + def findAll(): Action[AnyContent] = + CmsApiAction.async { ctx => + entityStore(ctx.tenant, env.dataStore) + .findAllNotDeleted() + .map(entities => Ok(JsArray(entities.map(_.asJson)))) + } + + def getMailTranslations(domain: Option[String]) = + CmsApiAction.async { ctx => + translationsService.getMailTranslations( + ctx, + domain, + messagesApi, + supportedLangs + ) + } + + def getAllApis() = + CmsApiAction.async { ctx => + apiService.getApis(ctx, true) + } + + def getLoginToken() = + CmsApiAction.async { ctx => + { + Ok( + Json.obj( + "token" -> encrypt( + env.config.cypherSecret, + "daikoku-cli-login", + ctx.tenant + ) + ) + ).future + } + } + + def redirectToLoginPage() = + DaikokuActionMaybeWithoutUser.async { ctx => + ctx.request.queryString.get("redirect").flatMap(_.headOption) match { + case Some(redirect: String) => + ctx.tenant.authProvider match { + case Otoroshi => FastFuture.successful(Redirect(redirect)) + case OAuth2 => + val authConfig = OAuth2Config + .fromJson(ctx.tenant.authProviderSettings) + .toOption + .get + val clientId = authConfig.clientId + val responseType = "code" + val scope = authConfig.scope // "openid profile email name" + val redirectUri = authConfig.callbackUrl + + FastFuture.successful( + Redirect( + s"${authConfig.loginUrl}?scope=${scope}&client_id=$clientId&response_type=$responseType&redirect_uri=$redirectUri" + ).addingToSession( + s"redirect" -> redirect + )(ctx.request) + ) + case _ => + FastFuture.successful(Redirect(s"/?redirect=$redirect")) + } + case None => + NotFound(Json.obj("error" -> "redirect param is missing")).future + } + } +} + +class CmsApiSwaggerController(cc: ControllerComponents) + extends AbstractController(cc) { + + def swagger() = + Action { + Using( + scala.io.Source.fromResource("public/swaggers/cms-api-openapi.json") + ) { source => + source.mkString + } match { + case Failure(e) => + AppLogger.error(e.getMessage, e) + BadRequest(Json.obj("error" -> e.getMessage)) + case Success(value) => + Ok(Json.parse(value)).withHeaders( + "Access-Control-Allow-Origin" -> "*" + ) + } + } +} diff --git a/daikoku/app/controllers/HomeController.scala b/daikoku/app/controllers/HomeController.scala index ae41609ab..2b4ad3279 100644 --- a/daikoku/app/controllers/HomeController.scala +++ b/daikoku/app/controllers/HomeController.scala @@ -24,7 +24,7 @@ import fr.maif.otoroshi.daikoku.domain.json.{ } import fr.maif.otoroshi.daikoku.env.Env import fr.maif.otoroshi.daikoku.logger.AppLogger -import fr.maif.otoroshi.daikoku.utils.{Errors, IdGenerator, diff_match_patch} +import fr.maif.otoroshi.daikoku.utils.Errors import org.apache.pekko.http.scaladsl.util.FastFuture import org.joda.time.DateTime import play.api.i18n.{I18nSupport, MessagesApi} @@ -349,12 +349,15 @@ class HomeController( private def render[A]( ctx: DaikokuActionMaybeWithoutUserContext[A], r: CmsPage, - req: Option[CmsRequestRendering] = None + req: Option[CmsRequestRendering] = None, + skipCache: Boolean = false, + fields: Map[String, JsValue] = Map.empty[String, JsValue] ) = { + val isDraftRender: Boolean = ctx.request.getQueryString("draft").contains("true") val forceReloading: Boolean = - ctx.request.getQueryString("force_reloading").contains("true") + ctx.request.getQueryString("force_reloading").contains("true") || skipCache val cacheId = s"${ctx.user.map(_.id.value).getOrElse("")}-${r.path.getOrElse("")}" @@ -375,13 +378,13 @@ class HomeController( }) if (isDraftRender || forceReloading) - r.render(ctx, None, req = req).map(res => Ok(res._1).as(res._2)) + r.render(ctx, None, req = req, jsonToCombine = fields).map(res => Ok(res._1).as(res._2)) else cache.getIfPresent(cacheId) match { case Some(value) => FastFuture.successful(Ok(value.content).as(value.contentType)) case _ => - r.render(ctx, None, req = req) + r.render(ctx, None, req = req, jsonToCombine = fields) .map(res => { cache.put( cacheId, @@ -394,7 +397,9 @@ class HomeController( private def cmsPageByIdWithoutAction[A]( ctx: DaikokuActionMaybeWithoutUserContext[A], - id: String + id: String, + skipCache: Boolean = false, + fields: Map[String, JsValue] = Map.empty ) = { env.dataStore.cmsRepo.forTenant(ctx.tenant).findByIdNotDeleted(id).flatMap { case None => cmsPageNotFound(ctx) @@ -405,13 +410,24 @@ class HomeController( s"/auth/${ctx.tenant.authProvider.name}/login?redirect=${ctx.request.path}" ) ) - case Some(page) => render(ctx, page) + case Some(page) => + render(ctx, page, skipCache = skipCache, fields = fields) } } def cmsPageById(id: String) = DaikokuActionMaybeWithoutUser.async { ctx => - cmsPageByIdWithoutAction(ctx, id) + cmsPageByIdWithoutAction(ctx, id, skipCache = true) + } + + def advancedRenderCmsPageById(id: String) = + DaikokuActionMaybeWithoutUser.async(parse.json) { ctx => + cmsPageByIdWithoutAction(ctx, id, + skipCache = true, + fields = ctx.request.body + .asOpt[JsObject] + .flatMap(body => (body \ "fields").asOpt[Map[String, JsValue]]) + .getOrElse(Map.empty[String, JsValue])) } def getCmsPage(id: String) = @@ -431,265 +447,17 @@ class HomeController( } } - def session(userId: String) = - DaikokuAction.async { ctx => - DaikokuAdminOrSelf(AuditTrailEvent("@{user.name} get session"))( - UserId(userId), - ctx - ) { - val token = - ctx.request.cookies.get("daikoku-session").map(_.value).getOrElse("") - FastFuture.successful(Ok(Json.obj("token" -> token))) - } - } - - def sync() = - DaikokuAction.async(parse.json) { ctx => - TenantAdminOnly( - AuditTrailEvent("@{user.name} sync cms project") - )(ctx.tenant.id.value, ctx) { (tenant, _) => - { - val body = ctx.request.body - - (for { - _ <- env.dataStore.cmsRepo.forTenant(tenant).deleteAll() - } yield { - Future - .sequence( - body - .as(Reads.seq(CmsFileFormat.reads)) - .map(page => { - env.dataStore.cmsRepo - .forTenant(tenant) - .save(page.toCmsPage(ctx.tenant.id)) - }) - ) - .map(_ => NoContent) - .recover { - case e: Throwable => - BadRequest(Json.obj("error" -> e.getMessage)) - } - }).flatten - } - } - } - - def cmsDiffById(id: String, diffId: String) = - DaikokuAction.async { ctx => - TenantAdminOnly(AuditTrailEvent("@{user.name} has get a cms diff"))( - ctx.tenant.id.value, - ctx - ) { (tenant, _) => - { - val diffMatchPatch = new diff_match_patch() - - env.dataStore.cmsRepo - .forTenant(tenant) - .findById(id) - .map { - case None => NotFound(Json.obj("error" -> "cms page not found")) - case Some(page) => - val historySeq = buildCmsPageFromPatches(page.history, diffId) - val diffs = diffMatchPatch.diff_main( - page.draft, - historySeq - ) - Ok( - Json.obj( - "html" -> (if ( - ctx.request - .getQueryString("showDiffs") - .exists(_.toBoolean) - ) - diffMatchPatch.diff_prettyHtml(diffs) - else historySeq), - "hasDiff" -> !diffs.isEmpty - ) - ) - } - } - } - } - - def restoreDiff(id: String, diffId: String) = - DaikokuAction.async { ctx => - TenantAdminOnly( - AuditTrailEvent( - "@{user.name} has restore the cms page @{pageName} with revision of @{diffDate}" - ) - )(ctx.tenant.id.value, ctx) { (tenant, _) => - { - env.dataStore.cmsRepo - .forTenant(tenant) - .findById(id) - .flatMap { - case None => - FastFuture.successful( - NotFound(Json.obj("error" -> "cms page not found")) - ) - case Some(page) => - ctx.setCtxValue("pageName", page.name) - ctx.setCtxValue( - "diffDate", - page.history - .find(_.id == diffId) - .map(_.date) - .getOrElse("unknown date") - ) - - val newContentPage = - buildCmsPageFromPatches(page.history, diffId) - val history = diff(newContentPage, page.draft, ctx.user.id) - - env.dataStore.cmsRepo - .forTenant(tenant) - .save( - page.copy( - draft = newContentPage, - history = (page.history :+ history).take( - tenant.style.map(_.cmsHistoryLength).getOrElse(10) + 1 - ) - ) - ) - .map(_ => Ok(Json.obj("restored" -> true))) - } - } - } - } - - def createCmsPageWithName(name: String) = - DaikokuAction.async { ctx => - TenantAdminOnly( - AuditTrailEvent("@{user.name} has created a cms page with name") - )(ctx.tenant.id.value, ctx) { (tenant, _) => - { - val page = CmsPage( - id = CmsPageId(IdGenerator.token(32)), - tenant = tenant.id, - visible = true, - authenticated = false, - name = name, - forwardRef = None, - tags = List(), - metadata = Map(), - draft = "", - contentType = "text/html", - body = "", - path = Some("/" + IdGenerator.token(32)) - ) - env.dataStore.cmsRepo - .forTenant(tenant) - .save(page) - .map { - case true => Created(page.asJson) - case false => - BadRequest(Json.obj("error" -> "Error when creating cms page")) - } - } - } - } - - def diff(a: String, b: String, userId: UserId): CmsHistory = { - val patchMatch = new diff_match_patch() - val diff = patchMatch.patch_toText(patchMatch.patch_make(a, b)) - CmsHistory( - id = IdGenerator.token(32), - date = DateTime.now(), - diff = diff, - user = userId - ) - } - - private def buildCmsPageFromPatches( - history: Seq[CmsHistory], - diffId: String - ): String = { - var diffReached = false - val items = history.flatMap(item => { - if (item.id == diffId) { - diffReached = true - Some(item) - } else if (!diffReached) - Some(item) - else - None - }) - val diffMatchPatch = new diff_match_patch() - items.foldLeft("") { - case (text, current) => - diffMatchPatch.patch_apply( - new util.LinkedList(diffMatchPatch.patch_fromText(current.diff)), - text - ) - } - } - - def createCmsPage() = - DaikokuAction.async(parse.json) { ctx => - TenantAdminOnly(AuditTrailEvent("@{user.name} has created a cms page"))( - ctx.tenant.id.value, - ctx - ) { (tenant, _) => - { - val body = ctx.request.body.as[JsObject] - - val cmsPage = body ++ - Json.obj( - "_id" -> JsString( - (body \ "id") - .asOpt[String] - .getOrElse( - (body \ "_id") - .asOpt[String] - .getOrElse(IdGenerator.token(32)) - ) - ) - ) ++ - Json.obj("_tenant" -> tenant.id.value) - - json.CmsPageFormat.reads(cmsPage) match { - case JsSuccess(page, _) => - env.dataStore.cmsRepo - .forTenant(tenant) - .findByIdOrHrId(page.id.value) - .map { - case Some(cms) => - val d = diff(cms.draft, page.draft, ctx.user.id) - if (d.diff.nonEmpty) - page.copy(history = cms.history :+ d) - else - page.copy(history = cms.history) - case None => - val d = diff("", page.draft, ctx.user.id) - if (d.diff.nonEmpty) - page.copy(history = Seq(d)) - else - page - } - .flatMap(page => { - env.dataStore.cmsRepo - .forTenant(tenant) - .save( - page.copy(history = - page.history.takeRight( - tenant.style.map(_.cmsHistoryLength).getOrElse(10) + 1 - ) - ) - ) - .map { - case true => Created(Json.obj("created" -> true)) - case false => - BadRequest( - Json.obj("error" -> "Error when creating cms page") - ) - } - }) - case e: JsError => - FastFuture.successful(BadRequest(JsError.toJson(e))) - } - } - } - } +// def session(userId: String) = +// DaikokuAction.async { ctx => +// DaikokuAdminOrSelf(AuditTrailEvent("@{user.name} get session"))( +// UserId(userId), +// ctx +// ) { +// val token = +// ctx.request.cookies.get("daikoku-session").map(_.value).getOrElse("") +// FastFuture.successful(Ok(Json.obj("token" -> token))) +// } +// } def deleteCmsPage(id: String) = DaikokuAction.async { ctx => @@ -718,70 +486,24 @@ class HomeController( "text/xml" -> "xml" ) - def summary() = - DaikokuAction.async { ctx => - TenantAdminOnly( - AuditTrailEvent("@{user.name} has download the cms summary") - )(ctx.tenant.id.value, ctx) { (tenant, _) => - env.dataStore.cmsRepo - .forTenant(tenant) - .findAllNotDeleted() - .map(pages => { - val summary = pages.foldLeft(Json.arr()) { (acc, page) => - acc ++ Json - .arr(page.asJson.as[JsObject] - "draft" - "history") - } - - Ok(summary) - }) - } - } - - def download() = - DaikokuAction.async { ctx => - TenantAdminOnly( - AuditTrailEvent("@{user.name} has download all files of the cms") - )(ctx.tenant.id.value, ctx) { (tenant, _) => - env.dataStore.cmsRepo - .forTenant(tenant) - .findAllNotDeleted() - .map(pages => { - val outZip = new File(s"/tmp/${System.currentTimeMillis()}.zip") - val out = new ZipOutputStream(new FileOutputStream(outZip)) - - pages.foreach(page => { - val sb = new StringBuilder() - sb.append(page.body) - val data = sb.toString().getBytes() - - val e = new ZipEntry( - s"${page.name}.${contentTypeToExtension.getOrElse(page.contentType, ".txt")}" - ) - out.putNextEntry(e) - - out.write(data, 0, data.length) - }) - - val summary = pages.foldLeft(Json.arr()) { (acc, page) => - acc ++ Json - .arr(page.asJson.as[JsObject] - "body" - "draft" - "history") - } - - val sb = new StringBuilder() - sb.append(Json.stringify(summary)) - val data = sb.toString().getBytes() - - val e = new ZipEntry("summary.json") - out.putNextEntry(e) - out.write(data, 0, data.length) - - out.closeEntry() - out.close() - - Ok.sendFile(outZip) - }) - } - } +// def summary() = +// DaikokuAction.async { ctx => +// TenantAdminOnly( +// AuditTrailEvent("@{user.name} has download the cms summary") +// )(ctx.tenant.id.value, ctx) { (tenant, _) => +// env.dataStore.cmsRepo +// .forTenant(tenant) +// .findAllNotDeleted() +// .map(pages => { +// val summary = pages.foldLeft(Json.arr()) { (acc, page) => +// acc ++ Json +// .arr(page.asJson.as[JsObject] - "draft" - "history") +// } +// +// Ok(summary) +// }) +// } +// } def importFromZip() = DaikokuAction.async(parse.multipartFormData) { ctx => @@ -846,7 +568,6 @@ class HomeController( case Some((_, value)) => value case None => page.draft } - val d = diff("", content, ctx.user.id) env.dataStore.cmsRepo .forTenant(ctx.tenant) .save( @@ -854,7 +575,6 @@ class HomeController( draft = content, body = content, tenant = ctx.tenant.id, - history = if (d.diff.nonEmpty) Seq(d) else Seq.empty ) ) }) diff --git a/daikoku/app/controllers/LoginController.scala b/daikoku/app/controllers/LoginController.scala index f4314f644..5f5cf9f84 100644 --- a/daikoku/app/controllers/LoginController.scala +++ b/daikoku/app/controllers/LoginController.scala @@ -1,6 +1,7 @@ package fr.maif.otoroshi.daikoku.ctrls import com.eatthepath.otp.TimeBasedOneTimePasswordGenerator +import com.google.common.base.Charsets import controllers.Assets import fr.maif.otoroshi.daikoku.actions.{ DaikokuAction, @@ -46,58 +47,6 @@ class LoginController( implicit val ev: Env = env implicit val tr: Translator = translator - def redirectToLoginPage() = - DaikokuTenantAction.async { ctx => - { - val user = ctx.request.attrs.get(IdentityAttrs.UserKey) - val redirect = - ctx.request.queryString.get("redirect").flatMap(_.headOption) - - val hasUserAndRedirect = - redirect.isDefined && user.exists(u => !u.isGuest) - - println(hasUserAndRedirect, ctx.tenant.cmsRedirections) - - if ( - hasUserAndRedirect && (ctx.tenant.cmsRedirections.isEmpty || !ctx.tenant.cmsRedirections - .contains(redirect.get)) - ) { - FastFuture.successful( - BadRequest(Json.obj("error" -> "application not authorized")) - ) - } else { - if (hasUserAndRedirect) { - FastFuture.successful(Redirect(redirect.get)) - } else { - ctx.tenant.authProvider match { - case Otoroshi => FastFuture.successful(Redirect("/")) - case OAuth2 => - val authConfig = OAuth2Config - .fromJson(ctx.tenant.authProviderSettings) - .toOption - .get - val clientId = authConfig.clientId - val responseType = "code" - val scope = authConfig.scope // "openid profile email name" - val redirectUri = authConfig.callbackUrl - - FastFuture.successful( - Redirect( - s"${authConfig.loginUrl}?scope=${scope}&client_id=$clientId&response_type=$responseType&redirect_uri=$redirectUri" - ).addingToSession( - s"redirect" -> ctx.request - .getQueryString("redirect") - .getOrElse("/") - )(ctx.request) - ) - case _ => - assets.at("index.html").apply(ctx.request) - } - } - } - } - } - def loginContext(provider: String) = DaikokuActionMaybeWithoutUser { _ => Ok( @@ -236,11 +185,19 @@ class LoginController( AuthorizationLevel.AuthorizedSelf ) - val redirectUri = request.session + var redirectUri = request.session .get("redirect") .getOrElse(request.getQueryString("redirect").getOrElse("/")) - Redirect(if (redirectUri.startsWith("/api/")) "/" else redirectUri) + redirectUri = if (redirectUri.startsWith("/api/")) "/" else redirectUri + + try { + redirectUri = new String(Base64.getUrlDecoder.decode(redirectUri), Charsets.UTF_8) + } catch { + case _: Throwable => + } + + Redirect(redirectUri) .withSession("sessionId" -> session.sessionId.value) .removingFromSession("redirect")(request) } diff --git a/daikoku/app/controllers/TranslationController.scala b/daikoku/app/controllers/TranslationController.scala index 90d4c0431..dbcb20be7 100644 --- a/daikoku/app/controllers/TranslationController.scala +++ b/daikoku/app/controllers/TranslationController.scala @@ -4,16 +4,13 @@ import org.apache.pekko.http.scaladsl.util.FastFuture import org.apache.pekko.stream.Materializer import controllers.AppError import controllers.AppError.TranslationNotFound -import fr.maif.otoroshi.daikoku.actions.{ - DaikokuAction, - DaikokuActionMaybeWithGuest, - DaikokuActionMaybeWithoutUser -} +import fr.maif.otoroshi.daikoku.actions.{DaikokuAction, DaikokuActionMaybeWithGuest, DaikokuActionMaybeWithoutUser} import fr.maif.otoroshi.daikoku.audit.AuditTrailEvent import fr.maif.otoroshi.daikoku.ctrls.authorizations.async._ import fr.maif.otoroshi.daikoku.domain.json._ -import fr.maif.otoroshi.daikoku.domain.{DatastoreId, Translation} +import fr.maif.otoroshi.daikoku.domain.{DatastoreId, IntlTranslation, Translation} import fr.maif.otoroshi.daikoku.env.Env +import fr.maif.otoroshi.daikoku.services.TranslationsService import fr.maif.otoroshi.daikoku.utils.{IdGenerator, Translator} import org.joda.time.DateTime import play.api.i18n.I18nSupport @@ -27,7 +24,8 @@ class TranslationController( DaikokuActionMaybeWithoutUser: DaikokuActionMaybeWithoutUser, env: Env, cc: ControllerComponents, - translator: Translator + translator: Translator, + translationsService: TranslationsService ) extends AbstractController(cc) with I18nSupport { @@ -53,64 +51,7 @@ class TranslationController( TenantAdminOnly( AuditTrailEvent(s"@{user.name} has reset translations - @{tenant._id}") )(ctx.tenant.id.value, ctx) { (_, _) => - env.dataStore.translationRepo - .forTenant(ctx.tenant.id) - .find( - Json.obj( - "key" -> Json.obj( - "$regex" -> s".*${domain.getOrElse("mail")}", - "$options" -> "-i" - ) - ) - ) - .map(translations => { - val defaultTranslations = messagesApi.messages - .map(v => - ( - v._1, - v._2.filter(k => k._1.startsWith(domain.getOrElse("mail"))) - ) - ) - .flatMap { v => - v._2 - .map { - case (key, value) => - Translation( - id = DatastoreId(IdGenerator.token(32)), - tenant = ctx.tenant.id, - language = v._1, - key = key, - value = value - ) - } - .filter(t => languages.contains(t.language)) - } - - Ok( - Json.obj( - "translations" -> defaultTranslations - .map { translation => - translations.find(t => - t.key == translation.key && t.language == translation.language - ) match { - case None => translation - case Some(t) => t - } - } - .groupBy(_.key) - .map(v => - ( - v._1, - v._2.map(TranslationFormat.writes), - defaultTranslations - .find(p => p.key == v._1) - .map(_.value) - .getOrElse("") - ) - ) - ) - ) - }) + translationsService.getMailTranslations(ctx, domain, messagesApi, supportedLangs) } } diff --git a/daikoku/app/daikoku.scala b/daikoku/app/daikoku.scala index b93d1be35..eceb17150 100644 --- a/daikoku/app/daikoku.scala +++ b/daikoku/app/daikoku.scala @@ -4,36 +4,20 @@ import org.apache.pekko.http.scaladsl.util.FastFuture import org.apache.pekko.stream.Materializer import com.softwaremill.macwire._ import controllers.{Assets, AssetsComponents} -import fr.maif.otoroshi.daikoku.actions.{ - DaikokuAction, - DaikokuActionMaybeWithGuest, - DaikokuActionMaybeWithoutUser, - DaikokuTenantAction -} +import fr.maif.otoroshi.daikoku.actions.{CmsApiAction, DaikokuAction, DaikokuActionMaybeWithGuest, DaikokuActionMaybeWithoutUser, DaikokuTenantAction} import fr.maif.otoroshi.daikoku.ctrls._ import fr.maif.otoroshi.daikoku.env._ import fr.maif.otoroshi.daikoku.modules.DaikokuComponentsInstances +import fr.maif.otoroshi.daikoku.services.{AssetsService, TranslationsService} import fr.maif.otoroshi.daikoku.utils.RequestImplicits._ import fr.maif.otoroshi.daikoku.utils.admin._ -import fr.maif.otoroshi.daikoku.utils.{ - ApiService, - DeletionService, - Errors, - OtoroshiClient, - Translator -} +import fr.maif.otoroshi.daikoku.utils.{ApiService, DeletionService, Errors, OtoroshiClient, Translator} import io.vertx.core.Vertx import io.vertx.core.buffer.Buffer import io.vertx.core.net.{PemKeyCertOptions, PemTrustOptions} import io.vertx.pgclient.{PgConnectOptions, PgPool, SslMode} import io.vertx.sqlclient.PoolOptions -import jobs.{ - AnonymousReportingJob, - ApiKeyStatsJob, - AuditTrailPurgeJob, - OtoroshiVerifierJob, - QueueJob -} +import jobs.{AnonymousReportingJob, ApiKeyStatsJob, AuditTrailPurgeJob, OtoroshiVerifierJob, QueueJob} import play.api.ApplicationLoader.Context import play.api._ import play.api.http.{DefaultHttpFilters, HttpErrorHandler} @@ -77,6 +61,8 @@ package object modules { lazy val paymentClient = wire[PaymentClient] lazy val apiService = wire[ApiService] + lazy val assetsService = wire[AssetsService] + lazy val translationsService = wire[TranslationsService] lazy val deletionService = wire[DeletionService] lazy val translator = wire[Translator] @@ -95,6 +81,7 @@ package object modules { val daikokuTenantActionMaybeWithGuest = wire[DaikokuActionMaybeWithGuest] val daikokuActionMaybeWithoutUser = wire[DaikokuActionMaybeWithoutUser] val daikokuApiAction = wire[DaikokuApiAction] + val cmsApiAction = wire[CmsApiAction] val daikokuApiActionWithoutTenant = wire[DaikokuApiActionWithoutTenant] lazy val homeController = wire[HomeController] @@ -146,6 +133,8 @@ package object modules { lazy val subscriptionDemandsAdminApiController = wire[SubscriptionDemandsAdminApiController] lazy val graphQLController = wire[GraphQLController] + lazy val cmsApiController = wire[CmsApiController] + lazy val cmsApiSwaggerController = wire[CmsApiSwaggerController] override lazy val assets: Assets = wire[Assets] lazy val router: Router = { diff --git a/daikoku/app/domain/SchemaDefinition.scala b/daikoku/app/domain/SchemaDefinition.scala index 05740630f..1bd729adb 100644 --- a/daikoku/app/domain/SchemaDefinition.scala +++ b/daikoku/app/domain/SchemaDefinition.scala @@ -3859,12 +3859,6 @@ object SchemaDefinition { "lastPublishedDate", OptionType(LongType), resolve = _.value.lastPublishedDate.map(p => p.getMillis) - ), - Field( - "history", - ListType(CmsHistoryType), - resolve = _.value.history - .sortBy(_.date.toInstant.getMillis)(Ordering[Long].reverse) ) ) ) diff --git a/daikoku/app/domain/apiEntities.scala b/daikoku/app/domain/apiEntities.scala index dd62ecfab..3c407185f 100644 --- a/daikoku/app/domain/apiEntities.scala +++ b/daikoku/app/domain/apiEntities.scala @@ -873,6 +873,7 @@ case class ApiDocumentationPage( lastModificationAt: DateTime, content: String, contentType: String = "text/markdown", + cmsPage: Option[String] = None, remoteContentEnabled: Boolean = false, remoteContentUrl: Option[String] = None, remoteContentHeaders: Map[String, String] = Map.empty[String, String] @@ -991,6 +992,8 @@ case class Api( team: TeamId, name: String, smallDescription: String, + customHeaderCmsPage: Option[String] = None, + descriptionCmsPage: Option[String] = None, header: Option[String] = None, image: Option[String] = None, description: String, diff --git a/daikoku/app/domain/entities.scala b/daikoku/app/domain/entities.scala index cc41ce883..0effad959 100644 --- a/daikoku/app/domain/entities.scala +++ b/daikoku/app/domain/entities.scala @@ -145,6 +145,12 @@ case class Translation( } } +case class IntlTranslation( + id: String, + translations: Seq[Translation], + content: String +) + case class Evolution( id: DatastoreId, version: String, diff --git a/daikoku/app/domain/json.scala b/daikoku/app/domain/json.scala index 38c791215..06d8a02df 100644 --- a/daikoku/app/domain/json.scala +++ b/daikoku/app/domain/json.scala @@ -1858,6 +1858,7 @@ object json { lastModificationAt = (json \ "lastModificationAt").as(DateTimeFormat), content = (json \ "content").asOpt[String].getOrElse(""), + cmsPage = (json \ "cmsPage").asOpt[String], remoteContentEnabled = (json \ "remoteContentEnabled").asOpt[Boolean].getOrElse(false), contentType = @@ -1883,6 +1884,7 @@ object json { "title" -> o.title, "lastModificationAt" -> DateTimeFormat.writes(o.lastModificationAt), "content" -> o.content, + "cmsPage" -> o.cmsPage, "remoteContentEnabled" -> o.remoteContentEnabled, "contentType" -> o.contentType, "remoteContentUrl" -> o.remoteContentUrl @@ -2215,9 +2217,6 @@ object json { environments = (json \ "environments") .asOpt[Set[String]] .getOrElse(Set.empty), - cmsRedirections = (json \ "cmsRedirections") - .asOpt[Set[String]] - .getOrElse(Set.empty) ) ) } recover { @@ -2293,10 +2292,7 @@ object json { "thirdPartyPaymentSettings" -> SeqThirdPartyPaymentSettingsFormat .writes(o.thirdPartyPaymentSettings), "display" -> TenantDisplayFormat.writes(o.display), - "environments" -> JsArray(o.environments.map(JsString.apply).toSeq), - "cmsRedirections" -> JsArray( - o.cmsRedirections.map(JsString.apply).toSeq - ) + "environments" -> JsArray(o.environments.map(JsString.apply).toSeq) ) } val AuditTrailConfigFormat = new Format[AuditTrailConfig] { @@ -2567,6 +2563,10 @@ object json { description = (json \ "description").asOpt[String].getOrElse(""), smallDescription = (json \ "smallDescription").asOpt[String].getOrElse(""), + customHeaderCmsPage = + (json \ "customHeaderCmsPage").asOpt[String], + descriptionCmsPage = + (json \ "descriptionCmsPage").asOpt[String], header = (json \ "header").asOpt[String], image = (json \ "image").asOpt[String], currentVersion = (json \ "currentVersion").as(VersionFormat), @@ -2628,6 +2628,8 @@ object json { "lastUpdate" -> DateTimeFormat.writes(o.lastUpdate), "name" -> o.name, "smallDescription" -> o.smallDescription, + "customHeaderCmsPage" -> o.customHeaderCmsPage, + "descriptionCmsPage" -> o.descriptionCmsPage, "header" -> o.header.map(JsString).getOrElse(JsNull).as[JsValue], "image" -> o.image.map(JsString).getOrElse(JsNull).as[JsValue], "description" -> o.description, @@ -4230,6 +4232,30 @@ object json { ) } + val IntlTranslationFormat: Format[IntlTranslation] = new Format[IntlTranslation] { + override def reads(json: JsValue): JsResult[IntlTranslation] = + Try { + JsSuccess( + IntlTranslation( + id = (json \ "_id").as[String], + translations = (json \ "translations").asOpt(SeqTranslationFormat).getOrElse(Seq.empty), + content = (json \ "content").asOpt[String].getOrElse("") + ) + ) + } recover { + case e => + AppLogger.warn(e.getMessage) + JsError(e.getMessage) + } get + + override def writes(o: IntlTranslation): JsValue = + Json.obj( + "_id" -> o.id, + "translations" -> SeqTranslationFormat.writes(o.translations), + "content" -> o.content + ) + } + val TeamPermissionFormat = new Format[TeamPermission] { override def reads(json: JsValue) = json.as[String] match { @@ -4555,7 +4581,8 @@ object json { Json.obj( "name" -> o.name, "content" -> o.content, - "metadata" -> o.metadata + "metadata" -> o.metadata, + "daikoku_data" -> o.daikokuData ) override def reads(json: JsValue): JsResult[CmsFile] = Try { @@ -4563,7 +4590,9 @@ object json { name = (json \ "name").as[String], content = (json \ "content").as[String], metadata = - (json \ "metadata").asOpt[Map[String, JsValue]].getOrElse(Map.empty) + (json \ "metadata").asOpt[Map[String, JsValue]].getOrElse(Map.empty), + daikokuData = + (json \ "daikoku_data").asOpt[Map[String, String]], ) } match { case Failure(exception) => JsError(exception.getMessage) @@ -4633,8 +4662,7 @@ object json { "draft" -> o.draft, "path" -> o.path.map(JsString.apply).getOrElse(JsNull).as[JsValue], "exact" -> o.exact, - "lastPublishedDate" -> o.lastPublishedDate.map(DateTimeFormat.writes), - "history" -> SeqCmsHistoryFormat.writes(o.history) + "lastPublishedDate" -> o.lastPublishedDate.map(DateTimeFormat.writes) ) override def reads(json: JsValue): JsResult[CmsPage] = Try { @@ -4662,9 +4690,6 @@ object json { exact = (json \ "exact").asOpt[Boolean].getOrElse(false), lastPublishedDate = (json \ "lastPublishedDate").asOpt[DateTime](DateTimeFormat.reads), - history = (json \ "history") - .asOpt(SeqCmsHistoryFormat) - .getOrElse(Seq.empty) ) } match { case Failure(exception) => JsError(exception.getMessage) @@ -4819,8 +4844,6 @@ object json { Reads.set(OtoroshiServiceGroupIdFormat), Writes.set(OtoroshiServiceGroupIdFormat) ) - val SeqCmsHistoryFormat = - Format(Reads.seq(CmsHistoryFormat), Writes.seq(CmsHistoryFormat)) val SeqApiDocumentationDetailPageFormat : Format[Seq[ApiDocumentationDetailPage]] = Format( diff --git a/daikoku/app/domain/tenantEntities.scala b/daikoku/app/domain/tenantEntities.scala index 8eb588e9c..37c147efb 100644 --- a/daikoku/app/domain/tenantEntities.scala +++ b/daikoku/app/domain/tenantEntities.scala @@ -26,6 +26,7 @@ import play.api.libs.json._ import play.api.mvc.Request import storage.TenantCapableRepo +import java.util import java.util.concurrent.Executors import scala.concurrent.duration.DurationInt import scala.concurrent.{Await, ExecutionContext, Future} @@ -392,8 +393,7 @@ case class Tenant( robotTxt: Option[String] = None, thirdPartyPaymentSettings: Seq[ThirdPartyPaymentSettings] = Seq.empty, display: TenantDisplay = TenantDisplay.Default, - environments: Set[String] = Set.empty, - cmsRedirections: Set[String] = Set.empty + environments: Set[String] = Set.empty ) extends CanJson[Tenant] { override def asJson: JsValue = json.TenantFormat.writes(this) @@ -463,7 +463,6 @@ case class Tenant( "display" -> display.name, "environments" -> JsArray(environments.map(JsString.apply).toSeq), "loginProvider" -> authProvider.name, - "cmsRedirections" -> JsArray(cmsRedirections.map(JsString.apply).toSeq), "colorTheme" -> style .map(_.colorTheme) .map(JsString.apply) @@ -629,7 +628,8 @@ object CmsPage { case class CmsFile( name: String, content: String, - metadata: Map[String, JsValue] = Map.empty + metadata: Map[String, JsValue] = Map.empty, + daikokuData: Option[Map[String, String]] = None ) { def path(): String = metadata.getOrElse("_path", JsString("")).as[String] def contentType(): String = @@ -648,9 +648,16 @@ case class CmsFile( .parse(metadata.getOrElse("_exact", JsString("true")).as[String]) .as[Boolean] + def id() = { + val defaultId = path().replaceAll("/", "-") + daikokuData + .map(data => data.getOrElse("id", defaultId)) + .getOrElse(defaultId) + } + def toCmsPage(tenantId: TenantId): CmsPage = { CmsPage( - id = CmsPageId(path().replaceAll("/", "-")), + id = CmsPageId(id()), tenant = tenantId, visible = visible(), authenticated = authenticated(), @@ -692,8 +699,7 @@ case class CmsPage( draft: String, path: Option[String] = None, exact: Boolean = false, - lastPublishedDate: Option[DateTime] = None, - history: Seq[CmsHistory] = Seq.empty + lastPublishedDate: Option[DateTime] = None ) extends CanJson[CmsPage] { override def asJson: JsValue = json.CmsPageFormat.writes(this) @@ -1681,7 +1687,14 @@ case class CmsPage( fields: Map[String, Any], jsonToCombine: Map[String, JsValue] ): Context.Builder = - (fields ++ jsonToCombine).foldLeft(context) { (acc, item) => + (fields ++ jsonToCombine.map { case (key, value) => (key, value match { + case JsNull => null + case boolean: JsBoolean => boolean + case JsNumber(value) => value + case JsString(value) => value + case JsArray(value) => value + case o @ JsObject(underlying) => o + })}).foldLeft(context) { (acc, item) => acc.combine(item._1, item._2) } @@ -1743,7 +1756,13 @@ case class CmsPage( s"${env.getDaikokuUrl(ctx.tenant, "/assets/react-app/daikoku.min.css")}" } ), - fields, + fields.map { case (key, value) => (key, + value match { + case JsString(value) => + value // remove quotes framing string + case value => value + }) + }, jsonToCombine ) @@ -2050,6 +2069,7 @@ case class CmsPage( val result = handlebars.compileInline(template).apply(c) c.destroy() + FastFuture.successful((result, page.contentType)) } } catch { diff --git a/daikoku/app/env/env.scala b/daikoku/app/env/env.scala index c0e77a4e3..5f018f84c 100644 --- a/daikoku/app/env/env.scala +++ b/daikoku/app/env/env.scala @@ -150,6 +150,31 @@ object AdminApiConfig { } } +sealed trait CmsApiConfig + +case class LocalCmsApiConfig(key: String) extends CmsApiConfig + +case class OtoroshiCmsApiConfig(claimsHeaderName: String, algo: Algorithm) + extends CmsApiConfig + +object CmsApiConfig { + def apply(config: Configuration): CmsApiConfig = { + config.getOptional[String]("daikoku.cms.api.type") match { + case Some("local") => + LocalCmsApiConfig(config.getOptional[String]("daikoku.cms.api.key").get) + case Some("otoroshi") => + OtoroshiCmsApiConfig( + config.getOptional[String]("daikoku.cms.api.headerName").get, + Algorithm.HMAC512( + config.getOptional[String]("daikoku.cms.api.headerSecret").get + ) + ) + case _ => + LocalCmsApiConfig(config.getOptional[String]("daikoku.api.key").get) + } + } +} + class Config(val underlying: Configuration) { lazy val port: Int = underlying @@ -255,6 +280,8 @@ class Config(val underlying: Configuration) { lazy val adminApiConfig: AdminApiConfig = AdminApiConfig(underlying) + lazy val cmsApiConfig: CmsApiConfig = CmsApiConfig(underlying) + lazy val anonymousReportingUrl: String = underlying.get[String]("daikoku.anonymous-reporting.url") lazy val anonymousReportingTimeout: Int = @@ -439,6 +466,8 @@ class DaikokuEnv( val administrationTeamId = TeamId("administration") val adminApiDefaultTenantId = ApiId(s"admin-api-tenant-${Tenant.Default.value}") + val cmsApiDefaultTenantId = + ApiId(s"cms-api-tenant-${Tenant.Default.value}") val defaultAdminTeam = Team( id = TeamId(IdGenerator.token), tenant = Tenant.Default, @@ -489,6 +518,45 @@ class DaikokuEnv( authorizedTeams = Seq.empty, state = ApiState.Published ) + + val cmsApiDefaultPlan = FreeWithoutQuotas( + id = UsagePlanId(IdGenerator.token), + tenant = Tenant.Default, + billingDuration = BillingDuration(1, BillingTimeUnit.Month), + currency = Currency("EUR"), + customName = Some("admin"), + customDescription = None, + otoroshiTarget = None, + allowMultipleKeys = Some(true), + autoRotation = None, + subscriptionProcess = Seq.empty, + integrationProcess = IntegrationProcess.ApiKey + ) + + val cmsApiDefaultTenant = Api( + id = cmsApiDefaultTenantId, + tenant = Tenant.Default, + team = defaultAdminTeam.id, + name = s"cms-api-tenant-${Tenant.Default.value}", + lastUpdate = DateTime.now(), + smallDescription = "cms api", + description = "cms api", + currentVersion = Version("1.0.0"), + documentation = ApiDocumentation( + id = ApiDocumentationId(IdGenerator.token(32)), + tenant = Tenant.Default, + pages = Seq.empty[ApiDocumentationDetailPage], + lastModificationAt = DateTime.now() + ), + swagger = + Some(SwaggerAccess(url = "/cms-api/swagger.json".some)), + possibleUsagePlans = Seq(cmsApiDefaultPlan.id), + visibility = ApiVisibility.AdminOnly, + defaultUsagePlan = cmsApiDefaultPlan.id.some, + authorizedTeams = Seq.empty, + state = ApiState.Published + ) + val tenant = Tenant( id = Tenant.Default, name = "Daikoku Default Tenant", @@ -560,6 +628,14 @@ class DaikokuEnv( dataStore.usagePlanRepo .forTenant(tenant.id) .save(adminApiDefaultPlan) + _ <- + dataStore.apiRepo + .forTenant(tenant.id) + .save(cmsApiDefaultTenant) + _ <- + dataStore.usagePlanRepo + .forTenant(tenant.id) + .save(cmsApiDefaultPlan) _ <- dataStore.userRepo.save(user) } yield () diff --git a/daikoku/app/env/evolutions.scala b/daikoku/app/env/evolutions.scala index b483308ab..4e52a427d 100644 --- a/daikoku/app/env/evolutions.scala +++ b/daikoku/app/env/evolutions.scala @@ -6,17 +6,9 @@ import org.apache.pekko.stream.Materializer import org.apache.pekko.stream.scaladsl.{Sink, Source} import cats.data.OptionT import cats.implicits.catsSyntaxOptionId +import fr.maif.otoroshi.daikoku.domain.UsagePlan.FreeWithoutQuotas import fr.maif.otoroshi.daikoku.domain._ -import fr.maif.otoroshi.daikoku.domain.json.{ - ApiDocumentationPageFormat, - ApiFormat, - ApiSubscriptionFormat, - SeqApiDocumentationDetailPageFormat, - TeamFormat, - TeamIdFormat, - TenantFormat, - UserFormat -} +import fr.maif.otoroshi.daikoku.domain.json.{ApiDocumentationPageFormat, ApiFormat, ApiSubscriptionFormat, SeqApiDocumentationDetailPageFormat, TeamFormat, TeamIdFormat, TenantFormat, UserFormat} import fr.maif.otoroshi.daikoku.logger.AppLogger import fr.maif.otoroshi.daikoku.utils.{IdGenerator, OtoroshiClient} import org.joda.time.DateTime @@ -1093,6 +1085,95 @@ object evolution_1634 extends EvolutionScript { } } +object evolution_1750 extends EvolutionScript { + override def version: String = "17.5.0" + + override def script: ( + Option[DatastoreId], + DataStore, + Materializer, + ExecutionContext, + OtoroshiClient + ) => Future[Done] = + ( + _: Option[DatastoreId], + dataStore: DataStore, + mat: Materializer, + ec: ExecutionContext, + _: OtoroshiClient + ) => { + AppLogger.info( + s"Begin evolution $version - create cms api" + ) + + implicit val executionContext: ExecutionContext = ec + + val cmsApiDefaultPlan = FreeWithoutQuotas( + id = UsagePlanId(IdGenerator.token), + tenant = Tenant.Default, + billingDuration = BillingDuration(1, BillingTimeUnit.Month), + currency = Currency("EUR"), + customName = Some("admin"), + customDescription = None, + otoroshiTarget = None, + allowMultipleKeys = Some(true), + autoRotation = None, + subscriptionProcess = Seq.empty, + integrationProcess = IntegrationProcess.ApiKey + ) + + val cmsApiDefaultTenantId = + ApiId(s"cms-api-tenant-${Tenant.Default.value}") + + for { + tenants <-dataStore.tenantRepo.findAll() + _ <- Future.sequence(tenants.map(tenant => dataStore.teamRepo + .forTenant(tenant) + .findOne(Json.obj("type" -> TeamType.Admin.name)) + .flatMap(team => { + if(team.isDefined) { + val cmsApiDefaultTenant = Api( + id = cmsApiDefaultTenantId, + tenant = Tenant.Default, + team = team.get.id, + name = s"cms-api-tenant-${Tenant.Default.value}", + lastUpdate = DateTime.now(), + smallDescription = "cms api", + description = "cms api", + currentVersion = Version("1.0.0"), + documentation = ApiDocumentation( + id = ApiDocumentationId(IdGenerator.token(32)), + tenant = Tenant.Default, + pages = Seq.empty[ApiDocumentationDetailPage], + lastModificationAt = DateTime.now() + ), + swagger = + Some(SwaggerAccess(url = "/cms-api/swagger.json".some)), + possibleUsagePlans = Seq(cmsApiDefaultPlan.id), + visibility = ApiVisibility.AdminOnly, + defaultUsagePlan = cmsApiDefaultPlan.id.some, + authorizedTeams = Seq.empty, + state = ApiState.Published + ) + + Future.sequence(Seq( + dataStore.apiRepo + .forTenant(tenant.id) + .save(cmsApiDefaultTenant), + dataStore.usagePlanRepo + .forTenant(tenant.id) + .save(cmsApiDefaultPlan) + )) + } else { + Future.successful(()) + } + }))) + } yield { + Done + } + } +} + object evolutions { val list: List[EvolutionScript] = List( @@ -1109,7 +1190,8 @@ object evolutions { evolution_1613, evolution_1613_b, evolution_1630, - evolution_1634 + evolution_1634, + evolution_1750 ) def run( dataStore: DataStore, diff --git a/daikoku/app/services/AssetsService.scala b/daikoku/app/services/AssetsService.scala new file mode 100644 index 000000000..dece2cf71 --- /dev/null +++ b/daikoku/app/services/AssetsService.scala @@ -0,0 +1,466 @@ +package fr.maif.otoroshi.daikoku.services + +import fr.maif.otoroshi.daikoku.actions.ApiActionContext +import fr.maif.otoroshi.daikoku.audit.AuditTrailEvent +import fr.maif.otoroshi.daikoku.ctrls.CmsApiActionContext +import fr.maif.otoroshi.daikoku.domain.{Asset, AssetId} +import fr.maif.otoroshi.daikoku.env.Env +import fr.maif.otoroshi.daikoku.logger.AppLogger +import fr.maif.otoroshi.daikoku.services.NormalizeSupport.normalize +import fr.maif.otoroshi.daikoku.utils.IdGenerator +import fr.maif.otoroshi.daikoku.utils.StringImplicits.BetterString +import org.apache.pekko.http.scaladsl.util.FastFuture +import org.apache.pekko.stream.connectors.s3.ObjectMetadata +import org.apache.pekko.stream.scaladsl.{Sink, Source} +import org.apache.pekko.util.ByteString +import play.api.http.HttpEntity +import play.api.libs.json.{JsArray, Json} +import play.api.mvc.AnyContent +import play.api.mvc.Results._ + +import scala.concurrent.{ExecutionContext, Future} +import scala.concurrent.duration.DurationInt +import scala.jdk.CollectionConverters._ + +trait NormalizeSupport { + + import java.text.Normalizer.{normalize => jnormalize, _} + + def normalize(in: String): String = { + val cleaned = in.trim.toLowerCase + val tuple = cleaned.splitAt(cleaned.lastIndexOf('.')) + + val normalized = jnormalize(tuple._1, Form.NFC) + + val fileNameNormalized = normalized + .replaceAll("'s", "") + .replaceAll("ß", "ss") + .replaceAll("ø", "o") + .replaceAll("[^a-zA-Z0-9-]+", "-") + .replaceAll("-+", "-") + .stripSuffix("-") + + fileNameNormalized + tuple._2 + } +} + +object NormalizeSupport extends NormalizeSupport + +class AssetsService { + + def storeAssets[T]( + ctx: ApiActionContext[T], + body: Source[ByteString, _] + )(implicit env: Env) = { + + implicit val ec: ExecutionContext = env.defaultExecutionContext + + ctx.tenant.bucketSettings match { + case None => + FastFuture.successful( + NotFound(Json.obj("error" -> "No bucket config found !")) + ) + case Some(cfg) => + body + .runWith(Sink.reduce[ByteString](_ ++ _))(env.defaultMaterializer) + .map(str => str.utf8String) + .map(Json.parse) + .flatMap(items => + Future.sequence( + items + .as[JsArray] + .value + .map(item => { + val filename = (item \ "filename").as[String] + val assetId = AssetId(IdGenerator.uuid) + val slug = filename.slugify + + env.assetsStore + .storeTenantAsset( + ctx.tenant.id, + assetId, + name = filename, + title = filename, + desc = filename, + contentType = "application/octet-stream", + content = Source + .single((item \ "content").get) + .map(Json.stringify) + .map(ByteString.apply) + )(cfg) + .flatMap { _ => + internalDeleteAsset(slug, ctx) + .map(_ => + env.dataStore.assetRepo + .forTenant(ctx.tenant) + .save( + Asset( + assetId, + tenant = ctx.tenant.id, + slug = slug + ) + ) + ) + .map(_ => + Json.obj( + "done" -> true, + "id" -> assetId.value, + "slug" -> slug + ) + ) + } recover { + case e => + AppLogger.error( + s"Error during tenant asset storage: $filename", + e + ) + Json + .obj("id" -> assetId.value, "error" -> ec.toString) + } + }) + ) + ) + .map(results => Ok(Json.arr(results))) + .recover { + case e: Throwable => + BadRequest(Json.obj("error" -> e.getMessage)) + } + } + } + + def storeAsset[T]( + ctx: ApiActionContext[T], + body: Source[ByteString, _] + )(implicit env: Env) = { + implicit val ec = env.defaultExecutionContext + + val contentType = ctx.request.headers + .get("Asset-Content-Type") + .orElse(ctx.request.contentType) + .getOrElse("application/octet-stream") +// .replace("text/xml", "application/xml") + + val filename = normalize( + ctx.request + .getQueryString("filename") + .getOrElse(IdGenerator.token(16)) + ) + val title = + normalize(ctx.request.getQueryString("title").getOrElse("--")) + val desc = ctx.request.getQueryString("desc").getOrElse("--") + val querySlug: Option[String] = ctx.request + .getQueryString("slug") + .flatMap(slug => if (slug.isEmpty) None else Some(slug)) + val assetId = AssetId(IdGenerator.uuid) + + ctx.tenant.bucketSettings match { + case None => + FastFuture.successful( + NotFound(Json.obj("error" -> "No bucket config found !")) + ) + case Some(cfg) => + env.assetsStore + .storeTenantAsset( + ctx.tenant.id, + assetId, + filename, + title, + desc, + contentType, + body + )(cfg) + .flatMap { _ => + val slug = querySlug.map(_.slugify).getOrElse(filename.slugify) + + env.dataStore.assetRepo + .forTenant(ctx.tenant) + .save(Asset(assetId, tenant = ctx.tenant.id, slug = slug)) + .map(_ => + Ok( + Json.obj( + "done" -> true, + "id" -> assetId.value, + "slug" -> slug + ) + ) + ) + + } recover { + case e => + AppLogger.error( + s"Error during tenant asset storage: ${filename}", + e + ) + InternalServerError(Json.obj("error" -> ec.toString)) + } + } + } + + def replaceAsset[T <: Source[ByteString, _]]( + assetId: String, + ctx: ApiActionContext[T] + )(implicit + env: Env + ) = { + + implicit val ec = env.defaultExecutionContext + + def getMetaHeaderValue( + metadata: ObjectMetadata, + headerName: String + ): Option[String] = { + metadata.headers.asScala + .find(_.name() == s"x-amz-meta-$headerName") + .map(_.value()) + } + + ctx.tenant.bucketSettings match { + case None => + FastFuture.successful( + NotFound(Json.obj("error" -> "No bucket config found !")) + ) + case Some(cfg) => + env.assetsStore + .getTenantAssetMetaHeaders(ctx.tenant.id, AssetId(assetId))(cfg) + .flatMap { + case None => + FastFuture.successful( + NotFound(Json.obj("error" -> "Asset not found")) + ) + case Some(metadata) => + val filename = + getMetaHeaderValue(metadata, "filename").getOrElse("--") + val desc = + getMetaHeaderValue(metadata, "desc").getOrElse("--") + val title = + getMetaHeaderValue(metadata, "title").getOrElse("--") + val contentType = metadata.contentType + .orElse(ctx.request.contentType) + .getOrElse("application/octet-stream") + + env.assetsStore + .storeTenantAsset( + ctx.tenant.id, + AssetId(assetId), + filename, + title, + desc, + contentType, + ctx.request.body + )(cfg) + .flatMap { _ => + val slug = filename.slugify + env.dataStore.assetRepo + .forTenant(ctx.tenant) + .deleteById(assetId) + .flatMap(_ => + env.dataStore.assetRepo + .forTenant(ctx.tenant) + .save( + Asset( + AssetId(assetId), + tenant = ctx.tenant.id, + slug + ) + ) + ) + .map(_ => Ok(Json.obj("done" -> true, "id" -> assetId))) + } recover { + case e => + AppLogger + .error( + s"Error during update tenant asset: $filename", + e + ) + InternalServerError(Json.obj("error" -> ec.toString)) + } + } + } + } + + def listAssets[T](ctx: ApiActionContext[T])(implicit env: Env) = { + implicit val ec = env.defaultExecutionContext + + ctx.request.getQueryString("teamId") match { + case Some(teamId) => + ctx.tenant.bucketSettings match { + case None => + FastFuture.successful( + NotFound(Json.obj("error" -> "No bucket config found !")) + ) + case Some(cfg) => + env.assetsStore.listTenantAssets(ctx.tenant.id)(cfg).map { res => + Ok(JsArray(res.map(_.asJson))) + } + } + case None => + ctx.tenant.bucketSettings match { + case None => + FastFuture.successful( + NotFound(Json.obj("error" -> "No bucket config found !")) + ) + case Some(cfg) => + env.assetsStore.listTenantAssets(ctx.tenant.id)(cfg).map { res => + Ok(JsArray(res.map(_.asJson))) + } + } + } + } + + def slugifiedAssets[T](ctx: ApiActionContext[T])(implicit env: Env) = { + implicit val ec = env.defaultExecutionContext + env.dataStore.assetRepo.forTenant(ctx.tenant).findAllNotDeleted().map { + res => Ok(JsArray(res.map(_.asJson))) + } + } + + def deleteAsset[T](assetId: String, ctx: ApiActionContext[T])(implicit env: Env) = { + implicit val ec = env.defaultExecutionContext + + ctx.setCtxValue("assetId", assetId) + internalDeleteAsset(assetId, ctx) + } + + private def internalDeleteAsset[T]( + assetId: String, + ctx: ApiActionContext[T] + )(implicit env: Env) = { + implicit val ec: ExecutionContext = env.defaultExecutionContext + + ctx.tenant.bucketSettings match { + case None => + FastFuture.successful( + NotFound(Json.obj("error" -> "No bucket config found !")) + ) + case Some(cfg) => + env.dataStore.assetRepo + .forTenant(ctx.tenant) + .findOne(Json.obj("slug" -> assetId)) + .map(_.map(_.id.value)) + .map { + case None => assetId + case Some(id) => id + } + .flatMap(id => { + env.assetsStore + .deleteTenantAsset(ctx.tenant.id, AssetId(id))(cfg) + .flatMap { _ => + env.dataStore.assetRepo + .forTenant(ctx.tenant) + .deleteById(id) + .map(_ => Ok(Json.obj("done" -> true))) + } + }) + } + } + + def doesAssetExists[T](slug: String, ctx: ApiActionContext[T])(implicit + env: Env + ) = { + implicit val ec = env.defaultExecutionContext + + ctx.tenant.bucketSettings match { + case None => + FastFuture.successful( + NotFound(Json.obj("error" -> "No bucket config found !")) + ) + case Some(cfg) => + env.dataStore.assetRepo + .forTenant(ctx.tenant) + .findOne(Json.obj("slug" -> slug)) + .map { + case Some(_) => NoContent + case None => NotFound + } + } + } + + def getAsset[T](assetId: String, ctx: ApiActionContext[T])(implicit + env: Env + ) = { + implicit val ec = env.defaultExecutionContext + + ctx.tenant.bucketSettings match { + case None => + FastFuture.successful( + NotFound(Json.obj("error" -> "No bucket config found !")) + ) + case Some(cfg) => + val download = ctx.request.getQueryString("download").contains("true") + val redirect = ctx.request.getQueryString("redirect").contains("true") + + env.dataStore.assetRepo + .forTenant(ctx.tenant) + .findOne(Json.obj("slug" -> assetId)) + .map { + case Some(asset) => + env.assetsStore.getTenantAssetPresignedUrl( + ctx.tenant.id, + asset.id + )(cfg) + case None => + env.assetsStore.getTenantAssetPresignedUrl( + ctx.tenant.id, + AssetId(assetId) + )(cfg) + } + .flatMap { + case None => + FastFuture.successful( + NotFound(Json.obj("error" -> "Asset not found!")) + ) + case Some(url) if redirect => FastFuture.successful(Redirect(url)) + case Some(_) if download => + env.assetsStore + .getTenantAsset(ctx.tenant.id, AssetId(assetId))(cfg) + .map { + case None => + NotFound(Json.obj("error" -> "Asset not found!")) + case Some((source, meta)) => + val filename = meta.metadata + .filter(_.name().startsWith("x-amz-meta-")) + .find(_.name() == "x-amz-meta-filename") + .map(_.value()) + .getOrElse("asset.txt") + + Ok.sendEntity( + HttpEntity.Streamed( + source, + None, + meta.contentType + .map(Some.apply) + .getOrElse(Some("application/octet-stream")) + ) + ) + .withHeaders( + "Content-Disposition" -> s"""attachment; filename="$filename"""" + ) + } + case Some(url) => + env.wsClient + .url(url) + .withRequestTimeout(10.minutes) + .get() + .map(resp => { + resp.status match { + case 200 => + Ok.sendEntity( + HttpEntity.Streamed( + resp.bodyAsSource, + None, + Option(resp.contentType) + ) + ) + case _ => + NotFound(Json.obj("error" -> "Asset not found!")) + } + }) + .recover { + case err => + InternalServerError(Json.obj("error" -> err.getMessage)) + } + } + } + } + +} diff --git a/daikoku/app/services/TranslationsService.scala b/daikoku/app/services/TranslationsService.scala new file mode 100644 index 000000000..7f98a2f4d --- /dev/null +++ b/daikoku/app/services/TranslationsService.scala @@ -0,0 +1,94 @@ +package fr.maif.otoroshi.daikoku.services + +import fr.maif.otoroshi.daikoku.actions.{ApiActionContext} +import fr.maif.otoroshi.daikoku.domain.json.IntlTranslationFormat +import fr.maif.otoroshi.daikoku.domain.{ + DatastoreId, + IntlTranslation, + Translation +} +import fr.maif.otoroshi.daikoku.env.Env +import fr.maif.otoroshi.daikoku.utils.IdGenerator +import play.api.i18n.Langs +import play.api.libs.json.Json +import play.api.mvc.Results._ + +import scala.concurrent.ExecutionContext + +class TranslationsService { + + def getMailTranslations[T]( + ctx: ApiActionContext[T], + domain: Option[String], + messagesApi: play.api.i18n.MessagesApi, + supportedLangs: Langs + )(implicit + env: Env + ) = { + + implicit val ec: ExecutionContext = env.defaultExecutionContext + implicit val languages: Seq[String] = + supportedLangs.availables.map(_.language) + + env.dataStore.translationRepo + .forTenant(ctx.tenant.id) + .find( + Json.obj( + "key" -> Json.obj( + "$regex" -> s".*${domain.getOrElse("mail")}", + "$options" -> "-i" + ) + ) + ) + .map(translations => { + val defaultTranslations = messagesApi.messages + .map(v => + ( + v._1, + v._2.filter(k => k._1.startsWith(domain.getOrElse("mail"))) + ) + ) + .flatMap { v => + v._2 + .map { + case (key, value) => + Translation( + id = DatastoreId(IdGenerator.token(32)), + tenant = ctx.tenant.id, + language = v._1, + key = key, + value = value + ) + } + .filter(t => languages.contains(t.language)) + } + + Ok( + Json.obj( + "translations" -> defaultTranslations + .map { translation => + translations.find(t => + t.key == translation.key && t.language == translation.language + ) match { + case None => translation + case Some(t) => t + } + } + .groupBy(_.key) + .map(v => + IntlTranslationFormat.writes( + IntlTranslation( + id = v._1, + translations = v._2.toSeq, + content = defaultTranslations + .find(p => p.key == v._1) + .map(_.value) + .getOrElse("") + ) + ) + ) + ) + ) + }) + } +} diff --git a/daikoku/app/utils/ApiService.scala b/daikoku/app/utils/ApiService.scala index edb65d00b..c115aec88 100644 --- a/daikoku/app/utils/ApiService.scala +++ b/daikoku/app/utils/ApiService.scala @@ -9,10 +9,12 @@ import cats.data.{EitherT, OptionT} import cats.implicits.catsSyntaxOptionId import controllers.AppError import controllers.AppError._ +import fr.maif.otoroshi.daikoku.actions.ApiActionContext import fr.maif.otoroshi.daikoku.ctrls.PaymentClient import fr.maif.otoroshi.daikoku.domain.TeamPermission.Administrator import fr.maif.otoroshi.daikoku.domain.UsagePlan._ import fr.maif.otoroshi.daikoku.domain._ +import fr.maif.otoroshi.daikoku.domain.json.SeqApiFormat import fr.maif.otoroshi.daikoku.env.Env import fr.maif.otoroshi.daikoku.logger.AppLogger import fr.maif.otoroshi.daikoku.utils.Cypher.{decrypt, encrypt} @@ -30,14 +32,14 @@ import scala.concurrent.{ExecutionContext, Future} import scala.util.Try class ApiService( - env: Env, - otoroshiClient: OtoroshiClient, - messagesApi: MessagesApi, - translator: Translator, - apiKeyStatsJob: ApiKeyStatsJob, - otoroshiSynchronisator: OtoroshiVerifierJob, - paymentClient: PaymentClient -) { + env: Env, + otoroshiClient: OtoroshiClient, + messagesApi: MessagesApi, + translator: Translator, + apiKeyStatsJob: ApiKeyStatsJob, + otoroshiSynchronisator: OtoroshiVerifierJob, + paymentClient: PaymentClient + ) { implicit val ec: ExecutionContext = env.defaultExecutionContext implicit val ev: Env = env @@ -47,14 +49,14 @@ class ApiService( def jsonToOtoroshiMetadata(json: JsObject) = { json.fieldSet.map { case (a, b: JsString) => a -> b.value - case (a, b) => a -> Json.stringify(b) + case (a, b) => a -> Json.stringify(b) }.toMap + ("raw_custom_metadata" -> Json.stringify(json)) } def getListFromStringMap( - key: String, - metadata: Map[String, String] - ): Set[String] = { + key: String, + metadata: Map[String, String] + ): Set[String] = { metadata .get(key) .map(_.split('|').toSeq.map(_.trim).toSet) @@ -62,10 +64,10 @@ class ApiService( } def mergeStringMapValue( - key: String, - meta1: Map[String, String], - meta2: Map[String, String] - ): String = { + key: String, + meta1: Map[String, String], + meta2: Map[String, String] + ): String = { val list1 = getListFromStringMap(key, meta1) val list2 = getListFromStringMap(key, meta2) (list1 ++ list2).mkString(" | ") @@ -90,10 +92,14 @@ class ApiService( OtoroshiApiKey( clientId = IdGenerator.token(32), clientSecret = IdGenerator.token(64), - clientName = s"daikoku-api-key-${api.humanReadableId}-${plan.customName - .getOrElse(plan.typeName) - .urlPathSegmentSanitized}-${team.humanReadableId}-${System - .currentTimeMillis()}-${api.currentVersion.value}" + clientName = s"daikoku-api-key-${api.humanReadableId}-${ + plan.customName + .getOrElse(plan.typeName) + .urlPathSegmentSanitized + }-${team.humanReadableId}-${ + System + .currentTimeMillis() + }-${api.currentVersion.value}" ) ) @@ -183,31 +189,31 @@ class ApiService( } def subscribeToApi( - tenant: Tenant, - user: User, - api: Api, - plan: UsagePlan, - team: Team, - parentSubscriptionId: Option[ApiSubscriptionId] = None, - customMetadata: Option[JsObject] = None, - customMaxPerSecond: Option[Long] = None, - customMaxPerDay: Option[Long] = None, - customMaxPerMonth: Option[Long] = None, - customReadOnly: Option[Boolean] = None, - adminCustomName: Option[String] = None, - thirdPartySubscriptionInformations: Option[ - ThirdPartySubscriptionInformations - ] - ): Future[Either[AppError, ApiSubscription]] = { + tenant: Tenant, + user: User, + api: Api, + plan: UsagePlan, + team: Team, + parentSubscriptionId: Option[ApiSubscriptionId] = None, + customMetadata: Option[JsObject] = None, + customMaxPerSecond: Option[Long] = None, + customMaxPerDay: Option[Long] = None, + customMaxPerMonth: Option[Long] = None, + customReadOnly: Option[Boolean] = None, + adminCustomName: Option[String] = None, + thirdPartySubscriptionInformations: Option[ + ThirdPartySubscriptionInformations + ] + ): Future[Either[AppError, ApiSubscription]] = { def createKey( - api: Api, - plan: UsagePlan, - team: Team, - authorizedEntities: AuthorizedEntities, - parentSubscriptionId: Option[ApiSubscriptionId] - )(implicit - otoroshiSettings: OtoroshiSettings - ): Future[Either[AppError, ApiSubscription]] = { + api: Api, + plan: UsagePlan, + team: Team, + authorizedEntities: AuthorizedEntities, + parentSubscriptionId: Option[ApiSubscriptionId] + )(implicit + otoroshiSettings: OtoroshiSettings + ): Future[Either[AppError, ApiSubscription]] = { import cats.implicits._ EitherT(parentSubscriptionId match { @@ -222,7 +228,7 @@ class ApiService( .findById(id.value) .map { case Some(sub) => Right((Some(sub), Some(sub.apiKey))) - case None => Left(AppError.SubscriptionNotFound) + case None => Left(AppError.SubscriptionNotFound) } }).flatMap { case (maybeParentSub, otoroshiApiKey) => @@ -281,7 +287,7 @@ class ApiService( ) val otoroshiApiKeyActionResult - : EitherT[Future, AppError, ActualOtoroshiApiKey] = + : EitherT[Future, AppError, ActualOtoroshiApiKey] = maybeParentSub match { case Some(subscription) => EitherT(otoroshiClient.getApikey(subscription.apiKey.clientId)) @@ -342,18 +348,20 @@ class ApiService( } def createAdminKey( - api: Api, - plan: UsagePlan - ): Future[Either[AppError, ApiSubscription]] = { + api: Api, + plan: UsagePlan + ): Future[Either[AppError, ApiSubscription]] = { import cats.implicits._ // TODO: verify if group is in authorized groups (if some) val clientId = IdGenerator.token(32) val clientSecret = IdGenerator.token(64) val clientName = - s"daikoku-api-key-${api.humanReadableId}-${plan.customName - .getOrElse(plan.typeName) - .urlPathSegmentSanitized}-${team.humanReadableId}-${System.currentTimeMillis()}" + s"daikoku-api-key-${api.humanReadableId}-${ + plan.customName + .getOrElse(plan.typeName) + .urlPathSegmentSanitized + }-${team.humanReadableId}-${System.currentTimeMillis()}" val apiSubscription = ApiSubscription( id = ApiSubscriptionId(IdGenerator.token(32)), tenant = tenant.id, @@ -685,10 +693,10 @@ class ApiService( } def updateSubscription( - tenant: Tenant, - subscription: ApiSubscription, - plan: UsagePlan - ): Future[Either[AppError, JsObject]] = { + tenant: Tenant, + subscription: ApiSubscription, + plan: UsagePlan + ): Future[Either[AppError, JsObject]] = { import cats.implicits._ val maybeTarget = plan.otoroshiTarget.map(_.otoroshiSettings).flatMap { @@ -777,11 +785,11 @@ class ApiService( } def deleteApiKey( - tenant: Tenant, - subscription: ApiSubscription, - plan: UsagePlan, - team: Team - ): Future[Either[AppError, JsObject]] = { + tenant: Tenant, + subscription: ApiSubscription, + plan: UsagePlan, + team: Team + ): Future[Either[AppError, JsObject]] = { def deleteKey()(implicit otoroshiSettings: OtoroshiSettings ): EitherT[Future, AppError, JsObject] = { @@ -914,11 +922,11 @@ class ApiService( } def archiveApiKey( - tenant: Tenant, - subscription: ApiSubscription, - plan: UsagePlan, - enabled: Boolean - ): Future[Either[AppError, JsObject]] = { + tenant: Tenant, + subscription: ApiSubscription, + plan: UsagePlan, + enabled: Boolean + ): Future[Either[AppError, JsObject]] = { import cats.implicits._ val updatedSubscription = subscription.copy(enabled = enabled) @@ -978,13 +986,13 @@ class ApiService( } def regenerateApiKeySecret( - tenant: Tenant, - subscription: ApiSubscription, - plan: UsagePlan, - api: Api, - team: Team, - user: User - ): Future[Either[AppError, JsObject]] = { + tenant: Tenant, + subscription: ApiSubscription, + plan: UsagePlan, + api: Api, + team: Team, + user: User + ): Future[Either[AppError, JsObject]] = { import cats.implicits._ plan.otoroshiTarget.map(_.otoroshiSettings).flatMap { id => @@ -1095,14 +1103,14 @@ class ApiService( } def toggleApiKeyRotation( - tenant: Tenant, - subscription: ApiSubscription, - plan: UsagePlan, - api: Api, - enabled: Boolean, - rotationEvery: Long, - gracePeriod: Long - ): Future[Either[AppError, JsObject]] = { + tenant: Tenant, + subscription: ApiSubscription, + plan: UsagePlan, + api: Api, + enabled: Boolean, + rotationEvery: Long, + gracePeriod: Long + ): Future[Either[AppError, JsObject]] = { import cats.implicits._ if (api.visibility == ApiVisibility.AdminOnly) { @@ -1197,8 +1205,8 @@ class ApiService( } def condenseEitherT[F[_], E, A]( - seq: Seq[EitherT[F, E, A]] - )(implicit F: Monad[F]): EitherT[F, E, Seq[A]] = { + seq: Seq[EitherT[F, E, A]] + )(implicit F: Monad[F]): EitherT[F, E, Seq[A]] = { seq.foldLeft(EitherT.pure[F, E](Seq.empty[A]))((a, b) => { for { seq <- a @@ -1208,21 +1216,21 @@ class ApiService( } /** - * remove a subcription from an aggregation, compute newly aggregation, save it in otoroshi and return new computed otoroshi apikey - * - * @param subscription the subscription to extract - * @param tenant the tenant - * @param user the user responsible for the extraction - * @param o the oto settings - * @return extracted otoroshi apikey (unsaved) - */ + * remove a subcription from an aggregation, compute newly aggregation, save it in otoroshi and return new computed otoroshi apikey + * + * @param subscription the subscription to extract + * @param tenant the tenant + * @param user the user responsible for the extraction + * @param o the oto settings + * @return extracted otoroshi apikey (unsaved) + */ def extractSubscriptionFromAggregation( - subscription: ApiSubscription, - tenant: Tenant, - user: User - )(implicit - o: OtoroshiSettings - ): Future[Either[AppError, ActualOtoroshiApiKey]] = { + subscription: ApiSubscription, + tenant: Tenant, + user: User + )(implicit + o: OtoroshiSettings + ): Future[Either[AppError, ActualOtoroshiApiKey]] = { (for { //get parent ApiSubscription parentSubscriptionId <- EitherT.fromOption[Future]( @@ -1392,10 +1400,10 @@ class ApiService( } def deleteApiSubscriptionsAsFlow( - tenant: Tenant, - apiOrGroupName: String, - user: User - ): Flow[(UsagePlan, Seq[ApiSubscription]), UsagePlan, NotUsed] = + tenant: Tenant, + apiOrGroupName: String, + user: User + ): Flow[(UsagePlan, Seq[ApiSubscription]), UsagePlan, NotUsed] = Flow[(UsagePlan, Seq[ApiSubscription])] .map { case (plan, subscriptions) => @@ -1421,10 +1429,10 @@ class ApiService( .mapAsync(1) { case (plan, subscription, notification) => def deaggregateSubsAndDelete( - subscription: ApiSubscription, - childs: Seq[ApiSubscription], - subscriberTeam: Team - )(implicit otoroshiSettings: OtoroshiSettings) = { + subscription: ApiSubscription, + childs: Seq[ApiSubscription], + subscriberTeam: Team + )(implicit otoroshiSettings: OtoroshiSettings) = { subscription.parent match { case Some(_) => extractSubscriptionFromAggregation(subscription, tenant, user) @@ -1542,11 +1550,11 @@ class ApiService( } def deleteUsagePlan( - plan: UsagePlan, - api: Api, - tenant: Tenant, - user: User - ): EitherT[Future, AppError, Api] = { + plan: UsagePlan, + api: Api, + tenant: Tenant, + user: User + ): EitherT[Future, AppError, Api] = { val updatedApi = api.copy(possibleUsagePlans = api.possibleUsagePlans.filter(pp => pp != plan.id) ) @@ -1588,11 +1596,11 @@ class ApiService( } def deleteApiPlansSubscriptions( - plans: Seq[UsagePlan], - api: Api, - tenant: Tenant, - user: User - ): Future[Done] = { + plans: Seq[UsagePlan], + api: Api, + tenant: Tenant, + user: User + ): Future[Done] = { implicit val mat: Materializer = env.defaultMaterializer Source(plans.toList) @@ -1618,11 +1626,11 @@ class ApiService( } def notifyApiSubscription( - demand: SubscriptionDemand, - tenant: Tenant, - step: SubscriptionDemandStep, - actualStep: ValidationStep.TeamAdmin - ): EitherT[Future, AppError, Result] = { + demand: SubscriptionDemand, + tenant: Tenant, + step: SubscriptionDemandStep, + actualStep: ValidationStep.TeamAdmin + ): EitherT[Future, AppError, Result] = { import cats.implicits._ val motivationPattern = "\\[\\[(.+?)\\]\\]".r @@ -1721,15 +1729,15 @@ class ApiService( } def callHttpRequestStep( - step: ValidationStep.HttpRequest, - _step: SubscriptionDemandStep, - demand: SubscriptionDemand, - tenant: Tenant, - maybeSessionId: Option[String] = None - )(implicit - language: String, - currentUser: User - ): EitherT[Future, AppError, Result] = { + step: ValidationStep.HttpRequest, + _step: SubscriptionDemandStep, + demand: SubscriptionDemand, + tenant: Tenant, + maybeSessionId: Option[String] = None + )(implicit + language: String, + currentUser: User + ): EitherT[Future, AppError, Result] = { def validStep(response: JsValue): EitherT[Future, AppError, Result] = { val customMedata = (response \ "customMetadata").asOpt[JsObject] @@ -1901,19 +1909,19 @@ class ApiService( } def runSubscriptionProcess( - demandId: SubscriptionDemandId, - tenant: Tenant, - from: Option[String] = None, - maybeSessionId: Option[String] = None - )(implicit - language: String, - currentUser: User - ): EitherT[Future, AppError, Result] = { + demandId: SubscriptionDemandId, + tenant: Tenant, + from: Option[String] = None, + maybeSessionId: Option[String] = None + )(implicit + language: String, + currentUser: User + ): EitherT[Future, AppError, Result] = { def runRightProcess( - step: SubscriptionDemandStep, - demand: SubscriptionDemand, - tenant: Tenant - ): EitherT[Future, AppError, Result] = { + step: SubscriptionDemandStep, + demand: SubscriptionDemand, + tenant: Tenant + ): EitherT[Future, AppError, Result] = { val run = step.step match { case ValidationStep.Email(_, emails, template, _) => @@ -2035,7 +2043,7 @@ class ApiService( .flatMap { //generate notification to checkout case (Some(step), demand) - if step.step.name == "payment" && demand.steps.size > 1 && step.state.name == "waiting" => + if step.step.name == "payment" && demand.steps.size > 1 && step.state.name == "waiting" => for { _ <- EitherT.liftF[Future, AppError, Boolean]( env.dataStore.notificationRepo @@ -2240,19 +2248,19 @@ class ApiService( } def _createOrExtendApiKey( - tenant: Tenant, - apiId: String, - planId: String, - teamId: String, - parentSubscriptionId: Option[ApiSubscriptionId] = None, - motivation: Option[JsObject] = None, - customMetadata: Option[JsObject], - customMaxPerSecond: Option[Long], - customMaxPerDay: Option[Long], - customMaxPerMonth: Option[Long], - customReadOnly: Option[Boolean], - adminCustomName: Option[String] - )(implicit language: String, currentUser: User) = { + tenant: Tenant, + apiId: String, + planId: String, + teamId: String, + parentSubscriptionId: Option[ApiSubscriptionId] = None, + motivation: Option[JsObject] = None, + customMetadata: Option[JsObject], + customMaxPerSecond: Option[Long], + customMaxPerDay: Option[Long], + customMaxPerMonth: Option[Long], + customReadOnly: Option[Boolean], + adminCustomName: Option[String] + )(implicit language: String, currentUser: User) = { import cats.implicits._ def controlApiAndPlan(api: Api): EitherT[Future, AppError, Unit] = { @@ -2268,10 +2276,10 @@ class ApiService( } def controlTeam( - team: Team, - api: Api, - plan: UsagePlan - ): EitherT[Future, AppError, Unit] = { + team: Team, + api: Api, + plan: UsagePlan + ): EitherT[Future, AppError, Unit] = { if (!currentUser.isDaikokuAdmin && !team.includeUser(currentUser.id)) { EitherT.leftT[Future, Unit](AppError.TeamUnauthorized) } else if ( @@ -2297,9 +2305,9 @@ class ApiService( } def controlSubscriptionExtension( - plan: UsagePlan, - team: Team - ): EitherT[Future, AppError, Unit] = { + plan: UsagePlan, + team: Team + ): EitherT[Future, AppError, Unit] = { parentSubscriptionId match { case Some(subId) => for { @@ -2354,10 +2362,10 @@ class ApiService( } def controlDemand( - team: Team, - api: Api, - plan: UsagePlan - ): EitherT[Future, AppError, Unit] = { + team: Team, + api: Api, + plan: UsagePlan + ): EitherT[Future, AppError, Unit] = { plan.allowMultipleKeys match { case Some(value) if value => EitherT.pure(()) case _ => @@ -2373,7 +2381,7 @@ class ApiService( ) .map { case Some(_) => Left(AppError.SubscriptionConflict) - case None => Right(()) + case None => Right(()) } ) } @@ -2417,32 +2425,32 @@ class ApiService( } def applyProcessForApiSubscription( - tenant: Tenant, - user: User, - api: Api, - plan: UsagePlan, - team: Team, - apiKeyId: Option[ApiSubscriptionId], - motivation: Option[JsObject], - customMetadata: Option[JsObject], - customMaxPerSecond: Option[Long], - customMaxPerDay: Option[Long], - customMaxPerMonth: Option[Long], - customReadOnly: Option[Boolean], - adminCustomName: Option[String] - )(implicit language: String): EitherT[Future, AppError, Result] = { + tenant: Tenant, + user: User, + api: Api, + plan: UsagePlan, + team: Team, + apiKeyId: Option[ApiSubscriptionId], + motivation: Option[JsObject], + customMetadata: Option[JsObject], + customMaxPerSecond: Option[Long], + customMaxPerDay: Option[Long], + customMaxPerMonth: Option[Long], + customReadOnly: Option[Boolean], + adminCustomName: Option[String] + )(implicit language: String): EitherT[Future, AppError, Result] = { import cats.implicits._ plan match { case _ - if api.visibility != ApiVisibility.Public && !api.authorizedTeams - .contains(team.id) && !user.isDaikokuAdmin => + if api.visibility != ApiVisibility.Public && !api.authorizedTeams + .contains(team.id) && !user.isDaikokuAdmin => EitherT.leftT[Future, Result](ApiUnauthorized) case _ - if api.visibility == ApiVisibility.AdminOnly && !user.isDaikokuAdmin => + if api.visibility == ApiVisibility.AdminOnly && !user.isDaikokuAdmin => EitherT.leftT[Future, Result](ApiUnauthorized) case _ - if plan.visibility == UsagePlanVisibility.Private && api.team != team.id => + if plan.visibility == UsagePlanVisibility.Private && api.team != team.id => EitherT.leftT[Future, Result](PlanUnauthorized) case _ => plan.subscriptionProcess match { @@ -2502,12 +2510,12 @@ class ApiService( } def declineSubscriptionDemand( - tenant: Tenant, - demandId: SubscriptionDemandId, - stepId: SubscriptionDemandStepId, - sender: NotificationSender, - maybeMessage: Option[String] = None - ): EitherT[Future, AppError, Result] = { + tenant: Tenant, + demandId: SubscriptionDemandId, + stepId: SubscriptionDemandStepId, + sender: NotificationSender, + maybeMessage: Option[String] = None + ): EitherT[Future, AppError, Result] = { for { demand <- EitherT.fromOptionF( @@ -2612,6 +2620,34 @@ class ApiService( } yield Ok(Json.obj("creation" -> "refused")) } + def getApis[T](ctx: ApiActionContext[T], notDeleted: Boolean = false) = { + val repo = env.dataStore.apiRepo.forTenant(ctx.tenant) + + (if (!notDeleted) repo.findAll() else repo.findAllNotDeleted()) + .map(apis => { + val fields: Seq[String] = ctx.request.getQueryString("fields").map(_.split(",").toSeq).getOrElse(Seq.empty[String]) + val hasFields = fields.nonEmpty + if (hasFields) { + Ok(JsArray(apis.map(api => { + val jsonAPI = api.asJson + val content = jsonAPI match { + case arr@JsArray(_) => + JsArray(arr.value.map { item => + JsonOperationsHelper.filterJson(item.as[JsObject], fields) + }) + case obj@JsObject(_) => JsonOperationsHelper.filterJson(obj, fields) + case _ => jsonAPI + } + + content + }))) + } else { + Ok(SeqApiFormat.writes(apis)) + } + }) + } + + case class ExtractTransferLink( subscription: ApiSubscription, childSubscriptions: Seq[ApiSubscription], diff --git a/daikoku/app/utils/JsonOperationsHelper.scala b/daikoku/app/utils/JsonOperationsHelper.scala new file mode 100644 index 000000000..1e5af51d4 --- /dev/null +++ b/daikoku/app/utils/JsonOperationsHelper.scala @@ -0,0 +1,60 @@ +package fr.maif.otoroshi.daikoku.utils + +import play.api.libs.json.{JsArray, JsDefined, JsObject, JsString, JsUndefined, JsValue, Json} + +object JsonOperationsHelper { + + private def getValueAtPath(input: String, obj: JsValue) = { + var acc = obj + var out = JsString("").as[JsValue] + + input + .split("\\.") + .foreach(path => { + if (path.forall(Character.isDigit)) { + acc.asOpt[JsArray] match { + case Some(value) => + acc = value.value(path.toInt) + out = acc + case None => acc = Json.obj() + } + } else { + acc \ path match { + case JsDefined(a @ JsObject(_)) => + acc = a + out = a + case JsDefined(a @ JsArray(_)) => + acc = a + out = a + case JsDefined(value) => + out = value + case _: JsUndefined => + acc = Json.obj() + out = Json.obj() + } + } + }) + + (input, out) + } + + def insertAtPath(acc: JsObject, path: Seq[String], value: JsValue): JsObject = { + if (path.length == 1) { + acc.deepMerge(Json.obj(path.head -> value)) + } else { + acc.deepMerge( + Json.obj( + path.head -> insertAtPath((acc \ path.head).asOpt[JsObject].getOrElse(Json.obj()), path.tail, value) + ) + ) + } + } + def filterJson(obj: JsValue, fields: Seq[String]): JsObject = { + val out = fields.map(input => getValueAtPath(input, obj)) + + out.foldLeft(Json.obj()) { case (acc, curr) => + insertAtPath(acc, curr._1.split("\\.").toIndexedSeq, curr._2) + } + } + +} diff --git a/daikoku/app/utils/s3.scala b/daikoku/app/utils/s3.scala index c0cdba93b..bea9e08bb 100644 --- a/daikoku/app/utils/s3.scala +++ b/daikoku/app/utils/s3.scala @@ -120,8 +120,9 @@ class AssetsDataStore(actorSystem: ActorSystem)(implicit s3RegionProvider = new AwsRegionProvider { override def getRegion: Region = Region.of(conf.region) }, - listBucketApiVersion = ApiVersion.ListBucketVersion2 - ).withEndpointUrl(conf.endpoint) + listBucketApiVersion = ApiVersion.ListBucketVersion2, + ) + .withEndpointUrl(conf.endpoint) S3Attributes.settings(settings) } @@ -260,6 +261,7 @@ class AssetsDataStore(actorSystem: ActorSystem)(implicit val ctype = ContentType .parse(contentType) .getOrElse(ContentTypes.`application/octet-stream`) + val meta = MetaHeaders( Map( "filename" -> name, diff --git a/daikoku/conf/routes b/daikoku/conf/routes index 96bb96fed..03d474df9 100644 --- a/daikoku/conf/routes +++ b/daikoku/conf/routes @@ -5,17 +5,36 @@ GET /_/ fr.maif.otoroshi.daikoku GET /_/*path fr.maif.otoroshi.daikoku.ctrls.HomeController.cmsPageByPath(path) POST /_*path fr.maif.otoroshi.daikoku.ctrls.HomeController.renderCmsPageFromBody(path) GET /cms/pages/:id fr.maif.otoroshi.daikoku.ctrls.HomeController.cmsPageById(id) -GET /api/cms/pages/:id/diffs/:diffId fr.maif.otoroshi.daikoku.ctrls.HomeController.cmsDiffById(id, diffId) -POST /api/cms/pages/:id/diffs/:diffId fr.maif.otoroshi.daikoku.ctrls.HomeController.restoreDiff(id, diffId) -POST /api/cms/download fr.maif.otoroshi.daikoku.ctrls.HomeController.download() -GET /api/cms fr.maif.otoroshi.daikoku.ctrls.HomeController.summary() +POST /cms/pages/:id fr.maif.otoroshi.daikoku.ctrls.HomeController.advancedRenderCmsPageById(id) POST /api/cms/import fr.maif.otoroshi.daikoku.ctrls.HomeController.importFromZip() -POST /api/cms/pages/:pageName fr.maif.otoroshi.daikoku.ctrls.HomeController.createCmsPageWithName(pageName) -POST /api/cms/pages fr.maif.otoroshi.daikoku.ctrls.HomeController.createCmsPage() -DELETE /api/cms/pages/:id fr.maif.otoroshi.daikoku.ctrls.HomeController.deleteCmsPage(id) -GET /api/cms/pages/:id fr.maif.otoroshi.daikoku.ctrls.HomeController.getCmsPage(id) -GET /api/users/:userId/session fr.maif.otoroshi.daikoku.ctrls.HomeController.session(userId) -POST /api/cms/sync fr.maif.otoroshi.daikoku.ctrls.HomeController.sync() +#GET /api/users/:userId/session fr.maif.otoroshi.daikoku.ctrls.HomeController.session(userId) +#GET /api/cms fr.maif.otoroshi.daikoku.ctrls.HomeController.summary() +#DELETE /api/cms/pages/:id fr.maif.otoroshi.daikoku.ctrls.HomeController.deleteCmsPage(id) +#GET /api/cms/pages/:id fr.maif.otoroshi.daikoku.ctrls.HomeController.getCmsPage(id) + +POST /cms-api/sync fr.maif.otoroshi.daikoku.ctrls.CmsApiController.sync() +GET /cms-api/health fr.maif.otoroshi.daikoku.ctrls.CmsApiController.health() +GET /cms-api/version fr.maif.otoroshi.daikoku.ctrls.CmsApiController.version() +GET /cms-api/tenants/default fr.maif.otoroshi.daikoku.ctrls.CmsApiController.defaultTenant() + +GET /cms-api/cli/redirect fr.maif.otoroshi.daikoku.ctrls.CmsApiController.redirectToLoginPage() +GET /cms-api/cli/login fr.maif.otoroshi.daikoku.ctrls.CmsApiController.getLoginToken() +GET /cms-api/translations/_mail fr.maif.otoroshi.daikoku.ctrls.CmsApiController.getMailTranslations(domain: Option[String] ?= None) +GET /cms-api/apis fr.maif.otoroshi.daikoku.ctrls.CmsApiController.getAllApis() + +GET /cms-api/pages fr.maif.otoroshi.daikoku.ctrls.CmsApiController.findAll() +GET /cms-api/pages/:id fr.maif.otoroshi.daikoku.ctrls.CmsApiController.getCmsPage(id) + +GET /cms-api/swagger.json fr.maif.otoroshi.daikoku.ctrls.CmsApiSwaggerController.swagger() +GET /cms-api/openapi.json fr.maif.otoroshi.daikoku.ctrls.CmsApiSwaggerController.swagger() + +GET /cms-api/tenant-assets/slugified fr.maif.otoroshi.daikoku.ctrls.CmsApiController.slugifiedAssets() +HEAD /cms-api/tenant-assets/:assetId fr.maif.otoroshi.daikoku.ctrls.CmsApiController.doesAssetExists(assetId) +DELETE /cms-api/tenant-assets/:assetId fr.maif.otoroshi.daikoku.ctrls.CmsApiController.deleteAsset(assetId) +GET /cms-api/tenant-assets/:assetId fr.maif.otoroshi.daikoku.ctrls.CmsApiController.getAsset(assetId) +GET /cms-api/tenant-assets fr.maif.otoroshi.daikoku.ctrls.CmsApiController.listAssets() +POST /cms-api/tenant-assets fr.maif.otoroshi.daikoku.ctrls.CmsApiController.storeAsset() +POST /cms-api/tenant-assets/bulk fr.maif.otoroshi.daikoku.ctrls.CmsApiController.storeAssets() GET / fr.maif.otoroshi.daikoku.ctrls.HomeController.index() GET /robots.txt fr.maif.otoroshi.daikoku.ctrls.HomeController.indexForRobots() @@ -42,7 +61,6 @@ POST /account/reset fr.maif.otoroshi.daikoku GET /account/reset fr.maif.otoroshi.daikoku.ctrls.LoginController.passwordResetValidation() GET /account/validate fr.maif.otoroshi.daikoku.ctrls.LoginController.createUserValidation() POST /account fr.maif.otoroshi.daikoku.ctrls.LoginController.createUser() -GET /cli/login fr.maif.otoroshi.daikoku.ctrls.LoginController.redirectToLoginPage() GET /api/auth/:provider/context fr.maif.otoroshi.daikoku.ctrls.LoginController.loginContext(provider) GET /auth/:provider/login fr.maif.otoroshi.daikoku.ctrls.LoginController.loginPage(provider) POST /auth/:provider/callback fr.maif.otoroshi.daikoku.ctrls.LoginController.login(provider) @@ -82,6 +100,7 @@ GET /api/apis/:apiId/issues/:issueId/comments fr.maif.otoroshi GET /api/apis/:apiId/issues/:issueId fr.maif.otoroshi.daikoku.ctrls.ApiController.getIssue(apiId, issueId) GET /api/apis/:apiId/default_version fr.maif.otoroshi.daikoku.ctrls.ApiController.getDefaultApiVersion(apiId) GET /api/apis/:apiId/:version/doc fr.maif.otoroshi.daikoku.ctrls.ApiController.getDocumentationDetails(apiId, version) +GET /api/apis fr.maif.otoroshi.daikoku.ctrls.ApiController.getAllApis() POST /api/teams/:teamId/subscription/:id/consumption/_sync fr.maif.otoroshi.daikoku.ctrls.ConsumptionController.syncSubscriptionConsumption(id, teamId) POST /api/teams/:teamId/apis/:apiId/consumption/_sync fr.maif.otoroshi.daikoku.ctrls.ConsumptionController.syncApiConsumption(apiId, teamId) diff --git a/daikoku/javascript/src/apps/DaikokuApp.tsx b/daikoku/javascript/src/apps/DaikokuApp.tsx index 91f4c23ba..66961b3b3 100644 --- a/daikoku/javascript/src/apps/DaikokuApp.tsx +++ b/daikoku/javascript/src/apps/DaikokuApp.tsx @@ -1,6 +1,6 @@ import { useContext, useEffect } from 'react'; import { Navigate } from 'react-router'; -import { BrowserRouter, Route, BrowserRouter as Router, Routes, createBrowserRouter, RouterProvider } from 'react-router-dom'; +import { BrowserRouter, Route, BrowserRouter as Router, Routes, createBrowserRouter, RouterProvider, useSearchParams } from 'react-router-dom'; import { TeamBackOffice } from '../components/backoffice/TeamBackOffice'; import { Footer, LoginPage, SideBar, tenant } from '../components/utils'; @@ -132,7 +132,7 @@ export const DaikokuApp = () => { /> } + element={} /> @@ -480,6 +480,19 @@ export const DaikokuApp = () => { ); }; +const ToLogin = ({ tenant }) => { + + const [searchParams] = useSearchParams(); + + const redirect = searchParams.get('redirect') + const to = `/auth/${tenant.authProvider}/login` + + if (redirect) + return + else + return +} + const FrontOfficeRoute = (props: { title?: string, children: JSX.Element }) => { return ( diff --git a/daikoku/javascript/src/apps/DaikokuHomeApp.tsx b/daikoku/javascript/src/apps/DaikokuHomeApp.tsx index 1e9ae0bed..950827c1a 100644 --- a/daikoku/javascript/src/apps/DaikokuHomeApp.tsx +++ b/daikoku/javascript/src/apps/DaikokuHomeApp.tsx @@ -315,7 +315,9 @@ export const TwoFactorAuthentication = () => { if (res.status >= 400) { setError(translate('2fa.wrong_code')); setCode(''); - } else if (res.redirected) window.location.href = res.url; + } else if (res.redirected) { + window.location.href = res.url; + } }); } } diff --git a/daikoku/javascript/src/components/adminbackoffice/cms/Pages.tsx b/daikoku/javascript/src/components/adminbackoffice/cms/Pages.tsx index f4523019f..d684d8071 100644 --- a/daikoku/javascript/src/components/adminbackoffice/cms/Pages.tsx +++ b/daikoku/javascript/src/components/adminbackoffice/cms/Pages.tsx @@ -1,7 +1,7 @@ import { createColumnHelper } from '@tanstack/react-table'; import classNames from 'classnames'; import moment from 'moment'; -import React, { useContext, useRef } from 'react'; +import React, { useContext, useEffect, useRef } from 'react'; import { Link, useNavigate } from 'react-router-dom'; import { IPage } from '..'; import { ModalContext } from '../../../contexts'; @@ -9,7 +9,7 @@ import { I18nContext } from '../../../contexts'; import * as Services from '../../../services'; import { Table, TableRef } from '../../inputs'; -const CONTENT_TYPES = [ +export const CONTENT_TYPES = [ { value: 'text/html', label: 'HTML' }, { value: 'text/css', label: 'CSS' }, { value: 'text/javascript', label: 'JS' }, @@ -31,6 +31,12 @@ export const Pages = ({ const { translate } = useContext(I18nContext); const { alert, confirm } = useContext(ModalContext); + useEffect(() => { + if (table.current) + //@ts-ignore + table.current.setPageSize(500) + }, [table]) + const navigate = useNavigate(); const columnHelper = createColumnHelper(); @@ -87,33 +93,33 @@ export const Pages = ({ let isCreatedFromCLI = false; try { - isCreatedFromCLI = JSON.parse(info.row.original.metadata).from + isCreatedFromCLI = !!JSON.parse(info.row.original.metadata).from } catch (_) { } + const itemPath = value.path ? (value.path.startsWith('/') ? `/_${value.path}` : `/_/${value.path}`) : '#' + return ( -
+
e.stopPropagation()}> - - e.stopPropagation()}> - + } {!isCreatedFromCLI &&