diff --git a/.github/ISSUE_TEMPLATE/flag-request.md b/.github/ISSUE_TEMPLATE/flag-request.md new file mode 100644 index 000000000..a969674df --- /dev/null +++ b/.github/ISSUE_TEMPLATE/flag-request.md @@ -0,0 +1,19 @@ +--- +name: Flag Request +about: Template for requesting new flags to be added to the Stats Viewer +title: "[FLAGS]" +labels: '' +assignees: '' + +--- + + + +Please add the following missing flag to the pointercrate stats viewer: + +**Country**: +**Subdivision**: +**ISO-Code**: +**Link to PUBLIC DOMAIN `.svg` of flag**: + +Thanks diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..8d75ff209 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,14 @@ +version: 2 +updates: + - package-ecosystem: "cargo" + directory: "/" + schedule: + interval: "weekly" + day: "sunday" + allow: + - dependency-type: "all" + open-pull-requests-limit: 1 + groups: + pointercrate: + patterns: + - "*" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..3adb3c674 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,3 @@ +## License Acceptance + +By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. diff --git a/.github/workflows/rustfmt.yml b/.github/workflows/rustfmt.yml index c381bf67d..1c5c51335 100644 --- a/.github/workflows/rustfmt.yml +++ b/.github/workflows/rustfmt.yml @@ -22,7 +22,7 @@ jobs: - uses: actions-rs/toolchain@v1 with: profile: minimal - toolchain: nightly + toolchain: stable override: true components: rustfmt - run: rustup component add rustfmt diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c1283fe56..126391a9d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -66,3 +66,21 @@ jobs: DATABASE_URL: postgresql://pointercrate:postgres@localhost/postgres RUST_BACKTRACE: 1 RUSTFLAGS: -Cinstrument-coverage + LIST_SIZE: 75 + EXNTEDED_LIST_SIZE: 150 + + - name: Install grcov + uses: actions-rs/cargo@v1 + with: + command: install + args: grcov + + - name: Generate coverage report + run: grcov . -s . --binary-path ./target/debug/ -t lcov --ignore-not-existing -o ./target/debug/coverage.lcov --ignore "pointercrate-test/*" --ignore "pointercrate-example/*" --keep-only "pointercrate-*" + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + with: + files: ./target/debug/coverage.lcov diff --git a/.gitignore b/.gitignore index 3f20d61b2..7ec79fc94 100644 --- a/.gitignore +++ b/.gitignore @@ -2,5 +2,4 @@ .env .secret .idea -run_coverage.sh -codecov \ No newline at end of file +.vscode \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 67e959e92..db9d2d6a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +dependencies = [ + "gimli", +] + [[package]] name = "adler" version = "1.0.2" @@ -10,15 +19,38 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "ahash" -version = "0.7.6" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ + "cfg-if", "getrandom", "once_cell", "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + [[package]] name = "android_system_properties" version = "0.1.5" @@ -47,77 +79,94 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.58", ] [[package]] name = "async-trait" -version = "0.1.68" +version = "0.1.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" +checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.58", ] [[package]] name = "atoi" -version = "1.0.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7c57d12312ff59c811c0643f4d80830505833c9ffaebd193d819392b265be8e" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" dependencies = [ "num-traits", ] [[package]] name = "atomic" -version = "0.5.1" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" + +[[package]] +name = "atomic" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b88d82667eca772c4aa12f0f1348b3ae643424c8876448f3f7bd5787032e234c" +checksum = "8d818003e740b63afc82337e3160717f4f63078720a810b7b903e70a5d1d2994" dependencies = [ - "autocfg", + "bytemuck", ] [[package]] name = "autocfg" -version = "1.1.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] -name = "base64" -version = "0.11.0" +name = "backtrace" +version = "0.3.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" +checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] [[package]] name = "base64" -version = "0.12.3" +version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" [[package]] name = "base64" -version = "0.13.1" +version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] -name = "base64" -version = "0.21.0" +name = "base64ct" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "bcrypt" -version = "0.9.0" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4d0faafe9e089674fc3efdb311ff5253d445c79d85d1d28bd3ace76d45e7164" +checksum = "e65938ed058ef47d92cf8b346cc76ef48984572ade631927e9937b5ffc7662c7" dependencies = [ - "base64 0.13.1", + "base64 0.22.1", "blowfish", "getrandom", + "subtle", + "zeroize", ] [[package]] @@ -143,9 +192,12 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.2.1" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24a6904aef64d73cf10ab17ebace7befb918b82164785cb89907993be7f83813" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" +dependencies = [ + "serde", +] [[package]] name = "block-buffer" @@ -158,38 +210,43 @@ dependencies = [ [[package]] name = "blowfish" -version = "0.7.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32fa6a061124e37baba002e496d203e23ba3d7b73750be82dbfbc92913048a5b" +checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7" dependencies = [ "byteorder", "cipher", - "opaque-debug", ] [[package]] name = "bumpalo" -version = "3.12.1" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "bytemuck" +version = "1.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b1ce199063694f33ffb7dd4e0ee620741495c32833cde5aa08f02a0bf96f0c8" +checksum = "b236fc92302c97ed75b38da1f4917b5cdda4984745740f153a5d3059e48d725e" [[package]] name = "byteorder" -version = "1.4.3" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.4.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "cc" -version = "1.0.79" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "2755ff20a1d93490d26ba33a6f092a38a508398a5320df5d4b3014fcccce9410" [[package]] name = "cfg-if" @@ -199,38 +256,34 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.24" +version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ + "android-tzdata", "iana-time-zone", "js-sys", - "num-integer", "num-traits", "serde", - "time 0.1.43", "wasm-bindgen", - "winapi", + "windows-targets 0.52.0", ] [[package]] name = "cipher" -version = "0.2.5" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f8e7987cbd042a63249497f41aed09f8e65add917ea6566effbc56578d6801" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ - "generic-array", + "crypto-common", + "inout", ] [[package]] -name = "codespan-reporting" -version = "0.11.1" +name = "const-oid" +version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" -dependencies = [ - "termcolor", - "unicode-width", -] +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "convert_case" @@ -240,20 +293,20 @@ checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" [[package]] name = "cookie" -version = "0.17.0" +version = "0.18.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7efb37c3e1ccb1ff97164ad95ac1606e8ccd35b3fa0a7d99a304c7f4a428cc24" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" dependencies = [ "percent-encoding", - "time 0.3.20", + "time", "version_check", ] [[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", @@ -261,61 +314,57 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "cpufeatures" -version = "0.2.7" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" dependencies = [ "libc", ] [[package]] name = "crc" -version = "3.0.1" +version = "3.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" +checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636" dependencies = [ "crc-catalog", ] [[package]] name = "crc-catalog" -version = "2.2.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cace84e55f07e7301bae1c519df89cdad8cc3cd868413d3fdbdeca9ff3db484" +checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" [[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-queue" -version = "0.3.8" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" +checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35" dependencies = [ - "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.15" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" -dependencies = [ - "cfg-if", -] +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crypto-common" @@ -328,88 +377,77 @@ dependencies = [ ] [[package]] -name = "cxx" -version = "1.0.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f61f1b6389c3fe1c316bf8a4dccc90a38208354b330925bce1f74a6c4756eb93" +name = "dash-rs" +version = "0.1.0" +source = "git+https://github.com/qimiko/dash-rs?rev=21b8e86aa3ffe9ba4ba4bfd147b94abf2afa50ef#21b8e86aa3ffe9ba4ba4bfd147b94abf2afa50ef" dependencies = [ - "cc", - "cxxbridge-flags", - "cxxbridge-macro", - "link-cplusplus", + "base64 0.21.7", + "dash-rs-derive", + "dtoa", + "flate2", + "itoa", + "log", + "percent-encoding", + "serde", + "serde_yaml", + "thiserror", + "variant_partial_eq", ] [[package]] -name = "cxx-build" -version = "1.0.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12cee708e8962df2aeb38f594aae5d827c022b6460ac71a7a3e2c3c2aae5a07b" +name = "dash-rs-derive" +version = "0.1.0" +source = "git+https://github.com/qimiko/dash-rs?rev=21b8e86aa3ffe9ba4ba4bfd147b94abf2afa50ef#21b8e86aa3ffe9ba4ba4bfd147b94abf2afa50ef" dependencies = [ - "cc", - "codespan-reporting", - "once_cell", "proc-macro2", "quote", - "scratch", - "syn 2.0.15", + "syn 2.0.58", ] [[package]] -name = "cxxbridge-flags" -version = "1.0.94" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7944172ae7e4068c533afbb984114a56c46e9ccddda550499caa222902c7f7bb" - -[[package]] -name = "cxxbridge-macro" -version = "1.0.94" +name = "dashmap" +version = "5.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2345488264226bf682893e25de0769f3360aac9957980ec49361b083ddaa5bc5" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.15", + "cfg-if", + "hashbrown", + "lock_api", + "once_cell", + "parking_lot_core", ] [[package]] -name = "dash-rs" -version = "0.1.0" -source = "git+https://github.com/qimiko/dash-rs#d935929542035fa845db7ddc9a565cb3881fa1a8" +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" dependencies = [ - "base64 0.11.0", - "dtoa", - "flate2", - "itoa 0.4.8", - "log", - "percent-encoding", - "serde", - "serde_yaml", + "const-oid", + "pem-rfc7468", + "zeroize", ] [[package]] -name = "dashmap" -version = "5.4.0" +name = "deranged" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ - "cfg-if", - "hashbrown", - "lock_api", - "once_cell", - "parking_lot_core 0.9.7", + "powerfmt", ] [[package]] name = "derive_more" -version = "0.99.17" +version = "0.99.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" dependencies = [ "convert_case", "proc-macro2", "quote", "rustc_version", - "syn 1.0.109", + "syn 2.0.58", ] [[package]] @@ -438,44 +476,25 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35b50dba0afdca80b187392b24f2499a88c336d5a8493e4b4ccfb608708be56a" dependencies = [ - "bitflags 2.2.1", + "bitflags 2.4.1", "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.15", + "syn 2.0.58", ] [[package]] name = "digest" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", + "const-oid", "crypto-common", "subtle", ] -[[package]] -name = "dirs" -version = "4.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - [[package]] name = "dotenv" version = "0.15.0" @@ -490,47 +509,53 @@ checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" [[package]] name = "dtoa" -version = "0.4.8" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" +checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653" [[package]] name = "either" -version = "1.8.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" dependencies = [ "serde", ] [[package]] name = "encoding_rs" -version = "0.8.32" +version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" dependencies = [ "cfg-if", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" -version = "0.3.1" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ - "errno-dragonfly", "libc", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] -name = "errno-dragonfly" -version = "0.1.2" +name = "etcetera" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" dependencies = [ - "cc", - "libc", + "cfg-if", + "home", + "windows-sys 0.48.0", ] [[package]] @@ -541,20 +566,17 @@ checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" [[package]] name = "fastrand" -version = "1.9.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" -dependencies = [ - "instant", -] +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "figment" -version = "0.10.8" +version = "0.10.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e56602b469b2201400dec66a66aec5a9b8761ee97cd1b8c96ab2483fcc16cc9" +checksum = "8cb01cd46b0cf372153850f4c6c272d9cbea2da513e07538405148f95bd789f3" dependencies = [ - "atomic", + "atomic 0.6.0", "pear", "serde", "toml", @@ -564,15 +586,26 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.26" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" dependencies = [ "crc32fast", "libz-sys", "miniz_oxide", ] +[[package]] +name = "flume" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181" +dependencies = [ + "futures-core", + "futures-sink", + "spin", +] + [[package]] name = "fnv" version = "1.0.7" @@ -596,18 +629,18 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" -version = "1.1.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] [[package]] name = "futures" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" dependencies = [ "futures-channel", "futures-core", @@ -620,9 +653,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", "futures-sink", @@ -630,15 +663,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-executor" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" dependencies = [ "futures-core", "futures-task", @@ -647,55 +680,55 @@ dependencies = [ [[package]] name = "futures-intrusive" -version = "0.4.2" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a604f7a68fbf8103337523b1fadc8ade7361ee3f112f7c680ad179651616aed5" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" dependencies = [ "futures-core", "lock_api", - "parking_lot 0.11.2", + "parking_lot", ] [[package]] name = "futures-io" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-macro" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.58", ] [[package]] name = "futures-sink" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-timer" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" [[package]] name = "futures-util" -version = "0.3.28" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-channel", "futures-core", @@ -711,9 +744,9 @@ dependencies = [ [[package]] name = "generator" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3e123d9ae7c02966b4d892e550bdc32164f05853cd40ab570650ad600596a8a" +checksum = "5cc16584ff22b460a382b7feec54b23d2908d858152e5739a120b949293bd74e" dependencies = [ "cc", "libc", @@ -734,15 +767,23 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.9" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", + "js-sys", "libc", - "wasi 0.11.0+wasi-snapshot-preview1", + "wasi", + "wasm-bindgen", ] +[[package]] +name = "gimli" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" + [[package]] name = "glob" version = "0.3.1" @@ -751,9 +792,9 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "governor" -version = "0.5.1" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c390a940a5d157878dd057c78680a33ce3415bcd05b4799509ea44210914b4d5" +checksum = "68a7f542ee6b35af73b06abc0dad1c1bae89964e4e253bc4b587b91c9637867b" dependencies = [ "cfg-if", "dashmap", @@ -761,24 +802,45 @@ dependencies = [ "futures-timer", "no-std-compat", "nonzero_ext", - "parking_lot 0.12.1", + "parking_lot", + "portable-atomic", "quanta", "rand", "smallvec", + "spinning_top", ] [[package]] name = "h2" -version = "0.3.18" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http 0.2.12", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "h2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f8a914c2987b688368b5138aa05321db91f4090cf26118185672ad588bce21" +checksum = "51ee2dd2e4f378392eeff5d51618cd9a63166a2513846bbc55f21cfacd9199d4" dependencies = [ "bytes", "fnv", "futures-core", "futures-sink", "futures-util", - "http", + "http 1.1.0", "indexmap", "slab", "tokio", @@ -788,18 +850,19 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.12.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash", + "allocator-api2", ] [[package]] name = "hashlink" -version = "0.8.1" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69fe1fcf8b4278d860ad0548329f892a3631fb63f82574df68275f34cdbe0ffa" +checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" dependencies = [ "hashbrown", ] @@ -815,18 +878,9 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" -dependencies = [ - "libc", -] - -[[package]] -name = "hermit-abi" -version = "0.3.1" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hex" @@ -836,9 +890,9 @@ checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hkdf" -version = "0.12.3" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" dependencies = [ "hmac", ] @@ -852,56 +906,99 @@ dependencies = [ "digest", ] +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "http" -version = "0.2.9" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" dependencies = [ "bytes", "fnv", - "itoa 1.0.6", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +dependencies = [ + "bytes", + "http 0.2.12", + "pin-project-lite", ] [[package]] name = "http-body" -version = "0.4.5" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +dependencies = [ + "bytes", + "http 1.1.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ "bytes", - "http", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", "pin-project-lite", ] [[package]] name = "httparse" -version = "1.8.0" +version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" [[package]] name = "httpdate" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "hyper" -version = "0.14.26" +version = "0.14.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4" +checksum = "f361cde2f109281a220d4307746cdfd5ee3f410da58a70377762396775634b33" dependencies = [ "bytes", "futures-channel", "futures-core", "futures-util", - "h2", - "http", - "http-body", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", "httparse", "httpdate", - "itoa 1.0.6", + "itoa", "pin-project-lite", "socket2", "tokio", @@ -911,144 +1008,177 @@ dependencies = [ ] [[package]] -name = "hyper-tls" -version = "0.5.0" +name = "hyper" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" dependencies = [ "bytes", - "hyper", - "native-tls", + "futures-channel", + "futures-util", + "h2 0.4.3", + "http 1.1.0", + "http-body 1.0.0", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", "tokio", - "tokio-native-tls", + "want", ] [[package]] -name = "iana-time-zone" -version = "0.1.56" +name = "hyper-rustls" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c" +checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "windows", + "futures-util", + "http 1.1.0", + "hyper 1.3.1", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", ] [[package]] -name = "iana-time-zone-haiku" -version = "0.1.1" +name = "hyper-tls" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ - "cxx", - "cxx-build", + "bytes", + "http-body-util", + "hyper 1.3.1", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", ] [[package]] -name = "idna" -version = "0.3.0" +name = "hyper-util" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +checksum = "7b875924a60b96e5d7b9ae7b066540b1dd1cbd90d1828f54c92e02a283351c56" dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "hyper 1.3.1", + "pin-project-lite", + "socket2", + "tokio", + "tower", + "tower-service", + "tracing", +] + [[package]] -name = "indexmap" -version = "1.9.3" +name = "iana-time-zone" +version = "0.1.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" dependencies = [ - "autocfg", - "hashbrown", - "serde", + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", ] [[package]] -name = "inlinable_string" -version = "0.1.15" +name = "iana-time-zone-haiku" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] [[package]] -name = "instant" -version = "0.1.12" +name = "idna" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ - "cfg-if", + "unicode-bidi", + "unicode-normalization", ] [[package]] -name = "io-lifetimes" -version = "1.0.10" +name = "indexmap" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c66c74d2ae7e79a5a8f7ac924adbe38ee42a859c6539ad869eb51f0b52dc220" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ - "hermit-abi 0.3.1", - "libc", - "windows-sys 0.48.0", + "equivalent", + "hashbrown", + "serde", ] [[package]] -name = "ipnet" -version = "2.7.2" +name = "inlinable_string" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f" +checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" [[package]] -name = "is-terminal" -version = "0.4.7" +name = "inout" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" dependencies = [ - "hermit-abi 0.3.1", - "io-lifetimes", - "rustix", - "windows-sys 0.48.0", + "generic-array", ] [[package]] -name = "itertools" -version = "0.10.5" +name = "ipnet" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] -name = "itoa" -version = "0.4.8" +name = "is-terminal" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" +checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.52.0", +] [[package]] name = "itoa" -version = "1.0.6" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "js-sys" -version = "0.3.61" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" dependencies = [ "wasm-bindgen", ] [[package]] name = "jsonwebtoken" -version = "7.2.0" +version = "9.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afabcc15e437a6484fc4f12d0fd63068fe457bf93f1c148d3d9649c60b103f32" +checksum = "b9ae10193d25051e74945f1ea2d0b42e03cc3b890f7e4cc5faa44997d808193f" dependencies = [ - "base64 0.12.3", + "base64 0.21.7", + "js-sys", "pem", "ring", "serde", @@ -1058,21 +1188,30 @@ dependencies = [ [[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" +dependencies = [ + "spin", +] [[package]] name = "libc" -version = "0.2.142" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] -name = "libz-sys" -version = "1.1.9" +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + +[[package]] +name = "libsqlite3-sys" +version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56ee889ecc9568871456d42f603d6a0ce59ff328d291063a45cbdf0036baf6db" +checksum = "cf4e226dcd58b4be396f7bd3c20da8fdee2911400705297ba7d2d7cc2c30f716" dependencies = [ "cc", "pkg-config", @@ -1080,31 +1219,27 @@ dependencies = [ ] [[package]] -name = "link-cplusplus" -version = "1.0.8" +name = "libz-sys" +version = "1.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecd207c9c713c34f95a097a5b029ac2ce6010530c7b49d7fea24d977dede04f5" +checksum = "c15da26e5af7e25c90b37a2d75cdbf940cf4a55316de9d84c679c9b8bfabf82e" dependencies = [ "cc", + "pkg-config", + "vcpkg", ] -[[package]] -name = "linked-hash-map" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" - [[package]] name = "linux-raw-sys" -version = "0.3.6" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b64f40e5e03e0d54f03845c8197d0291253cdbedfb1cb46b13c2c117554a9f4c" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "lock_api" -version = "0.4.9" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -1112,12 +1247,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "loom" @@ -1134,60 +1266,52 @@ dependencies = [ "tracing-subscriber", ] -[[package]] -name = "mach" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" -dependencies = [ - "libc", -] - [[package]] name = "matchers" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" dependencies = [ - "regex-automata", + "regex-automata 0.1.10", ] [[package]] name = "maud" -version = "0.25.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0bab19cef8a7fe1c18a43e881793bfc9d4ea984befec3ae5bd0415abf3ecf00" +checksum = "df518b75016b4289cdddffa1b01f2122f4a49802c93191f3133f6dc2472ebcaa" dependencies = [ - "itoa 1.0.6", + "itoa", "maud_macros", ] [[package]] name = "maud_macros" -version = "0.25.0" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0be95d66c3024ffce639216058e5bae17a83ecaf266ffc6e4d060ad447c9eed2" +checksum = "fa453238ec218da0af6b11fc5978d3b5c3a45ed97b722391a2a11f3306274e18" dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.58", ] [[package]] name = "md-5" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6365506850d44bff6e2fbcb5176cf63650e48bd45ef2fe2665ae1570e0f4b9ca" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" dependencies = [ + "cfg-if", "digest", ] [[package]] name = "memchr" -version = "2.5.0" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "mime" @@ -1203,40 +1327,38 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.1" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ "adler", ] [[package]] name = "mio" -version = "0.8.6" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", - "log", - "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.45.0", + "wasi", + "windows-sys 0.48.0", ] [[package]] name = "multer" -version = "2.1.0" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2" +checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b" dependencies = [ "bytes", "encoding_rs", "futures-util", - "http", + "http 1.1.0", "httparse", - "log", "memchr", "mime", - "spin 0.9.8", + "spin", "tokio", "tokio-util", "version_check", @@ -1244,11 +1366,10 @@ dependencies = [ [[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", @@ -1294,63 +1415,99 @@ dependencies = [ [[package]] name = "num-bigint" -version = "0.2.6" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ - "autocfg", "num-integer", "num-traits", ] +[[package]] +name = "num-bigint-dig" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +dependencies = [ + "byteorder", + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ "autocfg", + "num-integer", "num-traits", ] [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", + "libm", ] [[package]] name = "num_cpus" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ - "hermit-abi 0.2.6", + "hermit-abi", "libc", ] [[package]] -name = "once_cell" -version = "1.17.1" +name = "object" +version = "0.36.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" +checksum = "081b846d1d56ddfc18fdf1a922e4f6e07a11768ea1b92dec44e42b72712ccfce" +dependencies = [ + "memchr", +] [[package]] -name = "opaque-debug" -version = "0.3.0" +name = "once_cell" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "openssl" -version = "0.10.55" +version = "0.10.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "345df152bc43501c5eb9e4654ff05f794effb78d4efe3d53abc158baddc0703d" +checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.4.1", "cfg-if", "foreign-types", "libc", @@ -1367,7 +1524,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.58", ] [[package]] @@ -1378,9 +1535,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.90" +version = "0.9.102" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374533b0e45f3a7ced10fcaeccca020e66656bc03dac384f852e4e5a7a8104a6" +checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" dependencies = [ "cc", "libc", @@ -1396,63 +1553,38 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "parking_lot" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" -dependencies = [ - "instant", - "lock_api", - "parking_lot_core 0.8.6", -] - -[[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 0.9.7", -] - -[[package]] -name = "parking_lot_core" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" -dependencies = [ - "cfg-if", - "instant", - "libc", - "redox_syscall 0.2.16", - "smallvec", - "winapi", + "parking_lot_core", ] [[package]] name = "parking_lot_core" -version = "0.9.7" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.2.16", + "redox_syscall 0.5.1", "smallvec", - "windows-sys 0.45.0", + "windows-targets 0.52.0", ] [[package]] name = "paste" -version = "1.0.12" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pear" -version = "0.2.4" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ec95680a7087503575284e5063e14b694b7a9c0b065e5dceec661e0497127e8" +checksum = "bdeeaa00ce488657faba8ebf44ab9361f9365a97bd39ffb8a60663f57ff4b467" dependencies = [ "inlinable_string", "pear_codegen", @@ -1461,38 +1593,66 @@ dependencies = [ [[package]] name = "pear_codegen" -version = "0.2.4" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9661a3a53f93f09f2ea882018e4d7c88f6ff2956d809a276060476fd8c879d3c" +checksum = "4bab5b985dc082b345f812b7df84e1bef27e7207b39e448439ba8bd69c93f147" dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn 2.0.15", + "syn 2.0.58", ] [[package]] name = "pem" -version = "0.8.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd56cbd21fea48d0c440b41cd69c589faacade08c992d9a54e471b79d0fd13eb" +checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" dependencies = [ - "base64 0.13.1", - "once_cell", - "regex", + "base64 0.22.1", + "serde", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", ] [[package]] name = "percent-encoding" -version = "2.2.0" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] [[package]] name = "pin-project-lite" -version = "0.2.9" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" @@ -1500,11 +1660,32 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + [[package]] name = "pkg-config" -version = "0.3.26" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "pointercrate-core" @@ -1550,6 +1731,7 @@ dependencies = [ "futures", "log", "pointercrate-core", + "pointercrate-user", "serde", "sqlx", "tokio", @@ -1572,6 +1754,7 @@ dependencies = [ "pointercrate-integrate", "pointercrate-user", "pointercrate-user-api", + "rand", "reqwest", "rocket", "serde", @@ -1597,6 +1780,24 @@ dependencies = [ "url", ] +[[package]] +name = "pointercrate-example" +version = "0.1.0" +dependencies = [ + "dotenv", + "maud", + "pointercrate-core", + "pointercrate-core-api", + "pointercrate-core-pages", + "pointercrate-demonlist", + "pointercrate-demonlist-api", + "pointercrate-demonlist-pages", + "pointercrate-user", + "pointercrate-user-api", + "pointercrate-user-pages", + "rocket", +] + [[package]] name = "pointercrate-integrate" version = "0.1.0" @@ -1605,7 +1806,11 @@ dependencies = [ "chrono", "dash-rs", "futures", + "governor", "log", + "nonzero_ext", + "pointercrate-core", + "pointercrate-demonlist", "reqwest", "sqlx", "tokio", @@ -1617,6 +1822,7 @@ version = "0.1.0" dependencies = [ "dotenv", "pointercrate-core", + "pointercrate-core-api", "pointercrate-demonlist", "pointercrate-demonlist-api", "pointercrate-user", @@ -1625,6 +1831,7 @@ dependencies = [ "rocket", "serde", "serde_json", + "serde_urlencoded", "sqlx", ] @@ -1632,7 +1839,7 @@ dependencies = [ name = "pointercrate-user" version = "0.1.0" dependencies = [ - "base64 0.13.1", + "base64 0.22.1", "bcrypt", "derive_more", "futures", @@ -1650,7 +1857,7 @@ dependencies = [ name = "pointercrate-user-api" version = "0.1.0" dependencies = [ - "base64 0.21.0", + "base64 0.22.1", "governor", "log", "nonzero_ext", @@ -1675,6 +1882,18 @@ dependencies = [ "pointercrate-user", ] +[[package]] +name = "portable-atomic" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -1690,7 +1909,6 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "syn 1.0.109", "version_check", ] @@ -1707,47 +1925,46 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.56" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] [[package]] name = "proc-macro2-diagnostics" -version = "0.10.0" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "606c4ba35817e2922a308af55ad51bab3645b59eae5c570d4a6cf07e36bd493b" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.58", "version_check", "yansi", ] [[package]] name = "quanta" -version = "0.9.3" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20afe714292d5e879d8b12740aa223c6a88f118af41870e8b6196e39a02238a8" +checksum = "8e5167a477619228a0b284fac2674e3c388cba90631d7b7de620e6f1fcd08da5" dependencies = [ "crossbeam-utils", "libc", - "mach", "once_cell", "raw-cpuid", - "wasi 0.10.2+wasi-snapshot-preview1", + "wasi", "web-sys", "winapi", ] [[package]] name = "quote" -version = "1.0.26" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -1784,69 +2001,61 @@ dependencies = [ [[package]] name = "raw-cpuid" -version = "10.7.0" +version = "11.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c297679cb867470fa8c9f67dbba74a78d78e3e98d7cf2b08d6d71540f797332" +checksum = "e29830cbb1290e404f24c73af91c5d8d631ce7e128691e9477556b540cd01ecd" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.4.1", ] [[package]] name = "redox_syscall" -version = "0.2.16" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ "bitflags 1.3.2", ] [[package]] name = "redox_syscall" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" -dependencies = [ - "bitflags 1.3.2", -] - -[[package]] -name = "redox_users" -version = "0.4.3" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" dependencies = [ - "getrandom", - "redox_syscall 0.2.16", - "thiserror", + "bitflags 2.4.1", ] [[package]] name = "ref-cast" -version = "1.0.16" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f43faa91b1c8b36841ee70e97188a869d37ae21759da6846d4be66de5bf7b12c" +checksum = "ccf0a6f84d5f1d581da8b41b47ec8600871962f2a528115b542b362d4b744931" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.16" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d2275aab483050ab2a7364c1a46604865ee7d6906684e08db0f090acf74f9e7" +checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.58", ] [[package]] name = "regex" -version = "1.8.1" +version = "1.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370" +checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" dependencies = [ - "regex-syntax 0.7.1", + "aho-corasick", + "memchr", + "regex-automata 0.4.6", + "regex-syntax 0.8.2", ] [[package]] @@ -1858,6 +2067,17 @@ dependencies = [ "regex-syntax 0.6.29", ] +[[package]] +name = "regex-automata" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.2", +] + [[package]] name = "regex-syntax" version = "0.6.29" @@ -1866,26 +2086,29 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.7.1" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" [[package]] name = "reqwest" -version = "0.11.17" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13293b639a097af28fc8a90f22add145a9c954e49d77da06263d58cf44d5fb91" +checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37" dependencies = [ - "base64 0.21.0", + "base64 0.22.1", "bytes", "encoding_rs", "futures-core", "futures-util", - "h2", - "http", - "http-body", - "hyper", + "h2 0.4.3", + "http 1.1.0", + "http-body 1.0.0", + "http-body-util", + "hyper 1.3.1", + "hyper-rustls", "hyper-tls", + "hyper-util", "ipnet", "js-sys", "log", @@ -1894,9 +2117,12 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", + "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", + "sync_wrapper", + "system-configuration", "tokio", "tokio-native-tls", "tower-service", @@ -1909,40 +2135,39 @@ dependencies = [ [[package]] name = "ring" -version = "0.16.20" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", + "cfg-if", + "getrandom", "libc", - "once_cell", - "spin 0.5.2", + "spin", "untrusted", - "web-sys", - "winapi", + "windows-sys 0.52.0", ] [[package]] name = "rocket" -version = "0.5.0-rc.3" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58734f7401ae5cfd129685b48f61182331745b357b96f2367f01aebaf1cc9cc9" +checksum = "a516907296a31df7dc04310e7043b61d71954d703b603cc6867a026d7e72d73f" dependencies = [ "async-stream", "async-trait", - "atomic", + "atomic 0.5.3", "binascii", "bytes", "either", "figment", "futures", "indexmap", - "is-terminal", "log", "memchr", "multer", "num_cpus", - "parking_lot 0.12.1", + "parking_lot", "pin-project-lite", "rand", "ref-cast", @@ -1952,7 +2177,7 @@ dependencies = [ "serde_json", "state", "tempfile", - "time 0.3.20", + "time", "tokio", "tokio-stream", "tokio-util", @@ -1963,9 +2188,9 @@ dependencies = [ [[package]] name = "rocket_codegen" -version = "0.5.0-rc.3" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7093353f14228c744982e409259fb54878ba9563d08214f2d880d59ff2fc508b" +checksum = "575d32d7ec1a9770108c879fc7c47815a80073f96ca07ff9525a94fcede1dd46" dependencies = [ "devise", "glob", @@ -1973,21 +2198,22 @@ dependencies = [ "proc-macro2", "quote", "rocket_http", - "syn 2.0.15", + "syn 2.0.58", "unicode-xid", + "version_check", ] [[package]] name = "rocket_http" -version = "0.5.0-rc.3" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "936012c99162a03a67f37f9836d5f938f662e26f2717809761a9ac46432090f4" +checksum = "e274915a20ee3065f611c044bd63c40757396b6dbc057d6046aec27f14f882b9" dependencies = [ "cookie", "either", "futures", - "http", - "hyper", + "http 0.2.12", + "hyper 0.14.29", "indexmap", "log", "memchr", @@ -1999,13 +2225,39 @@ dependencies = [ "smallvec", "stable-pattern", "state", - "time 0.3.20", + "time", "tokio", "uncased", ] [[package]] -name = "rustc_version" +name = "rsa" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core", + "signature", + "spki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc_version" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" @@ -2015,37 +2267,76 @@ dependencies = [ [[package]] name = "rustix" -version = "0.37.18" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bbfc1d1c7c40c01715f47d71444744a81669ca84e8b63e25a55e169b1f86433" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.4.1", "errno", - "io-lifetimes", "libc", "linux-raw-sys", - "windows-sys 0.48.0", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.23.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05cff451f60db80f490f3c182b77c35260baace73209e9cdbbe526bfe3a4d402" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +dependencies = [ + "base64 0.22.1", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" + +[[package]] +name = "rustls-webpki" +version = "0.102.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", ] [[package]] name = "rustversion" -version = "1.0.12" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" [[package]] name = "ryu" -version = "1.0.13" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "schannel" -version = "0.1.21" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" dependencies = [ - "windows-sys 0.42.0", + "windows-sys 0.52.0", ] [[package]] @@ -2056,21 +2347,15 @@ checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" [[package]] name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "scratch" -version = "1.0.5" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" -version = "2.8.2" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" +checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" dependencies = [ "bitflags 1.3.2", "core-foundation", @@ -2081,9 +2366,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.8.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" +checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" dependencies = [ "core-foundation-sys", "libc", @@ -2091,41 +2376,50 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.17" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.160" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb2f3770c8bce3bcda7e149193a069a0f4365bda1fa5cd88e03bca26afc1216c" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.160" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291a097c63d8497e00160b166a967a4a79c64f3facdd01cbd7502231688d77df" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.58", ] [[package]] name = "serde_json" -version = "1.0.96" +version = "1.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" +checksum = "d947f6b3163d8857ea16c4fa0dd4840d52f3041039a85decd46867eb1abef2e4" dependencies = [ - "itoa 1.0.6", + "itoa", "ryu", "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -2133,28 +2427,29 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", - "itoa 1.0.6", + "itoa", "ryu", "serde", ] [[package]] name = "serde_yaml" -version = "0.8.26" +version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ "indexmap", + "itoa", "ryu", "serde", - "yaml-rust", + "unsafe-libyaml", ] [[package]] name = "sha1" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", @@ -2163,9 +2458,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.6" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", @@ -2174,147 +2469,179 @@ dependencies = [ [[package]] name = "sharded-slab" -version = "0.1.4" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" dependencies = [ "lazy_static", ] [[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", ] +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + [[package]] name = "simple_asn1" -version = "0.4.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692ca13de57ce0613a363c8c2f1de925adebc81b04c923ac60c5488bb44abe4b" +checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" dependencies = [ - "chrono", "num-bigint", "num-traits", + "thiserror", + "time", ] [[package]] name = "slab" -version = "0.4.8" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "smallvec" -version = "1.10.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" -version = "0.4.9" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", - "winapi", + "windows-sys 0.52.0", ] [[package]] name = "spin" -version = "0.5.2" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] [[package]] -name = "spin" -version = "0.9.8" +name = "spinning_top" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +checksum = "d96d2d1d716fb500937168cc09353ffdc7a012be8475ac7308e1bdf0e3923300" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] [[package]] name = "sqlformat" -version = "0.2.1" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c12bc9199d1db8234678b7051747c07f517cdcf019262d1847b94ec8b1aee3e" +checksum = "f895e3734318cc55f1fe66258926c9b910c124d47520339efecbb6c59cec7c1f" dependencies = [ - "itertools", "nom", "unicode_categories", ] [[package]] name = "sqlx" -version = "0.6.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8de3b03a925878ed54a954f621e64bf55a3c1bd29652d0d1a17830405350188" +checksum = "c9a2ccff1a000a5a59cd33da541d9f2fdcd9e6e8229cc200565942bff36d0aaa" dependencies = [ "sqlx-core", "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", ] [[package]] name = "sqlx-core" -version = "0.6.3" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa8241483a83a3f33aa5fff7e7d9def398ff9990b2752b6c6112b83c6d246029" +checksum = "24ba59a9342a3d9bab6c56c118be528b27c9b60e490080e9711a04dccac83ef6" dependencies = [ "ahash", "atoi", - "base64 0.13.1", - "bitflags 1.3.2", "byteorder", "bytes", "chrono", "crc", "crossbeam-queue", - "dirs", - "dotenvy", "either", "event-listener", "futures-channel", "futures-core", "futures-intrusive", + "futures-io", "futures-util", "hashlink", "hex", - "hkdf", - "hmac", "indexmap", - "itoa 1.0.6", - "libc", "log", - "md-5", "memchr", + "native-tls", "once_cell", "paste", "percent-encoding", - "rand", "serde", "serde_json", - "sha1", "sha2", "smallvec", "sqlformat", - "sqlx-rt", - "stringprep", "thiserror", + "tokio", "tokio-stream", + "tracing", "url", - "whoami", ] [[package]] name = "sqlx-macros" -version = "0.6.3" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea40e2345eb2faa9e1e5e326db8c34711317d2b5e08d0d5741619048a803127" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn 1.0.109", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9966e64ae989e7e575b19d7265cb79d7fc3cbbdf179835cb0d716f294c2049c9" +checksum = "5833ef53aaa16d860e92123292f1f6a3d53c34ba8b1969f152ef1a7bb803f3c8" dependencies = [ "dotenvy", "either", @@ -2327,21 +2654,119 @@ dependencies = [ "serde_json", "sha2", "sqlx-core", - "sqlx-rt", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", "syn 1.0.109", + "tempfile", + "tokio", "url", ] [[package]] -name = "sqlx-rt" -version = "0.6.3" +name = "sqlx-mysql" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "804d3f245f894e61b1e6263c84b23ca675d96753b5abfd5cc8597d86806e8024" +checksum = "1ed31390216d20e538e447a7a9b959e06ed9fc51c37b514b46eb758016ecd418" dependencies = [ - "native-tls", + "atoi", + "base64 0.21.7", + "bitflags 2.4.1", + "byteorder", + "bytes", + "chrono", + "crc", + "digest", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", "once_cell", - "tokio", - "tokio-native-tls", + "percent-encoding", + "rand", + "rsa", + "serde", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c824eb80b894f926f89a0b9da0c7f435d27cdd35b8c655b114e58223918577e" +dependencies = [ + "atoi", + "base64 0.21.7", + "bitflags 2.4.1", + "byteorder", + "chrono", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "rand", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "tracing", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b244ef0a8414da0bed4bb1910426e890b19e5e9bccc27ada6b797d05c55ae0aa" +dependencies = [ + "atoi", + "chrono", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "sqlx-core", + "tracing", + "url", + "urlencoding", ] [[package]] @@ -2355,28 +2780,29 @@ dependencies = [ [[package]] name = "state" -version = "0.5.3" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbe866e1e51e8260c9eed836a042a5e7f6726bb2b411dffeaa712e19c388f23b" +checksum = "2b8c4a4445d81357df8b1a650d0d0d6fbbbfe99d064aa5e02f3e4022061476d8" dependencies = [ "loom", ] [[package]] name = "stringprep" -version = "0.1.2" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" dependencies = [ "unicode-bidi", "unicode-normalization", + "unicode-properties", ] [[package]] name = "subtle" -version = "2.4.1" +version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" @@ -2391,9 +2817,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.15" +version = "2.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" +checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" dependencies = [ "proc-macro2", "quote", @@ -2401,52 +2827,69 @@ dependencies = [ ] [[package]] -name = "tempfile" -version = "3.5.0" +name = "sync_wrapper" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9fbec84f381d5795b08656e4912bec604d162bff9291d6189a78f4c8ab87998" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" dependencies = [ - "cfg-if", - "fastrand", - "redox_syscall 0.3.5", - "rustix", - "windows-sys 0.45.0", + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", ] [[package]] -name = "termcolor" -version = "1.2.0" +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ - "winapi-util", + "cfg-if", + "fastrand", + "rustix", + "windows-sys 0.52.0", ] [[package]] name = "thiserror" -version = "1.0.40" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.40" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.58", ] [[package]] name = "thread_local" -version = "1.1.7" +version = "1.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" dependencies = [ "cfg-if", "once_cell", @@ -2454,21 +2897,14 @@ dependencies = [ [[package]] name = "time" -version = "0.1.43" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "time" -version = "0.3.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" -dependencies = [ - "itoa 1.0.6", + "deranged", + "itoa", + "num-conv", + "powerfmt", "serde", "time-core", "time-macros", @@ -2476,24 +2912,25 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.0" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.8" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ + "num-conv", "time-core", ] [[package]] name = "tinyvec" -version = "1.6.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +checksum = "c55115c6fbe2d2bef26eb09ad74bde02d8255476fc0c7b515ef09fbb35742d82" dependencies = [ "tinyvec_macros", ] @@ -2506,11 +2943,11 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.28.0" +version = "1.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3c786bf8134e5a3a166db9b29ab8f48134739014a3eca7bc6bfa95d673b136f" +checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" dependencies = [ - "autocfg", + "backtrace", "bytes", "libc", "mio", @@ -2524,13 +2961,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.1.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.58", ] [[package]] @@ -2543,11 +2980,22 @@ 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.14" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" +checksum = "267ac89e0bec6e691e5813911606935d77c476ff49024f98abcea3e7b15e37af" dependencies = [ "futures-core", "pin-project-lite", @@ -2556,27 +3004,72 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.8" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", - "tracing", ] [[package]] name = "toml" -version = "0.5.11" +version = "0.8.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335" dependencies = [ "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", ] +[[package]] +name = "toml_datetime" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38" +dependencies = [ + "indexmap", + "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", +] + +[[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" @@ -2585,11 +3078,11 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.37" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "cfg-if", + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -2597,20 +3090,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.24" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.15", + "syn 2.0.58", ] [[package]] name = "tracing-core" -version = "0.1.30" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", "valuable", @@ -2618,20 +3111,20 @@ dependencies = [ [[package]] name = "tracing-log" -version = "0.1.3" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" dependencies = [ - "lazy_static", "log", + "once_cell", "tracing-core", ] [[package]] name = "tracing-subscriber" -version = "0.3.17" +version = "0.3.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" dependencies = [ "matchers", "nu-ansi-term", @@ -2647,30 +3140,30 @@ 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 = "typenum" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "ubyte" -version = "0.10.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c81f0dae7d286ad0d9366d7679a77934cfc3cf3a8d67e82669794412b2368fe6" +checksum = "f720def6ce1ee2fc44d40ac9ed6d3a59c361c80a75a7aa8e75bb9baed31cf2ea" dependencies = [ "serde", ] [[package]] name = "uncased" -version = "0.9.7" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b01702b0fd0b3fadcf98e098780badda8742d4f4a7676615cad90e8ac73622" +checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697" dependencies = [ "serde", "version_check", @@ -2678,36 +3171,36 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.13" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" -version = "1.0.8" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" -version = "0.1.22" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" dependencies = [ "tinyvec", ] [[package]] -name = "unicode-segmentation" -version = "1.10.1" +name = "unicode-properties" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +checksum = "e4259d9d4425d9f0661581b804cb85fe66a4c631cadd8f490d1c13a35d5d9291" [[package]] -name = "unicode-width" -version = "0.1.10" +name = "unicode-segmentation" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" [[package]] name = "unicode-xid" @@ -2721,29 +3214,51 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" +[[package]] +name = "unsafe-libyaml" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" + [[package]] name = "untrusted" -version = "0.7.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" -version = "2.3.1" +version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", "idna", "percent-encoding", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "valuable" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "variant_partial_eq" +version = "0.0.0" +source = "git+https://github.com/stadust/variant-partial-eq#bdccf434b502b36b77f23cc6d6411f580828c08a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + [[package]] name = "vcpkg" version = "0.2.15" @@ -2758,31 +3273,30 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "want" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ - "log", "try-lock", ] [[package]] name = "wasi" -version = "0.10.2+wasi-snapshot-preview1" +version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +name = "wasite" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" -version = "0.2.84" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -2790,24 +3304,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.84" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.58", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.34" +version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" dependencies = [ "cfg-if", "js-sys", @@ -2817,9 +3331,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.84" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2827,28 +3341,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.84" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.58", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.84" +version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "web-sys" -version = "0.3.61" +version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" dependencies = [ "js-sys", "wasm-bindgen", @@ -2856,12 +3370,12 @@ dependencies = [ [[package]] name = "whoami" -version = "1.4.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c70234412ca409cc04e864e89523cb0fc37f5e1344ebed5a3ebf4192b6b9f68" +checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9" dependencies = [ - "wasm-bindgen", - "web-sys", + "redox_syscall 0.4.1", + "wasite", ] [[package]] @@ -2880,15 +3394,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -2901,176 +3406,200 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ - "windows-targets 0.48.0", + "windows-targets 0.48.5", ] [[package]] -name = "windows-sys" -version = "0.42.0" +name = "windows-core" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", + "windows-targets 0.52.0", ] [[package]] name = "windows-sys" -version = "0.45.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets 0.42.2", + "windows-targets 0.48.5", ] [[package]] name = "windows-sys" -version = "0.48.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.48.0", + "windows-targets 0.52.0", ] [[package]] name = "windows-targets" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ - "windows_aarch64_gnullvm 0.42.2", - "windows_aarch64_msvc 0.42.2", - "windows_i686_gnu 0.42.2", - "windows_i686_msvc 0.42.2", - "windows_x86_64_gnu 0.42.2", - "windows_x86_64_gnullvm 0.42.2", - "windows_x86_64_msvc 0.42.2", + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", ] [[package]] name = "windows-targets" -version = "0.48.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" +checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", + "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", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" [[package]] name = "windows_aarch64_msvc" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" -version = "0.48.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" [[package]] name = "windows_i686_gnu" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" -version = "0.48.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" [[package]] name = "windows_i686_msvc" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" -version = "0.48.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" [[package]] name = "windows_x86_64_gnu" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" -version = "0.48.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" [[package]] name = "windows_x86_64_msvc" -version = "0.42.2" +version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" -version = "0.48.0" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" [[package]] -name = "winreg" -version = "0.10.1" +name = "winnow" +version = "0.6.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" +checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1" dependencies = [ - "winapi", + "memchr", ] [[package]] -name = "yaml-rust" -version = "0.4.5" +name = "winreg" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" dependencies = [ - "linked-hash-map", + "cfg-if", + "windows-sys 0.48.0", ] [[package]] name = "yansi" -version = "0.5.1" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" +dependencies = [ + "is-terminal", +] + +[[package]] +name = "zerocopy" +version = "0.7.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "zeroize" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/Cargo.toml b/Cargo.toml index 44bdf5586..36cae776e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,17 @@ members = [ "pointercrate-user-pages", "pointercrate-integrate", - # Create only containing integration tests - "pointercrate-test" -] \ No newline at end of file + # Crate only containing integration tests + "pointercrate-test", + + # Crate containing an example setup for a custom list + "pointercrate-example" +] +resolver = "2" + +[workspace.package] +authors = ["stadust"] +description = "Libraries for creating pointercrate-like demonlist websites" +homepage = "https://pointercrate.com" +edition = "2021" +repository = "https://github.com/stadust/pointercrate" \ No newline at end of file diff --git a/README.md b/README.md index c91aee36c..1e89755d7 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,174 @@ # Pointercrate -As of March 2nd 2019 this is the official repository for pointercrate. It does not contain all the code required to run a local copy of pointercrate, however, as parts of the code remain private. In particular, this repository does not contain +As of March 2nd 2019 this is the official repository for pointercrate. It contains the main parts of the backend code of [pointercrate.com](https://pointercrate.com). Specifically, it contains all the code for the demonlist and user area pages seen on pointercrate, but does not contain the code for the home page, API documentation and demonlist guidelines. It instead aims to be a framework that can be used as a stepping stone for creating custom pointercrate-like websites. The reason the home page and similar are not open source is that we have experienced people not customizing these parts when hosting their own lists, resulting in these websites displaying pointercrate branding despite not being associated with pointercrate. As a compromise, this repository instead contains code for an example binary that shows how to use the various library components in this repository to create a demonlist website. See the [getting started section](#getting-started) below for more information. -- a `main.rs` file stitching together the code in the different libraries -- various assets such as graphics used by pointercrate -- code specific to pointercrate that has no place on custom copies of pointercrate (such as the pointercrate homepage) +Note that exclusion of pointercrate-specific code from this repository is still a work-in-progress. For example, a lot of SEO related metadata included in the pages served for the demonlist still hardcodes the pointercrate.com URL. If you end up using this repository as a base for your own demonlist, we ask you to please update these. -This has both upsides and downsides. Since you'll have to write those components yourself, it will be very complicated to run custom pointercrate copies (especially since we do not actually support such endeavours). However, there are various advantages: +## Getting Started (Linux) -- No code in this repository explicitly references pointercrate. Everything from the logo in the navigation bar to the site metadata in the headers is configurable. This means I wont have to shout at you 7 times for failing to remove references to pointercrate on your website -- Each component is as independent as possible. For instance, you could run a pointercrate copy that does not use the `pointercrate-demonlist*` libraries and it would work just fine. +The goal of this section is to compile and successful run the `pointercrate-example` binary target to set up a locally running demonlist-clone. + +We assume that you have a rust toolchain set up. If not, install one using [`rustup`](https://rustup.rs). + +### Database Setup + +Pointercrate uses [`postgresql`](https://www.postgresql.org/) as its database. This guide assumes that you have a local postgres server running, and created both a role (user) and database for use with pointercrate. For simplicity, name both of these `pointercrate` (e.g. `psql -U pointercrate pointercrate` should drop you into a database prompt). + +To connect to the postgres database, pointercrate uses [`sqlx`](https://github.com/launchbadge/sqlx). This means that even to just compile pointercrate, a database with pointercrate's database schema needs to be available (as sqlx will validate SQL queries both syntactically and semantically at compile-time by sending them to a database server for validation). For this, the `DATABASE_URL` environment variable needs to be set to a [libpq connection string](https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING), e.g. + +```bash +# If you created the pointercrate database and the pointercrate role (without password but with login permissions), +# the connection string will be +export DATABASE_URL=postgresql://pointercrate@localhost/pointercrate +``` + +To set up a database with all the tables used by pointercrate, this repository contains a set of migrations that can be applied to an empty database (in the [`migrations`](migrations) directory). To apply the migrations, run + +```bash +cargo install sqlx-cli +cargo sqlx migrate run +``` + +### Pointercrate Configuration + +Pointercrate is configured via environment variables, which it reads from a `.env` file in the working directory. Additionally, it expects a secret for signing access tokens to be available in a `.secret` file. An example `.env` can be found under `pointercrate-example`. Copy this to the repository root and create the a dummy `.secret` file (for debug purposes only!) via + +```bash +cp pointercrate-example/.env.sample .env +echo "insecure-do-not-use-in-prod" > .secret +``` + +Then, open `.env` and fill out all the fields that do not have default values (e.g. `DATABASE_URL`). + +### Running `pointercrate-example` + +At this point, you should be able to run `pointercrate-example` via + +```bash +cargo run -p pointercrate-example +``` + +If everything is set up correctly, you should see `rocket`'s development server start up with output as follows: + +
+ +``` + Finished dev [unoptimized + debuginfo] target(s) in 0.16s + Running `target/debug/pointercrate-example` +🔧 Configured for debug. + >> address: 127.0.0.1 + >> port: 1971 + >> workers: 12 + >> max blocking threads: 512 + >> ident: Rocket + >> IP header: X-Real-IP + >> limits: bytes = 8KiB, data-form = 2MiB, file = 1MiB, form = 32KiB, json = 1MiB, msgpack = 1MiB, string = 8KiB + >> temp dir: /tmp + >> http/2: true + >> keep-alive: 5s + >> tls: disabled + >> shutdown: ctrlc = true, force = true, signals = [SIGTERM], grace = 2s, mercy = 3s + >> log level: normal + >> cli colors: true +📬 Routes: + >> (home) GET / + >> (login_page) GET /login + >> (login) POST /login + >> (account_page) GET /account + >> (register) POST /register + >> (overview) GET /demonlist/?& + >> (stats_viewer_redirect) GET /demonlist/?statsviewer=true + >> (demon_page) GET /demonlist/ + >> (stats_viewer) GET /demonlist/statsviewer + >> (nation_stats_viewer) GET /demonlist/statsviewer/nations + >> (demon_permalink) GET /demonlist/permalink/ + >> (heatmap_css) GET /demonlist/statsviewer/heatmap.css + >> (FileServer: pointercrate-core-pages/static) GET /static/core/ [10] + >> (FileServer: pointercrate-user-pages/static) GET /static/user/ [10] + >> (FileServer: pointercrate-demonlist-pages/static) GET /static/demonlist/ [10] + >> (login) POST /api/v1/auth/ + >> (get_me) GET /api/v1/auth/me + >> (patch_me) PATCH /api/v1/auth/me + >> (delete_me) DELETE /api/v1/auth/me + >> (register) POST /api/v1/auth/register + >> (invalidate) POST /api/v1/auth/invalidate + >> (verify_email) GET /api/v1/auth/verify_email? + >> (paginate) GET /api/v1/users/ + >> (get_user) GET /api/v1/users/ + >> (patch_user) PATCH /api/v1/users/ + >> (delete_user) DELETE /api/v1/users/ + >> (paginate) GET /api/v2/demons/ + >> (post) POST /api/v2/demons/ + >> (paginate_listed) GET /api/v2/demons/listed + >> (get) GET /api/v2/demons/ + >> (patch) PATCH /api/v2/demons/ + >> (audit) GET /api/v2/demons//audit + >> (post_creator) POST /api/v2/demons//creators + >> (movement_log) GET /api/v2/demons//audit/movement + >> (delete_creator) DELETE /api/v2/demons//creators/ + >> (paginate) GET /api/v1/records/ + >> (unauthed_pagination) GET /api/v1/records/ + >> (submit) POST /api/v1/records/ + >> (paginate) GET /api/v1/players/ + >> (paginate_claims) GET /api/v1/players/claims + >> (ranking) GET /api/v1/players/ranking + >> (delete) DELETE /api/v1/records/ + >> (get) GET /api/v1/records/ + >> (patch) PATCH /api/v1/records/ + >> (get) GET /api/v1/players/ + >> (patch) PATCH /api/v1/players/ + >> (get_notes) GET /api/v1/records//notes + >> (add_note) POST /api/v1/records//notes + >> (audit) GET /api/v1/records//audit + >> (put_claim) PUT /api/v1/players//claims + >> (geolocate_nationality) POST /api/v1/players//geolocate + >> (delete_note) DELETE /api/v1/records//notes/ + >> (patch_note) PATCH /api/v1/records//notes/ + >> (patch_claim) PATCH /api/v1/players//claims/ + >> (delete_claim) DELETE /api/v1/players//claims/ + >> (paginate) GET /api/v1/submitters/ + >> (get) GET /api/v1/submitters/ + >> (patch) PATCH /api/v1/submitters/ + >> (ranking) GET /api/v1/nationalities/ranking + >> (nation) GET /api/v1/nationalities/ + >> (subdivisions) GET /api/v1/nationalities//subdivisions + >> (list_information) GET /api/v1/list_information/ +🥅 Catchers: + >> (catch_404) 404 +📡 Fairings: + >> Shield (liftoff, response, singleton) + >> Maintenance (ignite, request) +🛡️ Shield: + >> X-Content-Type-Options: nosniff + >> Permissions-Policy: interest-cohort=() + >> X-Frame-Options: SAMEORIGIN +🚀 Rocket has launched from http://127.0.0.1:1971 +``` + +
+ +The last line will tell you the URL for accessing your local pointercrate instance (in this case, `127.0.0.1:1971`, since my `ROCKET_PORT` environment variable was set to `1971`)! + +### Next Steps + +If you want to use pointercrate as a framework for setting up your own demonlist-like website, check out the actual sample code contained in [`pointercrate-example/src/main.rs`](pointercrate-example/src/main.rs). As a first step, you will probably want to replace all the placeholder strings (such as replacing `""` with your domain). You probably also want to the "Hello World" home page with a proper home page of your own, and familiarize yourself with the demonlist administration interface in the "User Area". For the latter, you will need to create an account (via the usual registration routine), and then grant yourself (list) administrator permissions via the postgres shell: + +``` +$ psql -U pointercrate pointercrate +psql (16.1) +Type "help" for help. + +pointercrate=# -- assuming the user you just created was assigned member_id 1: +pointercrate=# UPDATE members SET permissions = '0100000000001000'::BIT(16) WHERE member_id = 1; +``` + +After reloading the user area, you should be able to see all administration tabs (both for website management and demonlist management). + +## Running Integration Tests + +Pointercrate's test suite can be executed via `cargo test` in the repository root. As running the example binary, it requires access to a database with the pointercrate scheme loaded via the `DATABASE_URL` environment variable. You should use a separate database for tests (say, `pointercrate_test`), as during setup and tear-down of each individual test, this database is dropped and recreated from scratch. + +Integration tests are also ran as part of the CI on each pull request. ## Special thanks diff --git a/migrations/20240328150521_simplify_gd_integration.down.sql b/migrations/20240328150521_simplify_gd_integration.down.sql new file mode 100644 index 000000000..93479c830 --- /dev/null +++ b/migrations/20240328150521_simplify_gd_integration.down.sql @@ -0,0 +1,42 @@ +-- Add down migration script here + +CREATE TABLE gj_creator_meta ( + user_id bigint PRIMARY KEY NOT NULL, -- No REFERENCES creator(user_id) as we also have to keep track of _missing_ entries here! + cached_at timestamp without time zone NOT NULL, + absent boolean DEFAULT false NOT NULL +); + +CREATE TABLE gj_level_meta ( + level_id bigint PRIMARY KEY NOT NULL, + cached_at timestamp without time zone NOT NULL, + absent boolean DEFAULT false NOT NULL +); + +CREATE TABLE gj_level_request_results ( + level_id bigint NOT NULL, + request_hash bigint NOT NULL +); + +CREATE TABLE gj_level_request_meta ( + request_hash bigint PRIMARY KEY NOT NULL, + cached_at timestamp without time zone NOT NULL, + absent boolean DEFAULT false NOT NULL +); + + +CREATE TABLE gj_level_data_meta ( + level_id bigint PRIMARY KEY NOT NULL, + cached_at timestamp without time zone NOT NULL, + absent boolean DEFAULT false NOT NULL +); + +CREATE TABLE gj_newgrounds_song_meta ( + song_id bigint PRIMARY KEY NOT NULL, + cached_at timestamp without time zone NOT NULL, + absent boolean DEFAULT false NOT NULL +); + + +CREATE TABLE download_lock( + level_id bigint not null +); \ No newline at end of file diff --git a/migrations/20240328150521_simplify_gd_integration.up.sql b/migrations/20240328150521_simplify_gd_integration.up.sql new file mode 100644 index 000000000..f220f7492 --- /dev/null +++ b/migrations/20240328150521_simplify_gd_integration.up.sql @@ -0,0 +1,9 @@ +-- Add up migration script here + +DROP TABLE download_lock; +DROP TABLE gj_newgrounds_song_meta; +DROP TABLE gj_level_data_meta; +DROP TABLE gj_level_request_meta; +DROP TABLE gj_level_request_results; +DROP TABLE gj_level_meta; +DROP TABLE gj_creator_meta; \ No newline at end of file diff --git a/migrations/20240404084539_nationalityupdate_again.down.sql b/migrations/20240404084539_nationalityupdate_again.down.sql new file mode 100644 index 000000000..814293862 --- /dev/null +++ b/migrations/20240404084539_nationalityupdate_again.down.sql @@ -0,0 +1,81 @@ +-- Add down migration script here +DELETE FROM subdivisions; + +INSERT INTO subdivisions (iso_code, name, nation) +VALUES + ('WA', 'Washington', 'US'), + ('MD', 'Maryland', 'US'), + ('WV', 'West Virginia', 'US'), + ('NY', 'New York', 'US'), + ('NJ', 'New Jersey', 'US'), + ('PA', 'Pennsylvania', 'US'), + ('VA', 'Virginia', 'US'), + ('KY', 'Kentucky', 'US'), + ('OH', 'Ohio', 'US'), + ('IN', 'Indiana', 'US'), + ('IL', 'Illinois', 'US'), + ('MI', 'Michigan', 'US'), + ('WI', 'Wisconsin', 'US'), + ('CT', 'Connecticut', 'US'), + ('RI', 'Rhode Island', 'US'), + ('VT', 'Vermont', 'US'), + ('NH', 'New Hampshire', 'US'), + ('MA', 'Massachusetts', 'US'), + ('ME', 'Maine', 'US'), + ('AL', 'Alabama', 'US'), + ('GA', 'Georgia', 'US'), + ('SC', 'South Carolina', 'US'), + ('FL', 'Florida', 'US'), + ('MS', 'Mississippi', 'US'), + ('TN', 'Tennessee', 'US'), + ('NC', 'North Carolina', 'US'), + ('TX', 'Texas', 'US'), + ('OK', 'Oklahoma', 'US'), + ('NM', 'New Mexico', 'US'), + ('NE', 'Nebraska', 'US'), + ('SD', 'South Dakota', 'US'), + ('KS', 'Kansas', 'US'), + ('CO', 'Colorado', 'US'), + ('ND', 'North Dakota', 'US'), + ('AR', 'Arkansas', 'US'), + ('MO', 'Missouri', 'US'), + ('LA', 'Louisiana', 'US'), + ('IA', 'Iowa', 'US'), + ('MN', 'Minnesota', 'US'), + ('AZ', 'Arizona', 'US'), + ('NV', 'Nevada', 'US'), + ('CA', 'California', 'US'), + ('UT', 'Utah', 'US'), + ('OR', 'Oregon', 'US'), + ('MT', 'Montana', 'US'), + ('ID', 'Idaho', 'US'), + ('WY', 'Wyoming', 'US'), + ('HI', 'Hawaii', 'US'), + ('AK', 'Alaska', 'US'), + ('DC', 'Washington, District of Columbia', 'US'), + ('DE', 'Delaware', 'US'), + ('MB', 'Manitoba', 'CA'), + ('NT', 'Northwest Territories', 'CA'), + ('NL', 'Newfoundland and Labrador', 'CA'), + ('NU', 'Nunavut', 'CA'), + ('QC', 'Quebec', 'CA'), + ('BC', 'British Columbia', 'CA'), + ('SK', 'Saskatchewan', 'CA'), + ('AB', 'Alberta', 'CA'), + ('ON', 'Ontario', 'CA'), + ('NB', 'New Brunswick', 'CA'), + ('NS', 'Nova Scotia', 'CA'), + ('PE', 'Prince Edward Island', 'CA'), + ('YT', 'Yukon', 'CA'), + ('SCT', 'Scotland', 'GB'), + ('WLS', 'Wales', 'GB'), + ('ENG', 'England', 'GB'), + ('NIR', 'Northern Ireland', 'GB'), + ('ACT', 'Australian Capital Territory', 'AU'), + ('TAS', 'Tasmania', 'AU'), + ('NT', 'Northern Territory', 'AU'), + ('WA', 'Western Australia', 'AU'), + ('QLD', 'Queensland', 'AU'), + ('NSW', 'New South Wales', 'AU'), + ('VIC', 'Victoria', 'AU'), + ('SA', 'South Australia', 'AU'); \ No newline at end of file diff --git a/migrations/20240404084539_nationalityupdate_again.up.sql b/migrations/20240404084539_nationalityupdate_again.up.sql new file mode 100644 index 000000000..627e29d8a --- /dev/null +++ b/migrations/20240404084539_nationalityupdate_again.up.sql @@ -0,0 +1,428 @@ +-- Add up migration script here +INSERT INTO subdivisions (iso_code, name, nation) +VALUES + ('V', 'Provincia de Tierra del Fuego', 'AR'), + ('N', 'Provincia de Misiones', 'AR'), + ('W', 'Provincia de Corrientes', 'AR'), + ('E', 'Provincia de Entre Ríos', 'AR'), + ('Y', 'Provincia de Jujuy', 'AR'), + ('A', 'Provincia de Salta', 'AR'), + ('T', 'Provincia de Tucumán', 'AR'), + ('G', 'Provincia de Santiago del Estero', 'AR'), + ('K', 'Provincia de Catamarca', 'AR'), + ('J', 'Provincia de San Juan', 'AR'), + ('F', 'Provincia de La Rioja', 'AR'), + ('P', 'Provincia de Formosa', 'AR'), + ('H', 'Provincia del Chaco', 'AR'), + ('S', 'Provincia de Santa Fe', 'AR'), + ('X', 'Provincia de Córdoba', 'AR'), + ('D', 'Provincia de San Luis', 'AR'), + ('M', 'Provincia de Mendoza', 'AR'), + ('L', 'Provincia de La Pampa', 'AR'), + ('R', 'Provincia de Río Negro', 'AR'), + ('Q', 'Provincia de Neuquén', 'AR'), + ('B', 'Provincia de Buenos Aires', 'AR'), + ('C', 'Ciudad Autónoma de Buenos Aires', 'AR'), + ('U', 'Provincia del Chubut', 'AR'), + ('Z', 'Provincia de Santa Cruz', 'AR'), + ('DF', 'Distrito Federal', 'BR'), + ('GO', 'Goiás', 'BR'), + ('RR', 'Roraima', 'BR'), + ('AM', 'Amazonas', 'BR'), + ('RO', 'Rondônia', 'BR'), + ('AC', 'Acre', 'BR'), + ('TO', 'Tocantins', 'BR'), + ('AP', 'Amapá', 'BR'), + ('PA', 'Pará', 'BR'), + ('MT', 'Mato Grosso', 'BR'), + ('MS', 'Mato Grosso do Sul', 'BR'), + ('MA', 'Maranhão', 'BR'), + ('PI', 'Piauí', 'BR'), + ('CE', 'Ceará', 'BR'), + ('BA', 'Bahia', 'BR'), + ('RN', 'Rio Grande do Norte', 'BR'), + ('PB', 'Paraíba', 'BR'), + ('PE', 'Pernambuco', 'BR'), + ('AL', 'Alagoas', 'BR'), + ('SE', 'Sergipe', 'BR'), + ('MG', 'Minas Gerais', 'BR'), + ('PR', 'Paraná', 'BR'), + ('SC', 'Santa Catarina', 'BR'), + ('RS', 'Rio Grande do Sul', 'BR'), + ('ES', 'Espírito Santo', 'BR'), + ('RJ', 'Rio de Janeiro', 'BR'), + ('SP', 'São Paulo', 'BR'), + ('MA', 'Magallanes', 'CL'), + ('LL', 'Los Lagos', 'CL'), + ('AI', 'Aysén', 'CL'), + ('AR', 'Araucanía', 'CL'), + ('NB', 'Ñuble', 'CL'), + ('ML', 'Maule', 'CL'), + ('VS', 'Valparaíso', 'CL'), + ('RM', 'Región Metropolitana de Santiago', 'CL'), + ('AT', 'Atacama', 'CL'), + ('LR', 'Los Ríos', 'CL'), + ('BI', 'Biobío', 'CL'), + ('LI', 'O''Higgins', 'CL'), + ('CO', 'Coquimbo', 'CL'), + ('TA', 'Tarapacá', 'CL'), + ('AN', 'Antofagasta', 'CL'), + ('AP', 'Arica y Parinacota', 'CL'), + ('SAP', 'San Andrés y Providencia', 'CO'), + ('LAG', 'Departamento de La Guajira', 'CO'), + ('CES', 'Departamento del Cesar', 'CO'), + ('NSA', 'Norte de Santander', 'CO'), + ('BOY', 'Departamento de Boyacá', 'CO'), + ('CUN', 'Departamento de Cundinamarca', 'CO'), + ('ATL', 'Departamento del Atlántico', 'CO'), + ('MAG', 'Departamento del Magdalena', 'CO'), + ('BOL', 'Departamento de Bolívar', 'CO'), + ('CHO', 'Departamento del Chocó', 'CO'), + ('ANT', 'Departamento de Antioquia', 'CO'), + ('CAL', 'Departamento de Caldas', 'CO'), + ('RIS', 'Departamento del Risaralda', 'CO'), + ('QUI', 'Departamento del Quindío', 'CO'), + ('VAC', 'Departamento del Valle del Cauca', 'CO'), + ('SUC', 'Departamento de Sucre', 'CO'), + ('COR', 'Departamento de Córdoba', 'CO'), + ('SAN', 'Departamento de Santander', 'CO'), + ('TOL', 'Departamento del Tolima', 'CO'), + ('DC', 'Bogotá', 'CO'), + ('HUI', 'Departamento del Huila', 'CO'), + ('CAU', 'Departamento del Cauca', 'CO'), + ('ARA', 'Departamento de Arauca', 'CO'), + ('CAS', 'Departamento de Casanare', 'CO'), + ('VID', 'Departamento del Vichada', 'CO'), + ('MET', 'Departamento del Meta', 'CO'), + ('GUV', 'Departamento del Guaviare', 'CO'), + ('VAU', 'Departamento del Vaupés', 'CO'), + ('GUA', 'Departamento de Guainía', 'CO'), + ('CAQ', 'Departamento del Caquetá', 'CO'), + ('NAR', 'Departamento de Nariño', 'CO'), + ('PUT', 'Departamento del Putumayo', 'CO'), + ('AMA', 'Departamento del Amazonas', 'CO'), + ('LOR', 'Loreto', 'PE'), + ('AMA', 'Amazоnas', 'PE'), + ('LAL', 'La Libertad', 'PE'), + ('LIM', 'Lima', 'PE'), + ('ANC', 'Áncash', 'PE'), + ('HUC', 'Huánuco', 'PE'), + ('PAS', 'Pasco', 'PE'), + ('JUN', 'Junín', 'PE'), + ('LAM', 'Lambayeque', 'PE'), + ('TUM', 'Tumbes', 'PE'), + ('PIU', 'Piura', 'PE'), + ('SAM', 'San Martín', 'PE'), + ('CAJ', 'Cajamarca', 'PE'), + ('CUS', 'Cuzco', 'PE'), + ('PUN', 'Puno', 'PE'), + ('HUV', 'Huancavelica', 'PE'), + ('ICA', 'Ica', 'PE'), + ('ARE', 'Arequipa', 'PE'), + ('AYA', 'Ayacucho', 'PE'), + ('APU', 'Apurímac', 'PE'), + ('MOQ', 'Moquegua', 'PE'), + ('TAC', 'Tacna', 'PE'), + ('UCA', 'Ucayali', 'PE'), + ('MDD', 'Madre de Dios', 'PE'), + ('NAY', 'Nayarit', 'MX'), + ('BCS', 'Baja California Sur', 'MX'), + ('SON', 'Sonora', 'MX'), + ('BCN', 'Baja California', 'MX'), + ('SIN', 'Sinaloa', 'MX'), + ('CHH', 'Chihuahua', 'MX'), + ('DUR', 'Durango', 'MX'), + ('ZAC', 'Zacatecas', 'MX'), + ('COL', 'Colima', 'MX'), + ('VER', 'Veracruz', 'MX'), + ('TAB', 'Tabasco', 'MX'), + ('GUA', 'Guanajuato', 'MX'), + ('MIC', 'Michoacán', 'MX'), + ('QUE', 'Queretaro', 'MX'), + ('MEX', 'México', 'MX'), + ('HID', 'Hidalgo', 'MX'), + ('PUE', 'Puebla', 'MX'), + ('MOR', 'Morelos', 'MX'), + ('CMX', 'Ciudad de México', 'MX'), + ('OAX', 'Oaxaca', 'MX'), + ('CHP', 'Chiapas', 'MX'), + ('CAM', 'Campeche', 'MX'), + ('GRO', 'Guerrero', 'MX'), + ('YUC', 'Yucatán', 'MX'), + ('ROO', 'Quintana Roo', 'MX'), + ('COA', 'Coahuila', 'MX'), + ('TAM', 'Tamaulipas', 'MX'), + ('NLE', 'Nuevo León', 'MX'), + ('SLP', 'San Luis Potosí', 'MX'), + ('AGU', 'Aguascalientes', 'MX'), + ('JAL', 'Jalisco', 'MX'), + ('02', 'Województwo dolnośląskie', 'PL'), + ('04', 'Województwo kujawsko-pomorskie', 'PL'), + ('06', 'Województwo lubelskie', 'PL'), + ('08', 'Województwo lubuskie', 'PL'), + ('10', 'Województwo łódzkie', 'PL'), + ('12', 'Województwo małopolskie', 'PL'), + ('14', 'Województwo mazowieckie', 'PL'), + ('16', 'Województwo opolskie', 'PL'), + ('18', 'Województwo podkarpackie', 'PL'), + ('20', 'Województwo podlaskie', 'PL'), + ('22', 'Województwo pomorskie', 'PL'), + ('24', 'Województwo śląskie', 'PL'), + ('26', 'Województwo świętokrzyskie', 'PL'), + ('28', 'Województwo warmińsko-mazurskie', 'PL'), + ('30', 'Województwo wielkopolskie', 'PL'), + ('32', 'Województwo zachodniopomorskie', 'PL'), + ('05', 'Vinnytsia Oblast', 'UA'), + ('07', 'Volyn Oblast', 'UA'), + ('09', 'Luhansk Oblast', 'UA'), + ('12', 'Dnipropetrovsk Oblast', 'UA'), + ('14', 'Donetsk Oblast', 'UA'), + ('18', 'Zhytomyr Oblast', 'UA'), + ('21', 'Zakarpattia Oblast', 'UA'), + ('23', 'Zaporizhzhia Oblast', 'UA'), + ('26', 'Ivano-Frankivsk Oblast', 'UA'), + ('30', 'Kyiv', 'UA'), + ('32', 'Kyiv Oblast', 'UA'), + ('35', 'Kirovohrad Oblast', 'UA'), + ('40', 'Sevastopol', 'UA'), + ('43', 'Autonomous Republic of Crimea', 'UA'), + ('46', 'Lviv Oblast', 'UA'), + ('48', 'Mykolaiv Oblast', 'UA'), + ('51', 'Odesa Oblast', 'UA'), + ('53', 'Poltava Oblast', 'UA'), + ('56', 'Rivne Oblast', 'UA'), + ('59', 'Sumy Oblast', 'UA'), + ('61', 'Ternopil Oblast', 'UA'), + ('63', 'Kharkiv Oblast', 'UA'), + ('65', 'Kherson Oblast', 'UA'), + ('68', 'Khmelnytskyi Oblast', 'UA'), + ('71', 'Cherkasy Oblast', 'UA'), + ('74', 'Chernihiv Oblast', 'UA'), + ('77', 'Chernivtsi Oblast', 'UA'), + ('01', 'Østfold', 'NO'), + ('02', 'Akershus', 'NO'), + ('03', 'Oslo', 'NO'), + ('06', 'Buskerud', 'NO'), + ('07', 'Vestfold', 'NO'), + ('08', 'Telemark', 'NO'), + ('11', 'Rogaland', 'NO'), + ('15', 'Møre og Romsdal', 'NO'), + ('18', 'Nordland', 'NO'), + ('19', 'Troms', 'NO'), + ('20', 'Finnmark', 'NO'), + ('34', 'Innlandet', 'NO'), + ('42', 'Agder', 'NO'), + ('46', 'Vestland', 'NO'), + ('50', 'Trøndelag', 'NO'), + ('SJM', 'Svalbard og Jan Mayen', 'NO'), + ('01', 'Ahvenanmaa', 'FI'), + ('02', 'Etelä-Karjala', 'FI'), + ('03', 'Etelä-Pohjanmaa', 'FI'), + ('04', 'Etelä-Savo', 'FI'), + ('05', 'Kainuu', 'FI'), + ('06', 'Kanta-Häme', 'FI'), + ('07', 'Keski-Pohjanmaa', 'FI'), + ('08', 'Keski-Suomi', 'FI'), + ('09', 'Kymenlaakso', 'FI'), + ('10', 'Lappi', 'FI'), + ('11', 'Pirkanmaa', 'FI'), + ('12', 'Pohjanmaa', 'FI'), + ('13', 'Pohjois-Karjala', 'FI'), + ('14', 'Pohjois-Pohjanmaa', 'FI'), + ('15', 'Pohjois-Savo', 'FI'), + ('16', 'Päijät-Häme', 'FI'), + ('17', 'Satakunta', 'FI'), + ('18', 'Uusimaa', 'FI'), + ('19', 'Varsinais-Suomi', 'FI'), + ('OV', 'Overijssel', 'NL'), + ('GR', 'Groningen', 'NL'), + ('FR', 'Friesland', 'NL'), + ('FL', 'Flevoland', 'NL'), + ('NH', 'Noord-Holland', 'NL'), + ('UT', 'Utrecht', 'NL'), + ('NB', 'Noord-Brabant', 'NL'), + ('ZE', 'Zeeland', 'NL'), + ('LI', 'Limburg', 'NL'), + ('GE', 'Gelderland', 'NL'), + ('DR', 'Drenthe', 'NL'), + ('ZH', 'Zuid-Holland', 'NL'), + ('BE', 'Berlin', 'DE'), + ('HB', 'Bremen', 'DE'), + ('SL', 'Saarland', 'DE'), + ('NW', 'Nordrhein-Westfalen', 'DE'), + ('RP', 'Rheinland-Pfalz', 'DE'), + ('BW', 'Baden-Württemberg', 'DE'), + ('NI', 'Niedersachsen', 'DE'), + ('HE', 'Hessen', 'DE'), + ('BY', 'Bayern', 'DE'), + ('MV', 'Mecklenburg-Vorpommern', 'DE'), + ('HH', 'Hamburg', 'DE'), + ('ST', 'Sachsen-Anhalt', 'DE'), + ('SH', 'Schleswig-Holstein', 'DE'), + ('SN', 'Sachsen', 'DE'), + ('TH', 'Thüringen', 'DE'), + ('BB', 'Brandenburg', 'DE'), + ('GF', 'French Guiana', 'FR'), + ('TF', 'French Southern and Antarctic Lands', 'FR'), + ('GP', 'Guadeloupe', 'FR'), + ('MQ', 'Martinique', 'FR'), + ('YT', 'Mayotte', 'FR'), + ('RE', 'La Réunion', 'FR'), + ('OCC', 'Occitanie', 'FR'), + ('PAC', 'Provence-Alpes-Côte d''Azur', 'FR'), + ('HDF', 'Hauts-de-France', 'FR'), + ('IDF', 'Île-de-France', 'FR'), + ('NOR', 'Normandie', 'FR'), + ('GES', 'Grand Est', 'FR'), + ('PDL', 'Pays de la Loire', 'FR'), + ('BRE', 'Bretagne', 'FR'), + ('BFC', 'Bourgogne-Franche-Comté', 'FR'), + ('CVL', 'Centre-Val de Loire', 'FR'), + ('ARA', 'Auvergne-Rhône-Alpes', 'FR'), + ('NAQ', 'Nouvelle-Aquitaine', 'FR'), + ('20R', 'Corse', 'FR'), + ('PM', 'Saint-Pierre-et-Miquelon', 'FR'), + ('BL', 'Saint-Barthélemy', 'FR'), + ('MF', 'Saint-Martin', 'FR'), + ('NC', 'Nouvelle-Calédonie', 'FR'), + ('WF', 'Wallis-et-Futuna', 'FR'), + ('PF', 'Polynésie Française', 'FR'), + ('21', 'Piemonte', 'IT'), + ('23', 'Valle d''Aosta', 'IT'), + ('25', 'Lombardia', 'IT'), + ('32', 'Trentino-Alto Adige', 'IT'), + ('34', 'Veneto', 'IT'), + ('36', 'Friuli-Venezia Giulia', 'IT'), + ('42', 'Liguria', 'IT'), + ('45', 'Emilia-Romagna', 'IT'), + ('52', 'Toscana', 'IT'), + ('55', 'Umbria', 'IT'), + ('57', 'Marche', 'IT'), + ('62', 'Lazio', 'IT'), + ('65', 'Abruzzo', 'IT'), + ('67', 'Molise', 'IT'), + ('72', 'Campania', 'IT'), + ('75', 'Puglia', 'IT'), + ('77', 'Basilicata', 'IT'), + ('78', 'Calabria', 'IT'), + ('82', 'Sicilia', 'IT'), + ('88', 'Sardegna', 'IT'), + ('ML', 'Melilla', 'ES'), + ('CE', 'Ceuta', 'ES'), + ('CL', 'Castilla y León', 'ES'), + ('PV', 'País Vasco', 'ES'), + ('IB', 'Islas Baleares', 'ES'), + ('CN', 'Canarias', 'ES'), + ('CT', 'Cataluña', 'ES'), + ('VC', 'Comunidad Valenciana', 'ES'), + ('MC', 'Región de Murcia', 'ES'), + ('AN', 'Andalucía', 'ES'), + ('CM', 'Castilla–La Mancha', 'ES'), + ('EX', 'Extremadura', 'ES'), + ('AR', 'Aragón', 'ES'), + ('MD', 'Comunidad de Madrid', 'ES'), + ('NA', 'Navarra', 'ES'), + ('RI', 'La Rioja', 'ES'), + ('GA', 'Galicia', 'ES'), + ('AS', 'Asturias', 'ES'), + ('CB', 'Cantabria', 'ES'), + ('MOW', 'Moscow', 'RU'), + ('SMO', 'Smolensk Oblast', 'RU'), + ('TUL', 'Tula Oblast', 'RU'), + ('LIP', 'Lipetsk Oblast', 'RU'), + ('KRS', 'Kursk Oblast', 'RU'), + ('VOR', 'Voronezh Oblast', 'RU'), + ('MUR', 'Murmansk Oblast', 'RU'), + ('NEN', 'Nenets Autonomous Okrug', 'RU'), + ('ARK', 'Arkhangelsk Oblast', 'RU'), + ('YAN', 'Yamalo-Nenets Autonomous Okrug', 'RU'), + ('KHM', 'Khanty-Mansi Autonomous Okrug', 'RU'), + ('TYU', 'Tyumen Oblast', 'RU'), + ('TOM', 'Tomsk Oblast', 'RU'), + ('OMS', 'Omsk Oblast', 'RU'), + ('NVS', 'Novosibirsk Oblast', 'RU'), + ('ALT', 'Altai Krai', 'RU'), + ('AL', 'Altai, Republic of', 'RU'), + ('KEM', 'Kemerovo Oblast', 'RU'), + ('IRK', 'Irkutsk Oblast', 'RU'), + ('TY', 'Tuva, Republic of', 'RU'), + ('KK', 'Khakassia, Republic of', 'RU'), + ('ZAB', 'Zabaykalsky Krai', 'RU'), + ('BU', 'Buryatia, Republic of', 'RU'), + ('KYA', 'Krasnoyarsk Krai', 'RU'), + ('SAK', 'Sakhalin Oblast', 'RU'), + ('KAM', 'Kamchatka Krai', 'RU'), + ('CHU', 'Chukotka Autonomous Okrug', 'RU'), + ('YEV', 'Jewish Autonomous Oblast', 'RU'), + ('MAG', 'Magadan Oblast', 'RU'), + ('PRI', 'Primorsky Krai', 'RU'), + ('AMU', 'Amur Oblast', 'RU'), + ('SA', 'Sakha, Republic of', 'RU'), + ('KHA', 'Khabarovsk Krai', 'RU'), + ('SVE', 'Sverdlovsk Oblast', 'RU'), + ('CHE', 'Chelyabinsk Oblast', 'RU'), + ('KGN', 'Kurgan Oblast', 'RU'), + ('KR', 'Karelia, Republic of', 'RU'), + ('LEN', 'Leningrad Oblast', 'RU'), + ('NGR', 'Novgorod Oblast', 'RU'), + ('YAR', 'Yaroslavl Oblast', 'RU'), + ('IVA', 'Ivanovo Oblast', 'RU'), + ('KO', 'Komi, Republic of', 'RU'), + ('PER', 'Perm Krai', 'RU'), + ('BA', 'Bashkortostan, Republic of', 'RU'), + ('ORE', 'Orenburg Oblast', 'RU'), + ('KIR', 'Kirov Oblast', 'RU'), + ('CU', 'Chuvashia, Republic of', 'RU'), + ('MO', 'Mordovia, Republic of', 'RU'), + ('SAM', 'Samara Oblast', 'RU'), + ('SAR', 'Saratov Oblast', 'RU'), + ('ROS', 'Rostov Oblast', 'RU'), + ('KL', 'Kalmykia, Republic of', 'RU'), + ('STA', 'Stavropol Krai', 'RU'), + ('KLU', 'Kaluga Oblast', 'RU'), + ('BRY', 'Bryansk Oblast', 'RU'), + ('ORL', 'Oryol Oblast', 'RU'), + ('AD', 'Adygea, Republic of', 'RU'), + ('TAM', 'Tambov Oblast', 'RU'), + ('BEL', 'Belgorod Oblast', 'RU'), + ('VLG', 'Vologda Oblast', 'RU'), + ('SPE', 'Saint Petersburg', 'RU'), + ('PSK', 'Pskov Oblast', 'RU'), + ('TVE', 'Tver Oblast', 'RU'), + ('VLA', 'Vladimir Oblast', 'RU'), + ('KOS', 'Kostroma Oblast', 'RU'), + ('UD', 'Udmurtia, Republic of', 'RU'), + ('ME', 'Mari El, Republic of', 'RU'), + ('TA', 'Tatarstan, Republic of', 'RU'), + ('PNZ', 'Penza Oblast', 'RU'), + ('VGG', 'Volgograd Oblast', 'RU'), + ('AST', 'Astrakhan Oblast', 'RU'), + ('DA', 'Dagestan, Republic of', 'RU'), + ('SE', 'North Ossetia-Alania, Republic of', 'RU'), + ('KC', 'Karachay-Cherkessia, Republic of', 'RU'), + ('KGD', 'Kaliningrad Oblast', 'RU'), + ('MOS', 'Moscow Oblast', 'RU'), + ('RYA', 'Ryazan Oblast', 'RU'), + ('NIZ', 'Nizhny Novgorod Oblast', 'RU'), + ('ULY', 'Ulyanovsk Oblast', 'RU'), + ('CE', 'Chechnya, Republic of', 'RU'), + ('IN', 'Ingushetia, Republic of', 'RU'), + ('KB', 'Kabardino-Balkaria, Republic of', 'RU'), + ('KDA', 'Krasnodar Krai', 'RU'), + ('11', 'Seoul', 'KR'), + ('26', 'Busan', 'KR'), + ('27', 'Daegu', 'KR'), + ('28', 'Incheon', 'KR'), + ('29', 'Gwangju', 'KR'), + ('30', 'Daejeon', 'KR'), + ('31', 'Ulsan', 'KR'), + ('41', 'Gyeonggi', 'KR'), + ('42', 'Gangwon', 'KR'), + ('43', 'Chungcheongbuk-do', 'KR'), + ('44', 'Chungcheongnam-do', 'KR'), + ('45', 'Jeonbuk', 'KR'), + ('46', 'Jeonnam', 'KR'), + ('47', 'Gyeongsangbuk-do', 'KR'), + ('48', 'Gyeongsangnam-do', 'KR'), + ('49', 'Jeju-do', 'KR'), + ('50', 'Sejong', 'KR'); diff --git a/migrations/20240504194923_cached_points.down.sql b/migrations/20240504194923_cached_points.down.sql new file mode 100644 index 000000000..12af63e14 --- /dev/null +++ b/migrations/20240504194923_cached_points.down.sql @@ -0,0 +1,183 @@ +-- Add down migration script here +DROP VIEW ranked_players; +DROP VIEW ranked_nations; + +ALTER TABLE players DROP COLUMN score; +ALTER TABLE nationalities DROP COLUMN score; +ALTER TABLE subdivisions DROP COLUMN score; + +DROP FUNCTION recompute_player_scores(); +DROP FUNCTION score_of_player(player_id INTEGER); +DROP FUNCTION recompute_nation_scores(); +DROP FUNCTION score_of_nation(iso_country_code VARCHAR(2)); +DROP FUNCTION recompute_subdivision_scores(); +DROP FUNCTION score_of_subdivision(iso_country_code VARCHAR(2), iso_code VARCHAR(3)); +DROP VIEW score_giving; + +ALTER TABLE players DROP CONSTRAINT nation_subdivions_fkey; + +-- Copied from 20210419002933.up +CREATE VIEW players_with_score AS +SELECT players.id, + players.name, + RANK() OVER(ORDER BY scores.total_score DESC) AS rank, + CASE WHEN scores.total_score IS NULL THEN 0.0::FLOAT ELSE scores.total_score END AS score, + ROW_NUMBER() OVER(ORDER BY scores.total_score DESC) AS index, + nationalities.iso_country_code, + nationalities.nation, + players.subdivision, + nationalities.continent +FROM + ( + SELECT pseudo_records.player, + SUM(record_score(pseudo_records.progress::FLOAT, pseudo_records.position::FLOAT, 100::FLOAT, pseudo_records.requirement)) as total_score + FROM ( + SELECT player, + progress, + position, + CASE WHEN demons.position > 75 THEN 100 ELSE requirement END AS requirement + FROM records + INNER JOIN demons + ON demons.id = demon + WHERE demons.position <= 150 AND status_ = 'APPROVED' AND (demons.position <= 75 OR progress = 100) + + UNION + + SELECT verifier as player, + CASE WHEN demons.position > 150 THEN 0.0::FLOAT ELSE 100.0::FLOAT END as progress, + position, + 100.0::FLOAT + FROM demons + + UNION + + SELECT publisher as player, + 0.0::FLOAT as progress, + position, + 100.0::FLOAT + FROM demons + + UNION + + SELECT creator as player, + 0.0::FLOAT as progress, + 1.0::FLOAT as position, -- doesn't matter + 100.0::FLOAT + FROM creators + ) AS pseudo_records + GROUP BY player + ) scores + INNER JOIN players + ON scores.player = players.id + LEFT OUTER JOIN nationalities + ON players.nationality = nationalities.iso_country_code +WHERE NOT players.banned AND players.id != 1534; + +-- Copied from 20210726174613 +CREATE VIEW nations_with_score AS + SELECT RANK() OVER(ORDER BY scores.total_score DESC) AS rank, + scores.total_score AS score, + nationalities.iso_country_code, + nationalities.nation, + nationalities.continent + FROM ( + SELECT nationality, + SUM(record_score(pseudo_records.progress::FLOAT, pseudo_records.position::FLOAT, + 100::FLOAT, pseudo_records.requirement)) as total_score + FROM ( + select distinct on (nationality, demon) + nationality, + progress, + position, + CASE WHEN demons.position > 75 THEN 100 ELSE requirement END AS requirement + from ( + select demon, player, progress + from records + where status_='APPROVED' + + union + + select id, verifier, 100 + from demons + ) records + inner join demons + on demons.id = records.demon + inner join players + on players.id=records.player + inner join nationalities + on iso_country_code=players.nationality + where position <= 150 and not players.banned + order by nationality, demon, progress desc + ) AS pseudo_records + GROUP BY nationality + ) scores +INNER JOIN nationalities + ON nationalities.iso_country_code = scores.nationality; + +-- Copied from 20210903174349 +CREATE OR REPLACE FUNCTION best_records_local(country VARCHAR(2), the_subdivision VARCHAR(3)) + RETURNS TABLE (LIKE records) +AS +$body$ +WITH grp AS ( + SELECT records.*, + RANK() OVER (PARTITION BY demon ORDER BY demon, progress DESC) AS rk + FROM records + INNER JOIN players + ON players.id = player + WHERE status_='APPROVED' AND players.nationality = country AND players.subdivision = the_subdivision +) +SELECT id, progress, video, status_, player, submitter, demon +FROM grp +WHERE rk = 1; +$body$ + LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION subdivision_ranking_of(country VARCHAR(2)) + RETURNS TABLE ( + rank BIGINT, + score FLOAT, + subdivision_code VARCHAR(3), + name TEXT + ) +AS + $body$ + SELECT RANK() OVER(ORDER BY scores.total_score DESC) AS rank, + scores.total_score AS score, + iso_code, + name + FROM ( + SELECT iso_code, name, + SUM(record_score(pseudo_records.progress::FLOAT, pseudo_records.position::FLOAT, + 100::FLOAT, pseudo_records.requirement)) as total_score + FROM ( + select distinct on (iso_code, demon) + iso_code, + subdivisions.name, + progress, + position, + CASE WHEN demons.position > 75 THEN 100 ELSE requirement END AS requirement + from ( + select demon, player, progress + from records + where status_='APPROVED' + + union + + select id, verifier, 100 + from demons + ) records + inner join demons + on demons.id = records.demon + inner join players + on players.id=records.player + inner join subdivisions + on (iso_code=players.subdivision and players.nationality = nation) + where position <= 150 and not players.banned and nation = country + order by iso_code, demon, progress desc + ) AS pseudo_records + GROUP BY iso_code, name + ) scores; + $body$ +LANGUAGE SQL; + diff --git a/migrations/20240504194923_cached_points.up.sql b/migrations/20240504194923_cached_points.up.sql new file mode 100644 index 000000000..306aa9af5 --- /dev/null +++ b/migrations/20240504194923_cached_points.up.sql @@ -0,0 +1,145 @@ +-- Add up migration script here + +ALTER TABLE players ADD COLUMN score DOUBLE PRECISION DEFAULT 0 NOT NULL; + +CREATE VIEW score_giving AS + SELECT records.progress, demons.position, demons.requirement, records.player + FROM records + INNER JOIN demons + ON demons.id = records.demon + WHERE records.status_ = 'APPROVED' + + UNION + + SELECT 100, demons.position, demons.requirement, demons.verifier + FROM demons; + +CREATE FUNCTION score_of_player(player_id INTEGER) RETURNS DOUBLE PRECISION AS $$ + SELECT SUM(record_score(progress, position, 150, requirement)) + FROM score_giving + WHERE player = player_id +$$ LANGUAGE SQL; + +-- This is slower than the old "select * from players_with_scores", but only needs to be called +-- when demons are being moved around, so overall cheaper. Should this ever become a bottleneck for +-- some obscure reason, we can separate the "score" column into a separate table, in which case the +-- below function becomes a "TRUNCATE + INSERT q" on that new table (but then we'd pay the cost of +-- combining these via a JOIN when requesting the stats viewer). +CREATE FUNCTION recompute_player_scores() RETURNS void AS $$ + -- The nested query is faster than the more obvious "UPDATE players SET score = score_of_player(id)", + -- as the latter would essentially have runtime O(|records| * |players|), which this solution as + -- runtime O(|records| + |players|^2) [approximately, technically its |players| * |players where score > 0| + -- and I'm sure the query planner is clever enough to not make it quadratic]. + -- Since |records| >> |players|, this is faster. + UPDATE players + SET score = coalesce(q.score, 0) + FROM ( + SELECT player, SUM(record_score(progress, position, 150, requirement)) as score + FROM score_giving + GROUP BY player + ) q + WHERE q.player = id; +$$ LANGUAGE SQL; + +SELECT recompute_player_scores(); + +DROP VIEW players_with_score; +CREATE VIEW ranked_players AS + SELECT + ROW_NUMBER() OVER(ORDER BY players.score DESC, id) AS index, + RANK() OVER(ORDER BY score DESC) AS rank, + id, name, players.score, subdivision, + nationalities.iso_country_code, + nationalities.nation, + nationalities.continent + FROM players + LEFT OUTER JOIN nationalities + ON players.nationality = nationalities.iso_country_code + WHERE NOT players.banned AND players.score > 0.0; + + +ALTER TABLE nationalities ADD COLUMN score DOUBLE PRECISION NOT NULL DEFAULT 0.0; + +CREATE FUNCTION score_of_nation(iso_country_code VARCHAR(2)) RETURNS DOUBLE PRECISION AS $$ + SELECT SUM(record_score(q.progress, q.position, 150, q.requirement)) + FROM ( + SELECT DISTINCT ON (position) * from score_giving + INNER JOIN players + ON players.id=player + WHERE players.nationality = iso_country_code + ORDER BY position, progress DESC + ) q +$$ LANGUAGE SQL; + +CREATE FUNCTION recompute_nation_scores() RETURNS void AS $$ + UPDATE nationalities + SET score = COALESCE(p.sum, 0) + FROM ( + SELECT nationality, SUM(record_score(q.progress, q.position, 150, q.requirement)) + FROM ( + SELECT DISTINCT ON (position, nationality) * from score_giving + INNER JOIN players + ON players.id=player + WHERE players.nationality IS NOT NULL + ORDER BY players.nationality, position, progress DESC + ) q + GROUP BY nationality + ) p + WHERE p.nationality = iso_country_code +$$ LANGUAGE SQL; + +SELECT recompute_nation_scores(); + +ALTER TABLE subdivisions ADD COLUMN score DOUBLE PRECISION NOT NULL DEFAULT 0.0; + +CREATE FUNCTION score_of_subdivision(iso_country_code VARCHAR(2), iso_code VARCHAR(3)) RETURNS DOUBLE PRECISION AS $$ + SELECT SUM(record_score(q.progress, q.position, 150, q.requirement)) + FROM ( + SELECT DISTINCT ON (position) * from score_giving + INNER JOIN players + ON players.id=player + WHERE players.nationality = iso_country_code + AND players.subdivision = iso_code + ORDER BY position, progress DESC + ) q +$$ LANGUAGE SQL; + +CREATE FUNCTION recompute_subdivision_scores() RETURNS void AS $$ + UPDATE subdivisions + SET score = COALESCE(p.sum, 0) + FROM ( + SELECT nationality, subdivision, SUM(record_score(q.progress, q.position, 150, q.requirement)) + FROM ( + SELECT DISTINCT ON (position, nationality, subdivision) * from score_giving + INNER JOIN players + ON players.id=player + WHERE players.nationality IS NOT NULL + AND players.subdivision IS NOT NULL + ORDER BY players.nationality, players.subdivision, position, progress DESC + ) q + GROUP BY nationality, subdivision + ) p + WHERE p.nationality = nation + AND p.subdivision = iso_code +$$ LANGUAGE SQL; + +SELECT recompute_subdivision_scores(); + +DROP VIEW nations_with_score; +CREATE VIEW ranked_nations AS + SELECT + ROW_NUMBER() OVER(ORDER BY score DESC, iso_country_code) AS index, + RANK() OVER(ORDER BY score DESC) AS rank, + score, + iso_country_code, + nation, + continent + FROM nationalities + WHERE score > 0.0; + +-- Now-unused functions +DROP FUNCTION subdivision_ranking_of(country varchar(2)); +DROP FUNCTION best_records_local(country VARCHAR(2), the_subdivision VARCHAR(3)); + +-- Hardening against invalid database state +ALTER TABLE players ADD CONSTRAINT nation_subdivions_fkey FOREIGN KEY (nationality, subdivision) REFERENCES subdivisions (nation, iso_code); \ No newline at end of file diff --git a/migrations/20240518154803_no_extended_progress_points.down.sql b/migrations/20240518154803_no_extended_progress_points.down.sql new file mode 100644 index 000000000..8f33ab529 --- /dev/null +++ b/migrations/20240518154803_no_extended_progress_points.down.sql @@ -0,0 +1,17 @@ +-- Add down migration script here + +CREATE OR REPLACE VIEW score_giving AS + SELECT records.progress, demons.position, demons.requirement, records.player + FROM records + INNER JOIN demons + ON demons.id = records.demon + WHERE records.status_ = 'APPROVED' + + UNION + + SELECT 100, demons.position, demons.requirement, demons.verifier + FROM demons; + +SELECT recompute_player_scores(); +SELECT recompute_nation_scores(); +SELECT recompute_subdivision_scores(); \ No newline at end of file diff --git a/migrations/20240518154803_no_extended_progress_points.up.sql b/migrations/20240518154803_no_extended_progress_points.up.sql new file mode 100644 index 000000000..8efed0766 --- /dev/null +++ b/migrations/20240518154803_no_extended_progress_points.up.sql @@ -0,0 +1,17 @@ +-- Add up migration script here +CREATE OR REPLACE VIEW score_giving AS + SELECT records.progress, demons.position, demons.requirement, records.player + FROM records + INNER JOIN demons + ON demons.id = records.demon + WHERE records.status_ = 'APPROVED' AND (demons.position <= 75 OR records.progress = 100) + + UNION + + SELECT 100, demons.position, demons.requirement, demons.verifier + FROM demons; + + +SELECT recompute_player_scores(); +SELECT recompute_nation_scores(); +SELECT recompute_subdivision_scores(); \ No newline at end of file diff --git a/migrations/20240518161548_properly_zero_scores.down.sql b/migrations/20240518161548_properly_zero_scores.down.sql new file mode 100644 index 000000000..f689e4bf7 --- /dev/null +++ b/migrations/20240518161548_properly_zero_scores.down.sql @@ -0,0 +1,53 @@ +-- Add down migration script here + +CREATE OR REPLACE FUNCTION recompute_player_scores() RETURNS void AS $$ + UPDATE players + SET score = coalesce(q.score, 0) + FROM ( + SELECT player, SUM(record_score(progress, position, 150, requirement)) as score + FROM score_giving + GROUP BY player + ) q + WHERE q.player = id; +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION recompute_nation_scores() RETURNS void AS $$ + UPDATE nationalities + SET score = COALESCE(p.sum, 0) + FROM ( + SELECT nationality, SUM(record_score(q.progress, q.position, 150, q.requirement)) + FROM ( + SELECT DISTINCT ON (position, nationality) * from score_giving + INNER JOIN players + ON players.id=player + WHERE players.nationality IS NOT NULL + ORDER BY players.nationality, position, progress DESC + ) q + GROUP BY nationality + ) p + WHERE p.nationality = iso_country_code +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION recompute_subdivision_scores() RETURNS void AS $$ + UPDATE subdivisions + SET score = COALESCE(p.sum, 0) + FROM ( + SELECT nationality, subdivision, SUM(record_score(q.progress, q.position, 150, q.requirement)) + FROM ( + SELECT DISTINCT ON (position, nationality, subdivision) * from score_giving + INNER JOIN players + ON players.id=player + WHERE players.nationality IS NOT NULL + AND players.subdivision IS NOT NULL + ORDER BY players.nationality, players.subdivision, position, progress DESC + ) q + GROUP BY nationality, subdivision + ) p + WHERE p.nationality = nation + AND p.subdivision = iso_code +$$ LANGUAGE SQL; + + +SELECT recompute_player_scores(); +SELECT recompute_nation_scores(); +SELECT recompute_subdivision_scores(); \ No newline at end of file diff --git a/migrations/20240518161548_properly_zero_scores.up.sql b/migrations/20240518161548_properly_zero_scores.up.sql new file mode 100644 index 000000000..dedf68fbf --- /dev/null +++ b/migrations/20240518161548_properly_zero_scores.up.sql @@ -0,0 +1,62 @@ +-- Add up migration script here + +-- We need LEFT OUTER JOINs below so that those players which DO NOT show up in the +-- SELECT player, SUM(...) query (because they no longer have any records that give scores) have their scores correctly +-- reset to 0! + +CREATE OR REPLACE FUNCTION recompute_player_scores() RETURNS void AS $$ + UPDATE players + SET score = coalesce(q.score, 0) + FROM players p + LEFT OUTER JOIN ( + SELECT player, SUM(record_score(progress, position, 150, requirement)) as score + FROM score_giving + GROUP BY player + ) q + ON q.player = p.id + WHERE players.id = p.id; +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION recompute_nation_scores() RETURNS void AS $$ + UPDATE nationalities + SET score = COALESCE(p.sum, 0) + FROM nationalities n + LEFT OUTER JOIN ( + SELECT nationality, SUM(record_score(q.progress, q.position, 150, q.requirement)) + FROM ( + SELECT DISTINCT ON (position, nationality) * from score_giving + INNER JOIN players + ON players.id=player + WHERE players.nationality IS NOT NULL + ORDER BY players.nationality, position, progress DESC + ) q + GROUP BY nationality + ) p + ON p.nationality = n.iso_country_code + WHERE n.iso_country_code = nationalities.iso_country_code +$$ LANGUAGE SQL; + +CREATE OR REPLACE FUNCTION recompute_subdivision_scores() RETURNS void AS $$ + UPDATE subdivisions + SET score = COALESCE(p.sum, 0) + FROM subdivisions s + LEFT OUTER JOIN ( + SELECT nationality, subdivision, SUM(record_score(q.progress, q.position, 150, q.requirement)) + FROM ( + SELECT DISTINCT ON (position, nationality, subdivision) * from score_giving + INNER JOIN players + ON players.id=player + WHERE players.nationality IS NOT NULL + AND players.subdivision IS NOT NULL + ORDER BY players.nationality, players.subdivision, position, progress DESC + ) q + GROUP BY nationality, subdivision + ) p + ON s.nation = p.nationality AND s.iso_code = p.subdivision + WHERE s.nation = subdivisions.nation + AND s.iso_code = subdivisions.iso_code +$$ LANGUAGE SQL; + +SELECT recompute_player_scores(); +SELECT recompute_nation_scores(); +SELECT recompute_subdivision_scores(); \ No newline at end of file diff --git a/pointercrate-core-api/Cargo.toml b/pointercrate-core-api/Cargo.toml index 13dde7881..aef057866 100644 --- a/pointercrate-core-api/Cargo.toml +++ b/pointercrate-core-api/Cargo.toml @@ -1,17 +1,18 @@ [package] name = "pointercrate-core-api" version = "0.1.0" -edition = "2021" +authors.workspace = true +edition.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -serde = "1.0.118" -rocket = {version = "0.5.0-rc.3", features = ["json"]} +serde = "1.0.203" +rocket = {version = "0.5.1", features = ["json"]} pointercrate-core = {path = "../pointercrate-core"} pointercrate-core-pages = {path = "../pointercrate-core-pages"} -serde_json = "1.0.60" -sqlx = { version = "0.6", default-features = false, features = [ "runtime-tokio-native-tls", "macros", "postgres", "chrono", "offline" ] } -log = "0.4.11" +serde_json = "1.0.118" +sqlx = { version = "0.7", default-features = false, features = [ "runtime-tokio-native-tls", "macros", "postgres", "chrono" ] } +log = "0.4.22" serde_urlencoded = "0.7.0" -maud = "0.25" +maud = "0.26.0" diff --git a/pointercrate-core-api/src/error.rs b/pointercrate-core-api/src/error.rs index 7940b0119..910719de4 100644 --- a/pointercrate-core-api/src/error.rs +++ b/pointercrate-core-api/src/error.rs @@ -1,5 +1,5 @@ use crate::response::Page; -use log::{error, info}; +use log::info; use pointercrate_core::error::PointercrateError; use pointercrate_core_pages::error::ErrorFragment; use rocket::{ @@ -34,10 +34,6 @@ impl<'r> Responder<'r, 'static> for ErrorResponder { let status = Status::from_code(self.error_code / 100).unwrap_or(Status::InternalServerError); - if status.code >= 500 { - error!("Encountered an internal server error: {:?}", self); - } - if accept == MediaType::HTML { Response::build_from( Page::new(ErrorFragment { diff --git a/pointercrate-core-api/src/etag.rs b/pointercrate-core-api/src/etag.rs index 4ab057c2a..72c6bd2ec 100644 --- a/pointercrate-core-api/src/etag.rs +++ b/pointercrate-core-api/src/etag.rs @@ -37,7 +37,7 @@ impl<'r> FromRequest<'r> for Precondition { async fn from_request(request: &'r Request<'_>) -> Outcome { match request.headers().get_one("if-match") { Some(if_match) => Outcome::Success(Precondition(if_match.split(',').map(ToString::to_string).collect())), - None => Outcome::Failure((Status::PreconditionRequired, CoreError::PreconditionRequired)), + None => Outcome::Error((Status::PreconditionRequired, CoreError::PreconditionRequired)), } } } @@ -47,18 +47,20 @@ impl<'r, T: Taggable> Responder<'r, 'static> for Tagged { let response_etag = self.0.etag_string(); match request.method() { - Method::Get => + Method::Get => { if let Some(if_none_match) = request.headers().get_one("if-none-match") { if if_none_match.contains(&response_etag) { - return Response::build().status(Status::NotModified).ok() + return Response::build().status(Status::NotModified).ok(); } - }, - Method::Patch | Method::Delete => + } + }, + Method::Patch | Method::Delete => { if let Some(if_none_match) = request.headers().get_one("if-match") { if if_none_match.contains(&response_etag) { - return Response::build().status(Status::NotModified).ok() + return Response::build().status(Status::NotModified).ok(); } - }, + } + }, _ => (), } diff --git a/pointercrate-core-api/src/lib.rs b/pointercrate-core-api/src/lib.rs index 26b958086..ffe9d847e 100644 --- a/pointercrate-core-api/src/lib.rs +++ b/pointercrate-core-api/src/lib.rs @@ -1,5 +1,6 @@ pub mod error; pub mod etag; +pub mod maintenance; +pub mod pagination; pub mod query; -#[macro_use] pub mod response; diff --git a/pointercrate-core-api/src/maintenance.rs b/pointercrate-core-api/src/maintenance.rs new file mode 100644 index 000000000..53ef16959 --- /dev/null +++ b/pointercrate-core-api/src/maintenance.rs @@ -0,0 +1,55 @@ +//! Module providing a "maintenance mode" fairing (middleware) + +use crate::error::Result; +use pointercrate_core::error::CoreError; +use rocket::{ + fairing::{Fairing, Info, Kind}, + http::Method, + routes, uri, Build, Data, Request, Rocket, +}; + +/// Rocket fairing that causes all mutating requests (aka non-GET requests) to return 503 SERVICE UNAVAILABLE if `.0` is `true`. +/// +/// Works in a very hacky way, as rocket does not allow fairing to terminate requests. Thus we instead rewrite the +/// request on the fly to be a GET /maintenance, which is an endpoint that unconditionally returns a 503 response. +/// This endpoint only exists when maintainence mode is active (it is dynamically mounted in `on_ignite`). +/// +/// Idea taken from https://stackoverflow.com/questions/70011965/global-authentication-authorization-in-rocket-based-on-a-header +#[derive(Default)] +pub struct MaintenanceFairing(bool); + +impl MaintenanceFairing { + pub fn new(read_only: bool) -> Self { + MaintenanceFairing(read_only) + } +} + +#[rocket::async_trait] +impl Fairing for MaintenanceFairing { + fn info(&self) -> Info { + Info { + name: "Maintenance", + kind: Kind::Ignite | Kind::Request, + } + } + + async fn on_ignite(&self, mut rocket: Rocket) -> rocket::fairing::Result { + if self.0 { + log::warn!("Maintenance mode activated! All non-GET requests will receive a 503 response!"); + rocket = rocket.mount("/", routes![maintenance]); + } + Ok(rocket) + } + + async fn on_request(&self, request: &mut Request<'_>, _: &mut Data<'_>) { + if self.0 && request.method() != Method::Get { + request.set_uri(uri!("/maintenance")); + request.set_method(Method::Get); + } + } +} + +#[rocket::get("/maintenance")] +async fn maintenance() -> Result<()> { + Err(CoreError::ReadOnlyMaintenance.into()) +} diff --git a/pointercrate-core-api/src/pagination.rs b/pointercrate-core-api/src/pagination.rs new file mode 100644 index 000000000..17df9b75a --- /dev/null +++ b/pointercrate-core-api/src/pagination.rs @@ -0,0 +1,193 @@ +use std::collections::BTreeMap; + +use pointercrate_core::{ + error::CoreError, + pagination::{Paginatable, PaginationParameters, PaginationQuery}, +}; +use rocket::serde::json::Json; +use sqlx::PgConnection; + +use crate::response::Response2; + +#[derive(Debug)] +pub struct LinksBuilder { + endpoint: &'static str, + rels: BTreeMap<&'static str, PaginationParameters>, +} + +impl LinksBuilder { + pub fn new(endpoint: &'static str) -> Self { + LinksBuilder { + endpoint, + rels: BTreeMap::new(), + } + } + + pub fn with_first(mut self, id_before_first: i32) -> Self { + self.rels.insert( + "first", + PaginationParameters { + after: Some(id_before_first), + before: None, + ..Default::default() + }, + ); + self + } + + pub fn with_last(mut self, id_after_last: i32) -> Self { + self.rels.insert( + "last", + PaginationParameters { + after: None, + before: Some(id_after_last), + ..Default::default() + }, + ); + self + } + + pub fn with_next(mut self, after: i32) -> Self { + self.rels.insert( + "next", + PaginationParameters { + after: Some(after), + before: None, + ..Default::default() + }, + ); + self + } + + pub fn with_previous(mut self, before: i32) -> Self { + self.rels.insert( + "prev", + PaginationParameters { + after: None, + before: Some(before), + ..Default::default() + }, + ); + self + } + + pub fn generate(&self, base: &P) -> Result { + let mut buf = String::new(); + let mut is_first = true; + // The build functions set a default value for "limit" - copy the actual value from the given base here + let limit = base.parameters().limit; + + for (rel, param) in &self.rels { + if !is_first { + buf.push_str(","); + } + is_first = false; + + let query_string = + serde_urlencoded::to_string(base.with_parameters(PaginationParameters { limit, ..*param })).map_err(|err| { + CoreError::internal_server_error(format!( + "Failed to serialize pagination query string: {:?}. Base: {:?}, Builder: {:?}, Current Rel: {}", + err, base, self, rel + )) + })?; + + buf += &format!("<{}?{}>; rel={}", self.endpoint, query_string, rel); + } + + Ok(buf) + } +} + +pub async fn pagination_response>( + endpoint: &'static str, query: Q, connection: &mut PgConnection, +) -> Result>>, CoreError> { + let parameters = query.parameters(); + + parameters.validate()?; + + let (objects, context) = P::page(&query, &mut *connection).await?; + + let mut links = LinksBuilder::new(endpoint); + + if let Some((min_id, max_id)) = P::first_and_last(connection).await? { + links = links.with_first(min_id - 1).with_last(max_id + 1); + } + + if context.has_next() { + let after = match objects.last() { + Some(obj) => obj.pagination_id(), + None => { + // If there exists a next page, but this page is empty, then + // we must have had a `before` value set (e.g. this is a page before the first object matching the pagination conditions). + parameters.before.ok_or_else(|| { + CoreError::internal_server_error(format!( + "Empty page claims next page exists, yet `before` not set on current request. Caused by {:?}", + query + )) + })? - 1 + }, + }; + + // TODO: Figure out the case where both `before` and `after` are set + // If `before` is set on this request, then we _could_ support one-way pagination up to `before` by preserving the "before" value here. + // Currently, this scenario cannot happen, as the documentation of `Pagination::page` we treat these pages as "standalone". + links = links.with_next(after); + } + + if context.has_previous() { + let before = match objects.first() { + Some(obj) => obj.pagination_id(), + None => { + parameters.after.ok_or_else(|| { + CoreError::internal_server_error(format!( + "Empty page claims previous page exists, yet `after` not set on current request. Caused by {:?}", + query + )) + })? + 1 + }, + }; + + // Either this request had the `after` parameter set, in which case we definitely do not want to preserve it as our "before" variable above is either + // the ID of the smallest object greater than `after`, or it is literally `after + 1`. + links = links.with_previous(before); + }; + + Ok(Response2::json(objects).with_header("Links", links.generate(&query)?)) +} + +#[cfg(test)] +mod tests { + use pointercrate_core::pagination::{PaginationParameters, PaginationQuery}; + use serde::Serialize; + + use super::LinksBuilder; + + #[derive(Debug, Default, Serialize)] + struct DummyQuery(PaginationParameters); + + impl PaginationQuery for DummyQuery { + fn parameters(&self) -> PaginationParameters { + self.0 + } + + fn with_parameters(&self, parameters: PaginationParameters) -> Self { + DummyQuery(parameters) + } + } + + #[test] + fn test_links_builder() { + let links_header = LinksBuilder::new("/dummies") + .with_first(0) + .with_last(1971) + .with_next(2) + .with_previous(100) + .generate(&DummyQuery::default()) + .unwrap(); + + assert_eq!( + links_header, + "; rel=first,; rel=last,; rel=next,; rel=prev" + ); + } +} diff --git a/pointercrate-core-api/src/query.rs b/pointercrate-core-api/src/query.rs index 4c2286e13..f2af799d3 100644 --- a/pointercrate-core-api/src/query.rs +++ b/pointercrate-core-api/src/query.rs @@ -14,11 +14,10 @@ impl<'r, T: DeserializeOwned> FromRequest<'r> for Query { async fn from_request(request: &'r Request<'_>) -> Outcome { match request.uri().query() { None => Outcome::Success(Query(serde_urlencoded::from_str("").unwrap())), - Some(query) => - match serde_urlencoded::from_str(query.as_str()) { - Ok(t) => Outcome::Success(Query(t)), - Err(err) => Outcome::Failure((Status::BadRequest, err)), - }, + Some(query) => match serde_urlencoded::from_str(query.as_str()) { + Ok(t) => Outcome::Success(Query(t)), + Err(err) => Outcome::Error((Status::BadRequest, err)), + }, } } } diff --git a/pointercrate-core-api/src/response.rs b/pointercrate-core-api/src/response.rs index b341ea50e..2f56d063c 100644 --- a/pointercrate-core-api/src/response.rs +++ b/pointercrate-core-api/src/response.rs @@ -114,109 +114,3 @@ impl<'r, 'o: 'r, T: Responder<'r, 'o>> Responder<'r, 'o> for Response2 { response_builder.ok() } } - -#[macro_export] -macro_rules! pagination_response { - ($endpoint: expr, $objects:expr, $pagination:expr, $min_id:expr, $max_id:expr, $before_field:ident, $after_field:ident, $($id_field:tt)*) => {{ - use pointercrate_core_api::response::Response2; - - log::debug!("Received pagination request {:?}", $pagination); - - let mut rel = String::new(); - - let limit = $pagination.limit.unwrap_or(50) as usize; - let next_page_exists = $objects.len() > limit; - - if !$objects.is_empty() { - if next_page_exists { - log::debug!("A new page exists!"); - - $objects.pop(); // remove the things from then next page - } - - let last = $objects.last().unwrap().$($id_field)*; - let first = $objects.first().unwrap().$($id_field)*; - - match ($pagination.$before_field, $pagination.$after_field) { - (None, after) => { - log::debug!("No before value set, assuming result is correctly ordered!"); - - // no 'before' value set. - // if 'after' is none, we're on the first page, otherwise we have ot generate a 'prev' link - - if next_page_exists { - - $pagination.$after_field = Some(last); - $pagination.$before_field = None; - - rel.push_str(&format!( - ",<{}?{}>; rel=next", - $endpoint, serde_urlencoded::to_string(&$pagination).unwrap() - )); - } - - if after.is_some() { - $pagination.$after_field = None; - $pagination.$before_field = Some(first); - - rel.push_str(&format!( - ",<{}?{}>; rel=prev", - $endpoint, serde_urlencoded::to_string(&$pagination).unwrap() - )); - } - } - (Some(_), None) => { - log::debug!("Before value set, assuming result is reverse ordered!"); - - // A previous page exists. This means "first" and "last" are actually to opposite of what the variables are named. - $pagination.$before_field = Some(last); - $pagination.$after_field = None; - - // In this case, the page was retrieved using 'ORDER BY ... DESC' so we need to reverse list order! - $objects.reverse(); - - if next_page_exists { - rel.push_str(&format!( - ",<{}?{}>; rel=prev", - $endpoint, serde_urlencoded::to_string(&$pagination).unwrap() - )); - } - $pagination.$after_field = Some(first); - $pagination.$before_field = None; - - rel.push_str(&format!( - ",<{}?{}>; rel=next", - $endpoint, serde_urlencoded::to_string(&$pagination).unwrap() - )); - } - (Some(_before), Some(_after)) => { - // We interpret this as that all objects _up to 'before'_ are supposed to be paginated. - // This means we keep the 'before' value and handle the 'after' value just as above. - // tODO: implement - } - } - } - - $pagination.$after_field = Some($min_id - 1); - $pagination.$before_field = None; - - let mut links = format!( - "<{}?{}>; rel=first", - $endpoint, serde_urlencoded::to_string(&$pagination).unwrap() - ); - - $pagination.$after_field = None; - $pagination.$before_field = Some($max_id + 1); - - links.push_str(&format!( - ",<{}?{}>; rel=last", - $endpoint, serde_urlencoded::to_string(&$pagination).unwrap() - )); - - links.push_str(&rel); - - log::debug!("Links headers has value '{}'", links); - - Ok(Response2::json($objects).with_header("Links", links)) - }}; -} diff --git a/pointercrate-core-pages/Cargo.toml b/pointercrate-core-pages/Cargo.toml index c7b7cae52..5ac01a6cf 100644 --- a/pointercrate-core-pages/Cargo.toml +++ b/pointercrate-core-pages/Cargo.toml @@ -1,10 +1,11 @@ [package] name = "pointercrate-core-pages" version = "0.1.0" -edition = "2021" +authors.workspace = true +edition.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -maud = "0.25" +maud = "0.26.0" pointercrate-core = {path = "../pointercrate-core"} diff --git a/pointercrate-core-pages/src/config.rs b/pointercrate-core-pages/src/config.rs index c9df8439d..9a6bf684e 100644 --- a/pointercrate-core-pages/src/config.rs +++ b/pointercrate-core-pages/src/config.rs @@ -1,7 +1,3 @@ -pub fn adsense_publisher_id() -> Option { - std::env::var("ADSENSE_PUBLISHER_ID").ok() -} - pub fn google_analytics_tag() -> Option { std::env::var("ANALYTICS_TAG").ok() } diff --git a/pointercrate-core-pages/src/footer.rs b/pointercrate-core-pages/src/footer.rs index de2c967ba..a43272df8 100644 --- a/pointercrate-core-pages/src/footer.rs +++ b/pointercrate-core-pages/src/footer.rs @@ -60,23 +60,21 @@ impl Render for &Link { impl Render for &FooterColumn { fn render(&self) -> Markup { match self { - FooterColumn::LinkList { heading, links } => - html! { - nav { - h2 {(heading)} - @for link in links { - (link) - br; - } + FooterColumn::LinkList { heading, links } => html! { + nav { + h2 {(heading)} + @for link in links { + (link) + br; } - }, - FooterColumn::Arbitrary { heading, content } => - html! { - div { - h2 {(heading)} - (*content) - } - }, + } + }, + FooterColumn::Arbitrary { heading, content } => html! { + div { + h2 {(heading)} + (*content) + } + }, } } } diff --git a/pointercrate-core-pages/src/head.rs b/pointercrate-core-pages/src/head.rs index cc7dafaed..f3f737675 100644 --- a/pointercrate-core-pages/src/head.rs +++ b/pointercrate-core-pages/src/head.rs @@ -136,7 +136,7 @@ impl Script { } } -impl Render for &Script { +impl Render for Script { fn render(&self) -> Markup { html! { @if self.module { @@ -172,15 +172,6 @@ impl Render for &Meta { } } -// this should be handled automatically on newer versions of maud -/* -impl Render for Script { - fn render(&self) -> Markup { - (&self).render() - } -} -*/ - /// Adds a version query string to a url based on the package version. /// /// A macro is used to make the version crate-independent. diff --git a/pointercrate-core-pages/src/lib.rs b/pointercrate-core-pages/src/lib.rs index 227f87d5c..b6a081926 100644 --- a/pointercrate-core-pages/src/lib.rs +++ b/pointercrate-core-pages/src/lib.rs @@ -27,20 +27,16 @@ impl HeadLike for PageConfiguration { impl PageConfiguration { pub fn new(site_name: impl Into, nav_bar: NavigationBar, footer: Footer) -> Self { let default_head_html = html! { - @if let Some(publisher_id) = config::adsense_publisher_id() { - (PreEscaped(format!(r#""#, publisher_id))) - } - @if let Some(analytics_tag) = config::google_analytics_tag() { (PreEscaped(format!(r#" "#, analytics_tag))); } @@ -57,7 +53,19 @@ impl PageConfiguration { .meta("og:site_name", site_name) .meta("og:type", "website") .meta("referrer", "no-referrer") - .meta("viewport", "initial-scale=1, maximum-scale=1"), + .meta("viewport", "initial-scale=1, maximum-scale=1") + .script("https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js") + .script("https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js") + .script("/static/core/js/ui.js") + .script("/static/core/js/nav.js") + .script("/static/core/js/misc.js") + .stylesheet("/static/core/css/icon.css") + .stylesheet("/static/core/css/nav.css") + .stylesheet("/static/core/css/main.css") + .stylesheet("/static/core/css/ui.css") + .stylesheet("/static/core/css/core.css") + .stylesheet("/static/core/css/fa.all.min.css") + .stylesheet("https://fonts.googleapis.com/css?family=Montserrat|Montserrat:light,bold"), } } diff --git a/pointercrate-core-pages/src/navigation.rs b/pointercrate-core-pages/src/navigation.rs index c51a2c912..a12223fe9 100644 --- a/pointercrate-core-pages/src/navigation.rs +++ b/pointercrate-core-pages/src/navigation.rs @@ -70,7 +70,7 @@ impl Render for NavigationBar { header { nav.center.collapse.underlined { div.nav-icon style = "margin-right: auto" { - a href = "/" { + a href = "/" aria-label = "Go to homepage" { img src = (self.logo_path) style="height:50px" alt="1.9 Demonlist"; } } diff --git a/pointercrate-core-pages/static/css/core.css b/pointercrate-core-pages/static/css/core.css index 74589638d..ed0f34972 100644 --- a/pointercrate-core-pages/static/css/core.css +++ b/pointercrate-core-pages/static/css/core.css @@ -32,15 +32,15 @@ /* center using margin */ .m-center { max-width: 1072px; - margin-left: calc(45% - 1072px / 2) !important; - margin-right: calc(55% - 1072px / 2) !important; + margin-left: calc(50% - 1072px / 2) !important; + margin-right: calc(50% - 1072px / 2) !important; } /* center using padding */ .center { max-width: 1072px; - padding-left: calc(45% - 1072px / 2); - padding-right: calc(55% - 1072px / 2); + padding-left: calc(50% - 1072px / 2); + padding-right: calc(50% - 1072px / 2); } } diff --git a/pointercrate-core-pages/static/css/nav.css b/pointercrate-core-pages/static/css/nav.css index 680930919..c44f9abf5 100644 --- a/pointercrate-core-pages/static/css/nav.css +++ b/pointercrate-core-pages/static/css/nav.css @@ -216,7 +216,7 @@ footer .flex { footer .flex > * { margin: 1% 1%; min-width: 150px; - max-width: 15%; + max-width: 30%; } footer a.link { @@ -241,7 +241,8 @@ footer * { } footer .flex { - justify-content: center; + flex-direction: column; + align-items: center; } footer .flex { diff --git a/pointercrate-core-pages/static/css/ui.css b/pointercrate-core-pages/static/css/ui.css index 01ca631bd..8c4951f12 100644 --- a/pointercrate-core-pages/static/css/ui.css +++ b/pointercrate-core-pages/static/css/ui.css @@ -262,6 +262,7 @@ input[type="text"], input[type="number"], input[type="url"], input[type="password"], +input[type="datetime-local"], textarea { border: 1px solid #444; min-width: 0; /* Firefox and Egde need this */ @@ -279,16 +280,29 @@ textarea { input[type="text"], input[type="number"], input[type="url"], -input[type="password"] { +input[type="password"], +input[type="datetime-local"]{ min-height: 1em; padding: 0.65rem; } +/* +form input[type="text"]:valid, +form input[type="number"]:valid, +form input[type="url"]:valid, +form input[type="password"]:valid, +form input[type="datetime-local"]:valid, +textarea:valid { + border-bottom: 1px solid lime; +} +*/ + form input[type="text"]:focus:invalid, form input[type="number"]:focus:invalid, form input[type="url"]:focus:invalid, form input[type="password"]:focus:invalid, -textarea:invalid { +form input[type="datetime-local"]:focus:invalid, +textarea:focus:invalid { border-color: rgb(255, 0, 0); } @@ -486,7 +500,7 @@ textarea:invalid { top: 2em; /* place it below the box! */ width: calc(100% - 2px); - max-height: 400px; + max-height: 290px; overflow-y: scroll; background: #1f1f1f; @@ -695,6 +709,19 @@ h2 .dropdown-menu ul { border-left-color: yellow; } +.info-blue { + background: #181818; + padding: 10px 15px; + color: white; + margin: 25px 0px; + + border-radius: 4px; + + border-left-width: 4px; + border-left-style: solid; + border-left-color: #6e7dff; +} + .info-red { background: #181818; padding: 10px 15px; diff --git a/pointercrate-core-pages/static/js/modules/form.js b/pointercrate-core-pages/static/js/modules/form.js index ba679e603..c63a394e8 100644 --- a/pointercrate-core-pages/static/js/modules/form.js +++ b/pointercrate-core-pages/static/js/modules/form.js @@ -1186,6 +1186,11 @@ const UNEXPECTED_REDIRECT = { code: 50000, data: null, }; +const RATELIMITED = { + message: "You have hit a Cloudflare ratelimit. Please wait a short time and try again.", + code: 42900, + data: null +} function mkReq(method, endpoint, headers = {}, data = null) { headers["Content-Type"] = "application/json"; @@ -1221,7 +1226,7 @@ function mkReq(method, endpoint, headers = {}, data = null) { var jsonError = JSON.parse(xhr.responseText); } catch (e) { return reject({ - data: SEVERE_ERROR, + data: xhr.status == 429 ? RATELIMITED : SEVERE_ERROR, headers: parseHeaders(xhr), status: xhr.status, }); diff --git a/pointercrate-core/Cargo.toml b/pointercrate-core/Cargo.toml index b09259a9e..71f337f86 100644 --- a/pointercrate-core/Cargo.toml +++ b/pointercrate-core/Cargo.toml @@ -1,13 +1,14 @@ [package] name = "pointercrate-core" version = "0.1.0" -edition = "2021" +authors.workspace = true +edition.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -serde = "1.0.118" -derive_more = "0.99.11" -sqlx = { version = "0.6", default-features = false, features = [ "runtime-tokio-native-tls", "macros", "postgres", "chrono", "migrate", "offline"] } -log = "0.4.8" -chrono = {version = "0.4.19", features = ["serde"]} +serde = "1.0.203" +derive_more = "0.99.18" +sqlx = { version = "0.7", default-features = false, features = [ "runtime-tokio-native-tls", "macros", "postgres", "chrono", "migrate"] } +log = "0.4.22" +chrono = {version = "0.4.38", features = ["serde"]} diff --git a/pointercrate-core/build.rs b/pointercrate-core/build.rs index 3a8149ef0..ce57e1a18 100644 --- a/pointercrate-core/build.rs +++ b/pointercrate-core/build.rs @@ -1,3 +1,5 @@ +// generated by `sqlx migrate build-script` fn main() { - println!("cargo:rerun-if-changed=migrations"); + // trigger recompilation when a new migration is added + println!("cargo:rerun-if-changed=../migrations"); } diff --git a/pointercrate-core/src/error.rs b/pointercrate-core/src/error.rs index 9fddd08c1..718d870f1 100644 --- a/pointercrate-core/src/error.rs +++ b/pointercrate-core/src/error.rs @@ -2,7 +2,6 @@ use crate::permission::Permission; use derive_more::Display; use log::error; use serde::Serialize; -use sqlx::postgres::PgDatabaseError; use std::{error::Error, time::Duration}; pub type Result = std::result::Result; @@ -194,10 +193,7 @@ pub enum CoreError { fmt = "The server encountered an internal error and was unable to complete your request. Either the server is overloaded or there \ is an error in the application. Please notify a server administrator and have them look at the server logs!" )] - InternalServerError { - #[serde(skip)] - message: String, - }, + InternalServerError, /// `500 INTERNAL SERVER ERROR` /// @@ -208,12 +204,34 @@ pub enum CoreError { )] DatabaseError, + /// `500 INTERNAL SERVER ERROR` reported when postgres terminates a query due to hitting `statement_timeout` + /// + /// Error Code `50004` + #[display( + fmt = "Internally, a database query timed out. This could be due to high server load, or because of a logic error resulting in a deadlock. If this issue persists after retrying, please notify a server administrator!" + )] + QueryTimeout, + /// `500 INTERNAL SERVER ERROR` variant returned if the server fails to acquire a database /// connection /// /// Error Code `50005` #[display(fmt = "Failed to retrieve connection to the database. The server might be temporarily overloaded.")] DatabaseConnectionError, + + /// `503 SERVICE UNAVAILABLE` variant returned by all non-GET (e.g. all possible mutating) requests if the server is in maintenance mode. + /// + /// Error Core `50301` + #[display(fmt = "The website is currently in read-only maintenance mode.")] + ReadOnlyMaintenance, +} + +impl CoreError { + pub fn internal_server_error(message: impl AsRef) -> CoreError { + error!("INTERNAL SERVER ERROR: {}", message.as_ref()); + + CoreError::InternalServerError + } } impl Error for CoreError {} @@ -244,41 +262,21 @@ impl PointercrateError for CoreError { CoreError::Ratelimited { .. } => 42900, CoreError::InternalServerError { .. } => 50000, CoreError::DatabaseError => 50003, + CoreError::QueryTimeout => 50004, CoreError::DatabaseConnectionError => 50005, + CoreError::ReadOnlyMaintenance => 50301, } } } impl From for CoreError { fn from(error: sqlx::Error) -> Self { - match error { - sqlx::Error::Database(database_error) => { - let database_error = database_error.downcast::(); - - error!("Database error: {:?}. ", database_error); + error!("Database error: {:?}. Backtrace:\n {}", error, std::backtrace::Backtrace::capture()); - CoreError::DatabaseError - }, - sqlx::Error::PoolClosed | sqlx::Error::PoolTimedOut => { - error!("Failed to acquire database connection"); - - CoreError::DatabaseConnectionError - }, - sqlx::Error::ColumnNotFound(column) => { - format!("Invalid access to column {}, which does not exist", column); - - CoreError::DatabaseError - }, - sqlx::Error::RowNotFound => { - error!("Unhandled 'NotFound', this is a logic or data consistency error"); - - CoreError::DatabaseError - }, - _ => { - error!("Database error: {:?}", error); - - CoreError::DatabaseError - }, + match error { + sqlx::Error::Database(err) if err.code().as_deref() == Some("57014") => CoreError::QueryTimeout, + sqlx::Error::PoolClosed | sqlx::Error::PoolTimedOut => CoreError::DatabaseConnectionError, + _ => CoreError::DatabaseError, } } } diff --git a/pointercrate-core/src/lib.rs b/pointercrate-core/src/lib.rs index fdda6c278..caa449d46 100644 --- a/pointercrate-core/src/lib.rs +++ b/pointercrate-core/src/lib.rs @@ -2,6 +2,7 @@ pub mod audit; pub mod config; pub mod error; pub mod etag; +pub mod pagination; pub mod permission; pub mod pool; pub mod util; diff --git a/pointercrate-core/src/pagination.rs b/pointercrate-core/src/pagination.rs new file mode 100644 index 000000000..a4e2a1787 --- /dev/null +++ b/pointercrate-core/src/pagination.rs @@ -0,0 +1,248 @@ +use std::fmt::{Debug, Display}; + +use crate::{error::CoreError, util::non_nullable}; +use serde::{de::Error, Deserialize, Serialize}; +use sqlx::PgConnection; + +/// The maximal number of entries that can be requested per page via the `limit` parameter. +pub const ENTRIES_PER_PAGE: i32 = 100; + +/// The default number of entries returned per page if the `limit` parameter was omited. +/// +/// Try not to directly rely on this constant, and instead use `PaginationParameters::default()` +pub const DEFAULT_ENTRIES_PER_PAGE: i32 = 50; + +#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone, Copy)] +pub struct PaginationParameters { + #[serde(default, deserialize_with = "from_str_non_nullable")] + pub before: Option, + + #[serde(default, deserialize_with = "from_str_non_nullable")] + pub after: Option, + + #[serde( + default = "default_limit", + deserialize_with = "from_str", + skip_serializing_if = "is_default_entries_per_page" + )] + pub limit: i32, +} + +impl Default for PaginationParameters { + fn default() -> Self { + Self { + before: None, + after: None, + limit: DEFAULT_ENTRIES_PER_PAGE, + } + } +} + +impl PaginationParameters { + pub fn validate(&self) -> Result<(), CoreError> { + if !(1..=ENTRIES_PER_PAGE).contains(&self.limit) { + return Err(CoreError::InvalidPaginationLimit); + } + + if let (Some(after), Some(before)) = (self.before, self.after) { + if after < before { + return Err(CoreError::AfterSmallerBefore); + } + } + + Ok(()) + } + + pub fn order(&self) -> &'static str { + if self.after.is_none() && self.before.is_some() { + "DESC" + } else { + "ASC" + } + } +} + +/// Enum describing what is going on "around" a page returned by [`Pagination::page`]. +/// +/// Describes whether [`Pagination::Item`] matching all properties of a given [`Pagination`] exist +/// with an id lower/larger than the smallest/largest on a given page +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum PageContext { + /// The page contains all possible items matching the given [`Pagination`] query. + /// No further pages exist. + /// + /// For example, given a list of ids such as `[1, 3, 5]`, a request such as `?after=0&before=6` + /// would return the page`[1, 3, 5]`, meaning this page is `Standalone`. + Standalone, + + /// There exist more items matching the given [`Pagination`] query whose ids are less than + /// the smallest of this page. + /// + /// For example, given a list of ids such as `[1, 3, 5]`, a request such as `?after=1&before=6` + /// would return the page`[3, 5]`, meaning there exists a previous page containing just the item `1`. + HasPrevious, + + /// There exist more items matching the given [`Pagination`] query whose ids are greater than + /// the largest of this page. + /// + /// For example, given a list of ids such as `[1, 3, 5]`, a request such as `?after=0&before=5` + /// would return the page`[1, 3]`, meaning there exists a next page containing just the item `5`. + HasNext, + + /// There exist more items matching the given [`Pagination`] query, some whose ids are less than + /// the smallest of this page, and some whose ids are greater than the greatest of this page. + /// + /// For example, given a list of ids such as `[1, 3, 5]`, a request such as `?after=1&before=5` + /// would return the page`[3]`, meaning there exists a previous page containing just the item `1`, + /// and a next page containing just the item `5`. + HasPreviousAndNext, +} + +impl PageContext { + pub fn has_next(&self) -> bool { + matches!(self, PageContext::HasNext | PageContext::HasPreviousAndNext) + } + + pub fn has_previous(&self) -> bool { + matches!(self, PageContext::HasPrevious | PageContext::HasPreviousAndNext) + } +} + +pub trait PaginationQuery: Serialize + Debug { + fn parameters(&self) -> PaginationParameters; + fn with_parameters(&self, parameters: PaginationParameters) -> Self; +} + +#[allow(async_fn_in_trait)] +pub trait Paginatable: Serialize + Sized { + /// Returns a page of objects matching the query described by tthe given [`PaginationQuery`]. + /// + /// The returned list of objects must have the following properties: + /// - They are sorted in ascending order according to the value of [`pagination_id`]. + /// - Their ids are consecutive, meaning if the object at index `i` in the list has ID `a`, and + /// the object at index `i + 1` has id `b`, then there exists no object also matching all conditions + /// of this `Pagination` in the _database_ with an ID `c` such that `a < c < b`. + /// - If the `after` parameter of the query's associated [`PaginationParameters`] is set, then the first + /// object in the returned list must have the smallest ID out of all objects matching the given + /// query greater than `after`. + /// - If the `after` parameter is not set, but `before` is, then the last object in the returned + /// list must have the greatest ID out of all objects matching the given query smaller than + /// `before`. + /// + /// The returned [`PageContext`] should describe whether more pages surrounding this page exist which + /// match all conditions of this [`Pagination`] object, with the exception of the `before` and `after` fields! + /// HOWEVER, if both `before` and `after` are set, then it should be [`PageContext::Standalone`]. + /// + /// The number of items in the returned `Vec` must not exceed [`PaginationParameters::limit`]. + async fn page(query: &Q, connection: &mut PgConnection) -> Result<(Vec, PageContext), sqlx::Error>; + + async fn first_and_last(connection: &mut PgConnection) -> Result, sqlx::Error>; + + fn pagination_id(&self) -> i32; +} + +/// Historically, pointercrate has been determining whether a new page exists by simply incrementing the "limit" parameter +/// by one, and seeing if we can get one extra object from the database. This object was then popped from the results, +/// and its presence indicated that "another page in the same direction" existed - e.g. if `after` was specified, it +/// meant that a "next" should be generated, and if `before` was specified (but not after), it meant that a "prev" should +/// be generated. While this logic is correct, it should never have leaked outside of the `page` implementation and into +/// the actual pagination API. +/// +/// Additionally, pointercrate assumes that a previous page exists whenever "after" is set, and that a next page exists +/// whenever "previous" is set (but that we have a standalong page if _both_ are set). This is not sound (we should really be +/// trying to find an "extra" object at the other end of the list), but fixing this would require a bigger refractor than I am +/// willing to do at the time of writing this. +/// +/// Lastly, pointercrate used to return the object list in reverse if `before` but not `after` was set, and left it up +/// to the caller to reverse it. That, too, is an implementation detail that should never become API. +/// +/// This compat function tries to fix these up as best as it can - it reverses the given list of objects if needed, and translates +/// the "extra" object into a `PageContext`. It doesn't solve the second point though. +#[doc(hidden)] +pub fn __pagination_compat(params: &PaginationParameters, mut objects: Vec) -> (Vec, PageContext) { + let has_followup_page = objects.len() > params.limit as usize; + + if has_followup_page { + objects.pop(); + } + + let ctx = match (params.before, params.after) { + (Some(_), None) => { + objects.reverse(); + + if has_followup_page { + PageContext::HasPreviousAndNext + } else { + PageContext::HasNext + } + }, + (None, Some(_)) => { + if has_followup_page { + PageContext::HasPreviousAndNext + } else { + PageContext::HasPrevious + } + }, + (Some(_), Some(_)) => PageContext::Standalone, + (None, None) => { + if has_followup_page { + PageContext::HasNext + } else { + PageContext::Standalone + } + }, + }; + + (objects, ctx) +} + +#[macro_export] +macro_rules! first_and_last { + ($table_name: expr, $id_column: expr) => { + async fn first_and_last(connection: &mut PgConnection) -> std::result::Result, sqlx::Error> { + let row = sqlx::query!( + "SELECT CAST(MIN(" + $id_column + ") AS INTEGER), CAST(MAX(" + $id_column + ") AS INTEGER) FROM " + $table_name + ) + .fetch_one(connection) + .await?; + + Ok(row.min.zip(row.max)) + } + }; + ($table_name: expr) => { + first_and_last!($table_name, "id"); + }; +} + +/// Helper function because serde does not allow literals/constants in #[serde(default = ...)] attributes. +/// See also https://github.com/serde-rs/serde/issues/368 +const fn default_limit() -> i32 { + DEFAULT_ENTRIES_PER_PAGE +} + +const fn is_default_entries_per_page(limit: &i32) -> bool { + *limit == DEFAULT_ENTRIES_PER_PAGE +} + +// Helper function needed because serde's flatten attribute does not work with non-self describing data formats (such as url-encoding) - it thinks everything is a string. +// See also https://github.com/nox/serde_urlencoded/issues/33 +fn from_str<'de, D, S>(deserializer: D) -> Result +where + D: serde::Deserializer<'de>, + S: std::str::FromStr, + S::Err: Display, +{ + let s = <&str as serde::Deserialize>::deserialize(deserializer)?; + S::from_str(&s).map_err(|err| D::Error::custom(err.to_string())) +} + +fn from_str_non_nullable<'de, S, D>(deserializer: D) -> Result, D::Error> +where + D: serde::Deserializer<'de>, + S: std::str::FromStr, + S::Err: Display, +{ + non_nullable::<'de, &'de str, D>(deserializer)? + .map(|s| S::from_str(&s).map_err(|err| D::Error::custom(err.to_string()))) + .transpose() +} diff --git a/pointercrate-core/src/permission.rs b/pointercrate-core/src/permission.rs index 9303f27d5..340bf9506 100644 --- a/pointercrate-core/src/permission.rs +++ b/pointercrate-core/src/permission.rs @@ -33,6 +33,60 @@ impl From for u16 { } } +/// Structure containing all information about different [`Permission`] levels +/// of a pointercrate instance +/// +/// Pointercrate's permission system is built on two concepts: Assignment and +/// implication +/// +/// ## Assignment +/// If permission `A` _assigns_ permission `B` then a user with permission `A` +/// can modify the permission bit associated with `B` for _any user they can +/// access via the API_. The set of users that a user `X`can access via the API +/// is the set of users that have a permission bit set which `X` has the ability +/// to assign. The exception are users with the `ADMINISTRATOR` permission, +/// which can access all other users. +/// +/// ### Example +/// +/// Consider three permissions, `A`, `B` and `C`, and two users `X`and `Y`. +/// Assume the following relations hold: +/// - Permission `A` can assign permission `B` and `C` +/// - User `X` has permission `A` +/// - User `Y` has permission `B` +/// +/// In this scenario, user `X` can +/// - access user `Y` via the API (because user `Y` has permission `B`, which +/// `X` can assign due to having permission `A`) +/// - grant user `Y` the permission `C` (because `X` can access `Y` and assign +/// `C`) +/// - revoke permission `B` from user `Y` (because `X` can access `Y` and assign +/// `B`) +/// +/// Note that in the last case, after revoking permission `B` from `Y`, user `X` +/// will no longer be able to access `Y` (as `Y` no longer has any permissions +/// that `X` can assign). Particularly, **if you revoke all permissions you can +/// assign from some user, you will not be able to re-grant that user any +/// permissions**. Only an Administrator will be able to do so. +/// +/// ## Implication +/// +/// If permissions `C` _implies_ permission `D` then a user with permission `C` +/// will be able to perform all tasks that a user with permission `D` could +/// perform (e.g. an endpoint that explicitly checks via +/// [`PermissionsManager::require_permission`] that the requestor has permission +/// `D` will allow users with only permission `D` to perform requests). Note +/// that "tasks" above also includes assignment! +/// +/// ### Example +/// +/// Extend the above example with a new permission `D` and a new user `Z`, and +/// the following relations: +/// - Permission `D` implies permission `A` +/// - User `Z` has permission `D` +/// +/// Then, user `Z` will be able to perform the same operations as user `X` +/// (w.r.t. assigning permissions and accessing users). #[derive(Clone)] pub struct PermissionsManager { permissions: HashSet, @@ -55,8 +109,27 @@ impl PermissionsManager { } } - // we should probably verify that added permissions are all part of what was in the constructor but - // whatever + pub fn merge_with(&mut self, other: PermissionsManager) { + for new_permission in &other.permissions { + if let Some(conflict) = self + .permissions + .iter() + .find(|&p| p.bit() == new_permission.bit() && p != new_permission) + { + panic!( + "Cannot merge permission managers, conflicting permissions {} and {}", + conflict, new_permission + ) + } + } + + self.permissions.extend(other.permissions); + self.implication_map.extend(other.implication_map); + self.assignable_map.extend(other.assignable_map); + } + + // we should probably verify that added permissions are all part of what was in + // the constructor but whatever pub fn assigns(mut self, perm1: Permission, perm2: Permission) -> Self { self.assignable_map.entry(perm1).or_insert_with(HashSet::new).insert(perm2); self @@ -132,7 +205,7 @@ impl PermissionsManager { if !self.implied_by_bits(permissions_we_have).contains(&permission_required) { return Err(CoreError::MissingPermissions { required: permission_required, - }) + }); } Ok(()) @@ -180,9 +253,10 @@ mod test { assert_eq!(permission_manager().implied_by(PERM1), set![PERM1, PERM2, PERM3]); assert_eq!(permission_manager().implied_by(PERM4), set![PERM4, PERM5]); - assert_eq!(permission_manager().implied_by_bits(0x1 | 0x8), set![ - PERM1, PERM2, PERM3, PERM4, PERM5, - ]); + assert_eq!( + permission_manager().implied_by_bits(0x1 | 0x8), + set![PERM1, PERM2, PERM3, PERM4, PERM5,] + ); } #[test] diff --git a/pointercrate-core/src/pool.rs b/pointercrate-core/src/pool.rs index fe0a58390..9e1546352 100644 --- a/pointercrate-core/src/pool.rs +++ b/pointercrate-core/src/pool.rs @@ -58,7 +58,7 @@ AND NOT EXISTS ( pub async fn connection(&self) -> Result> { let mut connection = self.connection_pool.acquire().await?; - audit_connection(&mut connection, 0).await?; + audit_connection(&mut *connection, 0).await?; Ok(connection) } @@ -66,7 +66,7 @@ AND NOT EXISTS ( pub async fn transaction(&self) -> Result> { let mut connection = self.connection_pool.begin().await?; - audit_connection(&mut connection, 0).await?; + audit_connection(&mut *connection, 0).await?; Ok(connection) } diff --git a/pointercrate-core/src/ratelimits.rs b/pointercrate-core/src/ratelimits.rs index c3548b1e6..8034c9d28 100644 --- a/pointercrate-core/src/ratelimits.rs +++ b/pointercrate-core/src/ratelimits.rs @@ -1,18 +1,6 @@ #[macro_export] macro_rules! ratelimits { ($struct_name: ident {$($tokens:tt)*}) => { - use nonzero_ext::nonzero; - use pointercrate_core::error::CoreError; - use std::{ - net::IpAddr, - time::{Duration, Instant}, - }; - use governor::{ - clock::{Clock, DefaultClock, Reference}, - state::{direct::NotKeyed, keyed::DefaultKeyedStateStore, InMemoryState}, - Quota, RateLimiter, - }; - ratelimits!(@struct@ $struct_name [] $($tokens)*); impl $struct_name { @@ -25,24 +13,26 @@ macro_rules! ratelimits { ratelimits!(@struct@ $struct_name [ $($field: $type | $init,)* // already processed fields - $name: RateLimiter | RateLimiter::direct(Quota::new(nonzero!($capacity), Duration::from_secs($seconds)).unwrap()) // new field + $name: governor::RateLimiter | governor::RateLimiter::direct(governor::Quota::new(nonzero_ext::nonzero!($capacity), std::time::Duration::from_secs($seconds)).unwrap()) // new field ] $($remaining)*); // remaining, unprocessed fields as token stream }; - (@struct@ $struct_name: ident [$($field: ident: $type: ty | $init: expr),*] $name: ident[$capacity: tt per $seconds: tt per ip] => $message: expr, $($remaining: tt)*) => { + (@struct@ $struct_name: ident [$($field: ident: $type: ty | $init: expr),*] $name: ident[$capacity: tt per $seconds: tt per $key_type: ty] => $message: expr, $($remaining: tt)*) => { ratelimits!(@struct@ $struct_name [ $($field: $type | $init,)* - $name: RateLimiter, DefaultClock> | RateLimiter::keyed(Quota::new(nonzero!($capacity), Duration::from_secs($seconds)).unwrap()) + $name: governor::RateLimiter<$key_type, governor::state::keyed::DefaultKeyedStateStore<$key_type>, governor::clock::DefaultClock> | governor::RateLimiter::keyed(governor::Quota::new(nonzero_ext::nonzero!($capacity), std::time::Duration::from_secs($seconds)).unwrap()) ] $($remaining)*); }; (@method@ $name: ident[$capacity: tt per $seconds: tt] => $message: expr, $($remaining: tt)*) => { - pub(crate) fn $name(&self) -> Result<(), CoreError> { - let now = DefaultClock::default().now(); + pub(crate) fn $name(&self) -> Result<(), pointercrate_core::error::CoreError> { + use governor::clock::{Clock, Reference}; + + let now = governor::clock::DefaultClock::default().now(); self.$name.check().map_err(|too_early| { - CoreError::Ratelimited { + pointercrate_core::error::CoreError::Ratelimited { message: $message.to_string(), remaining: too_early.earliest_possible().duration_since(now).into(), } @@ -51,12 +41,14 @@ macro_rules! ratelimits { ratelimits!(@method@ $($remaining)*); }; - (@method@ $name: ident[$capacity: tt per $seconds: tt per ip] => $message: expr, $($remaining: tt)*) => { - pub(crate) fn $name(&self, ip: IpAddr) -> Result<(), CoreError> { - let now = DefaultClock::default().now(); + (@method@ $name: ident[$capacity: tt per $seconds: tt per $key_type: ty] => $message: expr, $($remaining: tt)*) => { + pub(crate) fn $name(&self, ip: $key_type) -> Result<(), pointercrate_core::error::CoreError> { + use governor::clock::{Clock, Reference}; + + let now = governor::clock::DefaultClock::default().now(); self.$name.check_key(&ip).map_err(|too_early| { - CoreError::Ratelimited { + pointercrate_core::error::CoreError::Ratelimited { message: $message.to_string(), remaining: too_early.earliest_possible().duration_since(now).into(), } diff --git a/pointercrate-demonlist-api/Cargo.toml b/pointercrate-demonlist-api/Cargo.toml index 834d3d429..accac55dd 100644 --- a/pointercrate-demonlist-api/Cargo.toml +++ b/pointercrate-demonlist-api/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "pointercrate-demonlist-api" version = "0.1.0" -edition = "2021" +authors.workspace = true +edition.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -rocket = { version = "0.5.0-rc.3" } +rocket = "0.5.1" pointercrate-demonlist = {path = "../pointercrate-demonlist"} pointercrate-demonlist-pages = {path = "../pointercrate-demonlist-pages"} pointercrate-core = {path = "../pointercrate-core"} @@ -15,12 +16,13 @@ pointercrate-core-pages = {path = "../pointercrate-core-pages"} pointercrate-user = {path = "../pointercrate-user"} pointercrate-user-api = {path = "../pointercrate-user-api"} pointercrate-integrate = {path = "../pointercrate-integrate"} -sqlx = { version = "0.6", default-features = false, features = [ "runtime-tokio-native-tls", "macros", "postgres", "chrono", "offline", "migrate", "offline" ] } -serde_json = "1.0.60" -log = "0.4.11" +sqlx = { version = "0.7", default-features = false, features = [ "runtime-tokio-native-tls", "macros", "postgres", "chrono", "migrate" ] } +serde_json = "1.0.118" +log = "0.4.22" serde_urlencoded = "0.7.0" nonzero_ext = "0.3.0" -reqwest = {version = "0.11.*", features = ["json"]} -chrono = "0.4.19" -serde = "1.0.118" -governor = "0.5.1" +reqwest = {version = "0.12.*", features = ["json"]} +chrono = "0.4.38" +serde = "1.0.203" +governor = "0.6.0" +rand = "0.8.5" diff --git a/pointercrate-demonlist-api/src/endpoints/demon.rs b/pointercrate-demonlist-api/src/endpoints/demon.rs index 156c8f59a..6b64bde4a 100644 --- a/pointercrate-demonlist-api/src/endpoints/demon.rs +++ b/pointercrate-demonlist-api/src/endpoints/demon.rs @@ -3,7 +3,7 @@ use pointercrate_core::{audit::AuditLogEntry, pool::PointercratePool}; use pointercrate_core_api::{ error::Result, etag::{Precondition, TaggableExt, Tagged}, - pagination_response, + pagination::pagination_response, query::Query, response::Response2, }; @@ -22,35 +22,14 @@ use rocket::{http::Status, serde::json::Json, State}; #[rocket::get("/")] pub async fn paginate(pool: &State, pagination: Query) -> Result>>> { - let mut pagination = pagination.0; - let mut connection = pool.connection().await?; - - let mut demons = pagination.page(&mut connection).await?; - let (max_id, min_id) = Demon::extremal_demon_ids(&mut connection).await?; - - pagination_response!("/api/v2/demons/", demons, pagination, min_id, max_id, before_id, after_id, base.id) + Ok(pagination_response("/api/v2/demons/", pagination.0, &mut *pool.connection().await?).await?) } #[rocket::get("/listed")] pub async fn paginate_listed( pool: &State, pagination: Query, ) -> Result>>> { - let mut pagination = pagination.0; - let mut connection = pool.connection().await?; - - let mut demons = pagination.page(&mut connection).await?; - let max_position = Demon::max_position(&mut connection).await?; - - pagination_response!( - "/api/v2/demons/listed/", - demons, - pagination, - 1, - max_position, - before_position, - after_position, - base.position - ) + Ok(pagination_response("/api/v2/demons/listed/", pagination.0, &mut *pool.connection().await?).await?) } #[rocket::get("/")] @@ -65,7 +44,7 @@ pub async fn audit(demon_id: i32, mut auth: TokenAuth) -> Result) -> Resu let log = pointercrate_demonlist::demon::audit::movement_log_for_demon(demon_id, &mut *pool.connection().await?).await?; if log.is_empty() { - return Err(DemonlistError::DemonNotFound { demon_id }.into()) + return Err(DemonlistError::DemonNotFound { demon_id }.into()); } Ok(Json(log)) diff --git a/pointercrate-demonlist-api/src/endpoints/nationality.rs b/pointercrate-demonlist-api/src/endpoints/nationality.rs index bd43219c5..9c40ead70 100644 --- a/pointercrate-demonlist-api/src/endpoints/nationality.rs +++ b/pointercrate-demonlist-api/src/endpoints/nationality.rs @@ -8,9 +8,9 @@ pub async fn subdivisions(pool: &State, iso_code: String) -> R let mut connection = pool.connection().await?; // good code - let nationality = Nationality::by_country_code_or_name(iso_code.to_uppercase().as_ref(), &mut connection).await?; + let nationality = Nationality::by_country_code_or_name(iso_code.to_uppercase().as_ref(), &mut *connection).await?; - Ok(Json(nationality.subdivisions(&mut connection).await?)) + Ok(Json(nationality.subdivisions(&mut *connection).await?)) } #[rocket::get("/ranking")] @@ -23,7 +23,7 @@ pub async fn nation(pool: &State, iso_code: String) -> Result< let mut connection = pool.connection().await?; // good code - let nationality = Nationality::by_country_code_or_name(iso_code.to_uppercase().as_ref(), &mut connection).await?; + let nationality = Nationality::by_country_code_or_name(iso_code.to_uppercase().as_ref(), &mut *connection).await?; - Ok(Tagged(nationality.upgrade(&mut connection).await?)) + Ok(Tagged(nationality.upgrade(&mut *connection).await?)) } diff --git a/pointercrate-demonlist-api/src/endpoints/player.rs b/pointercrate-demonlist-api/src/endpoints/player.rs index 855c906bf..f7f639348 100644 --- a/pointercrate-demonlist-api/src/endpoints/player.rs +++ b/pointercrate-demonlist-api/src/endpoints/player.rs @@ -1,9 +1,10 @@ use crate::{config, ratelimits::DemonlistRatelimits}; +use log::warn; use pointercrate_core::{error::CoreError, pool::PointercratePool}; use pointercrate_core_api::{ error::Result, etag::{Precondition, TaggableExt, Tagged}, - pagination_response, + pagination::pagination_response, query::Query, response::Response2, }; @@ -26,7 +27,6 @@ pub async fn paginate( pool: &State, query: Query, auth: Option, ) -> Result>>> { let mut pagination = query.0; - let mut connection = pool.connection().await?; if let Some(auth) = auth { if !auth.has_permission(LIST_HELPER) { @@ -36,39 +36,12 @@ pub async fn paginate( pagination.banned = Some(false); } - let mut players = pagination.page(&mut connection).await?; - let (max_id, min_id) = Player::extremal_player_ids(&mut connection).await?; - - pagination_response!( - "/api/v1/players/", - players, - pagination, - min_id, - max_id, - before_id, - after_id, - base.id - ) + Ok(pagination_response("/api/v1/players/", pagination, &mut *pool.connection().await?).await?) } #[rocket::get("/ranking")] pub async fn ranking(pool: &State, query: Query) -> Result>>> { - let mut pagination = query.0; - let mut connection = pool.connection().await?; - - let mut players = pagination.page(&mut connection).await?; - let max_index = RankedPlayer::max_index(&mut connection).await?; - - pagination_response!( - "/api/v1/players/ranking/", - players, - pagination, - 1, - max_index, - before_index, - after_index, - index - ) + Ok(pagination_response("/api/v1/players/ranking/", query.0, &mut *pool.connection().await?).await?) } #[rocket::get("/")] @@ -76,7 +49,7 @@ pub async fn get(player_id: i32, pool: &State) -> Result claim, - Err(_) => + Err(_) => { return Err(DemonlistError::ClaimNotFound { member_id: user_id, player_id, } - .into()), + .into()) + }, }; let claim = claim.apply_patch(data.0, &mut auth.connection).await?; @@ -169,24 +143,7 @@ pub async fn delete_claim(player_id: i32, user_id: i32, mut auth: TokenAuth) -> pub async fn paginate_claims(mut auth: TokenAuth, pagination: Query) -> Result>>> { auth.require_permission(LIST_MODERATOR)?; - let mut pagination = pagination.0; - - let mut claims = pagination.page(&mut auth.connection).await?; - let (max_id, min_id) = match ListedClaim::extremal_ids(&mut auth.connection).await { - Err(_) => return Ok(Response2::json(Vec::new())), // handle empty table case! - Ok(data) => data, - }; - - pagination_response!( - "/api/v1/players/claims/", - claims, - pagination, - min_id, - max_id, - before_id, - after_id, - id - ) + Ok(pagination_response("/api/v1/players/claims/", pagination.0, &mut auth.connection).await?) } #[derive(Deserialize, Debug)] @@ -209,45 +166,46 @@ pub async fn geolocate_nationality( let claim = PlayerClaim::get(auth.user.inner().id, player_id, &mut auth.connection).await?; if !claim.verified { - return Err(DemonlistError::ClaimUnverified.into()) + return Err(DemonlistError::ClaimUnverified.into()); } ratelimits.geolocate(ip)?; let response = reqwest::get(format!( "https://ipgeolocation.abstractapi.com/v1/?api_key={}&ip_address={}&fields=security,country_code,region_iso_code", - config::abstract_api_key().ok_or(CoreError::InternalServerError { - message: "No API key for abstract configured".to_string() - })?, + config::abstract_api_key().ok_or_else(|| CoreError::internal_server_error("No API key for abstract configured"))?, ip )) .await - .map_err(|err| { - CoreError::InternalServerError { - message: format!("Ip Geolocation failed: {}", err), - } - })?; + .map_err(|err| CoreError::internal_server_error(format!("Ip Geolocation failed: {}", err)))?; let data = response.json::().await.map_err(|err| { - CoreError::InternalServerError { - message: format!("Ip Geolocation succeeded, but we could not deserialize the response: {}", err), - } + CoreError::internal_server_error(format!( + "Ip Geolocation succeeded, but we could not deserialize the response: {}", + err + )) })?; if data.security.is_vpn { - return Err(DemonlistError::VpsDetected.into()) + return Err(DemonlistError::VpsDetected.into()); } - let nationality = Nationality::by_country_code_or_name(&data.country_code, &mut auth.connection).await?; - - player.set_nationality(nationality, &mut auth.connection).await?; - - if ["US", "CA", "GB", "AU"].map(ToString::to_string).contains(&data.country_code) { - if let Some(region) = data.region_iso_code { - player.set_subdivision(region, &mut auth.connection).await?; - } + let mut nationality = Nationality::by_country_code_or_name(&data.country_code, &mut auth.connection).await?; + if let Some(region) = data.region_iso_code { + nationality.subdivision = nationality + .subdivision_by_code(®ion, &mut auth.connection) + .await + .inspect_err(|err| { + warn!( + "No subdivision {} for nation {}, or nation does not support subdivisions: {:?}", + region, nationality.iso_country_code, err + ) + }) + .ok(); } + player.set_nationality(Some(nationality), &mut auth.connection).await?; + auth.commit().await?; Ok(Json(player.nationality.unwrap())) diff --git a/pointercrate-demonlist-api/src/endpoints/record.rs b/pointercrate-demonlist-api/src/endpoints/record.rs index 5b54704d7..5bb008ee7 100644 --- a/pointercrate-demonlist-api/src/endpoints/record.rs +++ b/pointercrate-demonlist-api/src/endpoints/record.rs @@ -4,7 +4,7 @@ use pointercrate_core::{audit::AuditLogEntry, error::CoreError, pool::Pointercra use pointercrate_core_api::{ error::Result, etag::{Precondition, TaggableExt, Tagged}, - pagination_response, + pagination::pagination_response, query::Query, response::Response2, }; @@ -47,17 +47,13 @@ pub async fn paginate(mut auth: TokenAuth, query: Query) -> Re if (claim.is_none() || claim.map(|c| c.player.id) != pagination.player) && !auth.has_permission(LIST_HELPER) { if pagination.status.is_some() && pagination.status != Some(RecordStatus::Approved) { - return Err(CoreError::MissingPermissions { required: LIST_HELPER }.into()) + return Err(CoreError::MissingPermissions { required: LIST_HELPER }.into()); } pagination.status = Some(RecordStatus::Approved); } - let mut records = pagination.page(&mut auth.connection).await?; - - let (max_id, min_id) = FullRecord::extremal_record_ids(&mut auth.connection).await?; - - pagination_response!("/api/v1/records/", records, pagination, min_id, max_id, before_id, after_id, id) + Ok(pagination_response("/api/v1/records/", pagination, &mut auth.connection).await?) } #[rocket::get("/", rank = 1)] @@ -68,20 +64,16 @@ pub async fn unauthed_pagination( let mut pagination = query.0; if pagination.submitter.is_some() { - return Err(CoreError::Unauthorized.into()) + return Err(CoreError::Unauthorized.into()); } if pagination.status.is_some() && pagination.status != Some(RecordStatus::Approved) { - return Err(CoreError::Unauthorized.into()) + return Err(CoreError::Unauthorized.into()); } pagination.status = Some(RecordStatus::Approved); - let mut records = pagination.page(&mut connection).await?; - - let (max_id, min_id) = FullRecord::extremal_record_ids(&mut connection).await?; - - pagination_response!("/api/v1/records/", records, pagination, min_id, max_id, before_id, after_id, id) + Ok(pagination_response("/api/v1/records/", pagination, &mut *connection).await?) } #[rocket::post("/", data = "")] @@ -107,24 +99,24 @@ pub async fn submit( None => pool.transaction().await?, }; - let submitter = match Submitter::by_ip(ip, &mut connection).await? { + let submitter = match Submitter::by_ip(ip, &mut *connection).await? { Some(submitter) => submitter, None => { ratelimits.new_submitters()?; - Submitter::create_submitter(ip, &mut connection).await? + Submitter::create_submitter(ip, &mut *connection).await? }, }; // Banned submitters cannot submit records if submitter.banned { - return Err(DemonlistError::BannedFromSubmissions.into()) + return Err(DemonlistError::BannedFromSubmissions.into()); } - let normalized = submission.normalize(&mut connection).await?; + let normalized = submission.normalize(&mut *connection).await?; // check if the player is claimed with submissions locked - if let Some(claim) = normalized.verified_player_claim(&mut connection).await? { + if let Some(claim) = normalized.verified_player_claim(&mut *connection).await? { if claim.lock_submissions { match user_id { Some(user_id) if user_id == claim.user_id => (), @@ -133,7 +125,7 @@ pub async fn submit( } } - let validated = normalized.validate(&mut connection).await?; + let validated = normalized.validate(&mut *connection).await?; if !is_team_member { // Check ratelimits before any change is made to the database so that the transaction rollback is @@ -144,7 +136,7 @@ pub async fn submit( ratelimits.record_submission_global()?; } - let mut record = validated.create(submitter, &mut connection).await?; + let mut record = validated.create(submitter, &mut *connection).await?; connection.commit().await.map_err(DemonlistError::from)?; @@ -179,11 +171,14 @@ pub async fn get(record_id: i32, auth: Option, pool: &State pool.transaction().await?, }; - let record = FullRecord::by_id(record_id, &mut connection).await?; + let mut record = FullRecord::by_id(record_id, &mut *connection).await?; // TODO: allow access if auth is provided and a verified claim on the record's player is given - if !is_helper && record.status != RecordStatus::Approved { - return Err(DemonlistError::RecordNotFound { record_id }.into()) + if !is_helper { + if record.status != RecordStatus::Approved { + return Err(DemonlistError::RecordNotFound { record_id }.into()); + } + record.submitter = None; } Ok(Tagged(record)) @@ -196,7 +191,7 @@ pub async fn audit(record_id: i32, mut auth: TokenAuth) -> Result/notes")] pub async fn get_notes(record_id: i32, mut auth: TokenAuth) -> Result>>> { let record_holder_id = sqlx::query!("SELECT player FROM records WHERE id = $1", record_id) - .fetch_one(&mut auth.connection) + .fetch_one(&mut *auth.connection) .await .map_err(|err| { if let sqlx::Error::RowNotFound = err { @@ -294,7 +289,7 @@ pub async fn add_note(record_id: i32, mut auth: TokenAuth, data: Json) pub async fn patch_note(record_id: i32, note_id: i32, mut auth: TokenAuth, patch: Json) -> Result> { let note = Note::by_id(record_id, note_id, &mut auth.connection).await?; - if note.author.as_ref() == Some(&auth.user.inner().name) { + if note.author.as_ref() != Some(&auth.user.inner().name) { auth.require_permission(LIST_ADMINISTRATOR)?; } else { auth.require_permission(LIST_HELPER)?; @@ -311,7 +306,7 @@ pub async fn patch_note(record_id: i32, note_id: i32, mut auth: TokenAuth, patch pub async fn delete_note(record_id: i32, note_id: i32, mut auth: TokenAuth) -> Result { let note = Note::by_id(record_id, note_id, &mut auth.connection).await?; - if note.author.as_ref() == Some(&auth.user.inner().name) { + if note.author.as_ref() != Some(&auth.user.inner().name) { auth.require_permission(LIST_ADMINISTRATOR)?; } else { auth.require_permission(LIST_HELPER)?; @@ -338,7 +333,7 @@ async fn validate(record_id: i32, video: String, body: serde_json::Value, mut co } else { warn!("Server response to 'GET {}' was {:?}, deleting submission!", video, response); - match FullRecord::delete_by_id(record_id, &mut connection).await { + match FullRecord::delete_by_id(record_id, &mut *connection).await { Ok(_) => (), Err(error) => error!("INTERNAL SERVER ERROR: Failure to delete record - {:?}!", error), } @@ -350,7 +345,7 @@ async fn validate(record_id: i32, video: String, body: serde_json::Value, mut co error ); - match FullRecord::delete_by_id(record_id, &mut connection).await { + match FullRecord::delete_by_id(record_id, &mut *connection).await { Ok(_) => (), Err(error) => error!("INTERNAL SERVER ERROR: Failure to delete record - {:?}!", error), } diff --git a/pointercrate-demonlist-api/src/endpoints/submitter.rs b/pointercrate-demonlist-api/src/endpoints/submitter.rs index 20e85fc0a..f874efa3f 100644 --- a/pointercrate-demonlist-api/src/endpoints/submitter.rs +++ b/pointercrate-demonlist-api/src/endpoints/submitter.rs @@ -1,37 +1,22 @@ use pointercrate_core_api::{ error::Result, etag::{Precondition, TaggableExt, Tagged}, - pagination_response, + pagination::pagination_response, query::Query, response::Response2, }; use pointercrate_demonlist::{ submitter::{PatchSubmitter, Submitter, SubmitterPagination}, - LIST_ADMINISTRATOR, LIST_MODERATOR, + LIST_MODERATOR, }; use pointercrate_user_api::auth::TokenAuth; use rocket::serde::json::Json; #[rocket::get("/")] pub async fn paginate(mut auth: TokenAuth, pagination: Query) -> Result>>> { - auth.require_permission(LIST_ADMINISTRATOR)?; - - let mut pagination = pagination.0; - - let mut submitters = pagination.page(&mut auth.connection).await?; - - let (max_id, min_id) = Submitter::extremal_submitter_ids(&mut auth.connection).await?; + auth.require_permission(LIST_MODERATOR)?; - pagination_response!( - "/api/v1/submitters/", - submitters, - pagination, - min_id, - max_id, - before_id, - after_id, - id - ) + Ok(pagination_response("/api/v1/submitters/", pagination.0, &mut auth.connection).await?) } #[rocket::get("/")] diff --git a/pointercrate-demonlist-api/src/lib.rs b/pointercrate-demonlist-api/src/lib.rs index f0a97130b..74799c522 100644 --- a/pointercrate-demonlist-api/src/lib.rs +++ b/pointercrate-demonlist-api/src/lib.rs @@ -1,7 +1,6 @@ use crate::{endpoints::misc, ratelimits::DemonlistRatelimits}; -use chrono::Duration; use pointercrate_core::pool::PointercratePool; -use pointercrate_integrate::gd::PgCache; +use pointercrate_integrate::gd::GeometryDashConnector; use rocket::{Build, Rocket}; pub(crate) mod config; @@ -11,64 +10,82 @@ pub(crate) mod ratelimits; pub fn setup(rocket: Rocket) -> Rocket { let ratelimits = DemonlistRatelimits::new(); - let dash_rs = PgCache::new(rocket.state::().unwrap().clone_inner(), Duration::minutes(30)); + let dash_rs = GeometryDashConnector::new(rocket.state::().unwrap().clone_inner()); rocket .manage(ratelimits) .manage(dash_rs) .mount("/api/v1/list_information/", rocket::routes![misc::list_information]) - .mount("/api/v1/submitters/", rocket::routes![ - endpoints::submitter::paginate, - endpoints::submitter::get, - endpoints::submitter::patch - ]) - .mount("/api/v1/records/", rocket::routes![ - endpoints::record::get_notes, - endpoints::record::add_note, - endpoints::record::audit, - endpoints::record::delete, - endpoints::record::delete_note, - endpoints::record::get, - endpoints::record::paginate, - endpoints::record::unauthed_pagination, - endpoints::record::patch, - endpoints::record::patch_note, - endpoints::record::submit - ]) - .mount("/api/v1/players/", rocket::routes![ - endpoints::player::get, - endpoints::player::paginate, - endpoints::player::patch, - endpoints::player::ranking, - endpoints::player::put_claim, - endpoints::player::patch_claim, - endpoints::player::paginate_claims, - endpoints::player::delete_claim, - endpoints::player::geolocate_nationality - ]) - .mount("/api/v1/nationalities/", rocket::routes![ - endpoints::nationality::subdivisions, - endpoints::nationality::ranking, - endpoints::nationality::nation - ]) - .mount("/api/v2/demons/", rocket::routes![ - endpoints::demon::get, - endpoints::demon::paginate, - endpoints::demon::paginate_listed, - endpoints::demon::audit, - endpoints::demon::movement_log, - endpoints::demon::patch, - endpoints::demon::post, - endpoints::demon::post_creator, - endpoints::demon::delete_creator - ]) - .mount("/demonlist/", rocket::routes![ - pages::overview, - pages::stats_viewer_redirect, - pages::stats_viewer, - pages::nation_stats_viewer, - pages::demon_page, - pages::demon_permalink, - pages::heatmap_css - ]) + .mount( + "/api/v1/submitters/", + rocket::routes![ + endpoints::submitter::paginate, + endpoints::submitter::get, + endpoints::submitter::patch + ], + ) + .mount( + "/api/v1/records/", + rocket::routes![ + endpoints::record::get_notes, + endpoints::record::add_note, + endpoints::record::audit, + endpoints::record::delete, + endpoints::record::delete_note, + endpoints::record::get, + endpoints::record::paginate, + endpoints::record::unauthed_pagination, + endpoints::record::patch, + endpoints::record::patch_note, + endpoints::record::submit + ], + ) + .mount( + "/api/v1/players/", + rocket::routes![ + endpoints::player::get, + endpoints::player::paginate, + endpoints::player::patch, + endpoints::player::ranking, + endpoints::player::put_claim, + endpoints::player::patch_claim, + endpoints::player::paginate_claims, + endpoints::player::delete_claim, + endpoints::player::geolocate_nationality + ], + ) + .mount( + "/api/v1/nationalities/", + rocket::routes![ + endpoints::nationality::subdivisions, + endpoints::nationality::ranking, + endpoints::nationality::nation + ], + ) + .mount( + "/api/v2/demons/", + rocket::routes![ + endpoints::demon::get, + endpoints::demon::paginate, + endpoints::demon::paginate_listed, + endpoints::demon::audit, + endpoints::demon::movement_log, + endpoints::demon::patch, + endpoints::demon::post, + endpoints::demon::post_creator, + endpoints::demon::delete_creator + ], + ) + .mount( + "/demonlist/", + rocket::routes![ + pages::overview, + pages::stats_viewer_redirect, + pages::stats_viewer, + pages::nation_stats_viewer, + pages::demon_page, + pages::demon_permalink, + pages::heatmap_css + ], + ) } diff --git a/pointercrate-demonlist-api/src/pages.rs b/pointercrate-demonlist-api/src/pages.rs index 30493939b..4697bd2fa 100644 --- a/pointercrate-demonlist-api/src/pages.rs +++ b/pointercrate-demonlist-api/src/pages.rs @@ -1,6 +1,8 @@ +use std::collections::HashMap; + use rocket::{response::Redirect, State}; -use chrono::{DateTime, FixedOffset, NaiveDate, TimeZone, Utc}; +use chrono::{DateTime, Datelike, FixedOffset, NaiveDate, Utc}; use pointercrate_core::{audit::AuditLogEntryType, pool::PointercratePool}; use pointercrate_core_api::{ error::Result, @@ -19,9 +21,10 @@ use pointercrate_demonlist_pages::{ overview::OverviewPage, statsviewer::individual::IndividualStatsViewer, }; -use pointercrate_integrate::gd::{GDIntegrationResult, PgCache}; +use pointercrate_integrate::gd::GeometryDashConnector; use pointercrate_user::User; use pointercrate_user_api::auth::TokenAuth; +use rand::Rng; use rocket::{futures::StreamExt, http::CookieJar}; #[rocket::get("/?statsviewer=true")] @@ -33,35 +36,51 @@ pub fn stats_viewer_redirect() -> Redirect { pub async fn overview( pool: &State, timemachine: Option, submitter: Option, cookies: &CookieJar<'_>, auth: Option, ) -> Result { - // should be const, but chrono aint const :( - let beginning_of_time: DateTime = - FixedOffset::east(0).from_utc_datetime(&NaiveDate::from_ymd(2019, 4, 19).and_hms(0, 0, 0)); + // A few months before pointercrate first went live - definitely the oldest data we have + let beginning_of_time = NaiveDate::from_ymd_opt(2019, 4, 19).unwrap().and_hms_opt(0, 0, 0).unwrap(); let mut connection = pool.connection().await?; - let demonlist = current_list(&mut connection).await?; + let demonlist = current_list(&mut *connection).await?; - let specified_when = cookies + let mut specified_when = cookies .get("when") - .map(|cookie| DateTime::::parse_from_rfc3339(cookie.value())); + .map(|cookie| DateTime::::parse_from_rfc3339(cookie.value()).ok()) + .flatten(); + + // On april's fools, ignore the cookie and just pick a random day to display + let today = Utc::now().naive_utc(); + let is_april_1st = today.day() == 1 && today.month() == 4; + if is_april_1st { + let seconds_since_beginning_of_time = (today - beginning_of_time).num_seconds(); + let go_back_by = chrono::Duration::seconds(rand::thread_rng().gen_range(0..seconds_since_beginning_of_time)); + + if let Some(date) = today.checked_sub_signed(go_back_by) { + // We do not neccessarily know the time zone of the user here (we get it from the 'when' cookie in the normal case). + // This however is not a problem, the UI will simply display "GMT+0" instead of the correct local timezone. + specified_when = Some(date.and_utc().fixed_offset()); + } + } let specified_when = match specified_when { - Some(Ok(when)) if when < beginning_of_time => Some(beginning_of_time), - Some(Ok(when)) if when >= Utc::now() => None, - Some(Ok(when)) => Some(when), + Some(when) if when.naive_utc() < beginning_of_time => Some(DateTime::from_naive_utc_and_offset(beginning_of_time, *when.offset())), + Some(when) if when >= Utc::now() => None, + Some(when) => Some(when), _ => None, }; - let tardis = match specified_when { - Some(destination) => Tardis::new(timemachine.unwrap_or(false)).activate(destination, list_at(&mut connection, destination).await?), - _ => Tardis::new(timemachine.unwrap_or(false)), - }; + let mut tardis = Tardis::new(timemachine.unwrap_or(false)); + + if let Some(destination) = specified_when { + let demons_then = list_at(&mut *connection, destination.naive_utc()).await?; + tardis.activate(destination, demons_then, !is_april_1st) + } let mut page = Page::new(OverviewPage { team: Team { - admins: User::by_permission(LIST_ADMINISTRATOR, &mut connection).await?, - moderators: User::by_permission(LIST_MODERATOR, &mut connection).await?, - helpers: User::by_permission(LIST_HELPER, &mut connection).await?, + admins: User::by_permission(LIST_ADMINISTRATOR, &mut *connection).await?, + moderators: User::by_permission(LIST_MODERATOR, &mut *connection).await?, + helpers: User::by_permission(LIST_HELPER, &mut *connection).await?, }, demonlist, time_machine: tardis, @@ -76,65 +95,58 @@ pub async fn overview( } #[rocket::get("/permalink/")] -pub async fn demon_permalink(demon_id: i32, pool: &State, gd: &State, auth: Option) -> Result { +pub async fn demon_permalink( + demon_id: i32, pool: &State, gd: &State, auth: Option +) -> Result { let mut connection = pool.connection().await?; let full_demon = FullDemon::by_id(demon_id, &mut connection).await?; - let audit_log = audit_log_for_demon(full_demon.demon.base.id, &mut connection).await?; + let audit_log = audit_log_for_demon(full_demon.demon.base.id, &mut *connection).await?; let mut addition_time = None; let mut modifications = audit_log .iter() - .filter_map(|entry| { - match entry.r#type { - AuditLogEntryType::Modification(ref modification) => - match modification.position { - Some(old_position) if old_position > 0 => - Some(DemonMovement { - from_position: old_position, - at: entry.time, - }), - _ => None, - }, - AuditLogEntryType::Addition => { - addition_time = Some(entry.time); - - None - }, + .filter_map(|entry| match entry.r#type { + AuditLogEntryType::Modification(ref modification) => match modification.position { + Some(old_position) if old_position > 0 => Some(DemonMovement { + from_position: old_position, + at: entry.time, + }), _ => None, - } + }, + AuditLogEntryType::Addition => { + addition_time = Some(entry.time); + + None + }, + _ => None, }) .collect::>(); if let Some(addition) = addition_time { - modifications.insert(0, DemonMovement { - from_position: modifications - .first() - .map(|m| m.from_position) - .unwrap_or(full_demon.demon.base.position), - at: addition, - }); + modifications.insert( + 0, + DemonMovement { + from_position: modifications + .first() + .map(|m| m.from_position) + .unwrap_or(full_demon.demon.base.position), + at: addition, + }, + ); } let mut page = Page::new(DemonPage { team: Team { - admins: User::by_permission(LIST_ADMINISTRATOR, &mut connection).await?, - moderators: User::by_permission(LIST_MODERATOR, &mut connection).await?, - helpers: User::by_permission(LIST_HELPER, &mut connection).await?, + admins: User::by_permission(LIST_ADMINISTRATOR, &mut *connection).await?, + moderators: User::by_permission(LIST_MODERATOR, &mut *connection).await?, + helpers: User::by_permission(LIST_HELPER, &mut *connection).await?, }, - demonlist: current_list(&mut connection).await?, + demonlist: current_list(&mut *connection).await?, movements: modifications, - integration: gd - .data_for_demon( - reqwest::Client::new(), - full_demon.demon.level_id, - full_demon.demon.base.name.clone(), - full_demon.demon.base.id, - ) - .await - .unwrap_or(GDIntegrationResult::LevelDataNotFound), + integration: gd.load_level_for_demon(&full_demon.demon).await, data: full_demon, }); @@ -159,7 +171,7 @@ pub async fn stats_viewer(pool: &State) -> Result { let mut connection = pool.connection().await?; Ok(Page::new(IndividualStatsViewer { - nationalities_in_use: Nationality::used(&mut connection).await?, + nationalities_in_use: Nationality::used(&mut *connection).await?, })) } @@ -168,51 +180,53 @@ pub async fn nation_stats_viewer() -> Page { Page::new(pointercrate_demonlist_pages::statsviewer::national::nation_based_stats_viewer()) } -macro_rules! heatmap_query { - ($connection: expr, $query: expr, $($param:expr),*) => { - { - let mut css = String::new(); - let mut stream = sqlx::query!($query, $($param),*).fetch(&mut $connection); - - if let Some(firstrow) = stream.next().await { - // first one is the one with most score - let firstrow = firstrow.map_err(DemonlistError::from)?; - let highest_score = firstrow.score * 1.5; +#[rocket::get("/statsviewer/heatmap.css")] +pub async fn heatmap_css(pool: &State) -> Result> { + let mut connection = pool.connection().await?; + let mut css = String::new(); - css.push_str(&make_css_rule(&firstrow.code, firstrow.score, highest_score)); + let mut nation_scores = HashMap::new(); + let mut nations_stream = sqlx::query!("SELECT iso_country_code, score FROM nationalities WHERE score > 0.0").fetch(&mut *connection); - while let Some(row) = stream.next().await { - let row = row.map_err(DemonlistError::from)?; + while let Some(row) = nations_stream.next().await { + let row = row.map_err(DemonlistError::from)?; - css.push_str(&make_css_rule(&row.code, row.score, highest_score)); - } - } + nation_scores.insert(row.iso_country_code, row.score); + } - css - } + let Some(&max_nation_score) = nation_scores.values().max_by(|a, b| a.total_cmp(b)) else { + // Not a single nation has a score > 0. This means there are no approved records. So return no CSS + return Ok(Response2::new(css).with_header("Content-Type", "text/css")); }; -} -#[rocket::get("/statsviewer/heatmap.css")] -pub async fn heatmap_css(pool: &State) -> Result> { - let mut connection = pool.connection().await?; - let mut css = heatmap_query!( - connection, - r#"SELECT LOWER(iso_country_code) as "code!", score as "score!" from nations_with_score order by score desc"#, - ); - - for nation in ["AU", "CA", "US", "GB"] { - css.push_str(&heatmap_query!( - connection, - r#"SELECT CONCAT($1, '-', UPPER(subdivision_code)) AS "code!", score AS "score!" FROM subdivision_ranking_of($1) ORDER BY score DESC"#, - nation - )); + for (nation, &score) in &nation_scores { + css.push_str(&make_css_rule(&nation.to_lowercase(), score, max_nation_score)); + } + + // un-borrow `connection` + drop(nations_stream); + + let mut subdivisions_stream = + sqlx::query!("SELECT nation, iso_code, score FROM subdivisions WHERE score > 0.0").fetch(&mut *connection); + + while let Some(row) = subdivisions_stream.next().await { + let row = row.map_err(DemonlistError::from)?; + + css.push_str(&make_css_rule( + &format!("{}-{}", row.nation, row.iso_code), + row.score, + *nation_scores.get(&row.nation).unwrap_or(&f64::INFINITY), + )) } Ok(Response2::new(css).with_header("Content-Type", "text/css")) } fn make_css_rule(code: &str, score: f64, highest_score: f64) -> String { + // Artificially adjust the highest score so that score/high_score is never 1. If it were 1, the resulting + // color will be equal to the "hover"/"selected" color, which looks bad. + let highest_score = highest_score * 1.5; + format!( ".heatmapped #{0}, .heatmapped #{0} > path {{ fill: rgb({1}, {2}, {3}); }}", code, diff --git a/pointercrate-demonlist-api/src/ratelimits.rs b/pointercrate-demonlist-api/src/ratelimits.rs index 70c08c288..3d74ea6ae 100644 --- a/pointercrate-demonlist-api/src/ratelimits.rs +++ b/pointercrate-demonlist-api/src/ratelimits.rs @@ -1,14 +1,16 @@ +use std::net::IpAddr; + use pointercrate_core::ratelimits; ratelimits! { DemonlistRatelimits { - record_submission[3u32 per 1200 per ip] => "You're submitting too many records too fast!", + record_submission[3u32 per 1200 per IpAddr] => "You're submitting too many records too fast!", record_submission_global[20u32 per 3600] => "Too many records are being submitted right now!", new_submitters[7u32 per 3600] => "DDoS protection ratelimit", - geolocate[1u32 per 2_678_400 per ip] => "You can only geolocate once per month!", + geolocate[1u32 per 2_678_400 per IpAddr] => "You can only geolocate once per month!", add_demon[1u32 per 60] => "Please don't spam the button, rSteel", } diff --git a/pointercrate-demonlist-pages/Cargo.toml b/pointercrate-demonlist-pages/Cargo.toml index 78516956f..0cca0daaa 100644 --- a/pointercrate-demonlist-pages/Cargo.toml +++ b/pointercrate-demonlist-pages/Cargo.toml @@ -1,7 +1,8 @@ [package] name = "pointercrate-demonlist-pages" version = "0.1.0" -edition = "2021" +authors.workspace = true +edition.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -12,8 +13,8 @@ pointercrate-user = {path = "../pointercrate-user"} pointercrate-user-pages = {path = "../pointercrate-user-pages"} pointercrate-demonlist = {path = "../pointercrate-demonlist"} pointercrate-integrate = {path = "../pointercrate-integrate"} -maud = "0.25" -chrono = "0.4.19" -url = "2.2.0" -async-trait = "0.1.42" -log = "0.4.11" +maud = "0.26.0" +chrono = "0.4.38" +url = "2.5.2" +async-trait = "0.1.80" +log = "0.4.22" diff --git a/pointercrate-demonlist-pages/flag-sources.txt b/pointercrate-demonlist-pages/flag-sources.txt new file mode 100644 index 000000000..517fbde53 --- /dev/null +++ b/pointercrate-demonlist-pages/flag-sources.txt @@ -0,0 +1,459 @@ +Note: All SVG's have been minimized using `scour`: + +> find flags -name "*.svg" -type f -exec scour -i {} -o {}.min \; -exec mv {}.min {} \; + +Argentina: +Ciudad Autónomae Buenos Aires - TBA +Buenos Aires - https://commons.wikimedia.org/wiki/File:Bandera_de_la_Provincia_de_Buenos_Aires.svg +Catamarca - https://commons.wikimedia.org/wiki/File:Escudo_de_la_Provincia_de_Catamarca.svg +Chaco - TBA +Chubut - TBA +Cordoba - modified version of https://commons.wikimedia.org/wiki/File:Flag_of_C%C3%B3rdoba_1815.svg +Corrientes - https://commons.wikimedia.org/wiki/File:Flag_of_Corrientes_(1822).svg +Entre Rios - https://commons.wikimedia.org/wiki/File:Bandera_de_la_Provincia_de_Entre_R%C3%ADos.svg +Formosa - TBA +Jujuy - https://commons.wikimedia.org/wiki/File:Flag_of_the_Civil_Freedom_of_Argentina.svg +La Pampa - TBA +La Rioja - https://commons.wikimedia.org/wiki/File:Bandera_de_la_Provincia_de_La_Rioja.svg +Mendoza - modified version of https://commons.wikimedia.org/wiki/File:Flag_of_the_Army_of_the_Andes.svg +Misiones - https://commons.wikimedia.org/wiki/File:Bandera_de_la_Provincia_de_Misiones.svg +Neuquen - https://commons.wikimedia.org/wiki/File:Escudo_de_la_Provincia_de_Neuqu%C3%A9n.svg +Rio Negro - TBA +Salta - TBA +San Juan - https://commons.wikimedia.org/wiki/File:Flag_of_the_San_Juan_Province.svg +San Luis - https://commons.wikimedia.org/wiki/File:Bandera_de_la_Provincia_de_San_Luis.svg +Santa Cruz - TBA +Santa Fe - https://commons.wikimedia.org/wiki/File:Bandera_de_la_Provincia_de_Santa_Fe.svg +Santiagoel Estero - https://commons.wikimedia.org/wiki/File:Bandera_de_la_Provincia_de_Santiago_del_Estero.svg +Tierrael Fuego - https://commons.wikimedia.org/wiki/File:Bandera_de_la_Provincia_de_Tierra_del_Fuego.svg +Tucuman - https://commons.wikimedia.org/wiki/File:Bandera_de_la_Provincia_de_Tucum%C3%A1n.svg + +Brazil: +Acre - https://commons.wikimedia.org/wiki/File:Bandeira_do_Acre.svg +Alagoas - TBA +Amapa - https://commons.wikimedia.org/wiki/File:Bandeira_do_Amap%C3%A1.svg +Amazonas - https://commons.wikimedia.org/wiki/File:Bandeira_do_Amazonas.svg +Bahia - https://commons.wikimedia.org/wiki/File:Bandeira_da_Bahia.svg +Ceara - https://openclipart.org/detail/305363/flag-of-the-state-of-ceara-br +Distrito Federal - https://commons.wikimedia.org/wiki/File:Bandeira_do_Distrito_Federal_(Brasil).svg +Espirito Santo - https://commons.wikimedia.org/wiki/File:Bandeira_do_Esp%C3%ADrito_Santo.svg +Goias - https://commons.wikimedia.org/wiki/File:Flag_of_Goi%C3%A1s.svg +Maranhao - https://commons.wikimedia.org/wiki/File:Bandeira_do_Maranh%C3%A3o.svg +Mato Grosso - https://commons.wikimedia.org/wiki/File:Bandeira_de_Mato_Grosso.svg +Mato Grosso Do Sul - https://commons.wikimedia.org/wiki/File:Bandeira_de_Mato_Grosso_do_Sul.svg +Minas Gerais - https://commons.wikimedia.org/wiki/File:Bandeira_de_Minas_Gerais.svg +Para - https://commons.wikimedia.org/wiki/File:Bandeira_do_Par%C3%A1.svg +Paraiba - https://commons.wikimedia.org/wiki/File:Bandeira_da_Para%C3%ADba.svg +Parana - https://commons.wikimedia.org/wiki/File:Bandeira_do_Paran%C3%A1.svg +Pernambuco - https://commons.wikimedia.org/wiki/File:Bandeira_de_Pernambuco.svg +Piaui - https://commons.wikimedia.org/wiki/File:Bandeira_do_Piau%C3%AD.svg +Rio de Janeiro - TBA +Rio Grande Do Norte - TBA +Rio Grande Do Sul - TBA +Rondonia - https://commons.wikimedia.org/wiki/File:Bandeira_de_Rond%C3%B4nia.svg +Roraima - https://commons.wikimedia.org/wiki/File:Bandeira_de_Roraima.svg +Santa Catarina - https://commons.wikimedia.org/wiki/File:Bandeira_de_Santa_Catarina.svg +Sao Paulo - https://openclipart.org/detail/305382/flag-of-sao-paulo +Sergipe - https://commons.wikimedia.org/wiki/File:Bandeira_de_Sergipe.svg +Tocantins - https://commons.wikimedia.org/wiki/File:Bandeira_do_Tocantins.svg + +Chile: +Arica y Parinacota - TBA +Tarapaca - https://commons.wikimedia.org/wiki/File:Coat_of_arms_of_Tarapaca,_Chile.svg +Antofagasta - https://commons.wikimedia.org/wiki/File:Logo_del_Gobierno_Regional_de_Antofagasta.svg +Atacama - https://commons.wikimedia.org/wiki/File:Flag_of_Atacama,_Chile.svg +Coquimbo - https://commons.wikimedia.org/wiki/File:Flag_of_Coquimbo_Region,_Chile.svg +Valrapaiso - TBA +Metropolitan - TBA +O'Higgins - TBA +Maule - TBA +Nuble - TBA +Biobio - TBA +Araucania - TBA +Los Rios - TBA +Los Lagos - TBA +Aysen - https://commons.wikimedia.org/wiki/File:Coat_of_arms_of_Aysen,_Chile.svg +Magallanes - https://commons.wikimedia.org/wiki/File:Coat_of_arms_of_Magallanes,_Chile.svg + +Colombia: +Bogota - https://commons.wikimedia.org/wiki/File:Flag_of_Bogot%C3%A1.svg +Amazonas - TBA +Antioquia - https://commons.wikimedia.org/wiki/File:Flag_of_Antioquia_Department.svg +Arauca - https://commons.wikimedia.org/wiki/File:Flag_of_Arauca.svg +Atlantico - https://commons.wikimedia.org/wiki/File:Flag_of_Atl%C3%A1ntico.svg +Bolivar - https://commons.wikimedia.org/wiki/File:Flag_of_Bol%C3%ADvar_(Colombia).svg +Boyaca - https://commons.wikimedia.org/wiki/File:Flag_of_Boyac%C3%A1_Department.svg +Caldas - https://commons.wikimedia.org/wiki/File:Flag_of_Caldas.svg +Caqueta - own work +Casanare - own work +Cauca - https://commons.wikimedia.org/wiki/File:Flag_of_Cauca.svg +Cesar - https://commons.wikimedia.org/wiki/File:Flag_of_Cesar.svg +Choco - https://commons.wikimedia.org/wiki/File:Flag_of_Choc%C3%B3.svg +Cordoba - https://commons.wikimedia.org/wiki/File:Flag_of_C%C3%B3rdoba.svg +Cundinamarca - own work (no emblem sorry) +Guainia - https://commons.wikimedia.org/wiki/File:Flag_of_Guain%C3%ADa.svg +Guaviare - own work (no emblem again :p) +Huila - https://commons.wikimedia.org/wiki/File:Flag_of_Huila.svg +La Guajira - https://commons.wikimedia.org/wiki/File:Flag_of_La_Guajira.svg +Magdalena - own work (looks good enough) +Meta - https://commons.wikimedia.org/wiki/File:Flag_of_Meta.svg +Narino - https://commons.wikimedia.org/wiki/File:Flag_of_Nari%C3%B1o.svg +Norte de Santander - own work +Putumayo - https://commons.wikimedia.org/wiki/File:Flag_of_Putumayo.svg +Quindio - https://commons.wikimedia.org/wiki/File:Flag_of_Quind%C3%ADo.svg +Risaralda - own work +San Andres y Providencia - https://commons.wikimedia.org/wiki/File:Flag_of_San_Andr%C3%A9s_y_Providencia.svg +Santander - own work +Sucre - TBA +Tolima - https://commons.wikimedia.org/wiki/File:Flag_of_Tolima.svg +Valleel Cauca - https://commons.wikimedia.org/wiki/File:Flag_of_Ansermanuevo.svg +Vaupes - TBA +Vichada - https://commons.wikimedia.org/wiki/File:Flag_of_Vichada.svg + +Finland: +Lappi - https://commons.wikimedia.org/wiki/File:Lapin_maakunnan_vaakuna.svg +Pohjois-Pohjanmaa - https://commons.wikimedia.org/wiki/File:Pohjois-Pohjanmaan_vaakuna.svg +Kainuu - https://commons.wikimedia.org/wiki/File:Kainuu.vaakuna.svg +Pohjois-Karjala - https://commons.wikimedia.org/wiki/File:Pohjois-Karjala.vaakuna.svg +Pohjois-Savo - https://commons.wikimedia.org/wiki/File:Pohjois-Savo.vaakuna.svg +Etela-Savo - https://commons.wikimedia.org/wiki/File:Etel%C3%A4-Savo.vaakuna.svg +Etela-Karjala - https://commons.wikimedia.org/wiki/File:Etel%C3%A4-Karjala.vaakuna.svg +Keski-Suomi - https://commons.wikimedia.org/wiki/File:Keski-Suomi_Coat_of_Arms.svg +Etela-Pohjanmaa - https://commons.wikimedia.org/wiki/File:Etel%C3%A4-Pohjanmaan_maakunnan_vaakuna.svg +Pohjanmaa - https://commons.wikimedia.org/wiki/File:Pohjanmaan_maakunnan_vaakuna.svg +Keski-Pohjanmaa - https://commons.wikimedia.org/wiki/File:Keski-Pohjanmaa.vaakuna.svg +Pirkanmaa - https://commons.wikimedia.org/wiki/File:Pirkanmaa.vaakuna.svg +Satakunta - https://commons.wikimedia.org/wiki/File:Satakunta.vaakuna.svg +Paijat-Hame - https://commons.wikimedia.org/wiki/File:P%C3%A4ij%C3%A4t-H%C3%A4me.vaakuna.svg +Kanta-Hame - https://commons.wikimedia.org/wiki/File:Kanta-H%C3%A4me.vaakuna.svg +Kymenlaakso - https://commons.wikimedia.org/wiki/File:Kymenlaakson_maakunnan_vaakuna.svg +Uusimaa - https://commons.wikimedia.org/wiki/File:Uusimaa.vaakuna.svg +Varsinais-Suomi - https://commons.wikimedia.org/wiki/File:Varsinais-Suomen.vaakuna.svg +Ahvenanmaa - https://commons.wikimedia.org/wiki/File:Coat_of_arms_of_%C3%85land_3.svg + +France: +Auvergne-Rhone-Alpes - https://commons.wikimedia.org/wiki/File:Flag_of_the_region_Auvergne-Rh%C3%B4ne-Alpes.svg +Bourgogne-Franche-Comte - https://commons.wikimedia.org/wiki/File:Flag_of_the_region_Bourgogne-Franche-Comt%C3%A9_(fixed).svg +Bretagne - https://commons.wikimedia.org/wiki/File:Flag_of_Brittany_(Gwenn_ha_du).svg +Centre-Vale Loire - https://commons.wikimedia.org/wiki/File:Flag_of_Centre-Val_de_Loire.svg +Corse - https://commons.wikimedia.org/wiki/File:Flag_of_Corsica.svg +Grand Est - https://commons.wikimedia.org/wiki/File:Logo_R%C3%A9gion_Grand_Est_-_2016.svg +Hauts-de-France - https://commons.wikimedia.org/wiki/File:Flag_of_the_Region_of_Hauts-de-France.svg +ile-de-France - https://commons.wikimedia.org/wiki/File:Proposed_flag_of_%C3%8Ele-de-France.svg +Normandie - https://commons.wikimedia.org/wiki/File:Flag_of_Normandie.svg +Nouvelle-Aqutaine - https://commons.wikimedia.org/wiki/File:Flag_of_Nouvelle-Aquitaine.svg +Occitanie - https://commons.wikimedia.org/wiki/File:Flag_of_Occitania.svg +Payse la Loire - https://commons.wikimedia.org/wiki/File:Unofficial_flag_of_Pays-de-la-Loire.svg +Provence-Alpes-Cote d'Azur - https://openclipart.org/detail/61081/france-provence-alpes-cote-d-azur +Guadeloupe - https://en.wikipedia.org/wiki/File:Guadaloupe_logo.svg +Guyane - https://commons.wikimedia.org/wiki/File:Flag_of_French_Guiana.svg +La Reunion - https://commons.wikimedia.org/wiki/File:Proposed_flag_of_R%C3%A9union_(ARF).svg +Martinique - https://commons.wikimedia.org/wiki/File:Flag-of-Martinique.svg +Mayotte - https://commons.wikimedia.org/wiki/File:Coat_of_Arms_of_Mayotte.svg +French Polynesia - https://commons.wikimedia.org/wiki/File:Flag_of_French_Polynesia.svg +Saint-Barthelemy - TBA (this will go unused anyway) +Saint-Martin - https://commons.wikimedia.org/wiki/File:Flag_of_France.svg +Saint-Pierre-et-Miquelon - TBA +Wallis-et-Futuna - https://commons.wikimedia.org/wiki/File:Flag_of_Wallis_and_Futuna.svg +Nouvelle-Calédonie - https://commons.wikimedia.org/wiki/File:Flags_of_New_Caledonia.svg + +Germany: +Baden-Wurttemberg - https://commons.wikimedia.org/wiki/File:Flag_of_Baden-W%C3%BCrttemberg.svg +Bayern - https://commons.wikimedia.org/wiki/File:Flag_of_Bavaria_(striped).svg +Berlin - https://commons.wikimedia.org/wiki/File:Flag_of_Berlin.svg +Brandenburg - https://commons.wikimedia.org/wiki/File:Flag_of_Brandenburg.svg +Bremen - https://commons.wikimedia.org/wiki/File:Flag_of_Bremen.svg +Hamburg - https://commons.wikimedia.org/wiki/File:Flag_of_Hamburg.svg +Hessen - https://commons.wikimedia.org/wiki/File:Flag_of_Hesse.svg +Niedersachsen - https://commons.wikimedia.org/wiki/File:Flag_of_Lower_Saxony.svg +Mercklenburg-Vorpommern - https://commons.wikimedia.org/wiki/File:Flag_of_Mecklenburg-Western_Pomerania.svg +Nordrhein-Westfalen - https://commons.wikimedia.org/wiki/File:Flag_of_North_Rhine-Westphalia.svg +Rheinland-Pfalz - https://commons.wikimedia.org/wiki/File:Flag_of_Rhineland-Palatinate.svg +Saarland - https://commons.wikimedia.org/wiki/File:Flag_of_Saarland.svg +Sachsen - https://commons.wikimedia.org/wiki/File:Flag_of_Saxony.svg +Sachsen-Anhalt - https://commons.wikimedia.org/wiki/File:Flag_of_Saxony-Anhalt_(state).svg +Schleswig-Holstein - https://commons.wikimedia.org/wiki/File:Flag_of_Schleswig-Holstein.svg +Thuringen - https://commons.wikimedia.org/wiki/File:Flag_of_Thuringia.svg + +Italy: +Abruzzo - https://commons.wikimedia.org/wiki/File:Flag_of_Abruzzo.svg +Valle'Aosta - https://commons.wikimedia.org/wiki/File:Flag_of_Valle_d%27Aosta.svg +Puglia - TBA +Basilicata - https://commons.wikimedia.org/wiki/File:Flag_of_Basilicata.svg +Calabria - https://commons.wikimedia.org/wiki/File:Flag_of_Calabria.svg +Campania - https://commons.wikimedia.org/wiki/File:Flag_of_Campania.svg +Emilia-Romagna - TBA +Friuli-Venezia Giulia - https://commons.wikimedia.org/wiki/File:Flag_of_Friuli-Venezia_Giulia.svg +Lazio - https://commons.wikimedia.org/wiki/File:Flag_of_Lazio.svg +Liguria - https://commons.wikimedia.org/wiki/File:Flag_of_Liguria.svg +Lombardia - https://commons.wikimedia.org/wiki/File:Flag_of_Lombardy.svg +Marche - https://commons.wikimedia.org/wiki/File:Flag_of_Marche.svg +Molise - https://commons.wikimedia.org/wiki/File:Flag_of_Molise.svg +Piemonte - https://commons.wikimedia.org/wiki/File:Flag_of_Piedmont.svg +Sardegna - TBA +Sicilia - https://commons.wikimedia.org/wiki/File:Sicilian_Flag.svg +Trentino-Alto Adige - https://commons.wikimedia.org/wiki/File:Flag_of_Trentino-South_Tyrol.svg +Toscana - TBA +Umbria - https://commons.wikimedia.org/wiki/File:Flag_of_Umbria.svg +Veneto - TBA + +Mexico: +Aguascalientes - https://commons.wikimedia.org/wiki/File:Coat_of_arms_of_Aguascalientes.svg +Baja California - TBA +Baja California Sur - https://commons.wikimedia.org/wiki/File:Coat_of_arms_of_Baja_California_Sur.svg +Campeche - TBA +Chiapas - TBA +Chihuahua - modified version of https://commons.wikimedia.org/wiki/File:Flag_of_Chihuahua,_Chihuahua.svg +Coahuila - TBA +Colima - TBA +Durango - TBA +Guanajuato - TBA +Guerrero - TBA +Hidalgo - TBA +Jalisco - https://commons.wikimedia.org/wiki/File:Coat_of_arms_of_Jalisco.svg +Mexico - TBA +Ciudad de Mexico - https://upload.wikimedia.org/wikipedia/commons/6/69/Flag_of_Mexico_City_%28variant%29.svg +Michoacan - TBA +Morelos - TBA +Nayarit - https://commons.wikimedia.org/wiki/File:Coat_of_arms_of_Nayarit.svg +Nuevo Leon - TBA +Oaxaca - TBA +Puebla - TBA +Queretaro - https://commons.wikimedia.org/wiki/File:Flag_of_Queretaro.svg +Quintana Roo - https://commons.wikimedia.org/wiki/File:Flag_of_Quintana_Roo.svg +San Luis Potosi - TBA +Sinaloa - TBA +Sonora - https://commons.wikimedia.org/wiki/File:Flag_of_Sonora.svg +Tabasco - TBA +Tamaulipas - https://commons.wikimedia.org/wiki/File:Flag_of_Tamaulipas.svg +Tlaxcala - https://commons.wikimedia.org/wiki/File:Flag_of_Tlaxcala.svg +Veracruz - https://commons.wikimedia.org/wiki/File:Flag_of_Veracruz_City.svg +Yucatan - TBA +Zacatecas - https://commons.wikimedia.org/wiki/File:Escudo_de_armas_de_la_Ciudad_y_Estado_de_Zacatecas.svg + +Netherlands: +Drenthe - https://commons.wikimedia.org/wiki/File:Flag_of_Drenthe.svg +Flevoland - https://commons.wikimedia.org/wiki/File:Flag_of_Flevoland.svg +Friesland - https://commons.wikimedia.org/wiki/File:Frisian_flag.svg +Gelderland - https://commons.wikimedia.org/wiki/File:Flag_of_Gelderland.svg +Groningen - https://commons.wikimedia.org/wiki/File:Flag_of_Groningen.svg +Limburg - https://commons.wikimedia.org/wiki/File:Flag_of_Limburg_(Netherlands).svg +Noord-Brabant - https://commons.wikimedia.org/wiki/File:North_Brabant-Flag.svg +Noord-Holland - https://commons.wikimedia.org/wiki/File:Flag_of_North_Holland.svg +Overijssel - https://commons.wikimedia.org/wiki/File:Flag_of_Overijssel.svg +Zuid-Holland - https://commons.wikimedia.org/wiki/File:Flag_of_Zuid-Holland.svg +Utrecht - https://commons.wikimedia.org/wiki/File:Utrecht_(province)-Flag.svg +Zeeland - https://commons.wikimedia.org/wiki/File:Flag_of_Zeeland.svg + +Norway: +Troms - https://commons.wikimedia.org/wiki/File:Troms_v%C3%A5pen.svg +Finnmark - https://commons.wikimedia.org/wiki/File:Finnmark_v%C3%A5pen.svg +Nordland - https://commons.wikimedia.org/wiki/File:Nordland_v%C3%A5pen.svg +Trondelag - https://commons.wikimedia.org/wiki/File:Tr%C3%B8ndelag_v%C3%A5pen.svg +More og Romsdal - https://commons.wikimedia.org/wiki/File:M%C3%B8re_og_Romsdal_v%C3%A5pen.svg +Vestland - https://commons.wikimedia.org/wiki/File:Vestland_v%C3%A5pen.svg +Rogaland - https://commons.wikimedia.org/wiki/File:Rogaland_v%C3%A5pen.svg +Agder - https://commons.wikimedia.org/wiki/File:Agder_v%C3%A5pen.svg +Vestland - https://commons.wikimedia.org/wiki/File:Vestfold_v%C3%A5pen_2024.svg +Telemark - https://commons.wikimedia.org/wiki/File:Telemark_v%C3%A5pen_2024.svg +Buskerud - TBA +Akershus - https://commons.wikimedia.org/wiki/File:Akershus_v%C3%A5pen.svg +Ostfold - https://commons.wikimedia.org/wiki/File:%C3%98stfold_v%C3%A5pen.svg +Innlandet - https://commons.wikimedia.org/wiki/File:Innlandet_v%C3%A5pen.svg +Oslo - https://commons.wikimedia.org/wiki/File:Oslo_komm.svg + +Peru: +Amazonas - TBA +Ancash - https://commons.wikimedia.org/wiki/File:Bandera_Ancash.svg +Apurimac - TBA +Arequipa - TBA +Ayacucho - TBA +Cajamarca - https://commons.wikimedia.org/wiki/File:Bandera_de_Cajamarca.svg +Cuzco - own work (no emblem) +Huancavelica - TBA +Huanuco - https://commons.wikimedia.org/wiki/File:Flag_of_Hu%C3%A1nuco.svg +Ica - https://commons.wikimedia.org/wiki/File:Bandera_Regi%C3%B3n_Ica.svg +Junin - own work +La Libertad - TBA +Lambayeque - https://commons.wikimedia.org/wiki/File:Flag_of_Lambayeque_Department.svg +Lima - https://commons.wikimedia.org/wiki/File:Coat_of_arms_of_Lima,_Lima_region.svg +Loreto - https://commons.wikimedia.org/wiki/File:Bandera_Regi%C3%B3n_Loreto.svg +Madre de Dios - https://commons.wikimedia.org/wiki/File:Flag_of_Madre_de_Dios_Department.svg +Moquegua - own work +Pasco - https://commons.wikimedia.org/wiki/File:Flag_of_Pasco_Department.svg +Piura - own work +Puno - own work +San Martin - https://commons.wikimedia.org/wiki/File:Bandera_Regi%C3%B3n_San_Mart%C3%ADn.svg (nuked the CoA because it was 700kb) +Tacna - TBA +Tumbes - traced from https://commons.wikimedia.org/wiki/File:Bandera_de_Tumbes.png +Ucayali - TBA + +Poland: +Województwo wielkopolskie - TBA +Województwo kujawsko-pomorskie - https://commons.wikimedia.org/wiki/File:POL_wojew%C3%B3dztwo_kujawsko-pomorskie_flag.svg +Województwo małopolskie - https://commons.wikimedia.org/wiki/File:POL_wojew%C3%B3dztwo_ma%C5%82opolskie_flag_incorrect.svg +Województwo łódzkie - own work +Województwo dolnośląskie - eagle from https://commons.wikimedia.org/wiki/File:Dolny_%C5%9Al%C4%85sk.svg +Województwo lubelskie - https://commons.wikimedia.org/wiki/File:POL_wojew%C3%B3dztwo_lubelskie_COA.svg + lesser poland's flag as a base +Województwo lubuskie - https://commons.wikimedia.org/wiki/File:POL_wojew%C3%B3dztwo_lubuskie_flag.svg +Województwo mazowieckie - TBA +Województwo opolskie - https://commons.wikimedia.org/wiki/File:POL_wojew%C3%B3dztwo_opolskie_flag.svg +Województwo podlaskie - https://commons.wikimedia.org/wiki/File:POL_wojew%C3%B3dztwo_podlaskie_flag.svg +Województwo pomorskie - TBA +Województwo śląskie - https://commons.wikimedia.org/wiki/File:POL_wojew%C3%B3dztwo_%C5%9Bl%C4%85skie_flag.svg +Województwo podkarpackie - https://commons.wikimedia.org/wiki/File:POL_wojew%C3%B3dztwo_podkarpackie_flag.svg +Województwo świętokrzyskie - TBA +Województwo warmińsko-mazurskie - https://commons.wikimedia.org/wiki/File:POL_wojew%C3%B3dztwo_warmi%C5%84sko-mazurskie_COA.svg +Województwo zachodniopomorskie - TBA + +Russian Federation: +Adygea - https://commons.wikimedia.org/wiki/File:Flag_of_Adygea.svg +Bashkortostan - https://commons.wikimedia.org/wiki/File:Flag_of_Bashkortostan.svg +Buryatia - https://commons.wikimedia.org/wiki/File:Flag_of_Buryatia.svg +Altai Republic - https://commons.wikimedia.org/wiki/File:Flag_of_Altai_Republic.svg +Dagestan - https://commons.wikimedia.org/wiki/File:Flag_of_Dagestan.svg +Ingushetia - https://commons.wikimedia.org/wiki/File:Flag_of_Ingushetia.svg +Kabardino-Balkaria - https://commons.wikimedia.org/wiki/File:Flag_of_Kabardino-Balkaria.svg +Kalmykia - https://commons.wikimedia.org/wiki/File:Flag_of_Kalmykia.svg +Karachay-Cherkessia - https://commons.wikimedia.org/wiki/File:Flag_of_Karachay-Cherkessia.svg +Karelia - https://commons.wikimedia.org/wiki/File:Flag_of_Karelia.svg +Komi - https://commons.wikimedia.org/wiki/File:Flag_of_Komi.svg +Mari El - https://commons.wikimedia.org/wiki/File:Flag_of_Mari_El.svg +Mordovia - https://commons.wikimedia.org/wiki/File:Flag_of_Mordovia.svg +Sakha - https://commons.wikimedia.org/wiki/File:Flag_of_Sakha.svg +Severnaya Ossetia - https://commons.wikimedia.org/wiki/File:Flag_of_North_Ossetia.svg +Tatarstan - https://commons.wikimedia.org/wiki/File:Flag_of_Tatarstan.svg +Tuva - https://commons.wikimedia.org/wiki/File:Flag_of_Tuva.svg +Udmurtia - https://commons.wikimedia.org/wiki/File:Flag_of_Udmurtia.svg +Khakassia - modified version of https://commons.wikimedia.org/wiki/File:Flag_of_Khakassia_(2002%E2%80%932003).svg +Chechnya - https://commons.wikimedia.org/wiki/File:Flag_of_the_Chechen_Republic.svg +Chuvashia - https://commons.wikimedia.org/wiki/File:Flag_of_Chuvashia.svg +Altai Krai - TBA +Krasnodar Krai - https://commons.wikimedia.org/wiki/File:Flag_of_Krasnodar_Krai.svg +Krasnoyarsk Krai - https://commons.wikimedia.org/wiki/File:Flag_of_Krasnoyarsk_Krai.svg +Primorsky Krai - https://commons.wikimedia.org/wiki/File:Flag_of_Primorsky_Krai.svg +Stavropol Krai - https://commons.wikimedia.org/wiki/File:Flag_of_Stavropol_Krai.svg +Khabarovsk Krai - https://commons.wikimedia.org/wiki/File:Flag_of_Khabarovsk_Krai.svg +Amur Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Amur_Oblast.svg +Arkhangelsk Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Arkhangelsk_Oblast.svg +Astrakhan Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Astrakhan_Oblast.svg +Belgorod Oblast - TBA +Bryansk Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Bryansk_Oblast.svg +Vladimir Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Vladimir_Oblast.svg +Volgograd Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Volgograd_Oblast.svg +Vologda Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Vologda_oblast.svg +Voronezh Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Voronezh_Oblast.svg +Ivanovo Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Ivanovo_Oblast.svg +Irkutsk Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Irkutsk_Oblast.svg +Kaliningrad Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Kaliningrad_Oblast.svg +Kaluga Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Kaluga_Oblast.svg +Kamchatka Krai - https://commons.wikimedia.org/wiki/File:Flag_of_Kamchatka_Krai.svg +Kemerovo Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Kemerovo_Oblast.svg +Kirov Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Kirov_Oblast.svg +Kostroma Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Kostroma_Oblast.svg +Kurgan Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Kurgan_Oblast.svg +Kursk Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Kursk_Oblast.svg +Leningrad Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Leningrad_Oblast.svg +Lipetsk Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Lipetsk_Oblast.svg +Magadan Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Magadan_Oblast.svg +Moscow Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Moscow_oblast.svg +Murmansk Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Murmansk_Oblast.svg +Nizhny Novgorod Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Nizhny_Novgorod.svg +Novgorod Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Novgorod_Oblast.svg +Novosibirsk Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Novosibirsk_oblast.svg +Omsk Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Omsk_Oblast.svg +Orenburg Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Orenburg_Oblast.svg +Oryol Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Oryol_Oblast.svg +Penza Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Penza_Oblast.svg +Perm Krai - https://commons.wikimedia.org/wiki/File:Flag_of_Perm_Krai.svg +Pskov Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Pskov_Oblast.svg +Rostov Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Rostov_Oblast.svg +Ryazan Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Ryazan_Oblast.svg +Samara Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Samara_Oblast.svg +Saratov Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Saratov_Oblast.svg +Sakhalin Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Sakhalin_Oblast.svg +Sverdlovsk Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Sverdlovsk_Oblast.svg +Smolensk Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Smolensk_Oblast.svg +Tambov Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Tambov_Oblast.svg +Tver Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Tver_Oblast.svg +Tomsk Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Tomsk_Oblast.svg +Tula Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Tula_Oblast.svg +Tyumen Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Tyumen_Oblast.svg +Ulyanovsk Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Ulyanovsk_Oblast.svg +Chelyabinsk Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Chelyabinsk_Oblast.svg +Zabaykalsky Krai - modified version of https://commons.wikimedia.org/wiki/File:Flag_of_Ononsky_rayon_(Zabaykalsky_Krai)_(2011).svg +Yaroslavl Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Yaroslavl_Oblast.svg +Moscow - https://commons.wikimedia.org/wiki/File:Flag_of_Moscow,_Russia.svg +Saint Petersburg - https://commons.wikimedia.org/wiki/File:Flag_of_Saint_Petersburg.svg +Jewish Autonomous Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_the_Jewish_Autonomous_Oblast.svg +Nenets Autonomous Okrug - https://commons.wikimedia.org/wiki/File:Flag_of_Nenets_Autonomous_District.svg +Khanty-Mansi Autonomous Okrug - https://commons.wikimedia.org/wiki/File:Flag_of_Yugra.svg +Chukotka - modified version of https://commons.wikimedia.org/wiki/File:Flag_of_Chukotka_(1994-1997).svg +Yamalo-Nenets Autonomous Okrug - https://commons.wikimedia.org/wiki/File:Flag_of_Yamal-Nenets_Autonomous_District.svg + +South Korea: +Seoul - https://commons.wikimedia.org/wiki/File:Logo_of_Seoul,_South_Korea.svg +Busan - https://commons.wikimedia.org/wiki/File:Symbol_of_Busan.svg +Daegu - https://commons.wikimedia.org/wiki/File:Symbol_of_Daegu.svg +Incheon - https://commons.wikimedia.org/wiki/File:Emblem_of_Incheon.svg +Gwangju - https://commons.wikimedia.org/wiki/File:Emblem_of_Gwangju.svg +Daejeon - https://commons.wikimedia.org/wiki/File:Emblem_of_Daejeon.svg +Ulsan - https://commons.wikimedia.org/wiki/File:Symbol_of_Ulsan.svg +Sejong - https://commons.wikimedia.org/wiki/File:Flag_of_Sejong_City.svg +Gyeonggi-do - https://commons.wikimedia.org/wiki/File:Emblem_of_Gyeonggi_Province_(2021).svg +Gangwon - https://commons.wikimedia.org/wiki/File:Emblem_of_Gangwon_State.svg +Chungcheongnam-do - https://commons.wikimedia.org/wiki/File:Emblem_of_North_Chungcheong_Province.svg +Chungcheongbuk-do - https://commons.wikimedia.org/wiki/File:Seal_of_South_Chungcheong.svg +Jeonbuk - https://commons.wikimedia.org/wiki/File:Emblem_of_Jeonbuk_State.svg +Jeonnam - https://commons.wikimedia.org/wiki/File:Emblem_of_South_Jeolla_Province.svg +Gyeongsangbuk-do - https://commons.wikimedia.org/wiki/File:Seal_of_North_Gyeongsang.svg +Gyeongsangnam-do - https://en.wikipedia.org/wiki/File:Seal_of_South_Gyeongsang.svg +Jeju - https://en.wikipedia.org/wiki/File:Logo_of_Jeju_Province,_South_Korea.svg + +Spain: +Andalucia - https://commons.wikimedia.org/wiki/File:Escudo_de_Andaluc%C3%ADa_(oficial2).svg + 3 stripes +Aragon - https://commons.wikimedia.org/wiki/File:Bandera_de_Arag%C3%B3n.svg +Asturias - https://commons.wikimedia.org/wiki/File:Flag_of_Asturias.svg +Islas Baleares - https://commons.wikimedia.org/wiki/File:Flag_of_the_Balearic_Islands.svg +País Vasco - modified version of https://openclipart.org/detail/27802/flag-basque-country +Canarias - https://commons.wikimedia.org/wiki/File:Flag_of_the_Canary_Islands.svg +Cantabria - https://commons.wikimedia.org/wiki/File:Escudo_de_Cantabria_(oficial).svg + flag of poland XD +Castilla y Leon - https://commons.wikimedia.org/wiki/File:Escudo_de_Castilla_y_Le%C3%B3n_-_Versi%C3%B3n_her%C3%A1ldica_oficial.svg +Castilla–La Mancha - https://commons.wikimedia.org/wiki/File:Flag_of_Castile-La_Mancha.svg +Cataluña - https://commons.wikimedia.org/wiki/File:Flag_of_Catalonia.svg +Galicia - https://commons.wikimedia.org/wiki/File:Flag_of_Galicia.svg +Extremadura - https://commons.wikimedia.org/wiki/File:Flag_of_Extremadura,_Spain.svg (no emblem tho) +La Rioja - own work (no CoA sorry) +Comunidad de Madrid - https://commons.wikimedia.org/wiki/File:Flag_of_the_Community_of_Madrid.svg +Región de Murcia - https://commons.wikimedia.org/wiki/File:Flag_of_the_Region_of_Murcia.svg +Navarra - https://commons.wikimedia.org/wiki/File:Bandera_de_Navarra.svg +Comunidad Valenciana - https://commons.wikimedia.org/wiki/File:Flag_of_the_Land_of_Valencia_(official).svg + +Ukraine: +Crimea - https://commons.wikimedia.org/wiki/File:Flag_of_Crimea.svg +Vinnytsia Oblast - https://commons.wikimedia.org/wiki/File:Prapor_Vinnytskoyi_oblasti.svg +Volyn Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Volyn_Oblast.svg +Dnipropetrovsk Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Dnipropetrovsk_Oblast.svg +Donetsk Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Donetsk_Oblast.svg +Zhytomyr Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Zhytomyr_Oblast.svg +Zakarpattia Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Zakarpattia_Oblast.svg +Zaporizhzhia Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Zaporizhia_Oblast.svg +Ivano-Frankivsk Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Ivano-Frankivsk_Oblast2.svg +Kyiv Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Kyiv_Oblast.svg +Kirovohrad Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Kirovohrad_Oblast.svg +Luhansk Oblast - TBA +Lviv Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Lviv_Oblast.svg +Mykolaiv Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Mykolaiv_Oblast.svg +Poltava Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Poltava_Oblast.svg +Rivne Oblast - modified version of https://en.wikipedia.org/wiki/Rivne_Oblast#/media/File:Coat_of_Arms_of_Rivne_Oblast.svg +Sumy Oblast - modified version of https://commons.wikimedia.org/wiki/File:Coat_of_Arms_of_Sumy_Oblast.svg +Ternopil Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Ternopil_Oblast.svg +Kharkiv Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Kharkiv_Oblast.svg +Kherson Oblast - TBA +Khmelnytskyi Oblast - modified version of https://en.wikipedia.org/wiki/Khmelnytskyi_Oblast#/media/File:Coat_of_Arms_of_Khmelnytskyi_Oblast.svg +Cherkasy Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Cherkasy_Oblast.svg +Chernivtsi Oblast - TBA +Chernihiv Oblast - https://commons.wikimedia.org/wiki/File:Flag_of_Chernihiv_Oblast.svg +Kyiv - https://commons.wikimedia.org/wiki/File:Flag_of_Kyiv_Kurovskyi.svg +Sevastopol - https://commons.wikimedia.org/wiki/File:COA_of_Sevastopol.svg + diff --git a/pointercrate-demonlist-pages/src/account/demons.rs b/pointercrate-demonlist-pages/src/account/demons.rs index ca30eb689..667dadaf8 100644 --- a/pointercrate-demonlist-pages/src/account/demons.rs +++ b/pointercrate-demonlist-pages/src/account/demons.rs @@ -335,71 +335,73 @@ fn demon_submitter() -> Markup { html! { section.panel.fade.closable #demon-submitter style = "display: none" { span.plus.cross.hover {} - form #demon-submission-form novalidate = "" { - div.underlined { - h2 {"Add demon:"} - } - p.info-red.output {} - p.info-green.output {} - span.form-input.flex.col #demon-add-name { - label for = "name" { - "Demon name:" + div.flex { + form #demon-submission-form novalidate = "" { + div.underlined { + h2 {"Add demon:"} } - input type = "text" name = "name" required=""; - p.error {} - } - span.form-input.flex.col #demon-add-position { - label for = "position" { - "Position:" + p.info-red.output {} + p.info-green.output {} + span.form-input.flex.col #demon-add-name { + label for = "name" { + "Demon name:" + } + input type = "text" name = "name" required=""; + p.error {} } - input type = "number" name = "position" required="" min="1"; - p.error {} - } - span.form-input.flex.col #demon-add-requirement { - label for = "requirement" { - "Requirement:" + span.form-input.flex.col #demon-add-position { + label for = "position" { + "Position:" + } + input type = "number" name = "position" required="" min="1"; + p.error {} } - input type = "number" name = "requirement" required="" min="0" max = "100"; - p.error {} - } - span.form-input.flex.col #demon-add-verifier data-type = "html" data-target-id = "selected-verifier" data-default = "None Selected" { - label{"Verifier:"} - br; - span { - b { - i.fa.fa-pencil-alt.clickable #demon-add-verifier-pen aria-hidden = "true" {} - " " + span.form-input.flex.col #demon-add-requirement { + label for = "requirement" { + "Requirement:" } - i #selected-verifier data-name = "verifier" {"None Selected"} + input type = "number" name = "requirement" required="" min="0" max = "100"; + p.error {} } - p.error {} - } - span.form-input.flex.col #demon-add-publisher data-type = "html" data-target-id = "selected-publisher" data-default = "None Selected" { - label {"Publisher:"} - br; - span { - b { - i.fa.fa-pencil-alt.clickable #demon-add-publisher-pen aria-hidden = "true" {} - " " + span.form-input.flex.col #demon-add-verifier data-type = "html" data-target-id = "selected-verifier" data-default = "None Selected" { + label{"Verifier:"} + br; + span { + b { + i.fa.fa-pencil-alt.clickable #demon-add-verifier-pen aria-hidden = "true" {} + " " + } + i #selected-verifier data-name = "verifier" {"None Selected"} } - i #selected-publisher data-name = "publisher" {"None Selected"} + p.error {} } - p.error {} - } - span.form-input.flex.col #demon-add-video { - label for = "video" { - "Verification Video:" + span.form-input.flex.col #demon-add-publisher data-type = "html" data-target-id = "selected-publisher" data-default = "None Selected" { + label {"Publisher:"} + br; + span { + b { + i.fa.fa-pencil-alt.clickable #demon-add-publisher-pen aria-hidden = "true" {} + " " + } + i #selected-publisher data-name = "publisher" {"None Selected"} + } + p.error {} } - input type = "url" name = "video"; - p.error {} - } - span { - i.fa.fa-plus.clickable #add-demon-add-creator-pen aria-hidden = "true" {} i { - " Creators: " + span.form-input.flex.col #demon-add-video { + label for = "video" { + "Verification Video:" + } + input type = "url" name = "video"; + p.error {} + } + span { + i.fa.fa-plus.clickable #add-demon-add-creator-pen aria-hidden = "true" {} i { + " Creators: " + } + span #demon-add-creators {} } - span #demon-add-creators {} + input.button.purple.hover type = "submit" style = "margin: 15px auto 0px;" value="Add Demon"; } - input.button.purple.hover type = "submit" style = "margin: 15px auto 0px;" value="Add Demon"; } } (player_selection_dialog( diff --git a/pointercrate-demonlist-pages/src/account/list_integration.rs b/pointercrate-demonlist-pages/src/account/list_integration.rs index c6a236a75..68c6601e7 100644 --- a/pointercrate-demonlist-pages/src/account/list_integration.rs +++ b/pointercrate-demonlist-pages/src/account/list_integration.rs @@ -56,7 +56,7 @@ impl AccountPageTab for ListIntegrationTab { reason: "Internal Server Error".to_string(), message: err.to_string(), } - .body() + .body(); }, }; let is_moderator = permissions.require_permission(user.inner().permissions, LIST_MODERATOR).is_ok(); @@ -122,7 +122,7 @@ impl AccountPageTab for ListIntegrationTab { input #lock-submissions-checkbox type = "checkbox" name = "lock_submissions" style="margin-right: 8px;"; } b { - "Lock Submissions:" + "Lock Submissions" } } p { @@ -139,7 +139,11 @@ impl AccountPageTab for ListIntegrationTab { "Your claimed player's records" } p { - "A list of your claimed player's records, including all under consideration and rejected records and all submissions. Use this to track the status of your submissions. Clicking on a record will pull up any public notes a list mod left on the given record." + "A list of your claimed player's records, including all under consideration and rejected records and all submissions. Use this to track the status of your submissions. Clicking on a record will pull up any public notes a list mod left on the given record. The border of each record tells you whether the record is " + span style = "text-decoration: underline; text-decoration-color: #a4fd6acc" { "Approved" } ", " + span style = "text-decoration: underline; text-decoration-color: #ffff00cc" { "Unchecked" } ", " + span style = "text-decoration: underline; text-decoration-color: #dd364ecc" { "Rejected" } " or " + span style = "text-decoration: underline; text-decoration-color: #8ee6e6cc" { "Under Consideration" } "." } (paginator("claims-record-pagination", "/api/v1/records/")) } diff --git a/pointercrate-demonlist-pages/src/account/players.rs b/pointercrate-demonlist-pages/src/account/players.rs index f1878aac1..feae411b2 100644 --- a/pointercrate-demonlist-pages/src/account/players.rs +++ b/pointercrate-demonlist-pages/src/account/players.rs @@ -45,13 +45,14 @@ impl AccountPageTab for PlayersPage { async fn content(&self, _user: &AuthenticatedUser, _permissions: &PermissionsManager, connection: &mut PgConnection) -> Markup { let nationalities = match Nationality::all(connection).await { Ok(nationalities) => nationalities, - Err(err) => + Err(err) => { return ErrorFragment { status: err.status_code(), reason: "Internal Server Error".to_string(), message: err.to_string(), } - .body(), + .body() + }, }; html! { @@ -84,7 +85,7 @@ impl AccountPageTab for PlayersPage { "Banned:" } br; - div.dropdown-menu.js-search #edit-player-banned { + div.dropdown-menu.js-search #edit-player-banned style = "max-width: 50px" { div { input type="text" style = "font-weight: bold;"; } diff --git a/pointercrate-demonlist-pages/src/account/records.rs b/pointercrate-demonlist-pages/src/account/records.rs index 027c651e5..158e38cba 100644 --- a/pointercrate-demonlist-pages/src/account/records.rs +++ b/pointercrate-demonlist-pages/src/account/records.rs @@ -54,13 +54,14 @@ impl AccountPageTab for RecordsPage { async fn content(&self, _user: &AuthenticatedUser, _permissions: &PermissionsManager, connection: &mut PgConnection) -> Markup { let demons = match current_list(connection).await { Ok(demons) => demons, - Err(err) => + Err(err) => { return ErrorFragment { status: err.status_code(), reason: "Internal Server Error".to_string(), message: err.to_string(), } - .body(), + .body() + }, }; html! { @@ -319,7 +320,7 @@ fn note_adder() -> Markup { b { "Public note:" } - input #add-note-is-public-checkbox type = "checkbox" name = "is_public" style="margin-left: 0.5em;"; + input #add-note-is-public-checkbox type = "checkbox" name = "is_public"; } } p.info-red.output {} diff --git a/pointercrate-demonlist-pages/src/account/submitters.rs b/pointercrate-demonlist-pages/src/account/submitters.rs index 5d204e570..706874fe1 100644 --- a/pointercrate-demonlist-pages/src/account/submitters.rs +++ b/pointercrate-demonlist-pages/src/account/submitters.rs @@ -73,7 +73,7 @@ impl AccountPageTab for SubmittersPage { "Banned:" } br; - div.dropdown-menu.js-search #edit-submitter-banned { + div.dropdown-menu.js-search #edit-submitter-banned style = "max-width: 50px" { div{ input type="text" style = "font-weight: bold;"; } diff --git a/pointercrate-demonlist-pages/src/components/submitter.rs b/pointercrate-demonlist-pages/src/components/submitter.rs index faba1ed2d..b1aad998f 100644 --- a/pointercrate-demonlist-pages/src/components/submitter.rs +++ b/pointercrate-demonlist-pages/src/components/submitter.rs @@ -41,7 +41,7 @@ impl Render for RecordSubmitter<'_> { "Holder:" } p { - "The holder of the record. Please enter the holders Geometry Dash name here, even if their YouTube name differs! Click the pencil to select a player!" + "The player holding the record. Please enter the player's Geometry Dash name here, even if their YouTube name differs! Click the pencil to select a player!" } span.form-input.flex.col #id_player data-type = "html" data-target-id = "selected-holder" data-default = "None Selected" { span { diff --git a/pointercrate-demonlist-pages/src/components/time_machine.rs b/pointercrate-demonlist-pages/src/components/time_machine.rs index 065a04f93..b93c7776a 100644 --- a/pointercrate-demonlist-pages/src/components/time_machine.rs +++ b/pointercrate-demonlist-pages/src/components/time_machine.rs @@ -1,36 +1,42 @@ -use chrono::{DateTime, Datelike, FixedOffset, TimeZone, Utc}; +use chrono::{DateTime, Datelike, FixedOffset}; use maud::{html, Markup, Render}; -use pointercrate_core_pages::util::simple_dropdown; use pointercrate_demonlist::demon::TimeShiftedDemon; pub enum Tardis { Activated { destination: DateTime, demons: Vec, - visible: bool, + /// Whether the time selection panel should be visible. + show_selector: bool, + /// Whether the "You are currently looking at the demonlist as it was on ..." panel should be visible. + show_destination: bool, }, Deactivated { - visible: bool, + /// Whether the time selection panel should be visible. + show_selector: bool, }, } impl Tardis { pub fn new(visible: bool) -> Self { - Tardis::Deactivated { visible } + Tardis::Deactivated { show_selector: visible } } - pub fn activate(self, destination: DateTime, demons_then: Vec) -> Self { - Tardis::Activated { - visible: self.visible(), + pub fn activate(&mut self, destination: DateTime, demons_then: Vec, show_destination: bool) { + *self = Tardis::Activated { + show_selector: self.visible(), demons: demons_then, destination, - } + show_destination, + }; } pub fn visible(&self) -> bool { match self { - Tardis::Activated { visible, .. } => *visible, - Tardis::Deactivated { visible } => *visible, + Tardis::Activated { + show_selector: visible, .. + } => *visible, + Tardis::Deactivated { show_selector: visible } => *visible, } } } @@ -40,28 +46,9 @@ impl Render for Tardis { // {destination,..} = self". If errors out on the comma. #[allow(clippy::single_match)] fn render(&self) -> Markup { - let current_year = FixedOffset::east(3600 * 23 + 3599) - .from_utc_datetime(&Utc::now().naive_utc()) - .year(); - - let months = [ - "January", - "February", - "March", - "April", - "May", - "June", - "July", - "August", - "September", - "October", - "November", - "December", - ]; - html! { @match self { - Tardis::Activated { destination, ..} => { + Tardis::Activated { destination, show_destination, ..} if *show_destination => { div.panel.fade.medium-gray.flex style="align-items: center;" { span style = "text-align: end"{ "You are currently looking at the demonlist how it was on" @@ -70,6 +57,7 @@ impl Render for Tardis { @match destination.day() { 1 | 21 | 31 => (destination.format("%A, %B %est %Y at %l:%M:%S%P GMT%Z")), 2 | 22 => (destination.format("%A, %B %end %Y at %l:%M:%S%P GMT%Z")), + 3 | 23 => (destination.format("%A, %B %erd %Y at %l:%M:%S%P GMT%Z")), _ => (destination.format("%A, %B %eth %Y at %l:%M:%S%P GMT%Z")) } } @@ -89,36 +77,9 @@ impl Render for Tardis { "Enter the date you want to view the demonlist at below. For technical reasons, the earliest possible date is April 21st 2019." } div.flex { - span.form-input data-type = "dropdown" style = "max-width:33%" { - h3 {"Year:"} - (simple_dropdown("time-machine-year", None, 2019..=current_year)) - p.error {} - } - span.form-input data-type = "dropdown" style = "max-width:33%" { - h3 {"Month:"} - (simple_dropdown("time-machine-month", None, months.iter())) - p.error {} - } - span.form-input data-type = "dropdown" style = "max-width:33%" { - h3 {"Day:"} - (simple_dropdown("time-machine-day", None, 1..=31)) - p.error {} - } - } - div.flex { - span.form-input data-type = "dropdown" style = "max-width:33%" { - h3 {"Hour:"} - (simple_dropdown("time-machine-hour", Some(0), 0..24)) - p.error {} - } - span.form-input data-type = "dropdown" style = "max-width:33%" { - h3 {"Minute:"} - (simple_dropdown("time-machine-minute", Some(0), 0..=59)) - p.error {} - } - span.form-input data-type = "dropdown" style = "max-width:33%" { - h3 {"Second:"} - (simple_dropdown("time-machine-second", Some(0), 0..=59)) + span.form-input #time-machine-destination data-type = "datetime-local" { + h3 {"Destination:"} + input name="time-machine-destination" type="datetime-local" min="2019-04-19T00:00" required value="1971-06-19T00:00"; p.error {} } } diff --git a/pointercrate-demonlist-pages/src/demon_page.rs b/pointercrate-demonlist-pages/src/demon_page.rs index 64b4e54f3..4639331c0 100644 --- a/pointercrate-demonlist-pages/src/demon_page.rs +++ b/pointercrate-demonlist-pages/src/demon_page.rs @@ -6,13 +6,13 @@ use crate::{ statsviewer::stats_viewer_panel, }; use chrono::NaiveDateTime; -use maud::{html, Markup, PreEscaped, Render}; -use pointercrate_core_pages::{config as page_config, head::HeadLike, PageFragment}; +use maud::{html, Markup, PreEscaped}; +use pointercrate_core_pages::{head::HeadLike, PageFragment}; use pointercrate_demonlist::{ - config as list_config, + config::{self as list_config, extended_list_size}, demon::{Demon, FullDemon}, }; -use pointercrate_integrate::gd::{GDIntegrationResult, Thunk}; +use pointercrate_integrate::gd::{IntegrationLevel, Thunk}; use url::Url; #[derive(Debug)] @@ -26,7 +26,7 @@ pub struct DemonPage { pub demonlist: Vec, pub data: FullDemon, pub movements: Vec, - pub integration: GDIntegrationResult, + pub integration: Option, } impl From for PageFragment { @@ -48,17 +48,22 @@ impl From for PageFragment { impl DemonPage { fn title(&self) -> String { - format!( - "#{} - {} - 1.9 GDPS Demonlist", - self.data.demon.base.position, + let mut title = format!( + "{} - Geometry Dash Demonlist", self.data.demon.base.name // FIXME: flatten the structs, holy shit - ) + ); + + if self.data.demon.base.position <= extended_list_size() { + title = format!("#{} - {}", self.data.demon.base.position, title); + } + + title } fn description(&self) -> String { - if let GDIntegrationResult::Success(ref level, ..) = self.integration { + if let Some(ref level) = self.integration { if let Some(Thunk::Processed(ref description)) = level.description { - return format!("{}: {}", self.title(), description.0) + return format!("{}: {}", self.title(), description); } } format!("{}: ", self.title()) @@ -102,7 +107,7 @@ impl DemonPage { "url": "https://pointercrate.xyze.dev/demonlist/permalink/{0}/" }} - "##, self.data.demon.base.id, self.data.name(), self.description().render().0, self.data.position()))) + "##, self.data.demon.base.id, self.data.name(), self.description(), self.data.position()))) (PreEscaped(format!(" - - - - "#, publisher_id))) - } - } (RecordSubmitter::new(false, &self.demonlist)) (self.demon_panel()) (self.level_info_panel()) @@ -215,9 +204,9 @@ impl DemonPage { html! { section.panel.fade.js-scroll-anim data-anim = "fade" { div.underlined { - h1 #demon-heading style = "overflow: hidden; text-overflow: ellipsis;"{ - @if position != 1 { - a href=(format!("/demonlist/{:?}", position - 1)) { + h1 #demon-heading style = "overflow: hidden"{ + @if self.data.demon.base.position != 1 { + a href=(format!("/demonlist/{:?}", self.data.demon.base.position - 1)) { i class="fa fa-chevron-left" style="padding-right: 5%" {} } } @@ -244,11 +233,11 @@ impl DemonPage { } } } - @if let GDIntegrationResult::Success(ref level, ..) = self.integration { + @if let Some(ref level) = self.integration { @if let Some(Thunk::Processed(ref description)) = level.description { div.underlined.pad { q { - (description.0) + (description) } } } @@ -284,84 +273,63 @@ impl DemonPage { fn level_info_panel(&self) -> Markup { html! { - section.records.panel.fade.js-scroll-anim data-anim = "fade" { - div.underlined.pad { - h2 { - "Level Info" - } - } - div.underlined.pad.flex.wrap #level-info { - @match &self.integration { - GDIntegrationResult::DemonNotFoundByName => { - p.info-red { - "A demon with this name was not found on the 1.9 GDPS servers. Please notify a list moderator of this, as it means they most likely misspelled the name!" - } + @if let Some(ref level) = self.integration { + section.records.panel.fade.js-scroll-anim data-anim = "fade" { + div.underlined.pad { + h2 { + "Level Info" } - GDIntegrationResult::DemonNotYetCached => { - p.info-yellow { - "The data from the 1.9 GDPS servers has not yet been cached. Please wait a bit and refresh the page." + } + div.underlined.pad.flex.wrap #level-info { + span { + b { + "Level ID: " } + br; + (level.level_id) } - GDIntegrationResult::LevelDataNotFound => { - p.info-red { - "It seems like this level has been deleted from the 1.9 GDPS servers." + span { + b { + "Password: " } + br; + (level.level_data.password.to_string()) } - GDIntegrationResult::LevelDataNotCached => { - p.info-red { - "This demon's level data is not stored in our database, even though the demon ID was successfully resolved. If this issue persists, please contact an administrator!" + span { + b { + "Length: " } + br; + @let length_in_seconds = level.level_data.length; + (format!("{}:{:02}", length_in_seconds / 60, length_in_seconds % 60)) } - GDIntegrationResult::Success(level, level_data, song) => { - span { - b { - "ID: " - } - br; - (level.level_id) + span { + b { + "Objects: " } - span { - b { - "Password: " - } - br; - (level_data.password) + br; + span #level_info_objects { + (level.level_data.object_count) } - span { - b { - "Length: " - } - br; + } - @let length_in_seconds = level_data.length; - (format!("{}:{:02}", length_in_seconds / 60, length_in_seconds % 60)) - } - span { + // rust doesn't have a built in way to format with commas + // shift the work onto the browser instead + (PreEscaped(format!(r##" + + "##, level.level_data.object_count))) + + @if let Some(ref song) = level.custom_song { + span style = "width: 100%"{ b { - "Objects: " + "Song:" } br; - span #level_info_objects { - (level_data.object_count) - } - } - // rust doesn't have a built in way to format with commas - // shift the work onto the browser instead - (PreEscaped(format!(r##" - - "##, level_data.object_count))) - @if let Some(song) = song { - span style = "width: 100%"{ - b { - "Newgrounds Song:" - } - br; - @match song.link { - Thunk::Processed(ref link) => a.link href = (link.0) {(song.name) " by " (song.artist) " (ID " (song.song_id) ")"}, - _ => "unreachable!()" - } + @match song.link { + Thunk::Processed(ref link) => a.link href = (link) {(song.name) " by " (song.artist) " (ID " (song.song_id) ")"}, + _ => "unreachable!()" } } } @@ -432,7 +400,7 @@ impl DemonPage { tr style = { @if record.progress == 100 {"font-weight: bold"} @else {""} } { td { @if let Some(ref nationality) = record.nationality { - span.flag-icon style={"background-image: url(/static/demonlist/images/flags/" (nationality.iso_country_code.to_lowercase()) ".svg"} title = (nationality.nation) {} + span.flag-icon style={"background-image: url(/static/demonlist/images/flags/" (nationality.iso_country_code.to_lowercase()) ".svg)"} title = (nationality.nation) {} } } td { diff --git a/pointercrate-demonlist-pages/src/lib.rs b/pointercrate-demonlist-pages/src/lib.rs index fe6cdb501..31c216327 100644 --- a/pointercrate-demonlist-pages/src/lib.rs +++ b/pointercrate-demonlist-pages/src/lib.rs @@ -1,4 +1,4 @@ -use maud::{html, Markup, PreEscaped}; +use maud::{html, Markup}; use pointercrate_demonlist::{config, demon::Demon}; @@ -123,52 +123,6 @@ fn dropdown(section: &ListSection, demons: &[&Demon], current: Option<&Demon>) - } } -fn sidebar_ad() -> Markup { - html! { - @if let Some(publisher_id) = pointercrate_core_pages::config::adsense_publisher_id() { - section.panel.fade.js-scroll-anim data-anim = "fade" style = "order: 1; padding: 0px; border: 0" { - (PreEscaped(format!(r#" - - - - - "#, publisher_id))) - } - } - } -} - -fn besides_sidebar_ad() -> Markup { - html! { - @if let Some(publisher_id) = pointercrate_core_pages::config::adsense_publisher_id() { - div #outofboundsad style="margin-left: calc(45% + 1072px/2);position: fixed;padding-left: 15px;padding-top: 15px; max-width: 200px" { - (PreEscaped(format!(r#" - - - - - "#, publisher_id))) - } - } - } -} - fn rules_panel() -> Markup { html! { section #rules.panel.fade.js-scroll-anim data-anim = "fade" { diff --git a/pointercrate-demonlist-pages/src/overview.rs b/pointercrate-demonlist-pages/src/overview.rs index 7aa658d20..09da3a160 100644 --- a/pointercrate-demonlist-pages/src/overview.rs +++ b/pointercrate-demonlist-pages/src/overview.rs @@ -7,7 +7,7 @@ use crate::{ statsviewer::stats_viewer_panel, }; use maud::{html, Markup, PreEscaped}; -use pointercrate_core_pages::{config as page_config, head::HeadLike, PageFragment}; +use pointercrate_core_pages::{head::HeadLike, PageFragment}; use pointercrate_demonlist::{ config as list_config, demon::{Demon, TimeShiftedDemon}, @@ -22,76 +22,39 @@ pub struct OverviewPage { fn demon_panel(demon: &Demon, current_position: Option) -> Markup { html! { - section.panel.fade style="overflow:hidden" { - div.flex style = "align-items: center" { - div.thumb."ratio-16-9"."js-delay-css" style = "position: relative" data-property = "background-image" data-property-value = {"url('" (demon.thumbnail) "')"} { - @if let Some(video) = &demon.video { - a.play href = (video) {} - } - @else { - a.play href = "https://www.youtube.com/watch?v=dQw4w9WgXcQ" {} - } - } - div style = "padding-left: 15px" { - h2 style = "text-align: left; margin-bottom: 0px" { - a href = {"/demonlist/permalink/" (demon.base.id) "/"} { - "#" (demon.base.position) (PreEscaped(" – ")) (demon.base.name) - } - } - h3 style = "text-align: left" { - i { - (demon.publisher.name) - } - @if let Some(current_position) = current_position { - br; - @if current_position > list_config::extended_list_size() { - "Currently Legacy" - } - @else { - "Currently #"(current_position) - } - } - } - } - } - } - // Gotta put the ads in this method although they dont belong here, yikes - @if let Some(publisher_id) = page_config::adsense_publisher_id() { - @if demon.base.position == 1 { - section.panel.fade style = "padding: 0px; height: 90px"{ - (PreEscaped(format!(r#" - - - - - "#, publisher_id))) - } - } - // Place ad every 10th demon - @if demon.base.position % 10 == 0 { - section.panel.fade { - (PreEscaped(format!(r#" - - - - "#, publisher_id))) - } - } - } + section.panel.fade style="overflow:hidden" { + div.flex style = "align-items: center" { + div.thumb."ratio-16-9"."js-delay-css" style = "position: relative" data-property = "background-image" data-property-value = {"url('" (demon.thumbnail) "')"} { + @if let Some(video) = &demon.video { + a.play href = (video) {} + } + @else { + a.play href = "https://www.youtube.com/watch?v=dQw4w9WgXcQ" {} + } + } + div style = "padding-left: 15px" { + h2 style = "text-align: left; margin-bottom: 0px" { + a href = {"/demonlist/permalink/" (demon.base.id) "/"} { + "#" (demon.base.position) (PreEscaped(" – ")) (demon.base.name) + } + } + h3 style = "text-align: left" { + i { + (demon.publisher.name) + } + @if let Some(current_position) = current_position { + br; + @if current_position > list_config::extended_list_size() { + "Currently Legacy" + } + @else { + "Currently #"(current_position) + } + } + } + } + } + } } } diff --git a/pointercrate-demonlist-pages/src/statsviewer/individual.rs b/pointercrate-demonlist-pages/src/statsviewer/individual.rs index 0188128a8..ebac4b7b3 100644 --- a/pointercrate-demonlist-pages/src/statsviewer/individual.rs +++ b/pointercrate-demonlist-pages/src/statsviewer/individual.rs @@ -1,6 +1,6 @@ use crate::statsviewer::stats_viewer_html; -use maud::{html, Markup, PreEscaped}; -use pointercrate_core_pages::{config, head::HeadLike, PageFragment}; +use maud::{html, Markup}; +use pointercrate_core_pages::{head::HeadLike, PageFragment}; use pointercrate_demonlist::nationality::Nationality; #[derive(Debug)] @@ -41,50 +41,23 @@ impl IndividualStatsViewer { } div.flex.m-center.container { main.left { - @if let Some(publisher_id) = config::adsense_publisher_id() { - section.panel.fade style = "padding: 0px; height: 90px"{ - (PreEscaped(format!(r#" - - - - - "#, publisher_id))) - } - } (stats_viewer_html(Some(&self.nationalities_in_use), super::standard_stats_viewer_rows())) } aside.right { (super::continent_panel()) - @if let Some(publisher_id) = config::adsense_publisher_id() { - section.panel.fade.js-scroll-anim data-anim = "fade" style = "order: 1; padding: 0px; border: 0" { - (PreEscaped(format!(r#" - - - - - "#, publisher_id))) - } - } section.panel.fade style = "overflow: initial;" { h3.underlined { "Political Subdivision:" } p { - "For the " i {"United States of America"} ", " i {"The United Kingdom of Great Britain and Northern Ireland"} ", " i{"Australia"} " and " i{"Canada"} " you can select a state/province from the dropdown below to focus the stats viewer to that state/province." + "For the " + span.tooltip { + "following countries" + span.tooltiptext.fade { + "Argentina, Australia, Brazil, Canada, Chile, Colombia, Finland, France, Germany, Italy, Mexico, Netherlands, Norway, Peru, Poland, Russian Federation, South Korea, Spain, Ukraine, United Kingdom, United States" + } + } + " you can select a state/province from the dropdown below to focus the stats viewer to that state/province." } div.dropdown-menu.js-search #subdivision-dropdown data-default = "None" { div{ diff --git a/pointercrate-demonlist-pages/src/statsviewer/mod.rs b/pointercrate-demonlist-pages/src/statsviewer/mod.rs index b80c2a28e..9a4a25f49 100644 --- a/pointercrate-demonlist-pages/src/statsviewer/mod.rs +++ b/pointercrate-demonlist-pages/src/statsviewer/mod.rs @@ -49,6 +49,7 @@ fn hide_subdivision_panel() -> Markup { div.cb-container.flex.no-stretch style="margin-bottom:10px" { input #show-subdivisions-checkbox type = "checkbox" checked=""; i {"Show political subdivisions"} + input #show-subdivisions-checkbox type = "checkbox" checked=""; } } } diff --git a/pointercrate-demonlist-pages/src/statsviewer/national.rs b/pointercrate-demonlist-pages/src/statsviewer/national.rs index 114d26066..5f1128044 100644 --- a/pointercrate-demonlist-pages/src/statsviewer/national.rs +++ b/pointercrate-demonlist-pages/src/statsviewer/national.rs @@ -1,13 +1,13 @@ use crate::statsviewer::{stats_viewer_html, StatsViewerRow}; -use maud::{html, Markup, PreEscaped}; -use pointercrate_core_pages::{config, head::HeadLike, PageFragment}; +use maud::{html, Markup}; +use pointercrate_core_pages::{head::HeadLike, PageFragment}; pub fn nation_based_stats_viewer() -> PageFragment { use pointercrate_core_pages::{versioned_import, with_version_string}; PageFragment::new( "Nation Stats Viewer", - "The pointercrate nation stats viewer, ranking how well each nations player's are doing in their quest to collectively complete \ + "The pointercrate nation stats viewer, ranking how well each nation's players are doing in their quest to collectively complete \ the entire demonlist!", ) .module(with_version_string!("/static/demonlist/js/modules/statsviewer.js")) @@ -35,49 +35,12 @@ fn nation_based_stats_viewer_html() -> Markup { b {"Nations"} } } - div #world-map-wrapper { - object #world-map data="/static/demonlist/images/world.svg" type="image/svg+xml" {} - } div.flex.m-center.container { main.left { - @if let Some(publisher_id) = config::adsense_publisher_id() { - section.panel.fade style = "padding: 0px; height: 90px"{ - (PreEscaped(format!(r#" - - - - - "#, publisher_id))) - } - } (stats_viewer_html(None, rows)) } aside.right { (super::continent_panel()) - @if let Some(publisher_id) = config::adsense_publisher_id() { - section.panel.fade.js-scroll-anim data-anim = "fade" style = "order: 1; padding: 0px; border: 0" { - (PreEscaped(format!(r#" - - - - - "#, publisher_id))) - } - } } } } diff --git a/pointercrate-demonlist-pages/static/images/flags/ab.svg b/pointercrate-demonlist-pages/static/images/flags/ab.svg index a39478d1a..2c1394df0 100644 --- a/pointercrate-demonlist-pages/static/images/flags/ab.svg +++ b/pointercrate-demonlist-pages/static/images/flags/ab.svg @@ -1,195 +1,49 @@ - - - - - - image/svg+xml - - - - - - - Flag of Abkhazia - hand shape probably wrong - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + image/svg+xml + + + + + Flag of Abkhazia + hand shape probably wrong + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ad.svg b/pointercrate-demonlist-pages/static/images/flags/ad.svg index 9190d9ea1..d1f0f42f6 100644 --- a/pointercrate-demonlist-pages/static/images/flags/ad.svg +++ b/pointercrate-demonlist-pages/static/images/flags/ad.svg @@ -1,6 +1,6 @@ - + - + @@ -10,141 +10,141 @@ - + - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - + + + + + + + + + + + - + - + - - - - - - - - - - - + + + + + + + + + + + - + diff --git a/pointercrate-demonlist-pages/static/images/flags/ae.svg b/pointercrate-demonlist-pages/static/images/flags/ae.svg index e634982d8..2855f1726 100644 --- a/pointercrate-demonlist-pages/static/images/flags/ae.svg +++ b/pointercrate-demonlist-pages/static/images/flags/ae.svg @@ -1,4 +1,4 @@ - + - + @@ -31,14 +31,14 @@ methods that have not yet been invented or conceived. - - + + - - - - - + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/af.svg b/pointercrate-demonlist-pages/static/images/flags/af.svg index 937edcae3..55eccff26 100644 --- a/pointercrate-demonlist-pages/static/images/flags/af.svg +++ b/pointercrate-demonlist-pages/static/images/flags/af.svg @@ -1,4 +1,4 @@ - + - + @@ -32,168 +32,168 @@ - - - - + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + - - - - - + + + + + - - - - - - - - - - - + + + + + + + + + + + - - - - - - - - + + + + + + + + - - - - - + + + + + - - - - - - + + + + + + - - - - - + + + + + - - - - - - + + + + + + - - + + - - - - - + + + + + - - - - - + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -202,7 +202,7 @@ - + @@ -211,7 +211,7 @@ - + @@ -220,7 +220,7 @@ - + @@ -229,219 +229,219 @@ - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + - + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - + + + + + + + + + + + @@ -451,102 +451,102 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - + + + + + + + - - - - - - - - + + + + + + + + - - - - - - - + + + + + + + - + @@ -555,7 +555,7 @@ - + @@ -564,150 +564,150 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -717,7 +717,7 @@ - + @@ -727,53 +727,53 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - + + + - + - + diff --git a/pointercrate-demonlist-pages/static/images/flags/ag.svg b/pointercrate-demonlist-pages/static/images/flags/ag.svg index c7450f055..3a49b2836 100644 --- a/pointercrate-demonlist-pages/static/images/flags/ag.svg +++ b/pointercrate-demonlist-pages/static/images/flags/ag.svg @@ -1,25 +1,9 @@ - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - + + + Flag of Antigua and Barbuda + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ai.svg b/pointercrate-demonlist-pages/static/images/flags/ai.svg index 69c5041b3..2194e9b32 100644 --- a/pointercrate-demonlist-pages/static/images/flags/ai.svg +++ b/pointercrate-demonlist-pages/static/images/flags/ai.svg @@ -1,791 +1,32 @@ - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - - - + + + - - - + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/al.svg b/pointercrate-demonlist-pages/static/images/flags/al.svg index e9709ccb1..84e4b31bb 100644 --- a/pointercrate-demonlist-pages/static/images/flags/al.svg +++ b/pointercrate-demonlist-pages/static/images/flags/al.svg @@ -1,6 +1,6 @@ - + - + @@ -10,9 +10,9 @@ - + - - + + diff --git a/pointercrate-demonlist-pages/static/images/flags/am.svg b/pointercrate-demonlist-pages/static/images/flags/am.svg index 03054df0d..94b568286 100644 --- a/pointercrate-demonlist-pages/static/images/flags/am.svg +++ b/pointercrate-demonlist-pages/static/images/flags/am.svg @@ -1,5 +1,5 @@ - - + + @@ -9,8 +9,8 @@ - - - + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ao.svg b/pointercrate-demonlist-pages/static/images/flags/ao.svg index 2e5ca479e..851ea157d 100644 --- a/pointercrate-demonlist-pages/static/images/flags/ao.svg +++ b/pointercrate-demonlist-pages/static/images/flags/ao.svg @@ -1,6 +1,6 @@ - + - + @@ -10,30 +10,30 @@ - - - + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/aq.svg b/pointercrate-demonlist-pages/static/images/flags/aq.svg index 40dec4751..6675fc1c8 100644 --- a/pointercrate-demonlist-pages/static/images/flags/aq.svg +++ b/pointercrate-demonlist-pages/static/images/flags/aq.svg @@ -1,6 +1,6 @@ - + - + @@ -10,11 +10,11 @@ - + - - - - + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ar.svg b/pointercrate-demonlist-pages/static/images/flags/ar.svg index c36b65a8b..e0b11e379 100644 --- a/pointercrate-demonlist-pages/static/images/flags/ar.svg +++ b/pointercrate-demonlist-pages/static/images/flags/ar.svg @@ -1,6 +1,6 @@ - + - + @@ -10,151 +10,151 @@ - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ar/b.svg b/pointercrate-demonlist-pages/static/images/flags/ar/b.svg new file mode 100644 index 000000000..d9b19dc69 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ar/b.svg @@ -0,0 +1,458 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ar/d.svg b/pointercrate-demonlist-pages/static/images/flags/ar/d.svg new file mode 100644 index 000000000..4110a4f6a --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ar/d.svg @@ -0,0 +1,778 @@ + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ar/e.svg b/pointercrate-demonlist-pages/static/images/flags/ar/e.svg new file mode 100644 index 000000000..14f53404d --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ar/e.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ar/f.svg b/pointercrate-demonlist-pages/static/images/flags/ar/f.svg new file mode 100644 index 000000000..6fea583f5 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ar/f.svg @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ar/g.svg b/pointercrate-demonlist-pages/static/images/flags/ar/g.svg new file mode 100644 index 000000000..4f0181a97 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ar/g.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ar/j.svg b/pointercrate-demonlist-pages/static/images/flags/ar/j.svg new file mode 100644 index 000000000..7ec472886 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ar/j.svg @@ -0,0 +1,213 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ar/k.svg b/pointercrate-demonlist-pages/static/images/flags/ar/k.svg new file mode 100644 index 000000000..09a1618a6 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ar/k.svg @@ -0,0 +1,4745 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ar/m.svg b/pointercrate-demonlist-pages/static/images/flags/ar/m.svg new file mode 100644 index 000000000..d453d7c0b --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ar/m.svg @@ -0,0 +1,1005 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ar/n.svg b/pointercrate-demonlist-pages/static/images/flags/ar/n.svg new file mode 100644 index 000000000..dcdc1b43f --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ar/n.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ar/q.svg b/pointercrate-demonlist-pages/static/images/flags/ar/q.svg new file mode 100644 index 000000000..5869e625e --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ar/q.svg @@ -0,0 +1,224 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ar/s.svg b/pointercrate-demonlist-pages/static/images/flags/ar/s.svg new file mode 100644 index 000000000..3fc55245b --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ar/s.svg @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ar/t.svg b/pointercrate-demonlist-pages/static/images/flags/ar/t.svg new file mode 100644 index 000000000..0799567b5 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ar/t.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ar/v.svg b/pointercrate-demonlist-pages/static/images/flags/ar/v.svg new file mode 100644 index 000000000..0f5e1820d --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ar/v.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ar/w.svg b/pointercrate-demonlist-pages/static/images/flags/ar/w.svg new file mode 100644 index 000000000..fc65d72d0 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ar/w.svg @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ar/x.svg b/pointercrate-demonlist-pages/static/images/flags/ar/x.svg new file mode 100644 index 000000000..3f3dfc0a7 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ar/x.svg @@ -0,0 +1,17 @@ + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ar/y.svg b/pointercrate-demonlist-pages/static/images/flags/ar/y.svg new file mode 100644 index 000000000..22263314d --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ar/y.svg @@ -0,0 +1,266 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/as.svg b/pointercrate-demonlist-pages/static/images/flags/as.svg index b3585f3af..30428bf76 100644 --- a/pointercrate-demonlist-pages/static/images/flags/as.svg +++ b/pointercrate-demonlist-pages/static/images/flags/as.svg @@ -1,6 +1,6 @@ - + - + @@ -10,111 +10,111 @@ - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/at.svg b/pointercrate-demonlist-pages/static/images/flags/at.svg index 388a6b8b9..64f5d4522 100644 --- a/pointercrate-demonlist-pages/static/images/flags/at.svg +++ b/pointercrate-demonlist-pages/static/images/flags/at.svg @@ -1,6 +1,6 @@ - + - + @@ -9,9 +9,9 @@ - - - - + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/au.svg b/pointercrate-demonlist-pages/static/images/flags/au.svg index c064c7475..9ea66deba 100644 --- a/pointercrate-demonlist-pages/static/images/flags/au.svg +++ b/pointercrate-demonlist-pages/static/images/flags/au.svg @@ -1,6 +1,6 @@ - + - + @@ -10,14 +10,14 @@ - + - + diff --git a/pointercrate-demonlist-pages/static/images/flags/au/act.svg b/pointercrate-demonlist-pages/static/images/flags/au/act.svg index ca2ccc8d8..039338815 100644 --- a/pointercrate-demonlist-pages/static/images/flags/au/act.svg +++ b/pointercrate-demonlist-pages/static/images/flags/au/act.svg @@ -1,123 +1,121 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/au/nsw.svg b/pointercrate-demonlist-pages/static/images/flags/au/nsw.svg index 0f00dd587..732c417c6 100644 --- a/pointercrate-demonlist-pages/static/images/flags/au/nsw.svg +++ b/pointercrate-demonlist-pages/static/images/flags/au/nsw.svg @@ -1,406 +1,79 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/au/nt.svg b/pointercrate-demonlist-pages/static/images/flags/au/nt.svg index f6587debf..6abc445ba 100644 --- a/pointercrate-demonlist-pages/static/images/flags/au/nt.svg +++ b/pointercrate-demonlist-pages/static/images/flags/au/nt.svg @@ -1,120 +1,20 @@ - + +image/svg+xml -image/svg+xml - - - + - - - - - - - - - \ No newline at end of file + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/au/qld.svg b/pointercrate-demonlist-pages/static/images/flags/au/qld.svg index b936558c5..e94b227a8 100644 --- a/pointercrate-demonlist-pages/static/images/flags/au/qld.svg +++ b/pointercrate-demonlist-pages/static/images/flags/au/qld.svg @@ -1,912 +1,156 @@ - + - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/au/sa.svg b/pointercrate-demonlist-pages/static/images/flags/au/sa.svg index 7283ae87a..d65efab40 100644 --- a/pointercrate-demonlist-pages/static/images/flags/au/sa.svg +++ b/pointercrate-demonlist-pages/static/images/flags/au/sa.svg @@ -1,346 +1,124 @@ - + - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + - - - - - - - - oceania - flags - sign - signs_and_symbols - - australia - - - - - Caleb Moore - - - - - Caleb Moore - - - - - Caleb Moore - - - - image/svg+xml - - - - - en - - - - - - - - + + + + + + + + + oceania + flags + sign + signs_and_symbols + + australia + + + + + Caleb Moore + + + + + Caleb Moore + + + + + Caleb Moore + + + + image/svg+xml + + + + + en + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/au/tas.svg b/pointercrate-demonlist-pages/static/images/flags/au/tas.svg index 64f58cc81..e1119efa1 100644 --- a/pointercrate-demonlist-pages/static/images/flags/au/tas.svg +++ b/pointercrate-demonlist-pages/static/images/flags/au/tas.svg @@ -1,104 +1,104 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/au/vic.svg b/pointercrate-demonlist-pages/static/images/flags/au/vic.svg index f8f1f3d9b..725240f60 100644 --- a/pointercrate-demonlist-pages/static/images/flags/au/vic.svg +++ b/pointercrate-demonlist-pages/static/images/flags/au/vic.svg @@ -1,572 +1,142 @@ - + - - - - - - - - - - - - + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - + - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/au/wa.svg b/pointercrate-demonlist-pages/static/images/flags/au/wa.svg index 0763a2bae..f95d6f62d 100644 --- a/pointercrate-demonlist-pages/static/images/flags/au/wa.svg +++ b/pointercrate-demonlist-pages/static/images/flags/au/wa.svg @@ -1,275 +1,17 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/aw.svg b/pointercrate-demonlist-pages/static/images/flags/aw.svg index ac8440a83..21fe8003a 100644 --- a/pointercrate-demonlist-pages/static/images/flags/aw.svg +++ b/pointercrate-demonlist-pages/static/images/flags/aw.svg @@ -1,6 +1,6 @@ - + - + @@ -10,201 +10,201 @@ - - + + - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ax.svg b/pointercrate-demonlist-pages/static/images/flags/ax.svg index fd17f0bb8..2c226bd7a 100644 --- a/pointercrate-demonlist-pages/static/images/flags/ax.svg +++ b/pointercrate-demonlist-pages/static/images/flags/ax.svg @@ -1,5 +1,5 @@ - - + + @@ -9,19 +9,19 @@ - - + + - - + + - - + + - - + + diff --git a/pointercrate-demonlist-pages/static/images/flags/az.svg b/pointercrate-demonlist-pages/static/images/flags/az.svg index d77a2f53a..95d35f965 100644 --- a/pointercrate-demonlist-pages/static/images/flags/az.svg +++ b/pointercrate-demonlist-pages/static/images/flags/az.svg @@ -1,4 +1,4 @@ - + - + @@ -33,17 +33,17 @@ Guinea, Honduras, Israel, Jamaica, Jordan, and Romania Flags - - - - + + + + - + - + diff --git a/pointercrate-demonlist-pages/static/images/flags/ba.svg b/pointercrate-demonlist-pages/static/images/flags/ba.svg index 4336c07db..ff0904741 100644 --- a/pointercrate-demonlist-pages/static/images/flags/ba.svg +++ b/pointercrate-demonlist-pages/static/images/flags/ba.svg @@ -1,4 +1,4 @@ - + - + @@ -33,12 +33,12 @@ Guinea, Honduras, Israel, Jamaica, Jordan, and Romania Flags - - + + - - + + diff --git a/pointercrate-demonlist-pages/static/images/flags/bb.svg b/pointercrate-demonlist-pages/static/images/flags/bb.svg index 2bf861da5..cece9d1f3 100644 --- a/pointercrate-demonlist-pages/static/images/flags/bb.svg +++ b/pointercrate-demonlist-pages/static/images/flags/bb.svg @@ -1,23 +1,7 @@ - - - - - - - image/svg+xml - - - - - - - - - - - - - - - + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/bd.svg b/pointercrate-demonlist-pages/static/images/flags/bd.svg index 4b9d0c022..a340c3fe6 100644 --- a/pointercrate-demonlist-pages/static/images/flags/bd.svg +++ b/pointercrate-demonlist-pages/static/images/flags/bd.svg @@ -1,6 +1,6 @@ - + - + @@ -9,8 +9,8 @@ - - - + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/be.svg b/pointercrate-demonlist-pages/static/images/flags/be.svg index 891501cbd..e060ce41e 100644 --- a/pointercrate-demonlist-pages/static/images/flags/be.svg +++ b/pointercrate-demonlist-pages/static/images/flags/be.svg @@ -1,4 +1,4 @@ - + - + @@ -32,9 +32,9 @@ Guinea, Honduras, Israel, Jamaica, Jordan, and Romania Flags - - - - + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/bf.svg b/pointercrate-demonlist-pages/static/images/flags/bf.svg index 6d354c608..fa5f9a4b5 100644 --- a/pointercrate-demonlist-pages/static/images/flags/bf.svg +++ b/pointercrate-demonlist-pages/static/images/flags/bf.svg @@ -1,6 +1,6 @@ - + - + @@ -10,10 +10,10 @@ - - - + + + - + diff --git a/pointercrate-demonlist-pages/static/images/flags/bg.svg b/pointercrate-demonlist-pages/static/images/flags/bg.svg index 2cf07bfc9..6d47a6289 100644 --- a/pointercrate-demonlist-pages/static/images/flags/bg.svg +++ b/pointercrate-demonlist-pages/static/images/flags/bg.svg @@ -1,6 +1,6 @@ - + - + @@ -10,10 +10,10 @@ - - - - + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/bh.svg b/pointercrate-demonlist-pages/static/images/flags/bh.svg index a73eb8dde..a70a97a0e 100644 --- a/pointercrate-demonlist-pages/static/images/flags/bh.svg +++ b/pointercrate-demonlist-pages/static/images/flags/bh.svg @@ -1,6 +1,6 @@ - + - + @@ -10,12 +10,12 @@ - - + + - - + + diff --git a/pointercrate-demonlist-pages/static/images/flags/bi.svg b/pointercrate-demonlist-pages/static/images/flags/bi.svg index 20a3533f2..46af68632 100644 --- a/pointercrate-demonlist-pages/static/images/flags/bi.svg +++ b/pointercrate-demonlist-pages/static/images/flags/bi.svg @@ -1,6 +1,6 @@ - + - + @@ -10,20 +10,20 @@ - - + + - - - - - - - - - - - + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/bj.svg b/pointercrate-demonlist-pages/static/images/flags/bj.svg index 132f9b5ed..c520b5602 100644 --- a/pointercrate-demonlist-pages/static/images/flags/bj.svg +++ b/pointercrate-demonlist-pages/static/images/flags/bj.svg @@ -1,4 +1,4 @@ - + - + @@ -33,15 +33,15 @@ Guinea, Honduras, Israel, Jamaica, Jordan, and Romania Flags - - + + - - - - + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/bl.svg b/pointercrate-demonlist-pages/static/images/flags/bl.svg index 8b6f58938..00f9126be 100644 --- a/pointercrate-demonlist-pages/static/images/flags/bl.svg +++ b/pointercrate-demonlist-pages/static/images/flags/bl.svg @@ -1,4204 +1,953 @@ - - - - - - image/svg+xml - - Blason de corcieux - 26-11-2006 - - - Antoine Fabrice (Darkbob) - - - - - - - blason corcieux - - - - - - - - - - - - Flag of Canton of Valais (Wallis) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + image/svg+xml + + Blason de corcieux + 26-11-2006 + + + Antoine Fabrice (Darkbob) + + + + + + + blason corcieux + + + + + + + + + + + + Flag of Canton of Valais (Wallis) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + + + + + + - - - + + + + + + + + + + - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/bm.svg b/pointercrate-demonlist-pages/static/images/flags/bm.svg index b2d83f5c3..6bc88e06c 100644 --- a/pointercrate-demonlist-pages/static/images/flags/bm.svg +++ b/pointercrate-demonlist-pages/static/images/flags/bm.svg @@ -1,6 +1,6 @@ - + - + @@ -44,129 +44,129 @@ - + - + - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - + + + + + + + + + + - - + + - + @@ -175,85 +175,85 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - + + + + @@ -265,99 +265,99 @@ - + - - - - - - - + + + + + + + - - - - - - - + + + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + - + diff --git a/pointercrate-demonlist-pages/static/images/flags/bn.svg b/pointercrate-demonlist-pages/static/images/flags/bn.svg index 2cb386e86..aceaa649c 100644 --- a/pointercrate-demonlist-pages/static/images/flags/bn.svg +++ b/pointercrate-demonlist-pages/static/images/flags/bn.svg @@ -1,4 +1,4 @@ - + - + @@ -31,106 +31,106 @@ - - + + - - - - - - + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/bo.svg b/pointercrate-demonlist-pages/static/images/flags/bo.svg index 5952c3094..9e9a0cd9f 100644 --- a/pointercrate-demonlist-pages/static/images/flags/bo.svg +++ b/pointercrate-demonlist-pages/static/images/flags/bo.svg @@ -1,4 +1,4 @@ - + - + @@ -29,9 +29,9 @@ methods that have not yet been invented or conceived. - - - - + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/bq.svg b/pointercrate-demonlist-pages/static/images/flags/bq.svg index 5da21323d..f9e0c0b8e 100644 --- a/pointercrate-demonlist-pages/static/images/flags/bq.svg +++ b/pointercrate-demonlist-pages/static/images/flags/bq.svg @@ -1,17 +1,12 @@ - - - - - - image/svg+xml - - - - - - - - - + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/br.svg b/pointercrate-demonlist-pages/static/images/flags/br.svg index df1132002..51c81ae79 100644 --- a/pointercrate-demonlist-pages/static/images/flags/br.svg +++ b/pointercrate-demonlist-pages/static/images/flags/br.svg @@ -1,6 +1,6 @@ - + - + @@ -10,79 +10,79 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - + - - + + - - + + - + - - + + - + - + diff --git a/pointercrate-demonlist-pages/static/images/flags/br/ac.svg b/pointercrate-demonlist-pages/static/images/flags/br/ac.svg new file mode 100644 index 000000000..ea7fd0385 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/br/ac.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/br/am.svg b/pointercrate-demonlist-pages/static/images/flags/br/am.svg new file mode 100644 index 000000000..4ed4c2db3 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/br/am.svg @@ -0,0 +1,33 @@ + + + Bandeira do Amazonas + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/br/ap.svg b/pointercrate-demonlist-pages/static/images/flags/br/ap.svg new file mode 100644 index 000000000..8c26d3e85 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/br/ap.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/br/ba.svg b/pointercrate-demonlist-pages/static/images/flags/br/ba.svg new file mode 100644 index 000000000..c28c4f102 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/br/ba.svg @@ -0,0 +1,8 @@ + + + Bandeira da Bahia + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/br/ce.svg b/pointercrate-demonlist-pages/static/images/flags/br/ce.svg new file mode 100644 index 000000000..dc76e99ef --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/br/ce.svg @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/br/df.svg b/pointercrate-demonlist-pages/static/images/flags/br/df.svg new file mode 100644 index 000000000..ff91a2f64 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/br/df.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/br/es.svg b/pointercrate-demonlist-pages/static/images/flags/br/es.svg new file mode 100644 index 000000000..12583167a --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/br/es.svg @@ -0,0 +1,15 @@ + + + Bandeira do Espírito Santo + + + + + + + + Bandeira do Espírito Santo + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/br/go.svg b/pointercrate-demonlist-pages/static/images/flags/br/go.svg new file mode 100644 index 000000000..f6fcb8d00 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/br/go.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/br/ma.svg b/pointercrate-demonlist-pages/static/images/flags/br/ma.svg new file mode 100644 index 000000000..e284f2621 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/br/ma.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/br/mg.svg b/pointercrate-demonlist-pages/static/images/flags/br/mg.svg new file mode 100644 index 000000000..a4a34310b --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/br/mg.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/br/ms.svg b/pointercrate-demonlist-pages/static/images/flags/br/ms.svg new file mode 100644 index 000000000..c98faf472 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/br/ms.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/br/mt.svg b/pointercrate-demonlist-pages/static/images/flags/br/mt.svg new file mode 100644 index 000000000..b1e0a6593 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/br/mt.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/br/pa.svg b/pointercrate-demonlist-pages/static/images/flags/br/pa.svg new file mode 100644 index 000000000..8c96a21b0 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/br/pa.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/br/pb.svg b/pointercrate-demonlist-pages/static/images/flags/br/pb.svg new file mode 100644 index 000000000..054ae00d4 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/br/pb.svg @@ -0,0 +1,7 @@ + + + Bandeira da Paraíba" + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/br/pe.svg b/pointercrate-demonlist-pages/static/images/flags/br/pe.svg new file mode 100644 index 000000000..dafec8b22 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/br/pe.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/br/pi.svg b/pointercrate-demonlist-pages/static/images/flags/br/pi.svg new file mode 100644 index 000000000..fbd74a188 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/br/pi.svg @@ -0,0 +1,29 @@ + + + + Bandeira do Piauí + + + + + + + + + + + + + + + + + + + + + + + 13 DE MARÇO DE 1823 + + diff --git a/pointercrate-demonlist-pages/static/images/flags/br/pr.svg b/pointercrate-demonlist-pages/static/images/flags/br/pr.svg new file mode 100644 index 000000000..74526767f --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/br/pr.svg @@ -0,0 +1,3 @@ + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/br/ro.svg b/pointercrate-demonlist-pages/static/images/flags/br/ro.svg new file mode 100644 index 000000000..457c2dc46 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/br/ro.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/br/rr.svg b/pointercrate-demonlist-pages/static/images/flags/br/rr.svg new file mode 100644 index 000000000..0fc94cb5d --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/br/rr.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/br/sc.svg b/pointercrate-demonlist-pages/static/images/flags/br/sc.svg new file mode 100644 index 000000000..8c282d15a --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/br/sc.svg @@ -0,0 +1,373 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/br/se.svg b/pointercrate-demonlist-pages/static/images/flags/br/se.svg new file mode 100644 index 000000000..801059faa --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/br/se.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/br/sp.svg b/pointercrate-demonlist-pages/static/images/flags/br/sp.svg new file mode 100644 index 000000000..e21b89633 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/br/sp.svg @@ -0,0 +1,57 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/br/to.svg b/pointercrate-demonlist-pages/static/images/flags/br/to.svg new file mode 100644 index 000000000..e56f2b0cc --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/br/to.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/bs.svg b/pointercrate-demonlist-pages/static/images/flags/bs.svg index 570d7a5b3..20c734480 100644 --- a/pointercrate-demonlist-pages/static/images/flags/bs.svg +++ b/pointercrate-demonlist-pages/static/images/flags/bs.svg @@ -1,24 +1,7 @@ - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - + + + Flag of the Bahamas + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/bt.svg b/pointercrate-demonlist-pages/static/images/flags/bt.svg index fada20a30..8c0664016 100644 --- a/pointercrate-demonlist-pages/static/images/flags/bt.svg +++ b/pointercrate-demonlist-pages/static/images/flags/bt.svg @@ -1,6 +1,6 @@ - + - + @@ -10,208 +10,208 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/bv.svg b/pointercrate-demonlist-pages/static/images/flags/bv.svg index 104b29bd7..62fd1ddd5 100644 --- a/pointercrate-demonlist-pages/static/images/flags/bv.svg +++ b/pointercrate-demonlist-pages/static/images/flags/bv.svg @@ -1,6 +1,6 @@ - + - + @@ -10,17 +10,17 @@ - - + + - - - - - - - - + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/bw.svg b/pointercrate-demonlist-pages/static/images/flags/bw.svg index d7f0c92f3..44fda25f6 100644 --- a/pointercrate-demonlist-pages/static/images/flags/bw.svg +++ b/pointercrate-demonlist-pages/static/images/flags/bw.svg @@ -1,4 +1,4 @@ - + - + @@ -31,9 +31,9 @@ methods that have not yet been invented or conceived. - - - - + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/by.svg b/pointercrate-demonlist-pages/static/images/flags/by.svg index 260168059..8dabadf1f 100644 --- a/pointercrate-demonlist-pages/static/images/flags/by.svg +++ b/pointercrate-demonlist-pages/static/images/flags/by.svg @@ -1,6 +1,6 @@ - + - + @@ -10,257 +10,257 @@ - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/bz.svg b/pointercrate-demonlist-pages/static/images/flags/bz.svg index d352f9a15..81ac52c89 100644 --- a/pointercrate-demonlist-pages/static/images/flags/bz.svg +++ b/pointercrate-demonlist-pages/static/images/flags/bz.svg @@ -1,302 +1,155 @@ - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ca.svg b/pointercrate-demonlist-pages/static/images/flags/ca.svg index eb528d941..857e67f03 100644 --- a/pointercrate-demonlist-pages/static/images/flags/ca.svg +++ b/pointercrate-demonlist-pages/static/images/flags/ca.svg @@ -1,25 +1,6 @@ - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - + + + Flag of Canada (Pantone colours) + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ca/ab.svg b/pointercrate-demonlist-pages/static/images/flags/ca/ab.svg index 2170c004e..983bc3575 100644 --- a/pointercrate-demonlist-pages/static/images/flags/ca/ab.svg +++ b/pointercrate-demonlist-pages/static/images/flags/ca/ab.svg @@ -1,92 +1,92 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ca/bc.svg b/pointercrate-demonlist-pages/static/images/flags/ca/bc.svg index 1d82a0f42..d2360375d 100644 --- a/pointercrate-demonlist-pages/static/images/flags/ca/bc.svg +++ b/pointercrate-demonlist-pages/static/images/flags/ca/bc.svg @@ -1,45 +1,45 @@ - - - -Flag of British Columbia - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + Flag of British Columbia + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ca/mb.svg b/pointercrate-demonlist-pages/static/images/flags/ca/mb.svg index e0fe34531..495e76056 100644 --- a/pointercrate-demonlist-pages/static/images/flags/ca/mb.svg +++ b/pointercrate-demonlist-pages/static/images/flags/ca/mb.svg @@ -1,308 +1,62 @@ - - + + - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ca/nb.svg b/pointercrate-demonlist-pages/static/images/flags/ca/nb.svg index 1945fd1f7..10ce4efcd 100644 --- a/pointercrate-demonlist-pages/static/images/flags/ca/nb.svg +++ b/pointercrate-demonlist-pages/static/images/flags/ca/nb.svg @@ -1,8 +1,7 @@ - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - - + + + + - - - - - + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - + + + + - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/gu.svg b/pointercrate-demonlist-pages/static/images/flags/gu.svg index 797b88dc0..fe40604c1 100644 --- a/pointercrate-demonlist-pages/static/images/flags/gu.svg +++ b/pointercrate-demonlist-pages/static/images/flags/gu.svg @@ -1,4 +1,4 @@ - + - + @@ -32,44 +32,28 @@ - - + + - - - - - - - - G - - - U - - - A - - - M - - - - - - - - G - - - U - - - A - - - M - + + + + + + + G + U + A + M + + + + + + G + U + A + M diff --git a/pointercrate-demonlist-pages/static/images/flags/gw.svg b/pointercrate-demonlist-pages/static/images/flags/gw.svg index 277f3a178..81e7c925c 100644 --- a/pointercrate-demonlist-pages/static/images/flags/gw.svg +++ b/pointercrate-demonlist-pages/static/images/flags/gw.svg @@ -1,6 +1,6 @@ - + - + @@ -10,14 +10,14 @@ - - + + - - - - - + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/gy.svg b/pointercrate-demonlist-pages/static/images/flags/gy.svg index 626eff887..033b318cb 100644 --- a/pointercrate-demonlist-pages/static/images/flags/gy.svg +++ b/pointercrate-demonlist-pages/static/images/flags/gy.svg @@ -1,6 +1,6 @@ - + - + @@ -10,12 +10,12 @@ - - - + + + - + diff --git a/pointercrate-demonlist-pages/static/images/flags/hk.svg b/pointercrate-demonlist-pages/static/images/flags/hk.svg index 7ad03f360..3676be9ed 100644 --- a/pointercrate-demonlist-pages/static/images/flags/hk.svg +++ b/pointercrate-demonlist-pages/static/images/flags/hk.svg @@ -1,6 +1,6 @@ - + - + @@ -10,42 +10,42 @@ - - + + - - + + - - - - + + + + - - - - + + + + - - - - - + + + + + - - - - - + + + + + - - - - + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/hm.svg b/pointercrate-demonlist-pages/static/images/flags/hm.svg index a3cd071cd..86df14f7d 100644 --- a/pointercrate-demonlist-pages/static/images/flags/hm.svg +++ b/pointercrate-demonlist-pages/static/images/flags/hm.svg @@ -1,6 +1,6 @@ - + - + @@ -10,15 +10,15 @@ - - + + - - + + diff --git a/pointercrate-demonlist-pages/static/images/flags/hn.svg b/pointercrate-demonlist-pages/static/images/flags/hn.svg index 7e79933f5..5966ffb77 100644 --- a/pointercrate-demonlist-pages/static/images/flags/hn.svg +++ b/pointercrate-demonlist-pages/static/images/flags/hn.svg @@ -1,27 +1,12 @@ - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - + + + Bandera de Honduras + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/hr.svg b/pointercrate-demonlist-pages/static/images/flags/hr.svg index 4ba52b07f..ae1cf9326 100644 --- a/pointercrate-demonlist-pages/static/images/flags/hr.svg +++ b/pointercrate-demonlist-pages/static/images/flags/hr.svg @@ -1,6 +1,6 @@ - + - + @@ -10,83 +10,83 @@ - - + + - - - + + + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - - - - - + + + + + - + @@ -95,50 +95,50 @@ - - - - + + + + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ht.svg b/pointercrate-demonlist-pages/static/images/flags/ht.svg index 53475ab1e..eea966598 100644 --- a/pointercrate-demonlist-pages/static/images/flags/ht.svg +++ b/pointercrate-demonlist-pages/static/images/flags/ht.svg @@ -1,18 +1,484 @@ - - - - - - - image/svg+xml - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/hu.svg b/pointercrate-demonlist-pages/static/images/flags/hu.svg index 8ab9d1b3a..d3b721f69 100644 --- a/pointercrate-demonlist-pages/static/images/flags/hu.svg +++ b/pointercrate-demonlist-pages/static/images/flags/hu.svg @@ -1,6 +1,6 @@ - + - + @@ -9,11 +9,11 @@ - - - - - + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/id.svg b/pointercrate-demonlist-pages/static/images/flags/id.svg index ba4f9585b..2269a36a3 100644 --- a/pointercrate-demonlist-pages/static/images/flags/id.svg +++ b/pointercrate-demonlist-pages/static/images/flags/id.svg @@ -1,6 +1,6 @@ - + - + @@ -9,10 +9,10 @@ - - - - + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ie.svg b/pointercrate-demonlist-pages/static/images/flags/ie.svg index f10a9c558..06a936eca 100644 --- a/pointercrate-demonlist-pages/static/images/flags/ie.svg +++ b/pointercrate-demonlist-pages/static/images/flags/ie.svg @@ -1,6 +1,6 @@ - + - + @@ -9,9 +9,9 @@ - - - - + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/il.svg b/pointercrate-demonlist-pages/static/images/flags/il.svg index 518a2bc0d..7db009415 100644 --- a/pointercrate-demonlist-pages/static/images/flags/il.svg +++ b/pointercrate-demonlist-pages/static/images/flags/il.svg @@ -1,6 +1,6 @@ - + - + @@ -10,22 +10,22 @@ - - + + - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/im.svg b/pointercrate-demonlist-pages/static/images/flags/im.svg index 275cf86fa..6b0ff8937 100644 --- a/pointercrate-demonlist-pages/static/images/flags/im.svg +++ b/pointercrate-demonlist-pages/static/images/flags/im.svg @@ -1,6 +1,6 @@ - + - + @@ -10,62 +10,62 @@ - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/in.svg b/pointercrate-demonlist-pages/static/images/flags/in.svg index 3265f8f5f..89b630c06 100644 --- a/pointercrate-demonlist-pages/static/images/flags/in.svg +++ b/pointercrate-demonlist-pages/static/images/flags/in.svg @@ -1,6 +1,6 @@ - + - + @@ -10,18 +10,18 @@ - - + + - - - - - + + + + + - + @@ -39,7 +39,7 @@ - + diff --git a/pointercrate-demonlist-pages/static/images/flags/io.svg b/pointercrate-demonlist-pages/static/images/flags/io.svg index 6051b09b8..831d675b8 100644 --- a/pointercrate-demonlist-pages/static/images/flags/io.svg +++ b/pointercrate-demonlist-pages/static/images/flags/io.svg @@ -1,6 +1,6 @@ - + - + @@ -10,178 +10,178 @@ - - + + - - - - - - - - - - + + + + + + + + + + - - + + - - - - - - - - + + + + + + + + - - - - - - - - + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - + + + + + + + + - - - - - - - - + + + + + + + + - - - - - - - - + + + + + + + + - - - - - - - - + + + + + + + + - - + + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/iq.svg b/pointercrate-demonlist-pages/static/images/flags/iq.svg index e0dd0b9a9..310089162 100644 --- a/pointercrate-demonlist-pages/static/images/flags/iq.svg +++ b/pointercrate-demonlist-pages/static/images/flags/iq.svg @@ -1,6 +1,6 @@ - + - + @@ -10,18 +10,18 @@ - - + + - - - - - + + + + + - - + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ir.svg b/pointercrate-demonlist-pages/static/images/flags/ir.svg index 9779aeada..9d012a868 100644 --- a/pointercrate-demonlist-pages/static/images/flags/ir.svg +++ b/pointercrate-demonlist-pages/static/images/flags/ir.svg @@ -1,6 +1,6 @@ - + - + @@ -10,505 +10,505 @@ - - + + - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + - + diff --git a/pointercrate-demonlist-pages/static/images/flags/is.svg b/pointercrate-demonlist-pages/static/images/flags/is.svg index 046ffd0b5..498b34158 100644 --- a/pointercrate-demonlist-pages/static/images/flags/is.svg +++ b/pointercrate-demonlist-pages/static/images/flags/is.svg @@ -1,6 +1,6 @@ - + - + @@ -10,13 +10,13 @@ - - + + - - - + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/it.svg b/pointercrate-demonlist-pages/static/images/flags/it.svg index 1a9f297f2..7545750c8 100644 --- a/pointercrate-demonlist-pages/static/images/flags/it.svg +++ b/pointercrate-demonlist-pages/static/images/flags/it.svg @@ -1,6 +1,6 @@ - + - + @@ -9,9 +9,9 @@ - - - - + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/it/21.svg b/pointercrate-demonlist-pages/static/images/flags/it/21.svg new file mode 100644 index 000000000..d721d814e --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/it/21.svg @@ -0,0 +1,56 @@ + + + + + + Piemonte + + + + + region + flag + piedmont + italia + italy + europe + piemonte + sign + + + + + Luigi Maselli + + + + + Luigi Maselli + + + + + Luigi Maselli + + + + image/svg+xml + + + en + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/it/23.svg b/pointercrate-demonlist-pages/static/images/flags/it/23.svg new file mode 100644 index 000000000..af741d53e --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/it/23.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/it/25.svg b/pointercrate-demonlist-pages/static/images/flags/it/25.svg new file mode 100644 index 000000000..342f9673c --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/it/25.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/it/32.svg b/pointercrate-demonlist-pages/static/images/flags/it/32.svg new file mode 100644 index 000000000..febf60a66 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/it/32.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/it/36.svg b/pointercrate-demonlist-pages/static/images/flags/it/36.svg new file mode 100644 index 000000000..55ea18c9b --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/it/36.svg @@ -0,0 +1,341 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/it/42.svg b/pointercrate-demonlist-pages/static/images/flags/it/42.svg new file mode 100644 index 000000000..e55249ddf --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/it/42.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/it/55.svg b/pointercrate-demonlist-pages/static/images/flags/it/55.svg new file mode 100644 index 000000000..4c34a4dd8 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/it/55.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/it/57.svg b/pointercrate-demonlist-pages/static/images/flags/it/57.svg new file mode 100644 index 000000000..fa19e0f62 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/it/57.svg @@ -0,0 +1,29 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/it/62.svg b/pointercrate-demonlist-pages/static/images/flags/it/62.svg new file mode 100644 index 000000000..5086b9f59 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/it/62.svg @@ -0,0 +1,2 @@ + + diff --git a/pointercrate-demonlist-pages/static/images/flags/it/65.svg b/pointercrate-demonlist-pages/static/images/flags/it/65.svg new file mode 100644 index 000000000..0b733158a --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/it/65.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/it/67.svg b/pointercrate-demonlist-pages/static/images/flags/it/67.svg new file mode 100644 index 000000000..f7f62dfb8 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/it/67.svg @@ -0,0 +1,34 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/it/72.svg b/pointercrate-demonlist-pages/static/images/flags/it/72.svg new file mode 100644 index 000000000..0882fa0d8 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/it/72.svg @@ -0,0 +1,2 @@ + +image/svg+xml diff --git a/pointercrate-demonlist-pages/static/images/flags/it/77.svg b/pointercrate-demonlist-pages/static/images/flags/it/77.svg new file mode 100644 index 000000000..a9ecf610a --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/it/77.svg @@ -0,0 +1,21 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/it/78.svg b/pointercrate-demonlist-pages/static/images/flags/it/78.svg new file mode 100644 index 000000000..b982e84ef --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/it/78.svg @@ -0,0 +1,60 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/it/82.svg b/pointercrate-demonlist-pages/static/images/flags/it/82.svg new file mode 100644 index 000000000..65520947a --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/it/82.svg @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/je.svg b/pointercrate-demonlist-pages/static/images/flags/je.svg index 4dd97f1bf..731c02e58 100644 --- a/pointercrate-demonlist-pages/static/images/flags/je.svg +++ b/pointercrate-demonlist-pages/static/images/flags/je.svg @@ -1,5 +1,5 @@ - - + + @@ -9,34 +9,34 @@ - - + + - - - + + + - - + + - + - - - + + + - + - - + + @@ -44,7 +44,7 @@ - + @@ -53,15 +53,15 @@ - + - + - - + + diff --git a/pointercrate-demonlist-pages/static/images/flags/jm.svg b/pointercrate-demonlist-pages/static/images/flags/jm.svg index 5ccd524ff..b59929981 100644 --- a/pointercrate-demonlist-pages/static/images/flags/jm.svg +++ b/pointercrate-demonlist-pages/static/images/flags/jm.svg @@ -1,45 +1,11 @@ - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - + + + Flag of Jamaica + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/jo.svg b/pointercrate-demonlist-pages/static/images/flags/jo.svg index 152749e30..25739c352 100644 --- a/pointercrate-demonlist-pages/static/images/flags/jo.svg +++ b/pointercrate-demonlist-pages/static/images/flags/jo.svg @@ -1,4 +1,4 @@ - + - + @@ -33,15 +33,15 @@ Guinea, Honduras, Israel, Jamaica, Jordan, and Romania Flags - - + + - - - - - + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/jp.svg b/pointercrate-demonlist-pages/static/images/flags/jp.svg index f36071b8c..ead8d8059 100644 --- a/pointercrate-demonlist-pages/static/images/flags/jp.svg +++ b/pointercrate-demonlist-pages/static/images/flags/jp.svg @@ -1,6 +1,6 @@ - + - + @@ -10,12 +10,12 @@ - - + + - - - + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ke.svg b/pointercrate-demonlist-pages/static/images/flags/ke.svg index 79d223f2a..c142cea86 100644 --- a/pointercrate-demonlist-pages/static/images/flags/ke.svg +++ b/pointercrate-demonlist-pages/static/images/flags/ke.svg @@ -1,6 +1,6 @@ - + - + @@ -10,23 +10,23 @@ - - + + - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/kg.svg b/pointercrate-demonlist-pages/static/images/flags/kg.svg index e560aefb8..1f24d1980 100644 --- a/pointercrate-demonlist-pages/static/images/flags/kg.svg +++ b/pointercrate-demonlist-pages/static/images/flags/kg.svg @@ -1,4 +1,4 @@ - + - + @@ -32,16 +32,16 @@ - - + + - - + + - - - + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/kh.svg b/pointercrate-demonlist-pages/static/images/flags/kh.svg index 48e782e19..756b974b1 100644 --- a/pointercrate-demonlist-pages/static/images/flags/kh.svg +++ b/pointercrate-demonlist-pages/static/images/flags/kh.svg @@ -1,6 +1,6 @@ - + - + @@ -10,146 +10,146 @@ - - + + - - - - - - - - - - - + + + + + + + + + + + - - - + + + - - - - - - - - - + + + + + + + + + - - + + - - - - - - - - + + + + + + + + - + - - - - - - + + + + + + - - + + - - + + - - - - + + + + - + - + - - + + - - - - - - - - - + + + + + + + + + - - - + + + - + - - - - - - - - - + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - - - - + + + + + + + + + + - - - - - - - - - - + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ki.svg b/pointercrate-demonlist-pages/static/images/flags/ki.svg index c145dd325..708ded5d1 100644 --- a/pointercrate-demonlist-pages/static/images/flags/ki.svg +++ b/pointercrate-demonlist-pages/static/images/flags/ki.svg @@ -1,6 +1,6 @@ - + - + @@ -10,40 +10,40 @@ - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/km.svg b/pointercrate-demonlist-pages/static/images/flags/km.svg index 235ec0227..ea450c148 100644 --- a/pointercrate-demonlist-pages/static/images/flags/km.svg +++ b/pointercrate-demonlist-pages/static/images/flags/km.svg @@ -1,4 +1,4 @@ - + - + @@ -32,20 +32,20 @@ - - + + - - - - - - + + + + + + - - - - + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/kn.svg b/pointercrate-demonlist-pages/static/images/flags/kn.svg index d60ae2e76..ad4f51fd2 100644 --- a/pointercrate-demonlist-pages/static/images/flags/kn.svg +++ b/pointercrate-demonlist-pages/static/images/flags/kn.svg @@ -1,25 +1,16 @@ - - - - - - - image/svg+xml - - - - - - - + + + Flag of Saint Kitts and Nevis + + + - - - - - - - + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/kp.svg b/pointercrate-demonlist-pages/static/images/flags/kp.svg index 6ce531d50..5764f076f 100644 --- a/pointercrate-demonlist-pages/static/images/flags/kp.svg +++ b/pointercrate-demonlist-pages/static/images/flags/kp.svg @@ -1,6 +1,6 @@ - + - + @@ -10,16 +10,16 @@ - - + + - - - - - - + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/kr.svg b/pointercrate-demonlist-pages/static/images/flags/kr.svg index a0b266d50..3b45c27fe 100644 --- a/pointercrate-demonlist-pages/static/images/flags/kr.svg +++ b/pointercrate-demonlist-pages/static/images/flags/kr.svg @@ -1,6 +1,6 @@ - + - + @@ -10,33 +10,33 @@ - - + + - - - - - - - - - - - - - + + + + + + + + + + + + + - - - - + + + + - - - - + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/kr/11.svg b/pointercrate-demonlist-pages/static/images/flags/kr/11.svg new file mode 100644 index 000000000..75b494884 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/kr/11.svg @@ -0,0 +1,21 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/kr/26.svg b/pointercrate-demonlist-pages/static/images/flags/kr/26.svg new file mode 100644 index 000000000..ca3c364fa --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/kr/26.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/kr/27.svg b/pointercrate-demonlist-pages/static/images/flags/kr/27.svg new file mode 100644 index 000000000..5ba8225bc --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/kr/27.svg @@ -0,0 +1,16 @@ + + + +]> + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/kr/28.svg b/pointercrate-demonlist-pages/static/images/flags/kr/28.svg new file mode 100644 index 000000000..6ed623e00 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/kr/28.svg @@ -0,0 +1,11 @@ + + + +]> + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/kr/29.svg b/pointercrate-demonlist-pages/static/images/flags/kr/29.svg new file mode 100644 index 000000000..12db86127 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/kr/29.svg @@ -0,0 +1,10 @@ + + + +]> + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/kr/30.svg b/pointercrate-demonlist-pages/static/images/flags/kr/30.svg new file mode 100644 index 000000000..97233f46e --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/kr/30.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/kr/31.svg b/pointercrate-demonlist-pages/static/images/flags/kr/31.svg new file mode 100644 index 000000000..a72760b22 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/kr/31.svg @@ -0,0 +1,9 @@ + + +image/svg+xml + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/kr/41.svg b/pointercrate-demonlist-pages/static/images/flags/kr/41.svg new file mode 100644 index 000000000..24544d30b --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/kr/41.svg @@ -0,0 +1,28 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/kr/42.svg b/pointercrate-demonlist-pages/static/images/flags/kr/42.svg new file mode 100644 index 000000000..fd9b345a1 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/kr/42.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/kr/43.svg b/pointercrate-demonlist-pages/static/images/flags/kr/43.svg new file mode 100644 index 000000000..76db7c06b --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/kr/43.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/kr/44.svg b/pointercrate-demonlist-pages/static/images/flags/kr/44.svg new file mode 100644 index 000000000..f4c6859a9 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/kr/44.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/kr/45.svg b/pointercrate-demonlist-pages/static/images/flags/kr/45.svg new file mode 100644 index 000000000..08dbf8e7a --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/kr/45.svg @@ -0,0 +1,2 @@ + + diff --git a/pointercrate-demonlist-pages/static/images/flags/kr/46.svg b/pointercrate-demonlist-pages/static/images/flags/kr/46.svg new file mode 100644 index 000000000..a98d9d97c --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/kr/46.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/kr/47.svg b/pointercrate-demonlist-pages/static/images/flags/kr/47.svg new file mode 100644 index 000000000..4939a1170 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/kr/47.svg @@ -0,0 +1,14 @@ + + + +]> + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/kr/48.svg b/pointercrate-demonlist-pages/static/images/flags/kr/48.svg new file mode 100644 index 000000000..abd4012b4 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/kr/48.svg @@ -0,0 +1,12 @@ + + + +]> + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/kr/49.svg b/pointercrate-demonlist-pages/static/images/flags/kr/49.svg new file mode 100644 index 000000000..a5eaf2981 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/kr/49.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/kr/50.svg b/pointercrate-demonlist-pages/static/images/flags/kr/50.svg new file mode 100644 index 000000000..994fcd093 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/kr/50.svg @@ -0,0 +1,169 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/kw.svg b/pointercrate-demonlist-pages/static/images/flags/kw.svg index 5822eb614..41dccdff3 100644 --- a/pointercrate-demonlist-pages/static/images/flags/kw.svg +++ b/pointercrate-demonlist-pages/static/images/flags/kw.svg @@ -1,6 +1,6 @@ - + - + @@ -10,14 +10,14 @@ - - + + - - - - - + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ky.svg b/pointercrate-demonlist-pages/static/images/flags/ky.svg index fb5e915af..b973ff516 100644 --- a/pointercrate-demonlist-pages/static/images/flags/ky.svg +++ b/pointercrate-demonlist-pages/static/images/flags/ky.svg @@ -1,123 +1,229 @@ - - - - - - - image/svg+xml - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/kz.svg b/pointercrate-demonlist-pages/static/images/flags/kz.svg index bd7130af8..b6b7561e1 100644 --- a/pointercrate-demonlist-pages/static/images/flags/kz.svg +++ b/pointercrate-demonlist-pages/static/images/flags/kz.svg @@ -1,6 +1,6 @@ - + - + @@ -10,17 +10,17 @@ - + - - - - - + + + + + - - + + @@ -54,13 +54,13 @@ - - - - - - - + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/la.svg b/pointercrate-demonlist-pages/static/images/flags/la.svg index fe009db97..bfa7da7ed 100644 --- a/pointercrate-demonlist-pages/static/images/flags/la.svg +++ b/pointercrate-demonlist-pages/static/images/flags/la.svg @@ -1,6 +1,6 @@ - + - + @@ -10,13 +10,13 @@ - - + + - - - - + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/lb.svg b/pointercrate-demonlist-pages/static/images/flags/lb.svg index 433b2c28e..7b0a1c929 100644 --- a/pointercrate-demonlist-pages/static/images/flags/lb.svg +++ b/pointercrate-demonlist-pages/static/images/flags/lb.svg @@ -1,6 +1,6 @@ - + - + @@ -10,36 +10,36 @@ - - + + - - - - - + + + + + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/lc.svg b/pointercrate-demonlist-pages/static/images/flags/lc.svg index ab006b5ba..f800850fc 100644 --- a/pointercrate-demonlist-pages/static/images/flags/lc.svg +++ b/pointercrate-demonlist-pages/static/images/flags/lc.svg @@ -1,18 +1,8 @@ - - - - - - - image/svg+xml - - - - - - - - - - + + + Flag of Saint Lucia + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/li.svg b/pointercrate-demonlist-pages/static/images/flags/li.svg index 78d5325a1..9dc3932d9 100644 --- a/pointercrate-demonlist-pages/static/images/flags/li.svg +++ b/pointercrate-demonlist-pages/static/images/flags/li.svg @@ -1,6 +1,6 @@ - + - + @@ -10,8 +10,8 @@ - - + + @@ -26,9 +26,9 @@ - - - + + + @@ -41,154 +41,154 @@ - - - - + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - + + + + + + + + + + + - - - - - - - - + + + + + + + + - - - - - - - - + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/lk.svg b/pointercrate-demonlist-pages/static/images/flags/lk.svg index e28985d4d..d7e37a499 100644 --- a/pointercrate-demonlist-pages/static/images/flags/lk.svg +++ b/pointercrate-demonlist-pages/static/images/flags/lk.svg @@ -1,6 +1,6 @@ - + - + @@ -10,23 +10,23 @@ - - - + + + - - - + + + - + - - - - - - - + + + + + + + @@ -34,10 +34,10 @@ - - - - + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/lr.svg b/pointercrate-demonlist-pages/static/images/flags/lr.svg index f9d3939f3..b3a838a32 100644 --- a/pointercrate-demonlist-pages/static/images/flags/lr.svg +++ b/pointercrate-demonlist-pages/static/images/flags/lr.svg @@ -1,6 +1,6 @@ - + - + @@ -10,20 +10,20 @@ - - + + - - - - - - - - - - - + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ls.svg b/pointercrate-demonlist-pages/static/images/flags/ls.svg index d5836fa03..0122938a8 100644 --- a/pointercrate-demonlist-pages/static/images/flags/ls.svg +++ b/pointercrate-demonlist-pages/static/images/flags/ls.svg @@ -1,173 +1,39 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/lt.svg b/pointercrate-demonlist-pages/static/images/flags/lt.svg index b67359b99..c0fe71633 100644 --- a/pointercrate-demonlist-pages/static/images/flags/lt.svg +++ b/pointercrate-demonlist-pages/static/images/flags/lt.svg @@ -1,6 +1,6 @@ - + - + @@ -9,9 +9,9 @@ - - - - + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/lu.svg b/pointercrate-demonlist-pages/static/images/flags/lu.svg index 1f6fe91ce..e9e99d03b 100644 --- a/pointercrate-demonlist-pages/static/images/flags/lu.svg +++ b/pointercrate-demonlist-pages/static/images/flags/lu.svg @@ -1,4 +1,4 @@ - + - + @@ -28,9 +28,9 @@ methods that have not yet been invented or conceived. - - - - + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/lv.svg b/pointercrate-demonlist-pages/static/images/flags/lv.svg index a2fbe7538..583d2315b 100644 --- a/pointercrate-demonlist-pages/static/images/flags/lv.svg +++ b/pointercrate-demonlist-pages/static/images/flags/lv.svg @@ -1,4 +1,4 @@ - + - + @@ -29,9 +29,9 @@ - - - - + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ly.svg b/pointercrate-demonlist-pages/static/images/flags/ly.svg index 092275246..78849b0c6 100644 --- a/pointercrate-demonlist-pages/static/images/flags/ly.svg +++ b/pointercrate-demonlist-pages/static/images/flags/ly.svg @@ -1,5 +1,5 @@ - - + + @@ -10,14 +10,14 @@ - - + + - - - - - + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ma.svg b/pointercrate-demonlist-pages/static/images/flags/ma.svg index 457afd2bc..9bfd9b897 100644 --- a/pointercrate-demonlist-pages/static/images/flags/ma.svg +++ b/pointercrate-demonlist-pages/static/images/flags/ma.svg @@ -1,6 +1,6 @@ - + - + @@ -10,12 +10,12 @@ - - + + - - + + diff --git a/pointercrate-demonlist-pages/static/images/flags/mc.svg b/pointercrate-demonlist-pages/static/images/flags/mc.svg index 9bd4ab13b..82297be41 100644 --- a/pointercrate-demonlist-pages/static/images/flags/mc.svg +++ b/pointercrate-demonlist-pages/static/images/flags/mc.svg @@ -1,6 +1,6 @@ - + - + @@ -9,8 +9,8 @@ - - - + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/md.svg b/pointercrate-demonlist-pages/static/images/flags/md.svg index 27729f56b..c8b5c55ab 100644 --- a/pointercrate-demonlist-pages/static/images/flags/md.svg +++ b/pointercrate-demonlist-pages/static/images/flags/md.svg @@ -1,6 +1,6 @@ - + - + @@ -10,92 +10,92 @@ - - - - + + + + - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - + + + + + + + - - - - - - - + + + + + + + - + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + - - - - - - - - - - + + + + + + + + + + - - - - - + + + + + - + diff --git a/pointercrate-demonlist-pages/static/images/flags/me.svg b/pointercrate-demonlist-pages/static/images/flags/me.svg index 473f64045..a6f4ccd10 100644 --- a/pointercrate-demonlist-pages/static/images/flags/me.svg +++ b/pointercrate-demonlist-pages/static/images/flags/me.svg @@ -1,5 +1,5 @@ - + -image/svg+xml - +image/svg+xml + diff --git a/pointercrate-demonlist-pages/static/images/flags/mf.svg b/pointercrate-demonlist-pages/static/images/flags/mf.svg index 24f58b710..cb7912d8e 100644 --- a/pointercrate-demonlist-pages/static/images/flags/mf.svg +++ b/pointercrate-demonlist-pages/static/images/flags/mf.svg @@ -1,6 +1,6 @@ - + - + @@ -10,97 +10,97 @@ - - + + - - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + diff --git a/pointercrate-demonlist-pages/static/images/flags/mg.svg b/pointercrate-demonlist-pages/static/images/flags/mg.svg index 05bd5b982..826015613 100644 --- a/pointercrate-demonlist-pages/static/images/flags/mg.svg +++ b/pointercrate-demonlist-pages/static/images/flags/mg.svg @@ -1,4 +1,4 @@ - + - + @@ -30,9 +30,9 @@ - - - - + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/mh.svg b/pointercrate-demonlist-pages/static/images/flags/mh.svg index df29219e5..ba0daf8ca 100644 --- a/pointercrate-demonlist-pages/static/images/flags/mh.svg +++ b/pointercrate-demonlist-pages/static/images/flags/mh.svg @@ -1,4 +1,4 @@ - + - + @@ -33,10 +33,10 @@ - + - + diff --git a/pointercrate-demonlist-pages/static/images/flags/mk.svg b/pointercrate-demonlist-pages/static/images/flags/mk.svg index 62e5e37b9..d13c2cee2 100644 --- a/pointercrate-demonlist-pages/static/images/flags/mk.svg +++ b/pointercrate-demonlist-pages/static/images/flags/mk.svg @@ -1,6 +1,6 @@ - + - + @@ -10,21 +10,21 @@ - - + + - - + + - + - - - + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ml.svg b/pointercrate-demonlist-pages/static/images/flags/ml.svg index dc71966d9..7d240bb58 100644 --- a/pointercrate-demonlist-pages/static/images/flags/ml.svg +++ b/pointercrate-demonlist-pages/static/images/flags/ml.svg @@ -1,4 +1,4 @@ - + - + @@ -30,9 +30,9 @@ methods that have not yet been invented or conceived. - - - - + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/mm.svg b/pointercrate-demonlist-pages/static/images/flags/mm.svg index add89f8d2..61d8aca82 100644 --- a/pointercrate-demonlist-pages/static/images/flags/mm.svg +++ b/pointercrate-demonlist-pages/static/images/flags/mm.svg @@ -1,146 +1,31 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/mn.svg b/pointercrate-demonlist-pages/static/images/flags/mn.svg index 819ce2f07..3c63fcaa0 100644 --- a/pointercrate-demonlist-pages/static/images/flags/mn.svg +++ b/pointercrate-demonlist-pages/static/images/flags/mn.svg @@ -1,6 +1,6 @@ - + - + @@ -10,17 +10,17 @@ - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/mo.svg b/pointercrate-demonlist-pages/static/images/flags/mo.svg index 769e94a5f..f8b9905a2 100644 --- a/pointercrate-demonlist-pages/static/images/flags/mo.svg +++ b/pointercrate-demonlist-pages/static/images/flags/mo.svg @@ -1,6 +1,6 @@ - + - + @@ -10,22 +10,22 @@ - - + + - - - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/mp.svg b/pointercrate-demonlist-pages/static/images/flags/mp.svg index 93ca16865..1d0139714 100644 --- a/pointercrate-demonlist-pages/static/images/flags/mp.svg +++ b/pointercrate-demonlist-pages/static/images/flags/mp.svg @@ -1,6 +1,6 @@ - + - + @@ -39,17 +39,17 @@ - - + + - + - - - - + + + + @@ -72,197 +72,197 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - + + + + + + + + + + - - - - - - - - - - + + + + + + + + + + - - - - - + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/mq.svg b/pointercrate-demonlist-pages/static/images/flags/mq.svg index 950c26e6e..8d5b19690 100644 --- a/pointercrate-demonlist-pages/static/images/flags/mq.svg +++ b/pointercrate-demonlist-pages/static/images/flags/mq.svg @@ -1,9 +1,9 @@ - - - - - - - - \ No newline at end of file + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/mr.svg b/pointercrate-demonlist-pages/static/images/flags/mr.svg index 0066c90f9..f245beb54 100644 --- a/pointercrate-demonlist-pages/static/images/flags/mr.svg +++ b/pointercrate-demonlist-pages/static/images/flags/mr.svg @@ -1,6 +1,6 @@ - + - + @@ -10,12 +10,12 @@ - - + + - - + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ms.svg b/pointercrate-demonlist-pages/static/images/flags/ms.svg index c9ef72b10..ad1ff53d5 100644 --- a/pointercrate-demonlist-pages/static/images/flags/ms.svg +++ b/pointercrate-demonlist-pages/static/images/flags/ms.svg @@ -1,76 +1,86 @@ - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/mt.svg b/pointercrate-demonlist-pages/static/images/flags/mt.svg index b14194b75..2d865ed0b 100644 --- a/pointercrate-demonlist-pages/static/images/flags/mt.svg +++ b/pointercrate-demonlist-pages/static/images/flags/mt.svg @@ -1,6 +1,6 @@ - + - + @@ -10,73 +10,73 @@ - - - + + + - - - - - - - - - - + + + + + + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/mu.svg b/pointercrate-demonlist-pages/static/images/flags/mu.svg index 6ae235d89..7be647190 100644 --- a/pointercrate-demonlist-pages/static/images/flags/mu.svg +++ b/pointercrate-demonlist-pages/static/images/flags/mu.svg @@ -1,4 +1,4 @@ - + - + @@ -31,10 +31,10 @@ methods that have not yet been invented or conceived. - - - - - + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/mv.svg b/pointercrate-demonlist-pages/static/images/flags/mv.svg index eb20d9ef8..8a1844d12 100644 --- a/pointercrate-demonlist-pages/static/images/flags/mv.svg +++ b/pointercrate-demonlist-pages/static/images/flags/mv.svg @@ -1,6 +1,6 @@ - + - + @@ -10,11 +10,11 @@ - + - - - - + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/mw.svg b/pointercrate-demonlist-pages/static/images/flags/mw.svg index cf3922bc7..4d8732897 100644 --- a/pointercrate-demonlist-pages/static/images/flags/mw.svg +++ b/pointercrate-demonlist-pages/static/images/flags/mw.svg @@ -1,6 +1,6 @@ - + - + @@ -9,13 +9,13 @@ - - - - - + + + + + - + @@ -49,7 +49,7 @@ - + diff --git a/pointercrate-demonlist-pages/static/images/flags/mx.svg b/pointercrate-demonlist-pages/static/images/flags/mx.svg index 7fc1393f8..5492e8fab 100644 --- a/pointercrate-demonlist-pages/static/images/flags/mx.svg +++ b/pointercrate-demonlist-pages/static/images/flags/mx.svg @@ -1,349 +1,499 @@ - - - - - - - image/svg+xml - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/mx/agu.svg b/pointercrate-demonlist-pages/static/images/flags/mx/agu.svg new file mode 100644 index 000000000..11b58b073 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/mx/agu.svg @@ -0,0 +1,2 @@ + + diff --git a/pointercrate-demonlist-pages/static/images/flags/mx/bcs.svg b/pointercrate-demonlist-pages/static/images/flags/mx/bcs.svg new file mode 100644 index 000000000..eb3058ad8 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/mx/bcs.svg @@ -0,0 +1,180 @@ + + + Coat_of_arms_of_Baja_California_Sur-svg + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/mx/chh.svg b/pointercrate-demonlist-pages/static/images/flags/mx/chh.svg new file mode 100644 index 000000000..9be26f3fd --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/mx/chh.svg @@ -0,0 +1,583 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/mx/cmx.svg b/pointercrate-demonlist-pages/static/images/flags/mx/cmx.svg new file mode 100644 index 000000000..98b031815 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/mx/cmx.svg @@ -0,0 +1,1501 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/mx/jal.svg b/pointercrate-demonlist-pages/static/images/flags/mx/jal.svg new file mode 100644 index 000000000..12e04da40 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/mx/jal.svg @@ -0,0 +1,3 @@ + + +image/svg+xml diff --git a/pointercrate-demonlist-pages/static/images/flags/mx/nay.svg b/pointercrate-demonlist-pages/static/images/flags/mx/nay.svg new file mode 100644 index 000000000..911e09ca0 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/mx/nay.svg @@ -0,0 +1,2 @@ + + diff --git a/pointercrate-demonlist-pages/static/images/flags/mx/que.svg b/pointercrate-demonlist-pages/static/images/flags/mx/que.svg new file mode 100644 index 000000000..f868589aa --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/mx/que.svg @@ -0,0 +1,1727 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/mx/roo.svg b/pointercrate-demonlist-pages/static/images/flags/mx/roo.svg new file mode 100644 index 000000000..af4a5c7ec --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/mx/roo.svg @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/mx/son.svg b/pointercrate-demonlist-pages/static/images/flags/mx/son.svg new file mode 100644 index 000000000..e2e6b9f7e --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/mx/son.svg @@ -0,0 +1,161 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/mx/tam.svg b/pointercrate-demonlist-pages/static/images/flags/mx/tam.svg new file mode 100644 index 000000000..54dd61dc9 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/mx/tam.svg @@ -0,0 +1,257 @@ + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/mx/tla.svg b/pointercrate-demonlist-pages/static/images/flags/mx/tla.svg new file mode 100644 index 000000000..018292524 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/mx/tla.svg @@ -0,0 +1,653 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/mx/ver.svg b/pointercrate-demonlist-pages/static/images/flags/mx/ver.svg new file mode 100644 index 000000000..434b104f3 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/mx/ver.svg @@ -0,0 +1,66 @@ + +image/svg+xml + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/mx/zac.svg b/pointercrate-demonlist-pages/static/images/flags/mx/zac.svg new file mode 100644 index 000000000..b7a15a569 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/mx/zac.svg @@ -0,0 +1,1254 @@ + + + Escudo de armas de la Ciudad y Estado de Zacatecas + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + S + V + + I + I + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/my.svg b/pointercrate-demonlist-pages/static/images/flags/my.svg index 2654cef5e..be7f66756 100644 --- a/pointercrate-demonlist-pages/static/images/flags/my.svg +++ b/pointercrate-demonlist-pages/static/images/flags/my.svg @@ -1,6 +1,6 @@ - + - + @@ -9,17 +9,17 @@ - - - - - - - - - - - + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/mz.svg b/pointercrate-demonlist-pages/static/images/flags/mz.svg index 605a9d59c..2d92c3dec 100644 --- a/pointercrate-demonlist-pages/static/images/flags/mz.svg +++ b/pointercrate-demonlist-pages/static/images/flags/mz.svg @@ -1,6 +1,6 @@ - + - + @@ -10,25 +10,25 @@ - - + + - - - - - - - + + + + + + + - - - - - - - + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/na.svg b/pointercrate-demonlist-pages/static/images/flags/na.svg index af516371d..4be80dcaf 100644 --- a/pointercrate-demonlist-pages/static/images/flags/na.svg +++ b/pointercrate-demonlist-pages/static/images/flags/na.svg @@ -1,6 +1,6 @@ - + - + @@ -10,21 +10,21 @@ - - + + - - + + - - - + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/nc.svg b/pointercrate-demonlist-pages/static/images/flags/nc.svg index d0262cfae..2931198a2 100644 --- a/pointercrate-demonlist-pages/static/images/flags/nc.svg +++ b/pointercrate-demonlist-pages/static/images/flags/nc.svg @@ -1,39 +1,19 @@ - - -Flag of New Caledonia - - - - - - - - - - - - - - - - \ No newline at end of file + + + Flag of New Caledonia + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ne.svg b/pointercrate-demonlist-pages/static/images/flags/ne.svg index e6a778141..a2e28ee1a 100644 --- a/pointercrate-demonlist-pages/static/images/flags/ne.svg +++ b/pointercrate-demonlist-pages/static/images/flags/ne.svg @@ -1,6 +1,6 @@ - + - + @@ -11,10 +11,10 @@ - - - + + + - + diff --git a/pointercrate-demonlist-pages/static/images/flags/nf.svg b/pointercrate-demonlist-pages/static/images/flags/nf.svg index 84f84ec82..d9255f222 100644 --- a/pointercrate-demonlist-pages/static/images/flags/nf.svg +++ b/pointercrate-demonlist-pages/static/images/flags/nf.svg @@ -1,6 +1,6 @@ - + - + @@ -10,37 +10,37 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ng.svg b/pointercrate-demonlist-pages/static/images/flags/ng.svg index 4eccb54f7..b7558c06e 100644 --- a/pointercrate-demonlist-pages/static/images/flags/ng.svg +++ b/pointercrate-demonlist-pages/static/images/flags/ng.svg @@ -1,6 +1,6 @@ - + - + @@ -9,11 +9,11 @@ - - - - - + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ni.svg b/pointercrate-demonlist-pages/static/images/flags/ni.svg index 2fe085a17..c6c2275f1 100644 --- a/pointercrate-demonlist-pages/static/images/flags/ni.svg +++ b/pointercrate-demonlist-pages/static/images/flags/ni.svg @@ -1,122 +1,116 @@ - - - - - - - image/svg+xml - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/nl.svg b/pointercrate-demonlist-pages/static/images/flags/nl.svg index be962d33d..12d43765e 100644 --- a/pointercrate-demonlist-pages/static/images/flags/nl.svg +++ b/pointercrate-demonlist-pages/static/images/flags/nl.svg @@ -1,4 +1,4 @@ - + - + @@ -29,9 +29,9 @@ - - - - + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/nl/dr.svg b/pointercrate-demonlist-pages/static/images/flags/nl/dr.svg new file mode 100644 index 000000000..64f06edbe --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/nl/dr.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/nl/fl.svg b/pointercrate-demonlist-pages/static/images/flags/nl/fl.svg new file mode 100644 index 000000000..54a1f773e --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/nl/fl.svg @@ -0,0 +1,7 @@ + +image/svg+xml + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/nl/fr.svg b/pointercrate-demonlist-pages/static/images/flags/nl/fr.svg new file mode 100644 index 000000000..56537cae9 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/nl/fr.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/nl/ge.svg b/pointercrate-demonlist-pages/static/images/flags/nl/ge.svg new file mode 100644 index 000000000..ba3ea0493 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/nl/ge.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/nl/gr.svg b/pointercrate-demonlist-pages/static/images/flags/nl/gr.svg new file mode 100644 index 000000000..553f2bb5a --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/nl/gr.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/nl/li.svg b/pointercrate-demonlist-pages/static/images/flags/nl/li.svg new file mode 100644 index 000000000..edc7d3184 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/nl/li.svg @@ -0,0 +1,16 @@ + + + + + + image/svg+xml + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/nl/nb.svg b/pointercrate-demonlist-pages/static/images/flags/nl/nb.svg new file mode 100644 index 000000000..623926567 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/nl/nb.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/nl/nh.svg b/pointercrate-demonlist-pages/static/images/flags/nl/nh.svg new file mode 100644 index 000000000..93ccb510f --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/nl/nh.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/nl/ov.svg b/pointercrate-demonlist-pages/static/images/flags/nl/ov.svg new file mode 100644 index 000000000..e710a6781 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/nl/ov.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/nl/ut.svg b/pointercrate-demonlist-pages/static/images/flags/nl/ut.svg new file mode 100644 index 000000000..8e3d56e15 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/nl/ut.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/nl/ze.svg b/pointercrate-demonlist-pages/static/images/flags/nl/ze.svg new file mode 100644 index 000000000..b4375df40 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/nl/ze.svg @@ -0,0 +1,51 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/nl/zh.svg b/pointercrate-demonlist-pages/static/images/flags/nl/zh.svg new file mode 100644 index 000000000..95b247e36 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/nl/zh.svg @@ -0,0 +1,16 @@ + + + + + + + image/svg+xml + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/no.svg b/pointercrate-demonlist-pages/static/images/flags/no.svg index 699ed31ce..283a89a7a 100644 --- a/pointercrate-demonlist-pages/static/images/flags/no.svg +++ b/pointercrate-demonlist-pages/static/images/flags/no.svg @@ -1,6 +1,6 @@ - + - + @@ -10,18 +10,18 @@ - - + + - - - - - - - - - + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/no/01.svg b/pointercrate-demonlist-pages/static/images/flags/no/01.svg new file mode 100644 index 000000000..6b5644902 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/no/01.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/no/02.svg b/pointercrate-demonlist-pages/static/images/flags/no/02.svg new file mode 100644 index 000000000..ad25258c2 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/no/02.svg @@ -0,0 +1,12 @@ + + + + + + + image/svg+xml + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/no/03.svg b/pointercrate-demonlist-pages/static/images/flags/no/03.svg new file mode 100644 index 000000000..99eacc04a --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/no/03.svg @@ -0,0 +1,276 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/no/07.svg b/pointercrate-demonlist-pages/static/images/flags/no/07.svg new file mode 100644 index 000000000..50a279e04 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/no/07.svg @@ -0,0 +1,12 @@ + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/no/08.svg b/pointercrate-demonlist-pages/static/images/flags/no/08.svg new file mode 100644 index 000000000..40e701cd7 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/no/08.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/no/11.svg b/pointercrate-demonlist-pages/static/images/flags/no/11.svg new file mode 100644 index 000000000..f0fe2be23 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/no/11.svg @@ -0,0 +1,12 @@ + + + + + + + image/svg+xml + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/no/15.svg b/pointercrate-demonlist-pages/static/images/flags/no/15.svg new file mode 100644 index 000000000..ca240ddd9 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/no/15.svg @@ -0,0 +1,12 @@ + + + + + + + image/svg+xml + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/no/18.svg b/pointercrate-demonlist-pages/static/images/flags/no/18.svg new file mode 100644 index 000000000..de8564e25 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/no/18.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/no/19.svg b/pointercrate-demonlist-pages/static/images/flags/no/19.svg new file mode 100644 index 000000000..9a5d7dff1 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/no/19.svg @@ -0,0 +1,20 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/no/20.svg b/pointercrate-demonlist-pages/static/images/flags/no/20.svg new file mode 100644 index 000000000..856efdcbe --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/no/20.svg @@ -0,0 +1,12 @@ + + + + + + + image/svg+xml + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/no/34.svg b/pointercrate-demonlist-pages/static/images/flags/no/34.svg new file mode 100644 index 000000000..0d4599de6 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/no/34.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/no/42.svg b/pointercrate-demonlist-pages/static/images/flags/no/42.svg new file mode 100644 index 000000000..17bd28a28 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/no/42.svg @@ -0,0 +1,22 @@ + + + + + + image/svg+xml + + AGDER_fylkesvåpen_rgb + + + + + + + AGDER_fylkesvåpen_rgb + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/no/46.svg b/pointercrate-demonlist-pages/static/images/flags/no/46.svg new file mode 100644 index 000000000..1105b69dc --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/no/46.svg @@ -0,0 +1,3 @@ + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/no/50.svg b/pointercrate-demonlist-pages/static/images/flags/no/50.svg new file mode 100644 index 000000000..009f5033a --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/no/50.svg @@ -0,0 +1,28 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/northerncyprus.svg b/pointercrate-demonlist-pages/static/images/flags/northerncyprus.svg deleted file mode 100644 index a91b31385..000000000 --- a/pointercrate-demonlist-pages/static/images/flags/northerncyprus.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/pointercrate-demonlist-pages/static/images/flags/np.svg b/pointercrate-demonlist-pages/static/images/flags/np.svg index 373225e07..b90364529 100644 --- a/pointercrate-demonlist-pages/static/images/flags/np.svg +++ b/pointercrate-demonlist-pages/static/images/flags/np.svg @@ -1,4 +1,4 @@ - + - + @@ -30,17 +30,17 @@ - - + + - - - - - - + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/nr.svg b/pointercrate-demonlist-pages/static/images/flags/nr.svg index 78a65e02e..9b04d711f 100644 --- a/pointercrate-demonlist-pages/static/images/flags/nr.svg +++ b/pointercrate-demonlist-pages/static/images/flags/nr.svg @@ -1,6 +1,6 @@ - + - + @@ -10,13 +10,13 @@ - - + + - - - + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/nu.svg b/pointercrate-demonlist-pages/static/images/flags/nu.svg index 645a66e3e..9e1fbb4de 100644 --- a/pointercrate-demonlist-pages/static/images/flags/nu.svg +++ b/pointercrate-demonlist-pages/static/images/flags/nu.svg @@ -1,6 +1,6 @@ - + - + @@ -10,31 +10,31 @@ - - + + - - - - - - + + + + + + - - - + + + - - - - - - - + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/nz.svg b/pointercrate-demonlist-pages/static/images/flags/nz.svg index 0f7cf2f52..eedc2b4eb 100644 --- a/pointercrate-demonlist-pages/static/images/flags/nz.svg +++ b/pointercrate-demonlist-pages/static/images/flags/nz.svg @@ -1,4 +1,4 @@ - + - + @@ -36,33 +36,33 @@ Guinea, Honduras, Israel, Jamaica, Jordan, and Romania Flags - + - - - - - - - - + + + + + + + + - - + + - - + + - - + + - - + + diff --git a/pointercrate-demonlist-pages/static/images/flags/om.svg b/pointercrate-demonlist-pages/static/images/flags/om.svg index f28f32dae..8d7f3e9e8 100644 --- a/pointercrate-demonlist-pages/static/images/flags/om.svg +++ b/pointercrate-demonlist-pages/static/images/flags/om.svg @@ -1,6 +1,6 @@ - + - + @@ -10,229 +10,229 @@ - - + + - - - - - - - - - + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - + + + + + + + + + + - - - - - - - - - - + + + + + + + + + + - - - - - - - - - - + + + + + + + + + + - - - - - - - - - - + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + @@ -243,106 +243,106 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/pa.svg b/pointercrate-demonlist-pages/static/images/flags/pa.svg index c1368b259..7dd39d14d 100644 --- a/pointercrate-demonlist-pages/static/images/flags/pa.svg +++ b/pointercrate-demonlist-pages/static/images/flags/pa.svg @@ -1,25 +1,7 @@ - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - + + + Flag of Panama + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/pe.svg b/pointercrate-demonlist-pages/static/images/flags/pe.svg index ebd2e5f32..4216bd1fb 100644 --- a/pointercrate-demonlist-pages/static/images/flags/pe.svg +++ b/pointercrate-demonlist-pages/static/images/flags/pe.svg @@ -1,4 +1,4 @@ - + - + @@ -32,9 +32,9 @@ methods that have not yet been invented or conceived. - - - - + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/pe/anc.svg b/pointercrate-demonlist-pages/static/images/flags/pe/anc.svg new file mode 100644 index 000000000..658d6ad6b --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/pe/anc.svg @@ -0,0 +1,445 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/pe/caj.svg b/pointercrate-demonlist-pages/static/images/flags/pe/caj.svg new file mode 100644 index 000000000..093636d68 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/pe/caj.svg @@ -0,0 +1,333 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/pe/cus.svg b/pointercrate-demonlist-pages/static/images/flags/pe/cus.svg new file mode 100644 index 000000000..f69286b0f --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/pe/cus.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/pe/huc.svg b/pointercrate-demonlist-pages/static/images/flags/pe/huc.svg new file mode 100644 index 000000000..e423f1283 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/pe/huc.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/pe/ica.svg b/pointercrate-demonlist-pages/static/images/flags/pe/ica.svg new file mode 100644 index 000000000..a4a30d07f --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/pe/ica.svg @@ -0,0 +1,195 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/pe/jun.svg b/pointercrate-demonlist-pages/static/images/flags/pe/jun.svg new file mode 100644 index 000000000..1bd9694ac --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/pe/jun.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/pe/lam.svg b/pointercrate-demonlist-pages/static/images/flags/pe/lam.svg new file mode 100644 index 000000000..872543457 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/pe/lam.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/pe/lim.svg b/pointercrate-demonlist-pages/static/images/flags/pe/lim.svg new file mode 100644 index 000000000..81dc460cd --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/pe/lim.svg @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/pe/lor.svg b/pointercrate-demonlist-pages/static/images/flags/pe/lor.svg new file mode 100644 index 000000000..5b7a8c7e7 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/pe/lor.svg @@ -0,0 +1,244 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/pe/mdd.svg b/pointercrate-demonlist-pages/static/images/flags/pe/mdd.svg new file mode 100644 index 000000000..42ce140fc --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/pe/mdd.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/pe/moq.svg b/pointercrate-demonlist-pages/static/images/flags/pe/moq.svg new file mode 100644 index 000000000..a7f49266c --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/pe/moq.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/pe/pas.svg b/pointercrate-demonlist-pages/static/images/flags/pe/pas.svg new file mode 100644 index 000000000..a6a326501 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/pe/pas.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/pe/piu.svg b/pointercrate-demonlist-pages/static/images/flags/pe/piu.svg new file mode 100644 index 000000000..7251138b9 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/pe/piu.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/pe/pun.svg b/pointercrate-demonlist-pages/static/images/flags/pe/pun.svg new file mode 100644 index 000000000..5da6700c5 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/pe/pun.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/pe/sam.svg b/pointercrate-demonlist-pages/static/images/flags/pe/sam.svg new file mode 100644 index 000000000..d2ac2b2c8 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/pe/sam.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/pe/tum.svg b/pointercrate-demonlist-pages/static/images/flags/pe/tum.svg new file mode 100644 index 000000000..ba3ae882f --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/pe/tum.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/pf.svg b/pointercrate-demonlist-pages/static/images/flags/pf.svg index 63f056e71..3fa1351f5 100644 --- a/pointercrate-demonlist-pages/static/images/flags/pf.svg +++ b/pointercrate-demonlist-pages/static/images/flags/pf.svg @@ -1,6 +1,6 @@ - + - + @@ -10,68 +10,68 @@ - - + + - - - - - - - - + + + + + + + + - + - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/pg.svg b/pointercrate-demonlist-pages/static/images/flags/pg.svg index fd1fe93b2..9d6bad909 100644 --- a/pointercrate-demonlist-pages/static/images/flags/pg.svg +++ b/pointercrate-demonlist-pages/static/images/flags/pg.svg @@ -1,6 +1,6 @@ - + - + @@ -10,18 +10,18 @@ - - - + + + - - - - - - - - + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ph.svg b/pointercrate-demonlist-pages/static/images/flags/ph.svg index 95f9d3761..7a1433864 100644 --- a/pointercrate-demonlist-pages/static/images/flags/ph.svg +++ b/pointercrate-demonlist-pages/static/images/flags/ph.svg @@ -1,6 +1,6 @@ - + - + @@ -10,27 +10,27 @@ - - + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/pk.svg b/pointercrate-demonlist-pages/static/images/flags/pk.svg index 1c33e675f..da36f5220 100644 --- a/pointercrate-demonlist-pages/static/images/flags/pk.svg +++ b/pointercrate-demonlist-pages/static/images/flags/pk.svg @@ -1,6 +1,6 @@ - + - + @@ -10,14 +10,14 @@ - - + + - - - - + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/pl.svg b/pointercrate-demonlist-pages/static/images/flags/pl.svg index a8d406aff..9d47c1a1f 100644 --- a/pointercrate-demonlist-pages/static/images/flags/pl.svg +++ b/pointercrate-demonlist-pages/static/images/flags/pl.svg @@ -1,6 +1,6 @@ - + - + @@ -9,8 +9,8 @@ - - - + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/pl/02.svg b/pointercrate-demonlist-pages/static/images/flags/pl/02.svg new file mode 100644 index 000000000..145f63d28 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/pl/02.svg @@ -0,0 +1,2 @@ + +image/svg+xml diff --git a/pointercrate-demonlist-pages/static/images/flags/pl/04.svg b/pointercrate-demonlist-pages/static/images/flags/pl/04.svg new file mode 100644 index 000000000..b52997bf7 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/pl/04.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/pl/06.svg b/pointercrate-demonlist-pages/static/images/flags/pl/06.svg new file mode 100644 index 000000000..f469790f7 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/pl/06.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/pl/08.svg b/pointercrate-demonlist-pages/static/images/flags/pl/08.svg new file mode 100644 index 000000000..c36dcae00 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/pl/08.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/pl/10.svg b/pointercrate-demonlist-pages/static/images/flags/pl/10.svg new file mode 100644 index 000000000..fb2dba913 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/pl/10.svg @@ -0,0 +1,16 @@ + + + + + + image/svg+xml + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/pl/12.svg b/pointercrate-demonlist-pages/static/images/flags/pl/12.svg new file mode 100644 index 000000000..3587dba84 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/pl/12.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/pl/16.svg b/pointercrate-demonlist-pages/static/images/flags/pl/16.svg new file mode 100644 index 000000000..1547938d8 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/pl/16.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/pl/18.svg b/pointercrate-demonlist-pages/static/images/flags/pl/18.svg new file mode 100644 index 000000000..fb4ad516d --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/pl/18.svg @@ -0,0 +1,28 @@ + + +image/svg+xml + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/pl/20.svg b/pointercrate-demonlist-pages/static/images/flags/pl/20.svg new file mode 100644 index 000000000..cb69d058f --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/pl/20.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/pl/24.svg b/pointercrate-demonlist-pages/static/images/flags/pl/24.svg new file mode 100644 index 000000000..bedb8077a --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/pl/24.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/pl/28.svg b/pointercrate-demonlist-pages/static/images/flags/pl/28.svg new file mode 100644 index 000000000..bbc18948f --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/pl/28.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/pm.svg b/pointercrate-demonlist-pages/static/images/flags/pm.svg index b7555b0fd..1a9c97de5 100644 --- a/pointercrate-demonlist-pages/static/images/flags/pm.svg +++ b/pointercrate-demonlist-pages/static/images/flags/pm.svg @@ -1,2651 +1,247 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/pn.svg b/pointercrate-demonlist-pages/static/images/flags/pn.svg index d310c313f..667c21ae3 100644 --- a/pointercrate-demonlist-pages/static/images/flags/pn.svg +++ b/pointercrate-demonlist-pages/static/images/flags/pn.svg @@ -1,6 +1,6 @@ - + - + @@ -11,34 +11,34 @@ - + The above line is the ensign field color: #CF142B red and #00247D blue - + - - - - + + + + I think the above two lines give the simplest way to make the diagonals - - + + - - - - - - - - - - + + + + + + + + + + @@ -46,14 +46,14 @@ - + - - + + - + @@ -75,47 +75,47 @@ - + - + - - - - + + + + - - - - - - - - + + + + + + + + - - + + - - - - - - - - - - - + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/pr.svg b/pointercrate-demonlist-pages/static/images/flags/pr.svg index 771c304c1..7f78aa897 100644 --- a/pointercrate-demonlist-pages/static/images/flags/pr.svg +++ b/pointercrate-demonlist-pages/static/images/flags/pr.svg @@ -1,45 +1,7 @@ - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ps.svg b/pointercrate-demonlist-pages/static/images/flags/ps.svg index e960374a9..47b9b69fc 100644 --- a/pointercrate-demonlist-pages/static/images/flags/ps.svg +++ b/pointercrate-demonlist-pages/static/images/flags/ps.svg @@ -1,6 +1,6 @@ - + - + @@ -10,15 +10,15 @@ - - + + - - - - - + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/pt.svg b/pointercrate-demonlist-pages/static/images/flags/pt.svg index 752ea6476..06230eb1c 100644 --- a/pointercrate-demonlist-pages/static/images/flags/pt.svg +++ b/pointercrate-demonlist-pages/static/images/flags/pt.svg @@ -1,6 +1,6 @@ - + - + @@ -10,220 +10,220 @@ - + - - + + - - - - + + + + - - - - - + + + + + - - - + + + - - - - - + + + + + - - - + + + - - - - - + + + + + - - - + + + - - - - - + + + + + - - - + + + - - - - - + + + + + - - - + + + - - - - - + + + + + - - - + + + - - - + + + - - - - - + + + + + - - - + + + - - - - - - + + + + + + - - - + + + - - - - - - + + + + + + - - - + + + - - - - - + + + + + - - - + + + - - - - - - + + + + + + - - - + + + - - - - - + + + + + - - - + + + - - - - - + + + + + - - - + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + @@ -232,292 +232,292 @@ - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - - + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - - + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - - + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - - + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/pw.svg b/pointercrate-demonlist-pages/static/images/flags/pw.svg index 222f3c25b..b7448368a 100644 --- a/pointercrate-demonlist-pages/static/images/flags/pw.svg +++ b/pointercrate-demonlist-pages/static/images/flags/pw.svg @@ -1,6 +1,6 @@ - + - + @@ -10,12 +10,12 @@ - - + + - - - + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/py.svg b/pointercrate-demonlist-pages/static/images/flags/py.svg index 80d30bf02..e7e7bc90f 100644 --- a/pointercrate-demonlist-pages/static/images/flags/py.svg +++ b/pointercrate-demonlist-pages/static/images/flags/py.svg @@ -1,6 +1,6 @@ - + - + @@ -10,198 +10,198 @@ - - + + - - - - - + + + + + - - - + + + - - - - - - - + + + + + + + - - - - - - + + + + + + - - - - - - - + + + + + + + - - - - - - + + + + + + - - - - - - - + + + + + + + - - - - - - + + + + + + - - - + + + - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + @@ -215,27 +215,27 @@ - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/qa.svg b/pointercrate-demonlist-pages/static/images/flags/qa.svg index 099b508f1..5042b8778 100644 --- a/pointercrate-demonlist-pages/static/images/flags/qa.svg +++ b/pointercrate-demonlist-pages/static/images/flags/qa.svg @@ -1,6 +1,6 @@ - + - + @@ -10,12 +10,12 @@ - - + + - - - + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/re.svg b/pointercrate-demonlist-pages/static/images/flags/re.svg index d5a5e2158..6da81e50e 100644 --- a/pointercrate-demonlist-pages/static/images/flags/re.svg +++ b/pointercrate-demonlist-pages/static/images/flags/re.svg @@ -1,6 +1,6 @@ - + - + @@ -9,9 +9,9 @@ - - - - + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ro.svg b/pointercrate-demonlist-pages/static/images/flags/ro.svg index 311c372df..8bd23c636 100644 --- a/pointercrate-demonlist-pages/static/images/flags/ro.svg +++ b/pointercrate-demonlist-pages/static/images/flags/ro.svg @@ -1,4 +1,4 @@ - + - + @@ -33,10 +33,10 @@ Guinea, Honduras, Israel, Jamaica, Jordan, and Romania Flags - - - - + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/rs.svg b/pointercrate-demonlist-pages/static/images/flags/rs.svg index 21e21c2dd..a518f41ef 100644 --- a/pointercrate-demonlist-pages/static/images/flags/rs.svg +++ b/pointercrate-demonlist-pages/static/images/flags/rs.svg @@ -1,1562 +1,332 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru.svg b/pointercrate-demonlist-pages/static/images/flags/ru.svg index e931a6b6c..1f6b6f05b 100644 --- a/pointercrate-demonlist-pages/static/images/flags/ru.svg +++ b/pointercrate-demonlist-pages/static/images/flags/ru.svg @@ -1,6 +1,6 @@ - + - + @@ -9,9 +9,9 @@ - - - - + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/ad.svg b/pointercrate-demonlist-pages/static/images/flags/ru/ad.svg new file mode 100644 index 000000000..cd8afd133 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/ad.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/al.svg b/pointercrate-demonlist-pages/static/images/flags/ru/al.svg new file mode 100644 index 000000000..1798e9f1c --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/al.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/amu.svg b/pointercrate-demonlist-pages/static/images/flags/ru/amu.svg new file mode 100644 index 000000000..2295bb68b --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/amu.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/ark.svg b/pointercrate-demonlist-pages/static/images/flags/ru/ark.svg new file mode 100644 index 000000000..612ba1fa4 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/ark.svg @@ -0,0 +1,357 @@ + + + + + + + + + + + + Flag_of_Arkhangelsk_Oblast + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/ast.svg b/pointercrate-demonlist-pages/static/images/flags/ru/ast.svg new file mode 100644 index 000000000..1e87f21fa --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/ast.svg @@ -0,0 +1,192 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/ba.svg b/pointercrate-demonlist-pages/static/images/flags/ru/ba.svg new file mode 100644 index 000000000..06519df6b --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/ba.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/bry.svg b/pointercrate-demonlist-pages/static/images/flags/ru/bry.svg new file mode 100644 index 000000000..1cbdf95f6 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/bry.svg @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/bu.svg b/pointercrate-demonlist-pages/static/images/flags/ru/bu.svg new file mode 100644 index 000000000..939133b6f --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/bu.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/ce.svg b/pointercrate-demonlist-pages/static/images/flags/ru/ce.svg new file mode 100644 index 000000000..16bc4991a --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/ce.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/che.svg b/pointercrate-demonlist-pages/static/images/flags/ru/che.svg new file mode 100644 index 000000000..beba4c239 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/che.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/chu.svg b/pointercrate-demonlist-pages/static/images/flags/ru/chu.svg new file mode 100644 index 000000000..a29f7ea66 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/chu.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/cu.svg b/pointercrate-demonlist-pages/static/images/flags/ru/cu.svg new file mode 100644 index 000000000..0d2cf98a7 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/cu.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/da.svg b/pointercrate-demonlist-pages/static/images/flags/ru/da.svg new file mode 100644 index 000000000..ec289b3da --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/da.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/in.svg b/pointercrate-demonlist-pages/static/images/flags/ru/in.svg new file mode 100644 index 000000000..d70e7e47a --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/in.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/irk.svg b/pointercrate-demonlist-pages/static/images/flags/ru/irk.svg new file mode 100644 index 000000000..d35dc192f --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/irk.svg @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/iva.svg b/pointercrate-demonlist-pages/static/images/flags/ru/iva.svg new file mode 100644 index 000000000..2ad79b830 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/iva.svg @@ -0,0 +1,1834 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/kam.svg b/pointercrate-demonlist-pages/static/images/flags/ru/kam.svg new file mode 100644 index 000000000..b693b8e1f --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/kam.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/kb.svg b/pointercrate-demonlist-pages/static/images/flags/ru/kb.svg new file mode 100644 index 000000000..2784554c8 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/kb.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/kc.svg b/pointercrate-demonlist-pages/static/images/flags/ru/kc.svg new file mode 100644 index 000000000..3b32010fe --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/kc.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/kda.svg b/pointercrate-demonlist-pages/static/images/flags/ru/kda.svg new file mode 100644 index 000000000..f3a838475 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/kda.svg @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/kem.svg b/pointercrate-demonlist-pages/static/images/flags/ru/kem.svg new file mode 100644 index 000000000..0aa6d2abe --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/kem.svg @@ -0,0 +1,382 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/kgd.svg b/pointercrate-demonlist-pages/static/images/flags/ru/kgd.svg new file mode 100644 index 000000000..c1aeb3bf4 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/kgd.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/kgn.svg b/pointercrate-demonlist-pages/static/images/flags/ru/kgn.svg new file mode 100644 index 000000000..7a459b618 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/kgn.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/kha.svg b/pointercrate-demonlist-pages/static/images/flags/ru/kha.svg new file mode 100644 index 000000000..e56879ff3 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/kha.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/khm.svg b/pointercrate-demonlist-pages/static/images/flags/ru/khm.svg new file mode 100644 index 000000000..0558edad4 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/khm.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/kir.svg b/pointercrate-demonlist-pages/static/images/flags/ru/kir.svg new file mode 100644 index 000000000..84605f493 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/kir.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/kk.svg b/pointercrate-demonlist-pages/static/images/flags/ru/kk.svg new file mode 100644 index 000000000..dbb83f038 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/kk.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/kl.svg b/pointercrate-demonlist-pages/static/images/flags/ru/kl.svg new file mode 100644 index 000000000..687b5dc98 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/kl.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/klu.svg b/pointercrate-demonlist-pages/static/images/flags/ru/klu.svg new file mode 100644 index 000000000..388c0fb7d --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/klu.svg @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/ko.svg b/pointercrate-demonlist-pages/static/images/flags/ru/ko.svg new file mode 100644 index 000000000..1bef8d54a --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/ko.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/kos.svg b/pointercrate-demonlist-pages/static/images/flags/ru/kos.svg new file mode 100644 index 000000000..dad496aaf --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/kos.svg @@ -0,0 +1,829 @@ + + + Flag of Kostroma Oblast + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/kr.svg b/pointercrate-demonlist-pages/static/images/flags/ru/kr.svg new file mode 100644 index 000000000..ea10ca41b --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/kr.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/krs.svg b/pointercrate-demonlist-pages/static/images/flags/ru/krs.svg new file mode 100644 index 000000000..e2504f2c7 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/krs.svg @@ -0,0 +1,236 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/kya.svg b/pointercrate-demonlist-pages/static/images/flags/ru/kya.svg new file mode 100644 index 000000000..36659a424 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/kya.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/len.svg b/pointercrate-demonlist-pages/static/images/flags/ru/len.svg new file mode 100644 index 000000000..eaa98e5a2 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/len.svg @@ -0,0 +1,44 @@ + + + + Flag_of_Leningrad_Oblast + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/lip.svg b/pointercrate-demonlist-pages/static/images/flags/ru/lip.svg new file mode 100644 index 000000000..e33a4b183 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/lip.svg @@ -0,0 +1,2 @@ + +Flag of Lipetsk Oblast diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/mag.svg b/pointercrate-demonlist-pages/static/images/flags/ru/mag.svg new file mode 100644 index 000000000..3bfffbcc8 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/mag.svg @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/me.svg b/pointercrate-demonlist-pages/static/images/flags/ru/me.svg new file mode 100644 index 000000000..04eafb923 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/me.svg @@ -0,0 +1,196 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/mo.svg b/pointercrate-demonlist-pages/static/images/flags/ru/mo.svg new file mode 100644 index 000000000..2519d9044 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/mo.svg @@ -0,0 +1,4 @@ + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/mos.svg b/pointercrate-demonlist-pages/static/images/flags/ru/mos.svg new file mode 100644 index 000000000..151f86832 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/mos.svg @@ -0,0 +1,176 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/mow.svg b/pointercrate-demonlist-pages/static/images/flags/ru/mow.svg new file mode 100644 index 000000000..852865e2f --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/mow.svg @@ -0,0 +1,1145 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/mur.svg b/pointercrate-demonlist-pages/static/images/flags/ru/mur.svg new file mode 100644 index 000000000..9d48a4d4f --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/mur.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/nen.svg b/pointercrate-demonlist-pages/static/images/flags/ru/nen.svg new file mode 100644 index 000000000..7146991b1 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/nen.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/ngr.svg b/pointercrate-demonlist-pages/static/images/flags/ru/ngr.svg new file mode 100644 index 000000000..a3999c0d1 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/ngr.svg @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/niz.svg b/pointercrate-demonlist-pages/static/images/flags/ru/niz.svg new file mode 100644 index 000000000..314142cea --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/niz.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/nvs.svg b/pointercrate-demonlist-pages/static/images/flags/ru/nvs.svg new file mode 100644 index 000000000..543a724cd --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/nvs.svg @@ -0,0 +1,96 @@ + + + Flag of Novosibirsk oblast + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/oms.svg b/pointercrate-demonlist-pages/static/images/flags/ru/oms.svg new file mode 100644 index 000000000..9a5533491 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/oms.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/ore.svg b/pointercrate-demonlist-pages/static/images/flags/ru/ore.svg new file mode 100644 index 000000000..624ed61b7 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/ore.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/orl.svg b/pointercrate-demonlist-pages/static/images/flags/ru/orl.svg new file mode 100644 index 000000000..5d34f00ca --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/orl.svg @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/per.svg b/pointercrate-demonlist-pages/static/images/flags/ru/per.svg new file mode 100644 index 000000000..f859e3631 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/per.svg @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/pnz.svg b/pointercrate-demonlist-pages/static/images/flags/ru/pnz.svg new file mode 100644 index 000000000..572e624e1 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/pnz.svg @@ -0,0 +1,209 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/pri.svg b/pointercrate-demonlist-pages/static/images/flags/ru/pri.svg new file mode 100644 index 000000000..a89a71121 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/pri.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/psk.svg b/pointercrate-demonlist-pages/static/images/flags/ru/psk.svg new file mode 100644 index 000000000..ab68f6f31 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/psk.svg @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/ros.svg b/pointercrate-demonlist-pages/static/images/flags/ru/ros.svg new file mode 100644 index 000000000..6a41fe55e --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/ros.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/rya.svg b/pointercrate-demonlist-pages/static/images/flags/ru/rya.svg new file mode 100644 index 000000000..78f054b35 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/rya.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/sa.svg b/pointercrate-demonlist-pages/static/images/flags/ru/sa.svg new file mode 100644 index 000000000..28632f485 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/sa.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/sak.svg b/pointercrate-demonlist-pages/static/images/flags/ru/sak.svg new file mode 100644 index 000000000..0140b0f5d --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/sak.svg @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/sam.svg b/pointercrate-demonlist-pages/static/images/flags/ru/sam.svg new file mode 100644 index 000000000..cd2034e7d --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/sam.svg @@ -0,0 +1,512 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/sar.svg b/pointercrate-demonlist-pages/static/images/flags/ru/sar.svg new file mode 100644 index 000000000..581691a7e --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/sar.svg @@ -0,0 +1,3738 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/se.svg b/pointercrate-demonlist-pages/static/images/flags/ru/se.svg new file mode 100644 index 000000000..0765bd37e --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/se.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/smo.svg b/pointercrate-demonlist-pages/static/images/flags/ru/smo.svg new file mode 100644 index 000000000..57b50abfc --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/smo.svg @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/spe.svg b/pointercrate-demonlist-pages/static/images/flags/ru/spe.svg new file mode 100644 index 000000000..b15786e13 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/spe.svg @@ -0,0 +1,1100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/sta.svg b/pointercrate-demonlist-pages/static/images/flags/ru/sta.svg new file mode 100644 index 000000000..6bf8bb85b --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/sta.svg @@ -0,0 +1,630 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/sve.svg b/pointercrate-demonlist-pages/static/images/flags/ru/sve.svg new file mode 100644 index 000000000..c8779ddb9 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/sve.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/ta.svg b/pointercrate-demonlist-pages/static/images/flags/ru/ta.svg new file mode 100644 index 000000000..06ca77835 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/ta.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/tam.svg b/pointercrate-demonlist-pages/static/images/flags/ru/tam.svg new file mode 100644 index 000000000..c3c086cd2 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/tam.svg @@ -0,0 +1,898 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/tom.svg b/pointercrate-demonlist-pages/static/images/flags/ru/tom.svg new file mode 100644 index 000000000..a09eb15d6 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/tom.svg @@ -0,0 +1,889 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/tul.svg b/pointercrate-demonlist-pages/static/images/flags/ru/tul.svg new file mode 100644 index 000000000..9a8d3ff18 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/tul.svg @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/tve.svg b/pointercrate-demonlist-pages/static/images/flags/ru/tve.svg new file mode 100644 index 000000000..702b29786 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/tve.svg @@ -0,0 +1,15 @@ + + + Flag_of_Tver_Oblast + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/ty.svg b/pointercrate-demonlist-pages/static/images/flags/ru/ty.svg new file mode 100644 index 000000000..ea5caab3e --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/ty.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/tyu.svg b/pointercrate-demonlist-pages/static/images/flags/ru/tyu.svg new file mode 100644 index 000000000..52a45b999 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/tyu.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/ud.svg b/pointercrate-demonlist-pages/static/images/flags/ru/ud.svg new file mode 100644 index 000000000..1f4c6b2d7 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/ud.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/uly.svg b/pointercrate-demonlist-pages/static/images/flags/ru/uly.svg new file mode 100644 index 000000000..aeb91b040 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/uly.svg @@ -0,0 +1,386 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/vgg.svg b/pointercrate-demonlist-pages/static/images/flags/ru/vgg.svg new file mode 100644 index 000000000..7581e15c1 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/vgg.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/vla.svg b/pointercrate-demonlist-pages/static/images/flags/ru/vla.svg new file mode 100644 index 000000000..f88a58f65 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/vla.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/vlg.svg b/pointercrate-demonlist-pages/static/images/flags/ru/vlg.svg new file mode 100644 index 000000000..c50bccbba --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/vlg.svg @@ -0,0 +1,239 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/vor.svg b/pointercrate-demonlist-pages/static/images/flags/ru/vor.svg new file mode 100644 index 000000000..4b5661f1c --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/vor.svg @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/yan.svg b/pointercrate-demonlist-pages/static/images/flags/ru/yan.svg new file mode 100644 index 000000000..cade4c0ca --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/yan.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/yar.svg b/pointercrate-demonlist-pages/static/images/flags/ru/yar.svg new file mode 100644 index 000000000..8973e9030 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/yar.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/yev.svg b/pointercrate-demonlist-pages/static/images/flags/ru/yev.svg new file mode 100644 index 000000000..de71e902c --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/yev.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ru/zab.svg b/pointercrate-demonlist-pages/static/images/flags/ru/zab.svg new file mode 100644 index 000000000..199fa2d38 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ru/zab.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/rw.svg b/pointercrate-demonlist-pages/static/images/flags/rw.svg index 282288223..69bdd8c59 100644 --- a/pointercrate-demonlist-pages/static/images/flags/rw.svg +++ b/pointercrate-demonlist-pages/static/images/flags/rw.svg @@ -1,6 +1,6 @@ - + - + @@ -10,16 +10,16 @@ - - + + - - - - - - - + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/sa.svg b/pointercrate-demonlist-pages/static/images/flags/sa.svg index f2bacbb39..879fe3487 100644 --- a/pointercrate-demonlist-pages/static/images/flags/sa.svg +++ b/pointercrate-demonlist-pages/static/images/flags/sa.svg @@ -1,6 +1,6 @@ - + - + @@ -10,19 +10,19 @@ - - + + - - + + - + - + @@ -31,10 +31,10 @@ - - - - + + + + @@ -49,8 +49,8 @@ - - + + diff --git a/pointercrate-demonlist-pages/static/images/flags/sb.svg b/pointercrate-demonlist-pages/static/images/flags/sb.svg index 334ae6475..ec6b778a9 100644 --- a/pointercrate-demonlist-pages/static/images/flags/sb.svg +++ b/pointercrate-demonlist-pages/static/images/flags/sb.svg @@ -1,6 +1,6 @@ - + - + @@ -10,14 +10,14 @@ - - + + - - - - + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/sc.svg b/pointercrate-demonlist-pages/static/images/flags/sc.svg index a4841f81d..c4d9b2601 100644 --- a/pointercrate-demonlist-pages/static/images/flags/sc.svg +++ b/pointercrate-demonlist-pages/static/images/flags/sc.svg @@ -1,4 +1,4 @@ - + - + @@ -31,12 +31,12 @@ - - + + - - + + diff --git a/pointercrate-demonlist-pages/static/images/flags/sd.svg b/pointercrate-demonlist-pages/static/images/flags/sd.svg index bbfac97be..35696ae77 100644 --- a/pointercrate-demonlist-pages/static/images/flags/sd.svg +++ b/pointercrate-demonlist-pages/static/images/flags/sd.svg @@ -1,6 +1,6 @@ - + - + @@ -10,14 +10,14 @@ - - + + - - - - - + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/se.svg b/pointercrate-demonlist-pages/static/images/flags/se.svg index 913d8a83d..5ee9d28eb 100644 --- a/pointercrate-demonlist-pages/static/images/flags/se.svg +++ b/pointercrate-demonlist-pages/static/images/flags/se.svg @@ -1,6 +1,6 @@ - + - + @@ -10,19 +10,19 @@ - - + + - - - - - - - - - + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/sg.svg b/pointercrate-demonlist-pages/static/images/flags/sg.svg index 7a85ef521..7fec5a115 100644 --- a/pointercrate-demonlist-pages/static/images/flags/sg.svg +++ b/pointercrate-demonlist-pages/static/images/flags/sg.svg @@ -1,6 +1,6 @@ - + - + @@ -10,20 +10,20 @@ - - + + - - - - - - - - - + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/sh.svg b/pointercrate-demonlist-pages/static/images/flags/sh.svg index 21cf48865..b1ca274f5 100644 --- a/pointercrate-demonlist-pages/static/images/flags/sh.svg +++ b/pointercrate-demonlist-pages/static/images/flags/sh.svg @@ -1,5 +1,5 @@ - - + + @@ -9,44 +9,40 @@ - - - - + - - - + + - - + + - - + + - - + + - - + + - - + + - - + + - + - + - + @@ -59,13 +55,13 @@ - + - + @@ -197,10 +193,10 @@ - + - + @@ -211,409 +207,409 @@ - + - - + + - + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - + - - + + - - + + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -623,16 +619,16 @@ - + - + - + @@ -655,74 +651,74 @@ - - + + - - + + - - + + - - + + - - + + - - + + - + - + - - + + - - + + - - + + - - + + - - + + - + - + - + - + - + - + - + - + - + @@ -766,13 +762,13 @@ - + - + - + @@ -784,11 +780,11 @@ - + - - + + diff --git a/pointercrate-demonlist-pages/static/images/flags/si.svg b/pointercrate-demonlist-pages/static/images/flags/si.svg index 7461f6361..4d5d61714 100644 --- a/pointercrate-demonlist-pages/static/images/flags/si.svg +++ b/pointercrate-demonlist-pages/static/images/flags/si.svg @@ -1,6 +1,6 @@ - + - + @@ -10,14 +10,14 @@ - - + + - - - - + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/sj.svg b/pointercrate-demonlist-pages/static/images/flags/sj.svg index 7550d890e..e21dbd201 100644 --- a/pointercrate-demonlist-pages/static/images/flags/sj.svg +++ b/pointercrate-demonlist-pages/static/images/flags/sj.svg @@ -1,6 +1,6 @@ - + - + @@ -10,18 +10,18 @@ - - + + - - - - - - - - - + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/sk.svg b/pointercrate-demonlist-pages/static/images/flags/sk.svg index 84c09e8c8..2616e6e6e 100644 --- a/pointercrate-demonlist-pages/static/images/flags/sk.svg +++ b/pointercrate-demonlist-pages/static/images/flags/sk.svg @@ -1,6 +1,6 @@ - + - + @@ -10,14 +10,14 @@ - - + + - - - - + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/sl.svg b/pointercrate-demonlist-pages/static/images/flags/sl.svg index 662d1d71b..29a472bf9 100644 --- a/pointercrate-demonlist-pages/static/images/flags/sl.svg +++ b/pointercrate-demonlist-pages/static/images/flags/sl.svg @@ -1,4 +1,4 @@ - + - + @@ -35,9 +35,9 @@ methods that have not yet been invented or conceived. - - - - + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/sm.svg b/pointercrate-demonlist-pages/static/images/flags/sm.svg index 88284d40d..84134bbd3 100644 --- a/pointercrate-demonlist-pages/static/images/flags/sm.svg +++ b/pointercrate-demonlist-pages/static/images/flags/sm.svg @@ -1,4 +1,4 @@ - + - + @@ -31,179 +31,163 @@ - - - + + + - + - + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + - + - - + + - - + + - - - L - - - I - - - B - - - E - - - R - - - T - - - A - - - S - + + L + I + B + E + R + T + A + S - - - - - - - + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/sn.svg b/pointercrate-demonlist-pages/static/images/flags/sn.svg index a3fa37a44..09e2f845e 100644 --- a/pointercrate-demonlist-pages/static/images/flags/sn.svg +++ b/pointercrate-demonlist-pages/static/images/flags/sn.svg @@ -1,6 +1,6 @@ - + - + @@ -10,10 +10,10 @@ - - - - + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/so.svg b/pointercrate-demonlist-pages/static/images/flags/so.svg index 534c6abbf..f2bc63fe3 100644 --- a/pointercrate-demonlist-pages/static/images/flags/so.svg +++ b/pointercrate-demonlist-pages/static/images/flags/so.svg @@ -1,6 +1,6 @@ - + - + @@ -10,12 +10,12 @@ - - + + - - - + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/southossetia.svg b/pointercrate-demonlist-pages/static/images/flags/southossetia.svg deleted file mode 100644 index 101efdf69..000000000 --- a/pointercrate-demonlist-pages/static/images/flags/southossetia.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - \ No newline at end of file diff --git a/pointercrate-demonlist-pages/static/images/flags/sr.svg b/pointercrate-demonlist-pages/static/images/flags/sr.svg index 99514a928..6b37fc187 100644 --- a/pointercrate-demonlist-pages/static/images/flags/sr.svg +++ b/pointercrate-demonlist-pages/static/images/flags/sr.svg @@ -1,6 +1,6 @@ - + - + @@ -10,16 +10,16 @@ - - + + - - - - - - + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ss.svg b/pointercrate-demonlist-pages/static/images/flags/ss.svg index 56aff0574..90ae27deb 100644 --- a/pointercrate-demonlist-pages/static/images/flags/ss.svg +++ b/pointercrate-demonlist-pages/static/images/flags/ss.svg @@ -1,5 +1,5 @@ - - + + @@ -11,11 +11,11 @@ Flag of South Sudan - - - - - - + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/st.svg b/pointercrate-demonlist-pages/static/images/flags/st.svg index 8f09d115e..55c6ffcc7 100644 --- a/pointercrate-demonlist-pages/static/images/flags/st.svg +++ b/pointercrate-demonlist-pages/static/images/flags/st.svg @@ -1,6 +1,6 @@ - + - + @@ -10,15 +10,15 @@ - - + + - - - - - + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/sv.svg b/pointercrate-demonlist-pages/static/images/flags/sv.svg index 0fcd4f4a4..97c7724b3 100644 --- a/pointercrate-demonlist-pages/static/images/flags/sv.svg +++ b/pointercrate-demonlist-pages/static/images/flags/sv.svg @@ -1,301 +1,977 @@ - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/sx.svg b/pointercrate-demonlist-pages/static/images/flags/sx.svg index 142b30fce..550715cc3 100644 --- a/pointercrate-demonlist-pages/static/images/flags/sx.svg +++ b/pointercrate-demonlist-pages/static/images/flags/sx.svg @@ -1,106 +1,143 @@ - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/sy.svg b/pointercrate-demonlist-pages/static/images/flags/sy.svg index d85aadf9a..f70a86d16 100644 --- a/pointercrate-demonlist-pages/static/images/flags/sy.svg +++ b/pointercrate-demonlist-pages/static/images/flags/sy.svg @@ -1,6 +1,6 @@ - + - + @@ -10,14 +10,14 @@ - - - + + + - - - - + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/sz.svg b/pointercrate-demonlist-pages/static/images/flags/sz.svg index c92e5979d..0ec37abb1 100644 --- a/pointercrate-demonlist-pages/static/images/flags/sz.svg +++ b/pointercrate-demonlist-pages/static/images/flags/sz.svg @@ -1,6 +1,6 @@ - + - + @@ -10,84 +10,84 @@ - - + + - - - - - - - - - - - + + + + + + + + + + + - - - - - - + + + + + + - - - - - - - + + + + + + + - - - - - - - + + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/tc.svg b/pointercrate-demonlist-pages/static/images/flags/tc.svg index 865b4ad92..f92b54644 100644 --- a/pointercrate-demonlist-pages/static/images/flags/tc.svg +++ b/pointercrate-demonlist-pages/static/images/flags/tc.svg @@ -1,102 +1,65 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - + + + Flag of TCI + + + + + + + + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - + + + + + + + + + + + + - - - - - - - + + + + - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/td.svg b/pointercrate-demonlist-pages/static/images/flags/td.svg index 1d01cdc0d..e3af908ce 100644 --- a/pointercrate-demonlist-pages/static/images/flags/td.svg +++ b/pointercrate-demonlist-pages/static/images/flags/td.svg @@ -1,4 +1,4 @@ - + - + @@ -33,9 +33,9 @@ methods that have not yet been invented or conceived. - - - - + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/tf.svg b/pointercrate-demonlist-pages/static/images/flags/tf.svg index c485953ef..5f5c7249b 100644 --- a/pointercrate-demonlist-pages/static/images/flags/tf.svg +++ b/pointercrate-demonlist-pages/static/images/flags/tf.svg @@ -1,18 +1,16 @@ - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/tg.svg b/pointercrate-demonlist-pages/static/images/flags/tg.svg index 20dbfa347..4f432ad24 100644 --- a/pointercrate-demonlist-pages/static/images/flags/tg.svg +++ b/pointercrate-demonlist-pages/static/images/flags/tg.svg @@ -1,6 +1,6 @@ - + - + @@ -10,16 +10,16 @@ - - + + - - - - - + + + + + - + diff --git a/pointercrate-demonlist-pages/static/images/flags/th.svg b/pointercrate-demonlist-pages/static/images/flags/th.svg index a539e3825..af00ffb05 100644 --- a/pointercrate-demonlist-pages/static/images/flags/th.svg +++ b/pointercrate-demonlist-pages/static/images/flags/th.svg @@ -1,6 +1,6 @@ - + - + @@ -10,11 +10,11 @@ - - - - - + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/tj.svg b/pointercrate-demonlist-pages/static/images/flags/tj.svg index c1b0a89f6..59dc8297e 100644 --- a/pointercrate-demonlist-pages/static/images/flags/tj.svg +++ b/pointercrate-demonlist-pages/static/images/flags/tj.svg @@ -1,6 +1,6 @@ - + - + @@ -10,28 +10,28 @@ - - + + - - - - - - - - - - - - - + + + + + + + + + + + + + - + diff --git a/pointercrate-demonlist-pages/static/images/flags/tk.svg b/pointercrate-demonlist-pages/static/images/flags/tk.svg index 3327e0675..47a0614cc 100644 --- a/pointercrate-demonlist-pages/static/images/flags/tk.svg +++ b/pointercrate-demonlist-pages/static/images/flags/tk.svg @@ -1,139 +1,32 @@ - + - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/tl.svg b/pointercrate-demonlist-pages/static/images/flags/tl.svg index 3b56c52f9..02dbdcf8f 100644 --- a/pointercrate-demonlist-pages/static/images/flags/tl.svg +++ b/pointercrate-demonlist-pages/static/images/flags/tl.svg @@ -1,6 +1,6 @@ - + - + @@ -10,14 +10,14 @@ - - + + - - - - + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/tm.svg b/pointercrate-demonlist-pages/static/images/flags/tm.svg index 83cd9aa1b..84a46f99c 100644 --- a/pointercrate-demonlist-pages/static/images/flags/tm.svg +++ b/pointercrate-demonlist-pages/static/images/flags/tm.svg @@ -1,6 +1,6 @@ - + - + @@ -10,315 +10,315 @@ - - + + - - - - - + + + + + - - - - - + + + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - + + + - - - - - - - - - + + + + + + + + + - - - - - - - + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/tn.svg b/pointercrate-demonlist-pages/static/images/flags/tn.svg index ef0d0b737..3a67bf7dd 100644 --- a/pointercrate-demonlist-pages/static/images/flags/tn.svg +++ b/pointercrate-demonlist-pages/static/images/flags/tn.svg @@ -1,6 +1,6 @@ - + - + @@ -10,14 +10,14 @@ - - + + - - - - - + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/to.svg b/pointercrate-demonlist-pages/static/images/flags/to.svg index f51522f03..034d5277b 100644 --- a/pointercrate-demonlist-pages/static/images/flags/to.svg +++ b/pointercrate-demonlist-pages/static/images/flags/to.svg @@ -1,6 +1,6 @@ - + - + @@ -10,12 +10,12 @@ - + - - - - + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/tr.svg b/pointercrate-demonlist-pages/static/images/flags/tr.svg index 779779e41..8e7bd91c6 100644 --- a/pointercrate-demonlist-pages/static/images/flags/tr.svg +++ b/pointercrate-demonlist-pages/static/images/flags/tr.svg @@ -1,6 +1,6 @@ - + - + @@ -10,12 +10,12 @@ - + - - - - + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/tt.svg b/pointercrate-demonlist-pages/static/images/flags/tt.svg index 46e554773..d096d65f0 100644 --- a/pointercrate-demonlist-pages/static/images/flags/tt.svg +++ b/pointercrate-demonlist-pages/static/images/flags/tt.svg @@ -1,18 +1,6 @@ - - - - - - - image/svg+xml - - - - - - - - - - + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/tv.svg b/pointercrate-demonlist-pages/static/images/flags/tv.svg index d83f0d223..e4ce92f01 100644 --- a/pointercrate-demonlist-pages/static/images/flags/tv.svg +++ b/pointercrate-demonlist-pages/static/images/flags/tv.svg @@ -1,6 +1,6 @@ - + - + @@ -10,31 +10,31 @@ - - + + - - - + + + - - - - - + + + + + - - - + + + - + diff --git a/pointercrate-demonlist-pages/static/images/flags/tw.svg b/pointercrate-demonlist-pages/static/images/flags/tw.svg index e9fb0c2f7..a6515911f 100644 --- a/pointercrate-demonlist-pages/static/images/flags/tw.svg +++ b/pointercrate-demonlist-pages/static/images/flags/tw.svg @@ -1,6 +1,6 @@ - + - + @@ -10,15 +10,15 @@ - - + + - - - - - - + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/tz.svg b/pointercrate-demonlist-pages/static/images/flags/tz.svg index 85179d6e0..fffdfbfc7 100644 --- a/pointercrate-demonlist-pages/static/images/flags/tz.svg +++ b/pointercrate-demonlist-pages/static/images/flags/tz.svg @@ -1,4 +1,4 @@ - + - + @@ -31,15 +31,15 @@ - - + + - - + + - + diff --git a/pointercrate-demonlist-pages/static/images/flags/ua.svg b/pointercrate-demonlist-pages/static/images/flags/ua.svg index 6a8cf878a..92a96b0e4 100644 --- a/pointercrate-demonlist-pages/static/images/flags/ua.svg +++ b/pointercrate-demonlist-pages/static/images/flags/ua.svg @@ -1,6 +1,6 @@ - + - + @@ -9,8 +9,8 @@ - - - + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ua/05.svg b/pointercrate-demonlist-pages/static/images/flags/ua/05.svg new file mode 100644 index 000000000..c2beba3e9 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ua/05.svg @@ -0,0 +1,2 @@ + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ua/07.svg b/pointercrate-demonlist-pages/static/images/flags/ua/07.svg new file mode 100644 index 000000000..5fc6e01e5 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ua/07.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ua/12.svg b/pointercrate-demonlist-pages/static/images/flags/ua/12.svg new file mode 100644 index 000000000..8cda03892 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ua/12.svg @@ -0,0 +1,679 @@ + + + + + + + + + + + + + +]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ua/14.svg b/pointercrate-demonlist-pages/static/images/flags/ua/14.svg new file mode 100644 index 000000000..d01bb1ec7 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ua/14.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ua/18.svg b/pointercrate-demonlist-pages/static/images/flags/ua/18.svg new file mode 100644 index 000000000..e688f6755 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ua/18.svg @@ -0,0 +1,71 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ua/21.svg b/pointercrate-demonlist-pages/static/images/flags/ua/21.svg new file mode 100644 index 000000000..6ab8fd673 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ua/21.svg @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ua/23.svg b/pointercrate-demonlist-pages/static/images/flags/ua/23.svg new file mode 100644 index 000000000..8160a4805 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ua/23.svg @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ua/26.svg b/pointercrate-demonlist-pages/static/images/flags/ua/26.svg new file mode 100644 index 000000000..3f2eb4931 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ua/26.svg @@ -0,0 +1,65 @@ + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ua/30.svg b/pointercrate-demonlist-pages/static/images/flags/ua/30.svg new file mode 100644 index 000000000..28b43d342 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ua/30.svg @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ua/32.svg b/pointercrate-demonlist-pages/static/images/flags/ua/32.svg new file mode 100644 index 000000000..fc3b257ae --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ua/32.svg @@ -0,0 +1,819 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ua/35.svg b/pointercrate-demonlist-pages/static/images/flags/ua/35.svg new file mode 100644 index 000000000..1f1e9360d --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ua/35.svg @@ -0,0 +1,18 @@ + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ua/40.svg b/pointercrate-demonlist-pages/static/images/flags/ua/40.svg new file mode 100644 index 000000000..46a9d1666 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ua/40.svg @@ -0,0 +1,12 @@ + + +image/svg+xml + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ua/43.svg b/pointercrate-demonlist-pages/static/images/flags/ua/43.svg new file mode 100644 index 000000000..6044660b6 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ua/43.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ua/46.svg b/pointercrate-demonlist-pages/static/images/flags/ua/46.svg new file mode 100644 index 000000000..0e121623b --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ua/46.svg @@ -0,0 +1,72 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ua/48.svg b/pointercrate-demonlist-pages/static/images/flags/ua/48.svg new file mode 100644 index 000000000..20abc7f3a --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ua/48.svg @@ -0,0 +1,2 @@ + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ua/51.svg b/pointercrate-demonlist-pages/static/images/flags/ua/51.svg new file mode 100644 index 000000000..c8782e670 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ua/51.svg @@ -0,0 +1,7 @@ + + +image/svg+xml + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ua/53.svg b/pointercrate-demonlist-pages/static/images/flags/ua/53.svg new file mode 100644 index 000000000..b1c16064c --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ua/53.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ua/56.svg b/pointercrate-demonlist-pages/static/images/flags/ua/56.svg new file mode 100644 index 000000000..5dc17d383 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ua/56.svg @@ -0,0 +1,241 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ua/59.svg b/pointercrate-demonlist-pages/static/images/flags/ua/59.svg new file mode 100644 index 000000000..0080cfcac --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ua/59.svg @@ -0,0 +1,180 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ua/61.svg b/pointercrate-demonlist-pages/static/images/flags/ua/61.svg new file mode 100644 index 000000000..9e4e40b75 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ua/61.svg @@ -0,0 +1,124 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ua/63.svg b/pointercrate-demonlist-pages/static/images/flags/ua/63.svg new file mode 100644 index 000000000..1650ac786 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ua/63.svg @@ -0,0 +1,275 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ua/68.svg b/pointercrate-demonlist-pages/static/images/flags/ua/68.svg new file mode 100644 index 000000000..1af7ae018 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ua/68.svg @@ -0,0 +1,27 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ua/71.svg b/pointercrate-demonlist-pages/static/images/flags/ua/71.svg new file mode 100644 index 000000000..6a6ceff27 --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ua/71.svg @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ua/74.svg b/pointercrate-demonlist-pages/static/images/flags/ua/74.svg new file mode 100644 index 000000000..22e64183d --- /dev/null +++ b/pointercrate-demonlist-pages/static/images/flags/ua/74.svg @@ -0,0 +1,144 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ug.svg b/pointercrate-demonlist-pages/static/images/flags/ug.svg index 7facab455..a12c5c633 100644 --- a/pointercrate-demonlist-pages/static/images/flags/ug.svg +++ b/pointercrate-demonlist-pages/static/images/flags/ug.svg @@ -1,6 +1,6 @@ - + - + @@ -10,33 +10,33 @@ - - + + - - - - - - + + + + + + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - + diff --git a/pointercrate-demonlist-pages/static/images/flags/um.svg b/pointercrate-demonlist-pages/static/images/flags/um.svg index 88b7f77cc..5e18d3919 100644 --- a/pointercrate-demonlist-pages/static/images/flags/um.svg +++ b/pointercrate-demonlist-pages/static/images/flags/um.svg @@ -1,4 +1,4 @@ - + - + @@ -34,32 +34,32 @@ Guinea, Honduras, Israel, Jamaica, Jordan, and Romania Flags The United States of America flag, produced by Daniel McRae - - + + - - + + - - - - - - - + + + + + + + - - - - - - + + + + + + - - + + diff --git a/pointercrate-demonlist-pages/static/images/flags/us.svg b/pointercrate-demonlist-pages/static/images/flags/us.svg index b6516b4d5..fd5edf3ca 100644 --- a/pointercrate-demonlist-pages/static/images/flags/us.svg +++ b/pointercrate-demonlist-pages/static/images/flags/us.svg @@ -1,4 +1,4 @@ - + - + @@ -34,32 +34,32 @@ Guinea, Honduras, Israel, Jamaica, Jordan, and Romania Flags The United States of America flag, produced by Daniel McRae - - + + - - + + - - - - - - - + + + + + + + - - - - - - + + + + + + - - + + diff --git a/pointercrate-demonlist-pages/static/images/flags/us/ak.svg b/pointercrate-demonlist-pages/static/images/flags/us/ak.svg index 3ef019124..36899a3d5 100644 --- a/pointercrate-demonlist-pages/static/images/flags/us/ak.svg +++ b/pointercrate-demonlist-pages/static/images/flags/us/ak.svg @@ -1,7 +1,25 @@ - -Flag of Alaska - - - - \ No newline at end of file + + Flag of Alaska + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/us/al.svg b/pointercrate-demonlist-pages/static/images/flags/us/al.svg index 1190b196e..19bdbdb1c 100644 --- a/pointercrate-demonlist-pages/static/images/flags/us/al.svg +++ b/pointercrate-demonlist-pages/static/images/flags/us/al.svg @@ -1,6 +1,5 @@ - - - - \ No newline at end of file + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/us/ar.svg b/pointercrate-demonlist-pages/static/images/flags/us/ar.svg index f49ae23b3..344573e4d 100644 --- a/pointercrate-demonlist-pages/static/images/flags/us/ar.svg +++ b/pointercrate-demonlist-pages/static/images/flags/us/ar.svg @@ -1,244 +1,37 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/us/az.svg b/pointercrate-demonlist-pages/static/images/flags/us/az.svg index ee1b227ea..1b0d9caf0 100644 --- a/pointercrate-demonlist-pages/static/images/flags/us/az.svg +++ b/pointercrate-demonlist-pages/static/images/flags/us/az.svg @@ -1,7 +1,7 @@ - - - - - - \ No newline at end of file + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/us/ca.svg b/pointercrate-demonlist-pages/static/images/flags/us/ca.svg index fc2936373..26786b7c6 100644 --- a/pointercrate-demonlist-pages/static/images/flags/us/ca.svg +++ b/pointercrate-demonlist-pages/static/images/flags/us/ca.svg @@ -1,286 +1,104 @@ - + - - - - - - image/svg+xml - - - - - Devin Cook - - - - - Devin Cook - - - - - Public Domain - - - This is Flag of the California Republic. This version is designed specifically to match the description of the flag as described in California Law 54-J-03. The bear was rendered directly using the official drawing found in the law. This also includes the grass plot. - - - Devin Cook + + + + + image/svg+xml + + + + + Devin Cook + + + + + Devin Cook + + + + + Public Domain + + + This is Flag of the California Republic. This version is designed specifically to match the description of the flag as described in California Law 54-J-03. The bear was rendered directly using the official drawing found in the law. This also includes the grass plot. + + + Devin Cook flags@devincook.com www.devincook.com - - - California Law 54-J-03 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + California Law 54-J-03 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/us/co.svg b/pointercrate-demonlist-pages/static/images/flags/us/co.svg index 26bd135b3..ff1fb8efe 100644 --- a/pointercrate-demonlist-pages/static/images/flags/us/co.svg +++ b/pointercrate-demonlist-pages/static/images/flags/us/co.svg @@ -1,9 +1,9 @@ - - - + + + - + - + - \ No newline at end of file + diff --git a/pointercrate-demonlist-pages/static/images/flags/us/ct.svg b/pointercrate-demonlist-pages/static/images/flags/us/ct.svg index 726a725ba..e2044eb9d 100644 --- a/pointercrate-demonlist-pages/static/images/flags/us/ct.svg +++ b/pointercrate-demonlist-pages/static/images/flags/us/ct.svg @@ -1,455 +1,96 @@ - + - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/us/dc.svg b/pointercrate-demonlist-pages/static/images/flags/us/dc.svg index 3b7044ba4..6317f3119 100644 --- a/pointercrate-demonlist-pages/static/images/flags/us/dc.svg +++ b/pointercrate-demonlist-pages/static/images/flags/us/dc.svg @@ -1,18 +1,18 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/us/de.svg b/pointercrate-demonlist-pages/static/images/flags/us/de.svg index 540fcc943..199e646e0 100644 --- a/pointercrate-demonlist-pages/static/images/flags/us/de.svg +++ b/pointercrate-demonlist-pages/static/images/flags/us/de.svg @@ -1,1120 +1,274 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/us/fl.svg b/pointercrate-demonlist-pages/static/images/flags/us/fl.svg index 32c327eda..7ed5b0434 100644 --- a/pointercrate-demonlist-pages/static/images/flags/us/fl.svg +++ b/pointercrate-demonlist-pages/static/images/flags/us/fl.svg @@ -1,6784 +1,1713 @@ - + - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/us/ga.svg b/pointercrate-demonlist-pages/static/images/flags/us/ga.svg index f6c149bef..7c1ac6734 100644 --- a/pointercrate-demonlist-pages/static/images/flags/us/ga.svg +++ b/pointercrate-demonlist-pages/static/images/flags/us/ga.svg @@ -1,512 +1,3 @@ - + - - \ No newline at end of file + diff --git a/pointercrate-demonlist-pages/static/images/flags/us/hi.svg b/pointercrate-demonlist-pages/static/images/flags/us/hi.svg index 192a31589..e09330644 100644 --- a/pointercrate-demonlist-pages/static/images/flags/us/hi.svg +++ b/pointercrate-demonlist-pages/static/images/flags/us/hi.svg @@ -1,29 +1,25 @@ - + - - - - - - - - - - - - - - - - - - - -I think the above two lines give the simplest way to make the diagonals - - - + + + + + + + + + + + + + + + + + + + I think the above two lines give the simplest way to make the diagonals + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/us/ia.svg b/pointercrate-demonlist-pages/static/images/flags/us/ia.svg index 843462f38..ab4e2ab2a 100644 --- a/pointercrate-demonlist-pages/static/images/flags/us/ia.svg +++ b/pointercrate-demonlist-pages/static/images/flags/us/ia.svg @@ -1,740 +1,27 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/us/id.svg b/pointercrate-demonlist-pages/static/images/flags/us/id.svg index 4b3ec2bc4..85abd4692 100644 --- a/pointercrate-demonlist-pages/static/images/flags/us/id.svg +++ b/pointercrate-demonlist-pages/static/images/flags/us/id.svg @@ -1,4444 +1,1120 @@ - + - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/us/il.svg b/pointercrate-demonlist-pages/static/images/flags/us/il.svg index 6f99c6e0c..0991fa9d0 100644 --- a/pointercrate-demonlist-pages/static/images/flags/us/il.svg +++ b/pointercrate-demonlist-pages/static/images/flags/us/il.svg @@ -1,4156 +1,637 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/us/in.svg b/pointercrate-demonlist-pages/static/images/flags/us/in.svg index 7db5f8dd1..c8efcf184 100644 --- a/pointercrate-demonlist-pages/static/images/flags/us/in.svg +++ b/pointercrate-demonlist-pages/static/images/flags/us/in.svg @@ -1,38 +1,39 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/us/ks.svg b/pointercrate-demonlist-pages/static/images/flags/us/ks.svg index a1d056c52..e097093a1 100644 --- a/pointercrate-demonlist-pages/static/images/flags/us/ks.svg +++ b/pointercrate-demonlist-pages/static/images/flags/us/ks.svg @@ -1,2575 +1,603 @@ - + - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/us/ky.svg b/pointercrate-demonlist-pages/static/images/flags/us/ky.svg index 751bd9b73..f9ec972a1 100644 --- a/pointercrate-demonlist-pages/static/images/flags/us/ky.svg +++ b/pointercrate-demonlist-pages/static/images/flags/us/ky.svg @@ -1,2918 +1,615 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/us/la.svg b/pointercrate-demonlist-pages/static/images/flags/us/la.svg index 71ebd000b..c5c7d9d90 100644 --- a/pointercrate-demonlist-pages/static/images/flags/us/la.svg +++ b/pointercrate-demonlist-pages/static/images/flags/us/la.svg @@ -1,42 +1,8 @@ - + - -image/svg+xml - - +image/svg+xml + + @@ -418,1091 +384,6 @@ - - - \ No newline at end of file + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/us/ma.svg b/pointercrate-demonlist-pages/static/images/flags/us/ma.svg index bd85f978c..ab8854498 100644 --- a/pointercrate-demonlist-pages/static/images/flags/us/ma.svg +++ b/pointercrate-demonlist-pages/static/images/flags/us/ma.svg @@ -1,620 +1,3 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + diff --git a/pointercrate-demonlist-pages/static/images/flags/us/md.svg b/pointercrate-demonlist-pages/static/images/flags/us/md.svg index 0850b6d61..7016f3d46 100644 --- a/pointercrate-demonlist-pages/static/images/flags/us/md.svg +++ b/pointercrate-demonlist-pages/static/images/flags/us/md.svg @@ -1,24 +1,22 @@ - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/us/me.svg b/pointercrate-demonlist-pages/static/images/flags/us/me.svg index 63f082685..5895b3180 100644 --- a/pointercrate-demonlist-pages/static/images/flags/us/me.svg +++ b/pointercrate-demonlist-pages/static/images/flags/us/me.svg @@ -1,2631 +1,649 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/us/mi.svg b/pointercrate-demonlist-pages/static/images/flags/us/mi.svg index 7ae42ea01..b87d361de 100644 --- a/pointercrate-demonlist-pages/static/images/flags/us/mi.svg +++ b/pointercrate-demonlist-pages/static/images/flags/us/mi.svg @@ -1,792 +1,191 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/us/mn.svg b/pointercrate-demonlist-pages/static/images/flags/us/mn.svg index 02c1843b1..ab40feacf 100644 --- a/pointercrate-demonlist-pages/static/images/flags/us/mn.svg +++ b/pointercrate-demonlist-pages/static/images/flags/us/mn.svg @@ -1,2761 +1,14 @@ - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/us/mo.svg b/pointercrate-demonlist-pages/static/images/flags/us/mo.svg index 4511d34a8..293071909 100644 --- a/pointercrate-demonlist-pages/static/images/flags/us/mo.svg +++ b/pointercrate-demonlist-pages/static/images/flags/us/mo.svg @@ -1,2573 +1,608 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/us/ms.svg b/pointercrate-demonlist-pages/static/images/flags/us/ms.svg index 953f309c0..0cd2a7d6c 100644 --- a/pointercrate-demonlist-pages/static/images/flags/us/ms.svg +++ b/pointercrate-demonlist-pages/static/images/flags/us/ms.svg @@ -1,24 +1,10 @@ - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/us/mt.svg b/pointercrate-demonlist-pages/static/images/flags/us/mt.svg index 160ead17f..9be6926d1 100644 --- a/pointercrate-demonlist-pages/static/images/flags/us/mt.svg +++ b/pointercrate-demonlist-pages/static/images/flags/us/mt.svg @@ -1,1215 +1,298 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/us/nc.svg b/pointercrate-demonlist-pages/static/images/flags/us/nc.svg index ffe73b639..946d6ddd5 100644 --- a/pointercrate-demonlist-pages/static/images/flags/us/nc.svg +++ b/pointercrate-demonlist-pages/static/images/flags/us/nc.svg @@ -1,268 +1,64 @@ - + - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/us/nd.svg b/pointercrate-demonlist-pages/static/images/flags/us/nd.svg index dcf22306e..e87078bb2 100644 --- a/pointercrate-demonlist-pages/static/images/flags/us/nd.svg +++ b/pointercrate-demonlist-pages/static/images/flags/us/nd.svg @@ -1,2365 +1,584 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/us/ne.svg b/pointercrate-demonlist-pages/static/images/flags/us/ne.svg index f81131e82..ade06a4c2 100644 --- a/pointercrate-demonlist-pages/static/images/flags/us/ne.svg +++ b/pointercrate-demonlist-pages/static/images/flags/us/ne.svg @@ -1,1960 +1,485 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/us/nh.svg b/pointercrate-demonlist-pages/static/images/flags/us/nh.svg index f958c6d7f..5102881d0 100644 --- a/pointercrate-demonlist-pages/static/images/flags/us/nh.svg +++ b/pointercrate-demonlist-pages/static/images/flags/us/nh.svg @@ -1,1505 +1,371 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/us/nj.svg b/pointercrate-demonlist-pages/static/images/flags/us/nj.svg index 0ef75b4de..bfcbfd20a 100644 --- a/pointercrate-demonlist-pages/static/images/flags/us/nj.svg +++ b/pointercrate-demonlist-pages/static/images/flags/us/nj.svg @@ -1,1932 +1,483 @@ - + - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/us/nm.svg b/pointercrate-demonlist-pages/static/images/flags/us/nm.svg index 1bacaa048..6d95caaff 100644 --- a/pointercrate-demonlist-pages/static/images/flags/us/nm.svg +++ b/pointercrate-demonlist-pages/static/images/flags/us/nm.svg @@ -1,6 +1,9 @@ - - - - - - \ No newline at end of file + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/us/nv.svg b/pointercrate-demonlist-pages/static/images/flags/us/nv.svg index 2246be174..bcb5277d1 100644 --- a/pointercrate-demonlist-pages/static/images/flags/us/nv.svg +++ b/pointercrate-demonlist-pages/static/images/flags/us/nv.svg @@ -1,399 +1,111 @@ - + - - - - - - - - Flag of the State of Nevada - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + Flag of the State of Nevada + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/us/ny.svg b/pointercrate-demonlist-pages/static/images/flags/us/ny.svg index 2316c4250..4330fd803 100644 --- a/pointercrate-demonlist-pages/static/images/flags/us/ny.svg +++ b/pointercrate-demonlist-pages/static/images/flags/us/ny.svg @@ -1,232 +1,235 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/us/oh.svg b/pointercrate-demonlist-pages/static/images/flags/us/oh.svg index 877bbea19..666ed162b 100644 --- a/pointercrate-demonlist-pages/static/images/flags/us/oh.svg +++ b/pointercrate-demonlist-pages/static/images/flags/us/oh.svg @@ -1,42 +1,43 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/us/ok.svg b/pointercrate-demonlist-pages/static/images/flags/us/ok.svg index ecfcc50de..db1620c76 100644 --- a/pointercrate-demonlist-pages/static/images/flags/us/ok.svg +++ b/pointercrate-demonlist-pages/static/images/flags/us/ok.svg @@ -1,1036 +1,253 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/us/or.svg b/pointercrate-demonlist-pages/static/images/flags/us/or.svg index 75b6eaef6..e5ba5cc3a 100644 --- a/pointercrate-demonlist-pages/static/images/flags/us/or.svg +++ b/pointercrate-demonlist-pages/static/images/flags/us/or.svg @@ -1,509 +1,121 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/us/pa.svg b/pointercrate-demonlist-pages/static/images/flags/us/pa.svg index c9b7721d0..1ee8a5a9e 100644 --- a/pointercrate-demonlist-pages/static/images/flags/us/pa.svg +++ b/pointercrate-demonlist-pages/static/images/flags/us/pa.svg @@ -1,304 +1,40 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/us/ri.svg b/pointercrate-demonlist-pages/static/images/flags/us/ri.svg index 0cd9f20e9..b439d50ab 100644 --- a/pointercrate-demonlist-pages/static/images/flags/us/ri.svg +++ b/pointercrate-demonlist-pages/static/images/flags/us/ri.svg @@ -1,263 +1,30 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/us/sc.svg b/pointercrate-demonlist-pages/static/images/flags/us/sc.svg index cde76e7d6..15f616316 100644 --- a/pointercrate-demonlist-pages/static/images/flags/us/sc.svg +++ b/pointercrate-demonlist-pages/static/images/flags/us/sc.svg @@ -1,84 +1,51 @@ - + - - - - - - - - - - - 0xa10d420 - america - flag - unitedstates - north_america - sign - - - - - Steve Hall - - - - - Steve Hall - - - - - Steve Hall - - - - image/svg+xml - - - en - - - - - - - - - - - - + + + + + + + + + + 0xa10d420 + america + flag + unitedstates + north_america + sign + + + + + Steve Hall + + + + + Steve Hall + + + + + Steve Hall + + + + image/svg+xml + + + en + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/us/sd.svg b/pointercrate-demonlist-pages/static/images/flags/us/sd.svg index 442a827ec..4aa6012e1 100644 --- a/pointercrate-demonlist-pages/static/images/flags/us/sd.svg +++ b/pointercrate-demonlist-pages/static/images/flags/us/sd.svg @@ -1,3680 +1,914 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/us/tn.svg b/pointercrate-demonlist-pages/static/images/flags/us/tn.svg index 8dedbc095..e4912f255 100644 --- a/pointercrate-demonlist-pages/static/images/flags/us/tn.svg +++ b/pointercrate-demonlist-pages/static/images/flags/us/tn.svg @@ -1,23 +1,23 @@ - - Flag of Tenessee, United States - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + Flag of Tenessee, United States + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/us/tx.svg b/pointercrate-demonlist-pages/static/images/flags/us/tx.svg index d7b6a848c..8daa72791 100644 --- a/pointercrate-demonlist-pages/static/images/flags/us/tx.svg +++ b/pointercrate-demonlist-pages/static/images/flags/us/tx.svg @@ -1,16 +1,16 @@ - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/us/ut.svg b/pointercrate-demonlist-pages/static/images/flags/us/ut.svg index 8dbfaf421..bd7c49684 100644 --- a/pointercrate-demonlist-pages/static/images/flags/us/ut.svg +++ b/pointercrate-demonlist-pages/static/images/flags/us/ut.svg @@ -1,930 +1,31 @@ - - - -image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/us/va.svg b/pointercrate-demonlist-pages/static/images/flags/us/va.svg index 552a3d115..6dd356277 100644 --- a/pointercrate-demonlist-pages/static/images/flags/us/va.svg +++ b/pointercrate-demonlist-pages/static/images/flags/us/va.svg @@ -1,3347 +1,834 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/us/vt.svg b/pointercrate-demonlist-pages/static/images/flags/us/vt.svg index ef88c1223..db5e48580 100644 --- a/pointercrate-demonlist-pages/static/images/flags/us/vt.svg +++ b/pointercrate-demonlist-pages/static/images/flags/us/vt.svg @@ -1,809 +1,197 @@ - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/us/wa.svg b/pointercrate-demonlist-pages/static/images/flags/us/wa.svg index b7af90365..6073db4bb 100644 --- a/pointercrate-demonlist-pages/static/images/flags/us/wa.svg +++ b/pointercrate-demonlist-pages/static/images/flags/us/wa.svg @@ -1,2620 +1,954 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/us/wi.svg b/pointercrate-demonlist-pages/static/images/flags/us/wi.svg index 65ed15903..4f80d5c9a 100644 --- a/pointercrate-demonlist-pages/static/images/flags/us/wi.svg +++ b/pointercrate-demonlist-pages/static/images/flags/us/wi.svg @@ -1,3611 +1,740 @@ - + - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/us/wv.svg b/pointercrate-demonlist-pages/static/images/flags/us/wv.svg index f650ed5cf..f08a5781f 100644 --- a/pointercrate-demonlist-pages/static/images/flags/us/wv.svg +++ b/pointercrate-demonlist-pages/static/images/flags/us/wv.svg @@ -1,2307 +1,668 @@ - + - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/us/wy.svg b/pointercrate-demonlist-pages/static/images/flags/us/wy.svg index a4bff2c6c..f142e0285 100644 --- a/pointercrate-demonlist-pages/static/images/flags/us/wy.svg +++ b/pointercrate-demonlist-pages/static/images/flags/us/wy.svg @@ -1,10 +1,9 @@ - + - + - + ]]> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/uy.svg b/pointercrate-demonlist-pages/static/images/flags/uy.svg index 4a29ce5a7..e0774620c 100644 --- a/pointercrate-demonlist-pages/static/images/flags/uy.svg +++ b/pointercrate-demonlist-pages/static/images/flags/uy.svg @@ -1,6 +1,6 @@ - + - + @@ -14,50 +14,50 @@ - - - - - - - - - - + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/uz.svg b/pointercrate-demonlist-pages/static/images/flags/uz.svg index 97468a7fe..dd4f3b4a1 100644 --- a/pointercrate-demonlist-pages/static/images/flags/uz.svg +++ b/pointercrate-demonlist-pages/static/images/flags/uz.svg @@ -1,6 +1,6 @@ - + - + @@ -10,28 +10,28 @@ - - + + - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/va.svg b/pointercrate-demonlist-pages/static/images/flags/va.svg index 097e3b4b1..9e5c940d9 100644 --- a/pointercrate-demonlist-pages/static/images/flags/va.svg +++ b/pointercrate-demonlist-pages/static/images/flags/va.svg @@ -1,6 +1,6 @@ - + - + @@ -10,492 +10,492 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - + - + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + - + - + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - + + + + + + + + + + + - - - - - - - - + + + + + + + + - - - - - - - - + + + + + + + + - + - - - - - - - - - - - - + + + + + + + + + + + + - + diff --git a/pointercrate-demonlist-pages/static/images/flags/vc.svg b/pointercrate-demonlist-pages/static/images/flags/vc.svg index de35200cd..6876e9fe7 100644 --- a/pointercrate-demonlist-pages/static/images/flags/vc.svg +++ b/pointercrate-demonlist-pages/static/images/flags/vc.svg @@ -1,6 +1,6 @@ - + - + @@ -10,9 +10,9 @@ - - - + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ve.svg b/pointercrate-demonlist-pages/static/images/flags/ve.svg index c65fe82ae..f8f652e1e 100644 --- a/pointercrate-demonlist-pages/static/images/flags/ve.svg +++ b/pointercrate-demonlist-pages/static/images/flags/ve.svg @@ -1,29 +1,29 @@ - - - - - - - - - - - - - + + + + + + + + - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/vg.svg b/pointercrate-demonlist-pages/static/images/flags/vg.svg index fe88d47a8..8117eb428 100644 --- a/pointercrate-demonlist-pages/static/images/flags/vg.svg +++ b/pointercrate-demonlist-pages/static/images/flags/vg.svg @@ -1,249 +1,110 @@ - - - - - - - British Virgin Islands - - - - - united_kingdom - flags - caribbean - america - signs_and_symbols - sign - - - - - Tobias Jakobs - - - - - Tobias Jakobs - - - - - Tobias Jakobs - - - - image/svg+xml - - - en - - - - - - - - - - - - + + + + + + - - - - - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/vi.svg b/pointercrate-demonlist-pages/static/images/flags/vi.svg index f525b810e..5d32ac74e 100644 --- a/pointercrate-demonlist-pages/static/images/flags/vi.svg +++ b/pointercrate-demonlist-pages/static/images/flags/vi.svg @@ -1,449 +1,2 @@ - - - -image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + diff --git a/pointercrate-demonlist-pages/static/images/flags/vn.svg b/pointercrate-demonlist-pages/static/images/flags/vn.svg index ee7ab4f26..af8a2cfd2 100644 --- a/pointercrate-demonlist-pages/static/images/flags/vn.svg +++ b/pointercrate-demonlist-pages/static/images/flags/vn.svg @@ -1,6 +1,6 @@ - + - + @@ -10,12 +10,12 @@ - - + + - - - + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/vu.svg b/pointercrate-demonlist-pages/static/images/flags/vu.svg index bdea587c8..f9ba41be1 100644 --- a/pointercrate-demonlist-pages/static/images/flags/vu.svg +++ b/pointercrate-demonlist-pages/static/images/flags/vu.svg @@ -1,6 +1,6 @@ - + - + @@ -10,19 +10,19 @@ - - + + - - + + - - - - - - + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/wf.svg b/pointercrate-demonlist-pages/static/images/flags/wf.svg index b54835f77..98ac05044 100644 --- a/pointercrate-demonlist-pages/static/images/flags/wf.svg +++ b/pointercrate-demonlist-pages/static/images/flags/wf.svg @@ -1,7 +1,8 @@ - - - - - - - \ No newline at end of file + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ws.svg b/pointercrate-demonlist-pages/static/images/flags/ws.svg index e799dd161..0b563383d 100644 --- a/pointercrate-demonlist-pages/static/images/flags/ws.svg +++ b/pointercrate-demonlist-pages/static/images/flags/ws.svg @@ -1,6 +1,6 @@ - + - + @@ -10,9 +10,9 @@ - + - + diff --git a/pointercrate-demonlist-pages/static/images/flags/xk.svg b/pointercrate-demonlist-pages/static/images/flags/xk.svg index 4afbc18d8..8f31ca98d 100644 --- a/pointercrate-demonlist-pages/static/images/flags/xk.svg +++ b/pointercrate-demonlist-pages/static/images/flags/xk.svg @@ -1,17 +1,16 @@ - - -Flag of Kosovo - - - - - - - - - - - + + + Flag of Kosovo + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/ye.svg b/pointercrate-demonlist-pages/static/images/flags/ye.svg index 1d68b8216..b30d32372 100644 --- a/pointercrate-demonlist-pages/static/images/flags/ye.svg +++ b/pointercrate-demonlist-pages/static/images/flags/ye.svg @@ -1,6 +1,6 @@ - + - + @@ -9,9 +9,9 @@ - - - - + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/yt.svg b/pointercrate-demonlist-pages/static/images/flags/yt.svg index 2591a295b..da343a2ee 100644 --- a/pointercrate-demonlist-pages/static/images/flags/yt.svg +++ b/pointercrate-demonlist-pages/static/images/flags/yt.svg @@ -1,59 +1,55 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/za.svg b/pointercrate-demonlist-pages/static/images/flags/za.svg index d7a118700..4cac141c4 100644 --- a/pointercrate-demonlist-pages/static/images/flags/za.svg +++ b/pointercrate-demonlist-pages/static/images/flags/za.svg @@ -1,4 +1,4 @@ - + - + @@ -40,15 +40,15 @@ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> - - + + - - + + - + diff --git a/pointercrate-demonlist-pages/static/images/flags/zm.svg b/pointercrate-demonlist-pages/static/images/flags/zm.svg index bc16746a7..465f200eb 100644 --- a/pointercrate-demonlist-pages/static/images/flags/zm.svg +++ b/pointercrate-demonlist-pages/static/images/flags/zm.svg @@ -1,6 +1,6 @@ - + - + @@ -10,31 +10,31 @@ - - + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/zw.svg b/pointercrate-demonlist-pages/static/images/flags/zw.svg index 1227ba886..b45b483a5 100644 --- a/pointercrate-demonlist-pages/static/images/flags/zw.svg +++ b/pointercrate-demonlist-pages/static/images/flags/zw.svg @@ -1,6 +1,6 @@ - + - + @@ -10,43 +10,43 @@ - - + + - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/images/flags/zz.svg b/pointercrate-demonlist-pages/static/images/flags/zz.svg index 8b62444ee..0540f7611 100644 --- a/pointercrate-demonlist-pages/static/images/flags/zz.svg +++ b/pointercrate-demonlist-pages/static/images/flags/zz.svg @@ -1,4 +1,4 @@ - + - + @@ -29,12 +29,12 @@ - - - - - - - - + + + + + + + + diff --git a/pointercrate-demonlist-pages/static/js/account/records.js b/pointercrate-demonlist-pages/static/js/account/records.js index 97a3d49e8..59bfa81b7 100644 --- a/pointercrate-demonlist-pages/static/js/account/records.js +++ b/pointercrate-demonlist-pages/static/js/account/records.js @@ -328,7 +328,7 @@ function setupRecordFilterPlayerNameForm() { let json = response.data; if (!json || json.length == 0) { - playerName.setError("No player with that name found!"); + playerName.errorText = "No player with that name found!"; } else { recordManager.updateQueryData("player", json[0].id); } diff --git a/pointercrate-demonlist-pages/static/js/modules/demonlist.js b/pointercrate-demonlist-pages/static/js/modules/demonlist.js index 6e60e933c..157190703 100644 --- a/pointercrate-demonlist-pages/static/js/modules/demonlist.js +++ b/pointercrate-demonlist-pages/static/js/modules/demonlist.js @@ -39,39 +39,18 @@ export function initializeTimeMachine() { return; var timeMachineForm = new Form(formHtml); + var destination = timeMachineForm.input("time-machine-destination"); - var inputs = ['year', 'month', 'day', 'hour', 'minute', 'second'].map(name => timeMachineForm.input("time-machine-" + name)); - - for(let input of inputs) { - input.addValidator(input => input.dropdown.selected !== undefined, "Please specify a value"); - } + destination.addValidator(valueMissing, "Please specify a value"); + destination.addValidator(rangeUnderflow, "You cannot go back in time that far!"); var offset = new Date().getTimezoneOffset(); - var offsetHours = Math.abs(offset) / 60; + var offsetHours = offset / 60; var offsetMinutes = Math.abs(offset) % 60; - const MONTHS = [ - "January", - "February", - "March", - "April", - "May", - "June", - "July", - "August", - "September", - "October", - "November", - "December", - ]; - timeMachineForm.onSubmit(() => { - let when = inputs[0].value + "-" - + ("" + (MONTHS.indexOf(inputs[1].value) + 1)).padStart(2, '0') + "-" - + ("" + inputs[2].value).padStart(2, '0') + "T" - + ("" + inputs[3].value).padStart(2, '0') + ":" - + ("" + inputs[4].value).padStart(2, '0') + ":" - + ("" + inputs[5].value).padStart(2, '0') + (offsetHours < 0 ? "%2B" : "-") + (offsetHours + "").padStart(2, "0") + ":" + (offsetMinutes + "").padStart(2, "0"); + // datetime-local gives us a string in the format YYYY-MM-DDThh:mm. Thus, pad it with :ss and timezone information, as the backend expects (aka a rfc3339 date) + let when = destination.value + ":00" + (offsetHours < 0 ? "%2B" : "-") + (Math.abs(offsetHours) + "").padStart(2, "0") + ":" + (offsetMinutes + "").padStart(2, "0"); document.cookie = "when=" + when; diff --git a/pointercrate-demonlist-pages/static/js/modules/statsviewer.js b/pointercrate-demonlist-pages/static/js/modules/statsviewer.js index 4845a26ba..a5ed8d003 100644 --- a/pointercrate-demonlist-pages/static/js/modules/statsviewer.js +++ b/pointercrate-demonlist-pages/static/js/modules/statsviewer.js @@ -183,7 +183,10 @@ export class InteractiveWorldMap { } deselectSubdivision() { - return; + if (this.currentlySelected === undefined || !this.currentlySelected.id.includes("-")) + return; + + this.select(this.currentlySelected.id.substring(0, 2)); } deselect() { diff --git a/pointercrate-demonlist-pages/static/js/statsviewer/individual.js b/pointercrate-demonlist-pages/static/js/statsviewer/individual.js index ffb54b14c..4b6eb9499 100644 --- a/pointercrate-demonlist-pages/static/js/statsviewer/individual.js +++ b/pointercrate-demonlist-pages/static/js/statsviewer/individual.js @@ -55,12 +55,12 @@ class IndividualStatsViewer extends StatsViewer { } formatDemonsInto(element, demons) { - formatInto(element, demons.map(demon => this.formatDemon(demon, "/demonlist/permalink/" + demon.id + "/"))); + formatInto(element, demons.map(demon => this.formatDemon(demon, "/demonlist/permalink/" + demon.id + "/"))); } formatRecordsInto(element, records) { formatInto(element, records.map(record => { - let demon = this.formatDemon(record.demon, "/demonlist/permalink/" + record.demon.id + "/"); + let demon = this.formatDemon(record.demon, record.video ?? ("/demonlist/permalink/" + record.demon.id + "/")); if (record.progress !== 100) { demon.appendChild( document.createTextNode(" (" + record.progress + "%)") diff --git a/pointercrate-demonlist/Cargo.toml b/pointercrate-demonlist/Cargo.toml index 9febad39a..4cd30db31 100644 --- a/pointercrate-demonlist/Cargo.toml +++ b/pointercrate-demonlist/Cargo.toml @@ -1,20 +1,22 @@ [package] name = "pointercrate-demonlist" version = "0.1.0" -edition = "2021" +authors.workspace = true +edition.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -serde = "1.0.118" -sqlx = { version = "0.6", default-features = false, features = [ "runtime-tokio-native-tls", "macros", "postgres", "chrono", "offline" ] } -derive_more = "0.99.11" +serde = "1.0.203" +sqlx = { version = "0.7", default-features = false, features = [ "runtime-tokio-native-tls", "macros", "postgres", "chrono" ] } +derive_more = "0.99.18" pointercrate-core = {path = "../pointercrate-core"} -log = "0.4.11" +pointercrate-user = {path = "../pointercrate-user"} +log = "0.4.22" futures = "0.3.8" -chrono = {version = "0.4.10", features = ["serde"]} -url = "2.2.0" +chrono = {version = "0.4.38", features = ["serde"]} +url = "2.5.2" [dev-dependencies] dotenv = "0.15.0" -tokio = "1.20.4" +tokio = "1.38.0" diff --git a/pointercrate-demonlist/sql/paginate_player_ranking.sql b/pointercrate-demonlist/sql/paginate_player_ranking.sql index abc6b48d5..9fd2b94f9 100644 --- a/pointercrate-demonlist/sql/paginate_player_ranking.sql +++ b/pointercrate-demonlist/sql/paginate_player_ranking.sql @@ -1,10 +1,10 @@ -SELECT id, name::TEXT, rank, score, index, nation::TEXT, iso_country_code::TEXT -FROM players_with_score +SELECT index, rank, id, name, score, subdivision, iso_country_code, nation +FROM ranked_players WHERE (index < $1 OR $1 IS NULL) AND (index > $2 OR $2 IS NULL) AND (STRPOS(name, $3::CITEXT) > 0 OR $3 is NULL) AND (nation = $4 OR iso_country_code = $4 OR (nation IS NULL AND $5) OR ($4 IS NULL AND NOT $5)) AND (continent = CAST($6::TEXT AS continent) OR $6 IS NULL) AND (subdivision = $7 OR $7 IS NULL) -ORDER BY rank {} +ORDER BY rank {}, id LIMIT $8 \ No newline at end of file diff --git a/pointercrate-demonlist/sql/paginate_players_by_id.sql b/pointercrate-demonlist/sql/paginate_players_by_id.sql index b702c17ce..531affccb 100644 --- a/pointercrate-demonlist/sql/paginate_players_by_id.sql +++ b/pointercrate-demonlist/sql/paginate_players_by_id.sql @@ -1,4 +1,4 @@ -SELECT id, name::TEXT, banned, nation::TEXT, iso_country_code::TEXT +SELECT id, name::TEXT, banned, nation::TEXT, iso_country_code::TEXT, players.score FROM players LEFT OUTER JOIN nationalities ON nationality = iso_country_code WHERE (id < $1 OR $1 IS NULL) diff --git a/pointercrate-demonlist/src/creator/get.rs b/pointercrate-demonlist/src/creator/get.rs index 8d3bf0ec0..d9796ef2a 100644 --- a/pointercrate-demonlist/src/creator/get.rs +++ b/pointercrate-demonlist/src/creator/get.rs @@ -34,7 +34,7 @@ impl Creator { pub async fn creators_of(demon: &MinimalDemon, connection: &mut PgConnection) -> Result> { let mut stream = sqlx::query!( - r#"SELECT players.id, players.name AS "name: String", players.banned FROM players INNER JOIN creators ON players.id = creators.creator WHERE + r#"SELECT players.id, players.name, players.banned FROM players INNER JOIN creators ON players.id = creators.creator WHERE creators.demon = $1"#, demon.id ) @@ -57,7 +57,7 @@ pub async fn creators_of(demon: &MinimalDemon, connection: &mut PgConnection) -> pub async fn created_by(player_id: i32, connection: &mut PgConnection) -> Result> { query_many_demons!( connection, - r#"SELECT demons.id, demons.name as "name: String", demons.position FROM demons INNER JOIN creators ON demons.id = creators.demon WHERE + r#"SELECT demons.id, demons.name, demons.position FROM demons INNER JOIN creators ON demons.id = creators.demon WHERE creators.creator=$1"#, player_id ) diff --git a/pointercrate-demonlist/src/demon/audit.rs b/pointercrate-demonlist/src/demon/audit.rs index 031ea650c..519cfa2b6 100644 --- a/pointercrate-demonlist/src/demon/audit.rs +++ b/pointercrate-demonlist/src/demon/audit.rs @@ -54,10 +54,13 @@ pub async fn movement_log_for_demon(demon_id: i32, connection: &mut PgConnection while let Some(row) = addition_stream.next().await { let row = row?; - additions.insert(row.time, NamedId { - id: row.id, - name: row.name, - }); + additions.insert( + row.time, + NamedId { + id: row.id, + name: row.name, + }, + ); } } @@ -71,10 +74,13 @@ pub async fn movement_log_for_demon(demon_id: i32, connection: &mut PgConnection while let Some(row) = move_stream.next().await { let row = row?; - all_moves.insert(row.time, NamedId { - id: row.id, - name: row.name, - }); + all_moves.insert( + row.time, + NamedId { + id: row.id, + name: row.name, + }, + ); } } @@ -82,20 +88,19 @@ pub async fn movement_log_for_demon(demon_id: i32, connection: &mut PgConnection let time = log_entry.time; match log_entry.r#type { - AuditLogEntryType::Addition => - movement_log.push(MovementLogEntry { - time, - new_position: None, - reason: MovementReason::Added, - }), - AuditLogEntryType::Modification(data) => + AuditLogEntryType::Addition => movement_log.push(MovementLogEntry { + time, + new_position: None, + reason: MovementReason::Added, + }), + AuditLogEntryType::Modification(data) => { if let Some(old_position) = data.position { // whenever a demon is moved, its position is first set to -1, all other demons are shifted, and // then it is moved to its final position however since audit log entries with // the same timestamp are not ordered in any way, trying to use this entry to draw conclusions about // whether the demon we're looking at was moved leads to some very convoluted code . if old_position == -1 { - continue + continue; } // update the previous entry's "new_position" field @@ -103,49 +108,45 @@ pub async fn movement_log_for_demon(demon_id: i32, connection: &mut PgConnection // if the time part of the datetime object is just zeros, the log entry was generated from deltas, // meaning we can't figure out reasons accurately - if time.time() == NaiveTime::from_hms(12, 0, 0) { + if time.time() == NaiveTime::from_hms_opt(12, 0, 0).unwrap() { movement_log.push(MovementLogEntry { reason: MovementReason::Unknown, time, new_position: None, }); - continue + continue; } let moved = all_moves.get(&time); match moved { - Some(id) if id.id == demon_id => - movement_log.push(MovementLogEntry { - reason: MovementReason::Moved, - time, - new_position: None, - }), - Some(id) => - movement_log.push(MovementLogEntry { - reason: MovementReason::OtherMoved { other: id.clone() }, - new_position: Some(old_position), - time, - }), + Some(id) if id.id == demon_id => movement_log.push(MovementLogEntry { + reason: MovementReason::Moved, + time, + new_position: None, + }), + Some(id) => movement_log.push(MovementLogEntry { + reason: MovementReason::OtherMoved { other: id.clone() }, + new_position: Some(old_position), + time, + }), None => { let added_demon = additions.get(&time); match added_demon { - Some(added_demon) => - movement_log.push(MovementLogEntry { - reason: MovementReason::OtherAddedAbove { - other: added_demon.clone(), - }, - new_position: None, - time, - }), - None => - movement_log.push(MovementLogEntry { - reason: MovementReason::Unknown, - new_position: None, - time, - }), + Some(added_demon) => movement_log.push(MovementLogEntry { + reason: MovementReason::OtherAddedAbove { + other: added_demon.clone(), + }, + new_position: None, + time, + }), + None => movement_log.push(MovementLogEntry { + reason: MovementReason::Unknown, + new_position: None, + time, + }), } }, } @@ -160,7 +161,8 @@ pub async fn movement_log_for_demon(demon_id: i32, connection: &mut PgConnection // then this movement is the shift induced by that addition // otherwise, we do not know (the log entry is from before we kept track of // audit logs accurately) :( - }, + } + }, AuditLogEntryType::Deletion => unreachable!(), } } @@ -238,19 +240,17 @@ pub async fn audit_log_for_demon(demon_id: i32, connection: &mut PgConnection) - requirement: row.requirement, video: row.video, verifier: match row.verifier { - Some(id) => - Some(NamedId { - name: row.verifier_name, - id, - }), + Some(id) => Some(NamedId { + name: row.verifier_name, + id, + }), None => None, }, publisher: match row.publisher { - Some(id) => - Some(NamedId { - name: row.publisher_name, - id, - }), + Some(id) => Some(NamedId { + name: row.publisher_name, + id, + }), None => None, }, }), diff --git a/pointercrate-demonlist/src/demon/get.rs b/pointercrate-demonlist/src/demon/get.rs index 9cf663673..3a85fd835 100644 --- a/pointercrate-demonlist/src/demon/get.rs +++ b/pointercrate-demonlist/src/demon/get.rs @@ -5,21 +5,19 @@ use crate::{ player::DatabasePlayer, record::approved_records_on, }; -use chrono::{DateTime, FixedOffset}; +use chrono::NaiveDateTime; use futures::StreamExt; use sqlx::{Error, PgConnection}; impl MinimalDemon { pub async fn by_id(id: i32, connection: &mut PgConnection) -> Result { - let row = sqlx::query!(r#"SELECT id, name as "name: String", position FROM demons WHERE id = $1"#, id) + sqlx::query_as!(MinimalDemon, r#"SELECT id, name, position FROM demons WHERE id = $1"#, id) .fetch_one(connection) - .await?; - - Ok(MinimalDemon { - id, - position: row.position, - name: row.name, - }) + .await + .map_err(|err| match err { + Error::RowNotFound => DemonlistError::DemonNotFound { demon_id: id }, + _ => err.into(), + }) } pub async fn by_position(position: i16, connection: &mut PgConnection) -> Result { @@ -38,11 +36,7 @@ impl MinimalDemon { } pub async fn by_name(name: &str, connection: &mut PgConnection) -> Result { - let mut stream = sqlx::query!( - r#"SELECT id, name as "name: String", position FROM demons WHERE name = cast($1::text as citext)"#, // FIXME(sqlx) once CITEXT is supported - name.to_string() - ) - .fetch(connection); + let mut stream = sqlx::query!(r#"SELECT id, name, position FROM demons WHERE name = $1"#, name.to_string()).fetch(connection); let mut demon = None; let mut further_demons = Vec::new(); @@ -66,10 +60,9 @@ impl MinimalDemon { if further_demons.is_empty() { match demon { Some(demon) => Ok(demon), - None => - Err(DemonlistError::DemonNotFoundName { - demon_name: name.to_string(), - }), + None => Err(DemonlistError::DemonNotFoundName { + demon_name: name.to_string(), + }), } } else { further_demons.extend(demon); @@ -107,11 +100,9 @@ impl Demon { .fetch_one(connection) .await .map(Into::into) - .map_err(|err| { - match err { - Error::RowNotFound => DemonlistError::DemonNotFound { demon_id: id }, - _ => err.into(), - } + .map_err(|err| match err { + Error::RowNotFound => DemonlistError::DemonNotFound { demon_id: id }, + _ => err.into(), }) } @@ -120,28 +111,20 @@ impl Demon { .fetch_one(connection) .await .map(Into::into) - .map_err(|err| { - match err { - Error::RowNotFound => DemonlistError::DemonNotFoundPosition { demon_position: position }, - _ => err.into(), - } + .map_err(|err| match err { + Error::RowNotFound => DemonlistError::DemonNotFoundPosition { demon_position: position }, + _ => err.into(), }) } } macro_rules! query_many_demons { ($connection:expr, $query:expr, $id:expr) => {{ - let mut stream = sqlx::query!($query, $id).fetch($connection); + let mut stream = sqlx::query_as!(MinimalDemon, $query, $id).fetch($connection); let mut demons = Vec::new(); while let Some(row) = stream.next().await { - let row = row?; - - demons.push(MinimalDemon { - id: row.id, - position: row.position, - name: row.name, - }) + demons.push(row?) } Ok(demons) @@ -151,7 +134,7 @@ macro_rules! query_many_demons { pub async fn published_by(player: &DatabasePlayer, connection: &mut PgConnection) -> Result> { query_many_demons!( connection, - r#"SELECT id, name AS "name: String", position FROM demons WHERE publisher = $1"#, + r#"SELECT id, name, position FROM demons WHERE publisher = $1"#, player.id ) } @@ -159,7 +142,7 @@ pub async fn published_by(player: &DatabasePlayer, connection: &mut PgConnection pub async fn verified_by(player: &DatabasePlayer, connection: &mut PgConnection) -> Result> { query_many_demons!( connection, - r#"SELECT id, name as "name: String", position FROM demons WHERE verifier = $1"#, + r#"SELECT id, name, position FROM demons WHERE verifier = $1"#, player.id ) } @@ -215,8 +198,8 @@ pub async fn current_list(connection: &mut PgConnection) -> Result> { .collect()) } -pub async fn list_at(connection: &mut PgConnection, at: DateTime) -> Result> { - let mut stream = sqlx::query_file!("sql/all_demons_at.sql", at.naive_utc()).fetch(connection); +pub async fn list_at(connection: &mut PgConnection, at: NaiveDateTime) -> Result> { + let mut stream = sqlx::query_file!("sql/all_demons_at.sql", at).fetch(connection); let mut demons = Vec::new(); while let Some(row) = stream.next().await { diff --git a/pointercrate-demonlist/src/demon/mod.rs b/pointercrate-demonlist/src/demon/mod.rs index e66dfc8cd..328b16d39 100644 --- a/pointercrate-demonlist/src/demon/mod.rs +++ b/pointercrate-demonlist/src/demon/mod.rs @@ -11,8 +11,8 @@ use crate::{ }; use derive_more::Display; use log::info; -use pointercrate_core::{error::CoreError, etag::Taggable}; -use serde::Serialize; +use pointercrate_core::etag::Taggable; +use serde::{Deserialize, Serialize}; use sqlx::PgConnection; use std::{ collections::hash_map::DefaultHasher, @@ -32,7 +32,7 @@ pub struct TimeShiftedDemon { } /// Struct modelling a demon. These objects are returned from the paginating `/demons/` endpoint -#[derive(Debug, Serialize, Hash, Display, Eq, PartialEq)] +#[derive(Debug, Deserialize, Serialize, Hash, Display, Eq, PartialEq)] #[display(fmt = "{}", base)] pub struct Demon { #[serde(flatten)] @@ -60,7 +60,7 @@ pub struct Demon { } /// Absolutely minimal representation of a demon to be sent when a demon is part of another object -#[derive(Debug, Hash, Serialize, Display, PartialEq, Eq, Clone)] +#[derive(Debug, Hash, Serialize, Deserialize, Display, PartialEq, Eq, Clone)] #[display(fmt = "{} (at {})", name, position)] pub struct MinimalDemon { /// The [`Demon`]'s unique internal pointercrate ID @@ -81,7 +81,7 @@ pub struct MinimalDemon { /// /// In addition to containing publisher/verifier information it also contains a list of the demon's /// creators and a list of accepted records -#[derive(Debug, Serialize, Display, PartialEq, Eq, Hash)] +#[derive(Debug, Serialize, Deserialize, Display, PartialEq, Eq, Hash)] #[display(fmt = "{}", demon)] pub struct FullDemon { #[serde(flatten)] @@ -169,7 +169,7 @@ impl FullDemon { impl Demon { pub fn validate_requirement(requirement: i16) -> Result<()> { if !(0..=100).contains(&requirement) { - return Err(DemonlistError::InvalidRequirement) + return Err(DemonlistError::InvalidRequirement); } Ok(()) @@ -178,10 +178,10 @@ impl Demon { pub async fn validate_position(position: i16, connection: &mut PgConnection) -> Result<()> { // To prevent holes from being created in the list, the new position must lie between 1 and (current // last position + 1), inclusive - let maximal_position = Demon::max_position(connection).await.unwrap_or(0) + 1; + let maximal_position = Demon::max_position(connection).await? + 1; if position > maximal_position || position < 1 { - return Err(DemonlistError::InvalidPosition { maximal: maximal_position }) + return Err(DemonlistError::InvalidPosition { maximal: maximal_position }); } Ok(()) @@ -199,40 +199,19 @@ impl Demon { Ok(()) } - /// Decrements the position of all demons with positions equal to or smaller than the given one, - /// by one. - async fn shift_up(until: i16, connection: &mut PgConnection) -> Result<()> { - info!("Shifting up all demons until {}", until); - - sqlx::query!("UPDATE demons SET position = position - 1 WHERE position <= $1", until) - .execute(connection) - .await?; - - Ok(()) - } - - /// Gets the current max position a demon has, or `CoreError::NotFound` if there are no demons + /// Gets the current max position a demon has, or `0` if there are no demons /// in the database pub async fn max_position(connection: &mut PgConnection) -> Result { - sqlx::query!("SELECT MAX(position) as max_position FROM demons") + Ok(sqlx::query!("SELECT MAX(position) as max_position FROM demons") .fetch_one(connection) .await? .max_position - .ok_or_else(|| CoreError::NotFound.into()) - } - - /// Gets the maximal and minimal submitter id currently in use - /// - /// The returned tuple is of the form (max, min) - pub async fn extremal_demon_ids(connection: &mut PgConnection) -> Result<(i32, i32)> { - let row = sqlx::query!(r#"SELECT MAX(id) AS "max_id!: i32", MIN(id) AS "min_id!: i32" FROM demons"#) - .fetch_one(connection) - .await?; - Ok((row.max_id, row.min_id)) + .unwrap_or(0)) } pub fn score(&self, progress: i16) -> f64 { - let mut score = 150f64 * f64::exp((1f64 - f64::from(self.base.position)) * (1f64 / 30f64).ln() / (-149f64)); + let position = self.base.position; + let mut score = 150f64 * f64::exp((1f64 - f64::from(position)) * (1f64 / 30f64).ln() / (-149f64)); if progress != 100 { score *= 0.25f64 + (f64::from(progress) - f64::from(self.requirement)) / (100f64 - f64::from(self.requirement)) * 0.25f64 diff --git a/pointercrate-demonlist/src/demon/paginate.rs b/pointercrate-demonlist/src/demon/paginate.rs index b70c179b0..b27188f1e 100644 --- a/pointercrate-demonlist/src/demon/paginate.rs +++ b/pointercrate-demonlist/src/demon/paginate.rs @@ -1,25 +1,20 @@ use crate::{ demon::{Demon, MinimalDemon}, - error::Result, player::DatabasePlayer, }; use futures::stream::StreamExt; -use pointercrate_core::{error::CoreError, util::non_nullable}; +use pointercrate_core::{ + first_and_last, + pagination::{PageContext, Paginatable, PaginationParameters, PaginationQuery, __pagination_compat}, + util::non_nullable, +}; use serde::{Deserialize, Serialize}; use sqlx::{PgConnection, Row}; #[derive(Serialize, Deserialize, Clone, Debug)] pub struct DemonIdPagination { - #[serde(default, deserialize_with = "non_nullable")] - #[serde(rename = "before")] - pub before_id: Option, - - #[serde(default, deserialize_with = "non_nullable")] - #[serde(rename = "after")] - pub after_id: Option, - - #[serde(default, deserialize_with = "non_nullable")] - pub limit: Option, + #[serde(flatten)] + pub params: PaginationParameters, #[serde(default, deserialize_with = "non_nullable")] name: Option, @@ -49,42 +44,41 @@ pub struct DemonIdPagination { requirement_lt: Option, } -impl DemonIdPagination { - pub async fn page(&self, connection: &mut PgConnection) -> Result> { - if let Some(limit) = self.limit { - if !(1..=100).contains(&limit) { - return Err(CoreError::InvalidPaginationLimit.into()) - } - } +impl PaginationQuery for DemonIdPagination { + fn parameters(&self) -> PaginationParameters { + self.params + } - if let (Some(after), Some(before)) = (self.before_id, self.after_id) { - if after < before { - return Err(CoreError::AfterSmallerBefore.into()) - } + fn with_parameters(&self, parameters: PaginationParameters) -> Self { + Self { + params: parameters, + ..self.clone() } + } +} + +impl Paginatable for Demon { + first_and_last!("demons"); - let order = if self.after_id.is_none() && self.before_id.is_some() { - "DESC" - } else { - "ASC" - }; + async fn page(query: &DemonIdPagination, connection: &mut PgConnection) -> Result<(Vec, PageContext), sqlx::Error> { + let order = query.params.order(); - let query = format!(include_str!("../../sql/paginate_demons_by_id.sql"), order); + let sql_query = format!(include_str!("../../sql/paginate_demons_by_id.sql"), order); // FIXME(sqlx) once CITEXT is supported - let mut stream = sqlx::query(&query) - .bind(self.before_id) - .bind(self.after_id) - .bind(self.name.as_deref()) - .bind(self.requirement) - .bind(self.requirement_lt) - .bind(self.requirement_gt) - .bind(self.verifier_id) - .bind(self.verifier_name.as_deref()) - .bind(self.publisher_id) - .bind(self.publisher_name.as_deref()) - .bind(self.name_contains.as_deref()) - .bind(self.limit.unwrap_or(50) as i32 + 1) + let mut stream = sqlx::query(&sql_query) + .bind(query.params.before) + .bind(query.params.after) + .bind(query.name.as_deref()) + .bind(query.requirement) + .bind(query.requirement_lt) + .bind(query.requirement_gt) + .bind(query.verifier_id) + .bind(query.verifier_name.as_deref()) + .bind(query.publisher_id) + .bind(query.publisher_name.as_deref()) + .bind(query.name_contains.as_deref()) + .bind(query.params.limit + 1) .fetch(connection); let mut demons = Vec::new(); @@ -117,87 +111,82 @@ impl DemonIdPagination { }) } - Ok(demons) + Ok(__pagination_compat(&query.params, demons)) + } + + fn pagination_id(&self) -> i32 { + self.base.id } } -#[derive(Serialize, Deserialize, Clone, Debug)] +#[derive(Serialize, Deserialize, Clone, Debug, Default)] pub struct DemonPositionPagination { - #[serde(default, deserialize_with = "non_nullable")] - #[serde(rename = "before")] - pub before_position: Option, + #[serde(flatten)] + pub params: PaginationParameters, #[serde(default, deserialize_with = "non_nullable")] - #[serde(rename = "after")] - pub after_position: Option, + pub name: Option, #[serde(default, deserialize_with = "non_nullable")] - pub limit: Option, + pub name_contains: Option, #[serde(default, deserialize_with = "non_nullable")] - name: Option, + pub requirement: Option, #[serde(default, deserialize_with = "non_nullable")] - name_contains: Option, - + pub verifier_id: Option, #[serde(default, deserialize_with = "non_nullable")] - requirement: Option, + pub publisher_id: Option, #[serde(default, deserialize_with = "non_nullable")] - verifier_id: Option, + pub verifier_name: Option, #[serde(default, deserialize_with = "non_nullable")] - publisher_id: Option, - - #[serde(default, deserialize_with = "non_nullable")] - verifier_name: Option, - #[serde(default, deserialize_with = "non_nullable")] - publisher_name: Option, + pub publisher_name: Option, #[serde(default, deserialize_with = "non_nullable")] #[serde(rename = "requirement__gt")] - requirement_gt: Option, + pub requirement_gt: Option, #[serde(default, deserialize_with = "non_nullable")] #[serde(rename = "requirement__lt")] - requirement_lt: Option, + pub requirement_lt: Option, } -impl DemonPositionPagination { - pub async fn page(&self, connection: &mut PgConnection) -> Result> { - if let Some(limit) = self.limit { - if !(1..=100).contains(&limit) { - return Err(CoreError::InvalidPaginationLimit.into()) - } - } +impl PaginationQuery for DemonPositionPagination { + fn parameters(&self) -> PaginationParameters { + self.params + } - if let (Some(after), Some(before)) = (self.before_position, self.after_position) { - if after < before { - return Err(CoreError::AfterSmallerBefore.into()) - } + fn with_parameters(&self, parameters: PaginationParameters) -> Self { + Self { + params: parameters, + ..self.clone() } + } +} + +impl Paginatable for Demon { + first_and_last!("demons", "position"); - let order = if self.after_position.is_none() && self.before_position.is_some() { - "DESC" - } else { - "ASC" - }; + async fn page(query: &DemonPositionPagination, connection: &mut PgConnection) -> Result<(Vec, PageContext), sqlx::Error> { + let order = query.params.order(); - let query = format!(include_str!("../../sql/paginate_demons_by_position.sql"), order); + let sql_query = format!(include_str!("../../sql/paginate_demons_by_position.sql"), order); // FIXME(sqlx) once CITEXT is supported - let mut stream = sqlx::query(&query) - .bind(self.before_position) - .bind(self.after_position) - .bind(self.name.as_deref()) - .bind(self.requirement) - .bind(self.requirement_lt) - .bind(self.requirement_gt) - .bind(self.verifier_id) - .bind(self.verifier_name.as_deref()) - .bind(self.publisher_id) - .bind(self.publisher_name.as_deref()) - .bind(self.name_contains.as_deref()) - .bind(self.limit.unwrap_or(50) as i32 + 1) + let mut stream = sqlx::query(&sql_query) + .bind(query.params.before) + .bind(query.params.after) + .bind(query.name.as_deref()) + .bind(query.requirement) + .bind(query.requirement_lt) + .bind(query.requirement_gt) + .bind(query.verifier_id) + .bind(query.verifier_name.as_deref()) + .bind(query.publisher_id) + .bind(query.publisher_name.as_deref()) + .bind(query.name_contains.as_deref()) + .bind(query.params.limit + 1) .fetch(connection); let mut demons = Vec::new(); @@ -230,6 +219,10 @@ impl DemonPositionPagination { }) } - Ok(demons) + Ok(__pagination_compat(&query.params, demons)) + } + + fn pagination_id(&self) -> i32 { + self.base.position as i32 } } diff --git a/pointercrate-demonlist/src/demon/patch.rs b/pointercrate-demonlist/src/demon/patch.rs index 1bcdef816..c6cc16fce 100644 --- a/pointercrate-demonlist/src/demon/patch.rs +++ b/pointercrate-demonlist/src/demon/patch.rs @@ -1,7 +1,7 @@ use crate::{ demon::{Demon, FullDemon, MinimalDemon}, error::{DemonlistError, Result}, - player::DatabasePlayer, + player::{recompute_scores, DatabasePlayer}, }; use log::{debug, info, warn}; use pointercrate_core::util::{non_nullable, nullable}; @@ -95,9 +95,12 @@ impl Demon { pub async fn set_verifier(&mut self, verifier: DatabasePlayer, connection: &mut PgConnection) -> Result<()> { if verifier.id != self.verifier.id { sqlx::query!("UPDATE demons SET verifier = $1 WHERE id = $2", verifier.id, self.base.id) - .execute(connection) + .execute(&mut *connection) .await?; + self.verifier.update_score(connection).await?; + verifier.update_score(connection).await?; + self.verifier = verifier; } @@ -118,7 +121,7 @@ impl Demon { pub async fn set_requirement(&mut self, requirement: i16, connection: &mut PgConnection) -> Result<()> { if !(0..=100).contains(&requirement) { - return Err(DemonlistError::InvalidRequirement) + return Err(DemonlistError::InvalidRequirement); } // Delete associated notes @@ -186,16 +189,17 @@ impl MinimalDemon { /// Validates that `to` is `> 0` and less than or equal to the currently highest position on the /// list (to preven "holes") pub async fn mv(&mut self, to: i16, connection: &mut PgConnection) -> Result<()> { + // This returns 0 if the list is empty, but if the list is empty then there is no demon for us to do a move with, so we will never get here anyway. let maximal_position = Demon::max_position(connection).await?; if to > maximal_position || to < 1 { - return Err(DemonlistError::InvalidPosition { maximal: maximal_position }) + return Err(DemonlistError::InvalidPosition { maximal: maximal_position }); } if to == self.position { warn!("No-op move of demon {}", self); - return Ok(()) + return Ok(()); } // FIXME: Temporarily move the demon somewhere else because otherwise the unique constraints @@ -236,13 +240,15 @@ impl MinimalDemon { debug!("Performing actual move to position {}", to); sqlx::query!("UPDATE demons SET position = $2 WHERE id = $1", self.id, to) - .execute(connection) + .execute(&mut *connection) .await?; info!("Moved demon {} from {} to {} successfully!", self, self.position, to); self.position = to; + recompute_scores(connection).await?; + Ok(()) } } diff --git a/pointercrate-demonlist/src/demon/post.rs b/pointercrate-demonlist/src/demon/post.rs index 7d071a75f..d951a44cd 100644 --- a/pointercrate-demonlist/src/demon/post.rs +++ b/pointercrate-demonlist/src/demon/post.rs @@ -2,7 +2,7 @@ use crate::{ creator::Creator, demon::{Demon, FullDemon, MinimalDemon}, error::Result, - player::DatabasePlayer, + player::{recompute_scores, DatabasePlayer}, }; use log::info; use serde::Deserialize; @@ -74,6 +74,8 @@ impl FullDemon { creators.push(player); } + recompute_scores(connection).await?; + Ok(FullDemon { demon, creators, diff --git a/pointercrate-demonlist/src/error.rs b/pointercrate-demonlist/src/error.rs index 657fa6566..c6bf5e7d0 100644 --- a/pointercrate-demonlist/src/error.rs +++ b/pointercrate-demonlist/src/error.rs @@ -127,7 +127,7 @@ pub enum DemonlistError { /// `422 UNPROCESSABLE ENTITY` variant /// /// Error Code `42217` - #[display(fmt = "This record is already {}", status)] + #[display(fmt = "This record is already {} (existing record: {})", status, existing)] SubmissionExists { /// The [`RecordStatus`] of the existing [`Record`] status: RecordStatus, diff --git a/pointercrate-demonlist/src/lib.rs b/pointercrate-demonlist/src/lib.rs index 064ed4aeb..5d8df2e38 100644 --- a/pointercrate-demonlist/src/lib.rs +++ b/pointercrate-demonlist/src/lib.rs @@ -1,4 +1,5 @@ -use pointercrate_core::permission::Permission; +use pointercrate_core::permission::{Permission, PermissionsManager}; +use pointercrate_user::ADMINISTRATOR; #[macro_use] pub mod demon; @@ -14,3 +15,14 @@ mod video; pub const LIST_HELPER: Permission = Permission::new("List Helper", 0x2); pub const LIST_MODERATOR: Permission = Permission::new("List Moderator", 0x4); pub const LIST_ADMINISTRATOR: Permission = Permission::new("List Administrator", 0x8); + +pub fn default_permissions_manager() -> PermissionsManager { + PermissionsManager::new(vec![ADMINISTRATOR, LIST_HELPER, LIST_MODERATOR, LIST_ADMINISTRATOR]) + .assigns(ADMINISTRATOR, LIST_ADMINISTRATOR) + .assigns(ADMINISTRATOR, LIST_MODERATOR) + .assigns(ADMINISTRATOR, LIST_HELPER) + .assigns(LIST_ADMINISTRATOR, LIST_MODERATOR) + .assigns(LIST_ADMINISTRATOR, LIST_HELPER) + .implies(LIST_ADMINISTRATOR, LIST_MODERATOR) + .implies(LIST_MODERATOR, LIST_HELPER) +} diff --git a/pointercrate-demonlist/src/nationality/get.rs b/pointercrate-demonlist/src/nationality/get.rs index 632d494e3..72d8de006 100644 --- a/pointercrate-demonlist/src/nationality/get.rs +++ b/pointercrate-demonlist/src/nationality/get.rs @@ -52,6 +52,28 @@ impl Nationality { }) } + pub async fn subdivision_by_code(&self, code: &str, connection: &mut PgConnection) -> Result { + let result = sqlx::query!( + "SELECT name FROM subdivisions WHERE iso_code = $1 AND nation = $2", + code, + self.iso_country_code + ) + .fetch_one(&mut *connection) + .await; + + match result { + Ok(row) => Ok(Subdivision { + iso_code: code.to_string(), + name: row.name, + }), + Err(sqlx::Error::RowNotFound) => Err(DemonlistError::SubdivisionNotFound { + nation_code: self.iso_country_code.clone(), + subdivision_code: code.to_string(), + }), + Err(err) => Err(err.into()), + } + } + pub async fn all(connection: &mut PgConnection) -> Result> { let mut stream = sqlx::query!(r#"SELECT nation as "nation: String", iso_country_code as "iso_country_code: String" FROM nationalities"#) @@ -137,13 +159,12 @@ pub async fn created_in(nation: &Nationality, connection: &mut PgConnection) -> match creations.last_mut() { Some(mini_demon) if mini_demon.demon == row.demon_name => mini_demon.players.push(row.player_name), - _ => - creations.push(MiniDemonWithPlayers { - id: row.demon, - demon: row.demon_name, - position: row.position, - players: vec![row.player_name], - }), + _ => creations.push(MiniDemonWithPlayers { + id: row.demon, + demon: row.demon_name, + position: row.position, + players: vec![row.player_name], + }), } } @@ -204,14 +225,13 @@ pub async fn best_records_in(nation: &Nationality, connection: &mut PgConnection match records.last_mut() { Some(record) if record.demon == row.demon_name => record.players.push(row.player_name), - _ => - records.push(BestRecord { - id: row.demon_id, - demon: row.demon_name, - position: row.position, - progress: row.progress, - players: vec![row.player_name], - }), + _ => records.push(BestRecord { + id: row.demon_id, + demon: row.demon_name, + position: row.position, + progress: row.progress, + players: vec![row.player_name], + }), } } diff --git a/pointercrate-demonlist/src/nationality/mod.rs b/pointercrate-demonlist/src/nationality/mod.rs index de084fc23..67a00f17e 100644 --- a/pointercrate-demonlist/src/nationality/mod.rs +++ b/pointercrate-demonlist/src/nationality/mod.rs @@ -3,11 +3,12 @@ use derive_more::Constructor; pub use paginate::{NationalityRankingPagination, RankedNation}; use pointercrate_core::etag::Taggable; use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use sqlx::PgConnection; mod get; mod paginate; -#[derive(Debug, PartialEq, Eq, Serialize, Hash, Constructor, Deserialize)] +#[derive(Debug, PartialEq, Eq, Serialize, Hash, Constructor, Deserialize, Clone)] pub struct Nationality { #[serde(rename = "country_code")] pub iso_country_code: String, @@ -101,11 +102,10 @@ impl<'de> Deserialize<'de> for Continent { "north america" => Ok(Continent::NorthAmerica), "south america" => Ok(Continent::SouthAmerica), "central america" => Ok(Continent::MiddleAmerica), - _ => - Err(serde::de::Error::invalid_value( - serde::de::Unexpected::Str(&string), - &"'Asia', 'Europe', 'Australia', 'Africa', 'North America', 'South America' or 'Central America'", - )), + _ => Err(serde::de::Error::invalid_value( + serde::de::Unexpected::Str(&string), + &"'Asia', 'Europe', 'Australia', 'Africa', 'North America', 'South America' or 'Central America'", + )), } } } @@ -126,3 +126,31 @@ impl Serialize for Continent { }) } } + +impl Nationality { + /// Checks whether [`self`] and `other` refer to the same country (but potentially different subdivisions) + pub fn same_country_as(&self, other: &Nationality) -> bool { + self.iso_country_code == other.iso_country_code + } + + /// Updates the score for this [`Nationality`] and contained [`Subdivision`] (if set). + pub async fn update_nation_score(&self, connection: &mut PgConnection) -> Result<(), sqlx::Error> { + sqlx::query!( + "UPDATE nationalities SET score = coalesce(score_of_nation($1), 0) WHERE iso_country_code = $1", + self.iso_country_code + ) + .execute(&mut *connection) + .await?; + if let Some(ref subdivision) = self.subdivision { + sqlx::query!( + "UPDATE subdivisions SET score = coalesce(score_of_subdivision($1, $2), 0) WHERE nation = $1 AND iso_code = $2", + self.iso_country_code, + subdivision.iso_code + ) + .execute(&mut *connection) + .await?; + } + + Ok(()) + } +} diff --git a/pointercrate-demonlist/src/nationality/paginate.rs b/pointercrate-demonlist/src/nationality/paginate.rs index 7d6be99f6..c024ae42e 100644 --- a/pointercrate-demonlist/src/nationality/paginate.rs +++ b/pointercrate-demonlist/src/nationality/paginate.rs @@ -27,8 +27,8 @@ pub struct RankedNation { impl NationalityRankingPagination { pub async fn page(&self, connection: &mut PgConnection) -> Result> { let mut stream = sqlx::query!( - r#"SELECT rank as "rank!", score as "score!", nation::text as "nation!", iso_country_code as "iso_country_code!" FROM nations_with_score WHERE (STRPOS(nation, CAST($1::TEXT AS CITEXT)) > - 0 OR $1 is NULL) AND (continent = CAST($2::TEXT AS continent) OR $2 IS NULL)"#, + r#"SELECT rank as "rank!", score as "score!", nation as "nation!", iso_country_code as "iso_country_code!" FROM ranked_nations WHERE (STRPOS(nation, $1) > + 0 OR $1 is NULL) AND (continent::text = $2 OR $2 IS NULL)"#, self.name_contains, self.continent.map(|c| c.to_sql()) ) diff --git a/pointercrate-demonlist/src/player/claim/get.rs b/pointercrate-demonlist/src/player/claim/get.rs index 17d9e8c3f..bed5afc64 100644 --- a/pointercrate-demonlist/src/player/claim/get.rs +++ b/pointercrate-demonlist/src/player/claim/get.rs @@ -19,13 +19,12 @@ impl PlayerClaim { .fetch_one(connection) .await { - Ok(row) => - Ok(Some(PlayerClaim { - user_id: row.member_id, - player_id, - verified: true, - lock_submissions: row.lock_submissions, - })), + Ok(row) => Ok(Some(PlayerClaim { + user_id: row.member_id, + player_id, + verified: true, + lock_submissions: row.lock_submissions, + })), Err(sqlx::Error::RowNotFound) => Ok(None), Err(err) => Err(err.into()), } @@ -57,13 +56,12 @@ impl PlayerClaim { pub async fn get(member_id: i32, player_id: i32, connection: &mut PgConnection) -> Result { match PlayerClaim::by_user(member_id, connection).await? { - Some(claim) if claim.player.id == player_id => - Ok(PlayerClaim { - user_id: member_id, - player_id, - verified: claim.verified, - lock_submissions: claim.lock_submissions, - }), + Some(claim) if claim.player.id == player_id => Ok(PlayerClaim { + user_id: member_id, + player_id, + verified: claim.verified, + lock_submissions: claim.lock_submissions, + }), _ => Err(DemonlistError::ClaimNotFound { member_id, player_id }), } } diff --git a/pointercrate-demonlist/src/player/claim/paginate.rs b/pointercrate-demonlist/src/player/claim/paginate.rs index cad6fd54f..2a2aa94fd 100644 --- a/pointercrate-demonlist/src/player/claim/paginate.rs +++ b/pointercrate-demonlist/src/player/claim/paginate.rs @@ -1,21 +1,17 @@ -use crate::error::Result; use futures::StreamExt; -use pointercrate_core::{audit::NamedId, error::CoreError, util::non_nullable}; +use pointercrate_core::{ + audit::NamedId, + first_and_last, + pagination::{PageContext, Paginatable, PaginationParameters, PaginationQuery, __pagination_compat}, + util::non_nullable, +}; use serde::{Deserialize, Serialize}; use sqlx::{PgConnection, Row}; -#[derive(Deserialize, Serialize, Debug)] +#[derive(Deserialize, Serialize, Debug, Clone)] pub struct PlayerClaimPagination { - #[serde(default, deserialize_with = "non_nullable")] - #[serde(rename = "before")] - pub before_id: Option, - - #[serde(default, deserialize_with = "non_nullable")] - #[serde(rename = "after")] - pub after_id: Option, - - #[serde(default, deserialize_with = "non_nullable")] - pub limit: Option, + #[serde(flatten)] + pub params: PaginationParameters, #[serde(default, deserialize_with = "non_nullable")] any_name_contains: Option, @@ -33,43 +29,33 @@ pub struct ListedClaim { verified: bool, } -impl ListedClaim { - pub async fn extremal_ids(connection: &mut PgConnection) -> Result<(i32, i32)> { - let row = sqlx::query!(r#"SELECT MAX(id) AS "max_id!: i32", MIN(id) AS "min_id!: i32" FROM player_claims"#) - .fetch_one(connection) - .await?; // FIXME: crashes on empty table - Ok((row.max_id, row.min_id)) +impl PaginationQuery for PlayerClaimPagination { + fn parameters(&self) -> PaginationParameters { + self.params } -} -impl PlayerClaimPagination { - pub async fn page(&self, connection: &mut PgConnection) -> Result> { - if let Some(limit) = self.limit { - if !(1..=100).contains(&limit) { - return Err(CoreError::InvalidPaginationLimit.into()) - } + fn with_parameters(&self, parameters: PaginationParameters) -> Self { + Self { + params: parameters, + ..self.clone() } + } +} - if let (Some(after), Some(before)) = (self.before_id, self.after_id) { - if after < before { - return Err(CoreError::AfterSmallerBefore.into()) - } - } +impl Paginatable for ListedClaim { + first_and_last!("player_claims"); - let order = if self.after_id.is_none() && self.before_id.is_some() { - "DESC" - } else { - "ASC" - }; + async fn page(query: &PlayerClaimPagination, connection: &mut PgConnection) -> Result<(Vec, PageContext), sqlx::Error> { + let order = query.params.order(); - let query = format!(include_str!("../../../sql/paginate_claims.sql"), order); + let sql_query = format!(include_str!("../../../sql/paginate_claims.sql"), order); - let mut stream = sqlx::query(&query) - .bind(self.before_id) - .bind(self.after_id) - .bind(self.any_name_contains.as_ref()) - .bind(self.verified) - .bind(self.limit.unwrap_or(50) as i32 + 1) + let mut stream = sqlx::query(&sql_query) + .bind(query.params.before) + .bind(query.params.after) + .bind(query.any_name_contains.as_ref()) + .bind(query.verified) + .bind(query.params.limit + 1) .fetch(connection); let mut claims = Vec::new(); @@ -91,6 +77,10 @@ impl PlayerClaimPagination { }) } - Ok(claims) + Ok(__pagination_compat(&query.params, claims)) + } + + fn pagination_id(&self) -> i32 { + self.id } } diff --git a/pointercrate-demonlist/src/player/claim/put.rs b/pointercrate-demonlist/src/player/claim/put.rs index 49a852149..5928a0e27 100644 --- a/pointercrate-demonlist/src/player/claim/put.rs +++ b/pointercrate-demonlist/src/player/claim/put.rs @@ -17,7 +17,7 @@ impl DatabasePlayer { .is_claimed; if is_claimed { - return Err(DemonlistError::AlreadyClaimed) + return Err(DemonlistError::AlreadyClaimed); } // first, clear all claims by the given user diff --git a/pointercrate-demonlist/src/player/get.rs b/pointercrate-demonlist/src/player/get.rs index df481d501..86e17fee8 100644 --- a/pointercrate-demonlist/src/player/get.rs +++ b/pointercrate-demonlist/src/player/get.rs @@ -8,17 +8,6 @@ use crate::{ }; use sqlx::{Error, PgConnection}; -// Required until https://github.com/launchbadge/sqlx/pull/108 is merged -struct FetchedPlayer { - id: i32, - name: String, - banned: bool, - nation: Option, - iso_country_code: Option, - subdivision_name: Option, - subdivision_code: Option, -} - impl Player { pub async fn upgrade(self, connection: &mut PgConnection) -> Result { let records = approved_records_by(&self.base, connection).await?; @@ -36,9 +25,8 @@ impl Player { } pub async fn by_id(id: i32, connection: &mut PgConnection) -> Result { - let result = sqlx::query_as!( - FetchedPlayer, - r#"SELECT id, players.name AS "name: String", banned, nationalities.nation::text, iso_country_code::text, iso_code::text as subdivision_code, subdivisions.name::text as subdivision_name FROM players LEFT OUTER JOIN nationalities ON + let result = sqlx::query!( + r#"SELECT id, players.name, banned, players.score, nationalities.nation::text, iso_country_code::text, iso_code::text as subdivision_code, subdivisions.name::text as subdivision_name FROM players LEFT OUTER JOIN nationalities ON players.nationality = nationalities.iso_country_code LEFT OUTER JOIN subdivisions ON players.subdivision = subdivisions.iso_code WHERE id = $1 AND (subdivisions.nation=nationalities.iso_country_code or players.subdivision is null)"#, id ) @@ -69,6 +57,7 @@ impl Player { name: row.name, banned: row.banned, }, + score: row.score, nationality, }) }, @@ -82,40 +71,26 @@ impl DatabasePlayer { pub async fn by_name(name: &str, connection: &mut PgConnection) -> Result { let name = name.trim(); - let result = sqlx::query!( - "SELECT id, name::text, banned FROM players WHERE name = cast($1::text as citext)", - name.to_string() - ) // FIXME(sqlx) once CITEXT is supported - .fetch_one(connection) - .await; + let result = sqlx::query_as!(DatabasePlayer, "SELECT id, name, banned FROM players WHERE name = $1", name) + .fetch_one(connection) + .await; match result { - Ok(row) => - Ok(DatabasePlayer { - id: row.id, - name: row.name.unwrap(), // FIXME(sqlx) casted columns interpreted as nullable - banned: row.banned, - }), - Err(Error::RowNotFound) => - Err(DemonlistError::PlayerNotFoundName { - player_name: name.to_string(), - }), + Ok(player) => Ok(player), + Err(Error::RowNotFound) => Err(DemonlistError::PlayerNotFoundName { + player_name: name.to_string(), + }), Err(err) => Err(err.into()), } } pub async fn by_id(id: i32, connection: &mut PgConnection) -> Result { - let result = sqlx::query!(r#"SELECT id, name as "name: String", banned FROM players WHERE id = $1"#, id) + let result = sqlx::query_as!(DatabasePlayer, r#"SELECT id, name, banned FROM players WHERE id = $1"#, id) .fetch_one(connection) .await; match result { - Ok(row) => - Ok(DatabasePlayer { - id: row.id, - name: row.name, - banned: row.banned, - }), + Ok(player) => Ok(player), Err(Error::RowNotFound) => Err(DemonlistError::PlayerNotFound { player_id: id }), Err(err) => Err(err.into()), } @@ -126,7 +101,7 @@ impl DatabasePlayer { match Self::by_name(name, connection).await { Err(DemonlistError::PlayerNotFoundName { .. }) => { - let id = sqlx::query!("INSERT INTO players (name) VALUES ($1::text) RETURNING id", name.to_string()) + let id = sqlx::query!("INSERT INTO players (name) VALUES ($1) RETURNING id", name.to_string()) .fetch_one(connection) .await? .id; diff --git a/pointercrate-demonlist/src/player/mod.rs b/pointercrate-demonlist/src/player/mod.rs index 55b61650d..35e8fc7fe 100644 --- a/pointercrate-demonlist/src/player/mod.rs +++ b/pointercrate-demonlist/src/player/mod.rs @@ -1,10 +1,10 @@ pub use self::{ - paginate::{PlayerPagination, RankingPagination}, + paginate::{PlayerPagination, RankedPlayer, RankingPagination}, patch::PatchPlayer, }; -use crate::{demon::MinimalDemon, error::Result, nationality::Nationality, record::MinimalRecordD}; +use crate::{demon::MinimalDemon, nationality::Nationality, record::MinimalRecordD}; use derive_more::Display; -use pointercrate_core::etag::Taggable; +use pointercrate_core::{error::CoreError, etag::Taggable}; use serde::{Deserialize, Serialize}; use sqlx::PgConnection; use std::{ @@ -25,7 +25,7 @@ pub struct DatabasePlayer { pub banned: bool, } -#[derive(Debug, Serialize, Display, PartialEq, Eq, Hash)] +#[derive(Debug, Serialize, Deserialize, Display, PartialEq, Hash)] #[display(fmt = "{}", player)] pub struct FullPlayer { #[serde(flatten)] @@ -36,27 +36,42 @@ pub struct FullPlayer { pub published: Vec, } -#[derive(Debug, PartialEq, Serialize, Display)] -#[display(fmt = "{} (ID: {}) at rank {} with score {}", name, id, rank, score)] -pub struct RankedPlayer { - pub id: i32, - pub name: String, - pub rank: i64, - pub score: f64, - pub nationality: Option, - #[serde(skip)] - pub index: i64, -} - -#[derive(Debug, Eq, Hash, PartialEq, Serialize, Display, Deserialize)] +#[derive(Debug, PartialEq, Serialize, Display, Deserialize)] #[display(fmt = "{}", base)] pub struct Player { #[serde(flatten)] pub base: DatabasePlayer, + /// This [`Player`]'s score on the stats viewer + /// + /// This value is cached in the `score` column of the `players` table, and not computed on-demand! + /// Thus it needs to be updated on any event that can affect a player's score. These are + /// - Record updates + /// * Record status updated (to approved, or from approved) + /// * Record progress updated + /// * Record holder updated + /// * Record Added + /// - Demon updates + /// * Demon movement/addition (recompute all scores) + /// * Demon requirement updated (recompute all scores) + /// * Demon verifier updated + /// - Player updates + /// * Player banned + /// * Player objects merged + pub score: f64, pub nationality: Option, } +// `f64` does not implement hash. Most things in the pointercrate frontend only display score with an accuracy of two digits after the dot, +// so hashing only this part should be fine for ETag purposes. +impl Hash for Player { + fn hash(&self, state: &mut H) { + self.base.hash(state); + ((self.score * 100f64) as u64).hash(state); + self.nationality.hash(state); + } +} + impl Taggable for FullPlayer { fn patch_part(&self) -> u64 { let mut hasher = DefaultHasher::new(); @@ -65,26 +80,27 @@ impl Taggable for FullPlayer { } } -impl RankedPlayer { - /// Gets the highest index value generated by the `players_with_score` view - pub async fn max_index(connection: &mut PgConnection) -> Result { - Ok( - sqlx::query!(r#"SELECT COALESCE(MAX(index), 0) AS "max_index!: i64" FROM players_with_score"#) - .fetch_one(connection) - .await? - .max_index, +impl DatabasePlayer { + /// Recomputes this player's score and updates it in the database. + pub async fn update_score(&self, connection: &mut PgConnection) -> Result { + // No need to specially handle banned players - they have no approved records, so `score_of_player` will return 0 + let new_score = sqlx::query!( + "UPDATE players SET score = coalesce(score_of_player($1), 0) WHERE id = $1 RETURNING score", + self.id ) + .fetch_one(&mut *connection) + .await?; + + sqlx::query!("UPDATE nationalities SET score = coalesce(score_of_nation(nationalities.iso_country_code), 0) FROM players WHERE players.id = $1 AND players.nationality = nationalities.iso_country_code", self.id).execute(&mut *connection).await?; + sqlx::query!("UPDATE subdivisions SET score = coalesce(score_of_subdivision(subdivisions.nation, subdivisions.iso_code), 0) FROM players WHERE players.id = $1 AND players.nationality = subdivisions.nation AND players.subdivision = subdivisions.iso_code", self.id).execute(&mut *connection).await?; + + Ok(new_score.score) } } -impl Player { - /// Gets the maximal and minimal player id currently in use - /// - /// The returned tuple is of the form (max, min) - pub async fn extremal_player_ids(connection: &mut PgConnection) -> Result<(i32, i32)> { - let row = sqlx::query!(r#"SELECT COALESCE(MAX(id), 0) AS "max_id!: i32", COALESCE(MIN(id), 0) AS "min_id!: i32" FROM players"#) - .fetch_one(connection) - .await?; - Ok((row.max_id, row.min_id)) - } +pub async fn recompute_scores(connection: &mut PgConnection) -> Result<(), CoreError> { + sqlx::query!("SELECT recompute_player_scores();").execute(&mut *connection).await?; + sqlx::query!("SELECT recompute_nation_scores();").execute(&mut *connection).await?; + sqlx::query!("SELECT recompute_subdivision_scores();").execute(connection).await?; + Ok(()) } diff --git a/pointercrate-demonlist/src/player/paginate.rs b/pointercrate-demonlist/src/player/paginate.rs index e2099594a..8c5e4c32c 100644 --- a/pointercrate-demonlist/src/player/paginate.rs +++ b/pointercrate-demonlist/src/player/paginate.rs @@ -1,11 +1,11 @@ use crate::{ - error::Result, nationality::{Continent, Nationality}, - player::{DatabasePlayer, Player, RankedPlayer}, + player::{DatabasePlayer, Player}, }; use futures::StreamExt; use pointercrate_core::{ - error::CoreError, + first_and_last, + pagination::{PageContext, Paginatable, PaginationParameters, PaginationQuery, __pagination_compat}, util::{non_nullable, nullable}, }; use serde::{Deserialize, Serialize}; @@ -13,16 +13,8 @@ use sqlx::{postgres::PgConnection, Row}; #[derive(Serialize, Deserialize, Clone, Debug)] pub struct PlayerPagination { - #[serde(default, deserialize_with = "non_nullable")] - #[serde(rename = "before")] - pub before_id: Option, - - #[serde(default, deserialize_with = "non_nullable")] - #[serde(rename = "after")] - pub after_id: Option, - - #[serde(default, deserialize_with = "non_nullable")] - pub limit: Option, + #[serde(flatten)] + pub params: PaginationParameters, #[serde(default, deserialize_with = "non_nullable")] name: Option, @@ -37,32 +29,37 @@ pub struct PlayerPagination { nation: Option>, } -impl PlayerPagination { - pub async fn page(&self, connection: &mut PgConnection) -> Result> { - if let Some(limit) = self.limit { - if !(1..=100).contains(&limit) { - return Err(CoreError::InvalidPaginationLimit.into()) - } +impl PaginationQuery for PlayerPagination { + fn parameters(&self) -> PaginationParameters { + self.params + } + + fn with_parameters(&self, parameters: PaginationParameters) -> Self { + Self { + params: parameters, + ..self.clone() } + } +} + +impl Paginatable for Player { + first_and_last!("players"); - let order = if self.after_id.is_none() && self.before_id.is_some() { - "DESC" - } else { - "ASC" - }; + async fn page(query: &PlayerPagination, connection: &mut PgConnection) -> Result<(Vec, PageContext), sqlx::Error> { + let order = query.params.order(); - let query = format!(include_str!("../../sql/paginate_players_by_id.sql"), order); + let sql_query = format!(include_str!("../../sql/paginate_players_by_id.sql"), order); // FIXME(sqlx) once CITEXT is supported - let mut stream = sqlx::query(&query) - .bind(self.before_id) - .bind(self.after_id) - .bind(self.name.as_deref()) - .bind(self.name_contains.as_deref()) - .bind(self.banned) - .bind(&self.nation) - .bind(self.nation == Some(None)) - .bind(self.limit.unwrap_or(50) as i32 + 1) + let mut stream = sqlx::query(&sql_query) + .bind(query.params.before) + .bind(query.params.after) + .bind(query.name.as_deref()) + .bind(query.name_contains.as_deref()) + .bind(query.banned) + .bind(&query.nation) + .bind(query.nation == Some(None)) + .bind(query.params.limit + 1) .fetch(connection); let mut players = Vec::new(); @@ -71,12 +68,11 @@ impl PlayerPagination { let row = row?; let nationality = match (row.get("nation"), row.get("iso_country_code")) { - (Some(nation), Some(country_code)) => - Some(Nationality { - iso_country_code: country_code, - nation, - subdivision: None, // dont include subdivision in pagination data - }), + (Some(nation), Some(country_code)) => Some(Nationality { + iso_country_code: country_code, + nation, + subdivision: None, // dont include subdivision in pagination data + }), _ => None, }; @@ -86,26 +82,23 @@ impl PlayerPagination { name: row.get("name"), banned: row.get("banned"), }, + score: row.get("score"), nationality, }) } - Ok(players) + Ok(__pagination_compat(&query.params, players)) + } + + fn pagination_id(&self) -> i32 { + self.base.id } } #[derive(Serialize, Deserialize, Clone, Debug)] pub struct RankingPagination { - #[serde(default, deserialize_with = "non_nullable")] - #[serde(rename = "before")] - pub before_index: Option, - - #[serde(default, deserialize_with = "non_nullable")] - #[serde(rename = "after")] - pub after_index: Option, - - #[serde(default, deserialize_with = "non_nullable")] - pub limit: Option, + #[serde(flatten)] + pub params: PaginationParameters, #[serde(default, deserialize_with = "nullable")] nation: Option>, @@ -120,31 +113,51 @@ pub struct RankingPagination { name_contains: Option, } -impl RankingPagination { - pub async fn page(&self, connection: &mut PgConnection) -> Result> { - if let Some(limit) = self.limit { - if !(1..=100).contains(&limit) { - return Err(CoreError::InvalidPaginationLimit.into()) - } +impl PaginationQuery for RankingPagination { + fn parameters(&self) -> PaginationParameters { + self.params + } + + fn with_parameters(&self, parameters: PaginationParameters) -> Self { + Self { + params: parameters, + ..self.clone() } + } +} + +#[derive(Debug, Serialize)] +pub struct RankedPlayer { + rank: i64, + #[serde(skip)] + index: i64, + #[serde(flatten)] + player: Player, +} + +impl Paginatable for RankedPlayer { + async fn first_and_last(connection: &mut PgConnection) -> Result, sqlx::Error> { + Ok(sqlx::query!("SELECT COUNT(*) FROM players WHERE NOT banned AND score > 0.0") + .fetch_one(connection) + .await? + .count + .map(|max| (1, max as i32))) + } + + async fn page(query: &RankingPagination, connection: &mut PgConnection) -> Result<(Vec, PageContext), sqlx::Error> { + let order = query.params.order(); + + let sql_query = format!(include_str!("../../sql/paginate_player_ranking.sql"), order); - let order = if self.before_index.is_some() && self.after_index.is_none() { - "DESC" - } else { - "ASC" - }; - - let query = format!(include_str!("../../sql/paginate_player_ranking.sql"), order); - - let mut stream = sqlx::query(&query) - .bind(self.before_index) - .bind(self.after_index) - .bind(self.name_contains.as_deref()) - .bind(&self.nation) - .bind(self.nation == Some(None)) - .bind(self.continent.as_ref().map(|c| c.to_sql())) - .bind(&self.subdivision) - .bind(self.limit.unwrap_or(50) as i32 + 1) + let mut stream = sqlx::query(&sql_query) + .bind(query.params.before) + .bind(query.params.after) + .bind(query.name_contains.as_deref()) + .bind(&query.nation) + .bind(query.nation == Some(None)) + .bind(query.continent.as_ref().map(|c| c.to_sql())) + .bind(&query.subdivision) + .bind(query.params.limit + 1) .fetch(connection); let mut players = Vec::new(); @@ -153,25 +166,35 @@ impl RankingPagination { let row = row?; let nationality = match (row.get("nation"), row.get("iso_country_code")) { - (Some(nation), Some(country_code)) => - Some(Nationality { - iso_country_code: country_code, - nation, - subdivision: None, // dont include subdivision in pagination data - }), + (Some(nation), Some(country_code)) => Some(Nationality { + iso_country_code: country_code, + nation, + subdivision: None, // dont include subdivision in pagination data + }), _ => None, }; + let player = Player { + base: DatabasePlayer { + id: row.get("id"), + name: row.get("name"), + banned: false, + }, + score: row.get("score"), + nationality, + }; + players.push(RankedPlayer { - id: row.get("id"), - name: row.get("name"), rank: row.get("rank"), - nationality, - score: row.get("score"), index: row.get("index"), + player, }) } - Ok(players) + Ok(__pagination_compat(&query.params, players)) + } + + fn pagination_id(&self) -> i32 { + self.index as i32 } } diff --git a/pointercrate-demonlist/src/player/patch.rs b/pointercrate-demonlist/src/player/patch.rs index 61ea9b50c..5234dba9b 100644 --- a/pointercrate-demonlist/src/player/patch.rs +++ b/pointercrate-demonlist/src/player/patch.rs @@ -1,6 +1,6 @@ use crate::{ error::{DemonlistError, Result}, - nationality::{Nationality, Subdivision}, + nationality::Nationality, player::{claim::PlayerClaim, DatabasePlayer, FullPlayer, Player}, record::{approved_records_by, FullRecord}, }; @@ -26,21 +26,35 @@ pub struct PatchPlayer { impl FullPlayer { pub async fn apply_patch(mut self, patch: PatchPlayer, connection: &mut PgConnection) -> Result { - if let Some(nationality) = patch.nationality { - match nationality { - Some(ident) => - self.player - .set_nationality(Nationality::by_country_code_or_name(ident.as_ref(), connection).await?, connection) - .await?, - None => self.player.reset_nationality(connection).await?, - } + let mut new_nationality = match patch.nationality { + None => self.player.nationality.clone(), + Some(None) => None, + Some(Some(ref code_or_name)) => Some(Nationality::by_country_code_or_name(code_or_name, connection).await?), + }; + + match new_nationality { + Some(ref mut nationality) => { + nationality.subdivision = match patch.subdivision { + None => self + .player + .nationality + .as_ref() + .filter(|n| n.same_country_as(nationality)) + .map(|n| n.subdivision.clone()) + .unwrap_or(None), + Some(None) => None, + Some(Some(subdivision_code)) => Some(nationality.subdivision_by_code(&subdivision_code, connection).await?), + } + }, + None => { + if matches!(patch.subdivision, Some(Some(_))) { + return Err(DemonlistError::NoNationSet); + } + }, } - if let Some(subdivision) = patch.subdivision { - match subdivision { - Some(subdivision) => self.player.set_subdivision(subdivision, connection).await?, - None => self.player.reset_subdivision(connection).await?, - } + if new_nationality != self.player.nationality { + self.player.set_nationality(new_nationality, connection).await?; } if let Some(banned) = patch.banned { @@ -58,6 +72,8 @@ impl FullPlayer { self.set_name(name, connection).await?; } + self.player.score = self.player.base.update_score(connection).await?; + Ok(self) } @@ -66,7 +82,7 @@ impl FullPlayer { // Nothing to be done if name == self.player.base.name.as_ref() { - return Ok(()) + return Ok(()); } else if name.to_lowercase() != self.player.base.name.to_lowercase() { // If they are equal case insensitively, we're only doing a cosmetic rename, which won't // even require a merge @@ -102,11 +118,12 @@ impl FullPlayer { let claim_on_with = PlayerClaim::verified_claim_on(with.id, &mut *connection).await?; match (claim_on_self, claim_on_with) { - (Some(_), Some(_)) => + (Some(_), Some(_)) => { return Err(DemonlistError::ConflictingClaims { player1: self.player.base.name.clone(), player2: with.name.clone(), - }), + }) + }, (Some(_), None) => { sqlx::query!("DELETE FROM player_claims WHERE player_id = $1", with.id) .execute(&mut *connection) @@ -212,86 +229,26 @@ impl FullPlayer { } impl Player { - pub async fn reset_nationality(&mut self, connection: &mut PgConnection) -> Result<()> { - sqlx::query!( - "UPDATE players SET nationality = NULL, subdivision = NULL WHERE id = $1", - self.base.id - ) - .execute(connection) - .await?; - - self.nationality = None; - - Ok(()) - } + pub async fn set_nationality(&mut self, nationality: Option, connection: &mut PgConnection) -> Result<()> { + let iso_country_code = nationality.as_ref().map(|n| &n.iso_country_code); + let subdivision_code = nationality.as_ref().map(|n| n.subdivision.as_ref().map(|s| &s.iso_code)).flatten(); - pub async fn set_nationality(&mut self, nationality: Nationality, connection: &mut PgConnection) -> Result<()> { sqlx::query!( - "UPDATE players SET nationality = $1::text, subdivision = NULL WHERE id = $2", - nationality.iso_country_code, + "UPDATE players SET nationality = $1, subdivision = $2 WHERE id = $3", + iso_country_code, + subdivision_code, self.base.id ) - .execute(connection) + .execute(&mut *connection) .await?; - self.nationality = Some(nationality); - - Ok(()) - } - - pub async fn reset_subdivision(&mut self, connection: &mut PgConnection) -> Result<()> { if let Some(ref mut nationality) = self.nationality { - sqlx::query!("UPDATE players SET subdivision = NULL WHERE id = $1", self.base.id) - .execute(connection) - .await?; - - nationality.subdivision = None; + nationality.update_nation_score(connection).await?; } + self.nationality = nationality; Ok(()) } - - pub async fn set_subdivision(&mut self, subdivision_code: String, connection: &mut PgConnection) -> Result<()> { - match self.nationality { - Some(ref mut nationality) => { - let result = sqlx::query!( - "SELECT iso_code, name::TEXT FROM subdivisions WHERE iso_code = $1 AND nation = $2", - subdivision_code, - nationality.iso_country_code - ) - .fetch_one(&mut *connection) - .await; - - match result { - Ok(row) => { - let subdivision = Subdivision { - iso_code: row.iso_code, - name: row.name.unwrap(), - }; - - sqlx::query!( - "UPDATE players SET subdivision = $1::TEXT where id = $2", - subdivision_code, - self.base.id - ) - .execute(connection) - .await?; - - nationality.subdivision = Some(subdivision); - - Ok(()) - }, - Err(sqlx::Error::RowNotFound) => - Err(DemonlistError::SubdivisionNotFound { - nation_code: nationality.iso_country_code.clone(), - subdivision_code, - }), - Err(err) => Err(err.into()), - } - }, - None => Err(DemonlistError::NoNationSet), - } - } } impl DatabasePlayer { diff --git a/pointercrate-demonlist/src/record/audit.rs b/pointercrate-demonlist/src/record/audit.rs index 6da10b7fa..2d1935e5d 100644 --- a/pointercrate-demonlist/src/record/audit.rs +++ b/pointercrate-demonlist/src/record/audit.rs @@ -76,19 +76,17 @@ pub async fn audit_log_for_record(record_id: i32, connection: &mut PgConnection) progress: modification.progress, status: modification.status_.as_deref().map(RecordStatus::from_sql), player: match modification.player_id { - Some(id) => - Some(NamedId { - name: modification.player_name, - id, - }), + Some(id) => Some(NamedId { + name: modification.player_name, + id, + }), _ => None, }, demon: match modification.demon_id { - Some(id) => - Some(NamedId { - name: modification.demon_name, - id, - }), + Some(id) => Some(NamedId { + name: modification.demon_name, + id, + }), _ => None, }, video: modification.video, diff --git a/pointercrate-demonlist/src/record/delete.rs b/pointercrate-demonlist/src/record/delete.rs index f3afc51de..f328c88ba 100644 --- a/pointercrate-demonlist/src/record/delete.rs +++ b/pointercrate-demonlist/src/record/delete.rs @@ -6,10 +6,15 @@ impl FullRecord { pub async fn delete(self, connection: &mut PgConnection) -> Result<()> { info!("Deleting record {}", self); - FullRecord::delete_by_id(self.id, connection).await + FullRecord::delete_by_id(self.id, &mut *connection).await?; + + self.player.update_score(connection).await?; + + Ok(()) } - /// `FullRecord::delete` should be preferred + /// `FullRecord::delete` should be preferred. Only exists to delete invalid submissions + /// in the asychronous validation (which is why no score adjustment needs to take place here) pub async fn delete_by_id(record_id: i32, connection: &mut PgConnection) -> Result<()> { // Associated notes get deleted due to the ON DELETE CASCADE on record_notes.record diff --git a/pointercrate-demonlist/src/record/get.rs b/pointercrate-demonlist/src/record/get.rs index ae5901772..a2ed14313 100644 --- a/pointercrate-demonlist/src/record/get.rs +++ b/pointercrate-demonlist/src/record/get.rs @@ -31,27 +31,26 @@ impl FullRecord { .await; match result { - Ok(row) => - Ok(FullRecord { - id, - progress: row.progress, - video: row.video, - status: RecordStatus::from_sql(&row.status), - player: DatabasePlayer { - id: row.player_id, - name: row.player_name, - banned: row.player_banned, - }, - demon: MinimalDemon { - id: row.demon_id, - position: row.position, - name: row.demon_name, - }, - submitter: Some(Submitter { - id: row.submitter_id, - banned: row.submitter_banned, - }), + Ok(row) => Ok(FullRecord { + id, + progress: row.progress, + video: row.video, + status: RecordStatus::from_sql(&row.status), + player: DatabasePlayer { + id: row.player_id, + name: row.player_name, + banned: row.player_banned, + }, + demon: MinimalDemon { + id: row.demon_id, + position: row.position, + name: row.demon_name, + }, + submitter: Some(Submitter { + id: row.submitter_id, + banned: row.submitter_banned, }), + }), Err(Error::RowNotFound) => Err(DemonlistError::RecordNotFound { record_id: id }), Err(err) => Err(err.into()), @@ -62,7 +61,7 @@ impl FullRecord { pub async fn approved_records_by(player: &DatabasePlayer, connection: &mut PgConnection) -> Result> { let mut stream = sqlx::query!( r#"SELECT records.id, progress, CASE WHEN players.link_banned THEN NULL ELSE records.video::text END, demons.id AS demon_id, - demons.name as "name: String", demons.position FROM records INNER JOIN demons ON records.demon = demons.id INNER JOIN players ON players.id + demons.name, demons.position FROM records INNER JOIN demons ON records.demon = demons.id INNER JOIN players ON players.id = $1 WHERE status_ = 'APPROVED' AND records.player = $1"#, player.id ) @@ -104,7 +103,7 @@ pub async fn approved_records_on(demon: &MinimalDemon, connection: &mut PgConnec let mut stream = sqlx::query_as!( Fetched, r#"SELECT records.id, progress, CASE WHEN players.link_banned THEN NULL ELSE video::text END, players.id AS player_id, - players.name AS "name: String", players.banned, nation::TEXT, iso_country_code::TEXT FROM records INNER JOIN players ON records.player = players.id LEFT OUTER JOIN nationalities ON nationality = iso_country_code WHERE status_ = 'APPROVED' AND + players.name, players.banned, nation::TEXT, iso_country_code::TEXT FROM records INNER JOIN players ON records.player = players.id LEFT OUTER JOIN nationalities ON nationality = iso_country_code WHERE status_ = 'APPROVED' AND records.demon = $1 ORDER BY progress DESC, id ASC"#, demon.id ) @@ -126,12 +125,11 @@ pub async fn approved_records_on(demon: &MinimalDemon, connection: &mut PgConnec banned: row.banned, }, nationality: match (row.nation, row.iso_country_code) { - (Some(nation), Some(code)) => - Some(Nationality { - iso_country_code: code, - nation, - subdivision: None, // don't display states in the records list - }), + (Some(nation), Some(code)) => Some(Nationality { + iso_country_code: code, + nation, + subdivision: None, // don't display states in the records list + }), _ => None, }, }) diff --git a/pointercrate-demonlist/src/record/mod.rs b/pointercrate-demonlist/src/record/mod.rs index 4cb13656b..b9347407d 100644 --- a/pointercrate-demonlist/src/record/mod.rs +++ b/pointercrate-demonlist/src/record/mod.rs @@ -114,16 +114,15 @@ impl<'de> Deserialize<'de> for RecordStatus { "submitted" => Ok(RecordStatus::Submitted), "rejected" => Ok(RecordStatus::Rejected), "under consideration" => Ok(RecordStatus::UnderConsideration), - _ => - Err(serde::de::Error::invalid_value( - serde::de::Unexpected::Str(&string), - &"'approved', 'submitted', 'under consideration' or 'rejected'", - )), + _ => Err(serde::de::Error::invalid_value( + serde::de::Unexpected::Str(&string), + &"'approved', 'submitted', 'under consideration' or 'rejected'", + )), } } } -#[derive(Debug, Serialize, Display, Hash)] +#[derive(Debug, Deserialize, Serialize, Display, Hash)] #[display(fmt = "{} {}% on {} (ID: {})", player, progress, demon, id)] pub struct FullRecord { pub id: i32, @@ -162,7 +161,7 @@ pub struct MinimalRecordPD { pub player: DatabasePlayer, } -#[derive(Debug, Hash, Serialize, Display, PartialEq, Eq)] +#[derive(Debug, Hash, Serialize, Deserialize, Display, PartialEq, Eq)] #[display(fmt = "{}% on {} (ID: {})", progress, demon, id)] pub struct MinimalRecordD { pub id: i32, @@ -172,7 +171,7 @@ pub struct MinimalRecordD { pub demon: MinimalDemon, } -#[derive(Debug, Hash, Serialize, Display, PartialEq, Eq)] +#[derive(Debug, Hash, Serialize, Deserialize, Display, PartialEq, Eq)] #[display(fmt = "{} - {}% (ID: {})", player, progress, id)] pub struct MinimalRecordP { pub id: i32, @@ -184,16 +183,6 @@ pub struct MinimalRecordP { } impl FullRecord { - /// Gets the maximal and minimal submitter id currently in use - /// - /// The returned tuple is of the form (max, min) - pub async fn extremal_record_ids(connection: &mut PgConnection) -> Result<(i32, i32)> { - let row = sqlx::query!(r#"SELECT COALESCE(MAX(id), 0) AS "max_id!: i32", COALESCE(MIN(id), 0) AS "min_id!: i32" FROM records"#) - .fetch_one(connection) - .await?; // FIXME: crashes on empty table - Ok((row.max_id, row.min_id)) - } - pub async fn was_modified(&self, connection: &mut PgConnection) -> Result { Ok(sqlx::query!( r#"SELECT EXISTS (SELECT 1 FROM record_modifications WHERE id = $1 AND status_ IS NOT NULL) AS "was_modified!: bool""#, @@ -203,126 +192,4 @@ impl FullRecord { .await? .was_modified) } - - /* - pub async fn validate(self, state: PointercrateState) { - let mut connection = match state.connection().await { - Ok(connection) => connection, - Err(err) => return error!("INTERNAL SERVER ERROR: failed to acquire database connection: {:?}", err), - }; - - let video = match self.video { - Some(ref video) => video, - None => return, - }; - - debug!("Verifying that submission {} with video {} actually is valid", self, video); - - match state.http_client.head(video).send().await { - Ok(response) => { - let status = response.status().as_u16(); - - if status == 401 || status == 403 || status == 405 { - // Some websites (billibilli) respond unfavorably to HEAD requests. Retry with - // GET - match state.http_client.get(video).send().await { - Ok(response) => { - let status = response.status().as_u16(); - - if status >= 200 && status < 400 { - debug!("HEAD request yielded some sort of successful response, executing webhook"); - - self.execute_webhook(&state).await; - } - }, - Err(err) => { - error!( - "INTERNAL SERVER ERROR: HEAD request to verify video failed: {:?}. Deleting submission", - err - ); - - match self.delete(&mut connection).await { - Ok(_) => (), - Err(error) => error!("INTERNAL SERVER ERROR: Failure to delete record - {:?}!", error), - } - }, - } - } else if status >= 200 && status < 400 { - debug!("HEAD request yielded some sort of successful response, executing webhook"); - - self.execute_webhook(&state).await; - } else { - warn!("Server response to 'HEAD {}' was {:?}, deleting submission!", video, response); - - match self.delete(&mut connection).await { - Ok(_) => (), - Err(error) => error!("INTERNAL SERVER ERROR: Failure to delete record - {:?}!", error), - } - } - }, - Err(error) => { - error!( - "INTERNAL SERVER ERROR: HEAD request to verify video failed: {:?}. Deleting submission", - error - ); - - match self.delete(&mut connection).await { - Ok(_) => (), - Err(error) => error!("INTERNAL SERVER ERROR: Failure to delete record - {:?}!", error), - } - }, - } - }*/ - /* - async fn execute_webhook(&self, state: &PointercrateState) { - if let Some(ref webhook_url) = state.webhook_url { - match state - .http_client - .post(&**webhook_url) - .header("Content-Type", "application/json") - .body(self.webhook_embed().to_string()) - .send() - .await - { - Err(error) => error!("INTERNAL SERVER ERROR: Failure to execute discord webhook: {:?}", error), - Ok(_) => debug!("Successfully executed discord webhook"), - } - } else { - warn!("Trying to execute webhook, though no link was configured!"); - } - }*/ - /* - fn webhook_embed(&self) -> serde_json::Value { - let mut payload = json!({ - "content": format!("**New record submitted! ID: {}**", self.id), - "embeds": [ - { - "type": "rich", - "title": format!("{}% on {}", self.progress, self.demon.name), - "description": format!("{} just got {}% on {}! Go add their record!", self.player.name, self.progress, self.demon.name), - "footer": { - "text": format!("This record has been submitted by submitter #{}", self.submitter.map(|s|s.id).unwrap_or(1)) - }, - "author": { - "name": format!("{} (ID: {})", self.player.name, self.player.id), - "url": self.video - }, - "thumbnail": { - "url": "https://cdn.discordapp.com/avatars/277391246035648512/b03c85d94dc02084c413a7fdbe2cea79.webp?size=1024" - }, - } - ] - }); - - if let Some(ref video) = self.video { - payload["embeds"][0]["fields"] = json! { - [{ - "name": "Video Proof:", - "value": video - }] - }; - } - - payload - }*/ } diff --git a/pointercrate-demonlist/src/record/note/mod.rs b/pointercrate-demonlist/src/record/note/mod.rs index 3493801cb..9699b4229 100644 --- a/pointercrate-demonlist/src/record/note/mod.rs +++ b/pointercrate-demonlist/src/record/note/mod.rs @@ -5,13 +5,14 @@ mod post; pub use self::{get::notes_on, patch::PatchNote, post::NewNote}; use pointercrate_core::etag::Taggable; +use serde::Deserialize; use serde::Serialize; use std::{ collections::hash_map::DefaultHasher, hash::{Hash, Hasher}, }; -#[derive(Serialize, Debug, Hash)] +#[derive(Serialize, Deserialize, Debug, Hash)] pub struct Note { pub id: i32, diff --git a/pointercrate-demonlist/src/record/note/patch.rs b/pointercrate-demonlist/src/record/note/patch.rs index de782d448..af25c34c7 100644 --- a/pointercrate-demonlist/src/record/note/patch.rs +++ b/pointercrate-demonlist/src/record/note/patch.rs @@ -19,7 +19,7 @@ impl Note { pub async fn apply_patch(mut self, patch: PatchNote, connection: &mut PgConnection) -> Result { if let Some(content) = patch.content { if content.trim().is_empty() { - return Err(DemonlistError::NoteEmpty) + return Err(DemonlistError::NoteEmpty); } sqlx::query!("UPDATE record_notes SET content = $1 WHERE id = $2", content, self.id) diff --git a/pointercrate-demonlist/src/record/note/post.rs b/pointercrate-demonlist/src/record/note/post.rs index f61dcacf6..7d85edff4 100644 --- a/pointercrate-demonlist/src/record/note/post.rs +++ b/pointercrate-demonlist/src/record/note/post.rs @@ -20,7 +20,7 @@ impl Note { /// `author` field! pub async fn create_on(record: &FullRecord, new_note: NewNote, connection: &mut PgConnection) -> Result { if new_note.content.trim().is_empty() { - return Err(DemonlistError::NoteEmpty) + return Err(DemonlistError::NoteEmpty); } let note_id = sqlx::query!( diff --git a/pointercrate-demonlist/src/record/paginate.rs b/pointercrate-demonlist/src/record/paginate.rs index 15d0055ef..f58fce1e8 100644 --- a/pointercrate-demonlist/src/record/paginate.rs +++ b/pointercrate-demonlist/src/record/paginate.rs @@ -1,12 +1,12 @@ use crate::{ demon::MinimalDemon, - error::Result, player::DatabasePlayer, record::{MinimalRecordPD, RecordStatus}, }; use futures::StreamExt; use pointercrate_core::{ - error::CoreError, + first_and_last, + pagination::{PageContext, Paginatable, PaginationParameters, PaginationQuery, __pagination_compat}, util::{non_nullable, nullable}, }; use serde::{Deserialize, Serialize}; @@ -14,16 +14,8 @@ use sqlx::{postgres::PgRow, PgConnection, Row}; #[derive(Clone, Debug, Serialize, Deserialize, Default)] pub struct RecordPagination { - #[serde(default, deserialize_with = "non_nullable")] - #[serde(rename = "before")] - pub before_id: Option, - - #[serde(default, deserialize_with = "non_nullable")] - #[serde(rename = "after")] - pub after_id: Option, - - #[serde(default, deserialize_with = "non_nullable")] - pub limit: Option, + #[serde(flatten)] + pub params: PaginationParameters, progress: Option, @@ -64,55 +56,44 @@ pub struct RecordPagination { pub submitter: Option, } -impl RecordPagination { - /// Retrieves the page of records matching the pagination data in here - /// - /// Note that this method returns _one more record than requested_. This is used as a quick and - /// dirty way to determine if further pages exist: If the additional record was returned, more - /// pages obviously exist. This additional object is the last in the returned vector. - /// - /// Additionally, if _before_ is set, but not _after_, the page is returned in reverse order - /// (the additional object stays the last) - pub async fn page(&self, connection: &mut PgConnection) -> Result> { - if let Some(limit) = self.limit { - if !(1..=100).contains(&limit) { - return Err(CoreError::InvalidPaginationLimit.into()) - } - } +impl PaginationQuery for RecordPagination { + fn parameters(&self) -> PaginationParameters { + self.params + } - if let (Some(after), Some(before)) = (self.before_id, self.after_id) { - if after < before { - return Err(CoreError::AfterSmallerBefore.into()) - } + fn with_parameters(&self, parameters: PaginationParameters) -> Self { + Self { + params: parameters, + ..self.clone() } + } +} - let limit = self.limit.unwrap_or(50) as i32; - - let order = if self.after_id.is_none() && self.before_id.is_some() { - "DESC" - } else { - "ASC" - }; - - let query = format!(include_str!("../../sql/paginate_records.sql"), order); - - let mut stream = sqlx::query(&query) - .bind(self.before_id) - .bind(self.after_id) - .bind(self.progress) - .bind(self.progress_lt) - .bind(self.progress_gt) - .bind(self.demon_position) - .bind(self.demon_position_lt) - .bind(self.demon_position_gt) - .bind(self.status.map(|s| s.to_sql())) - .bind(self.demon.as_deref()) - .bind(self.demon_id) - .bind(&self.video) - .bind(self.video == Some(None)) - .bind(self.player) - .bind(self.submitter) - .bind(limit + 1) +impl Paginatable for MinimalRecordPD { + first_and_last!("records"); + + async fn page(query: &RecordPagination, connection: &mut PgConnection) -> Result<(Vec, PageContext), sqlx::Error> { + let order = query.params.order(); + + let sql_query = format!(include_str!("../../sql/paginate_records.sql"), order); + + let mut stream = sqlx::query(&sql_query) + .bind(query.params.before) + .bind(query.params.after) + .bind(query.progress) + .bind(query.progress_lt) + .bind(query.progress_gt) + .bind(query.demon_position) + .bind(query.demon_position_lt) + .bind(query.demon_position_gt) + .bind(query.status.map(|s| s.to_sql())) + .bind(query.demon.as_deref()) + .bind(query.demon_id) + .bind(&query.video) + .bind(query.video == Some(None)) + .bind(query.player) + .bind(query.submitter) + .bind(query.params.limit + 1) .fetch(&mut *connection); let mut records = Vec::new(); @@ -138,6 +119,10 @@ impl RecordPagination { }) } - Ok(records) + Ok(__pagination_compat(&query.params, records)) + } + + fn pagination_id(&self) -> i32 { + self.id } } diff --git a/pointercrate-demonlist/src/record/patch.rs b/pointercrate-demonlist/src/record/patch.rs index 8da81944a..8256a8c07 100644 --- a/pointercrate-demonlist/src/record/patch.rs +++ b/pointercrate-demonlist/src/record/patch.rs @@ -60,14 +60,19 @@ impl FullRecord { } match (data.demon, data.demon_id) { - (Some(demon_name), None) => + (Some(demon_name), None) => { self.set_demon(MinimalDemon::by_name(demon_name.as_ref(), connection).await?, connection) - .await?, + .await? + }, (None, Some(demon_id)) => self.set_demon(MinimalDemon::by_id(demon_id, connection).await?, connection).await?, (Some(_), Some(_)) => return Err(CoreError::MutuallyExclusive.into()), _ => (), } + // Not all record update require recomputing scores (for example, changing status from "submitted" to "under consideration") + // but the logic for correctly determining this is hard, and updating scores of individual players cheap, so we do not bother. + self.player.update_score(connection).await?; + Ok(self) } @@ -77,7 +82,7 @@ impl FullRecord { if self.player.id == player && self.demon.id == demon { warn!("Record::ensure_invariants was called, but the given player and demon ids match those we already have. Doing nothing."); - return Ok(()) + return Ok(()); } match self.status { @@ -197,14 +202,14 @@ impl FullRecord { let video = crate::video::validate(&video)?; if Some(&video) == self.video.as_ref() { - return Ok(()) + return Ok(()); } if let Some(row) = sqlx::query!(r#"SELECT id FROM records WHERE video = $1"#, video.to_string()) .fetch_optional(&mut *connection) .await? { - return Err(DemonlistError::DuplicateVideo { id: row.id }) + return Err(DemonlistError::DuplicateVideo { id: row.id }); } sqlx::query!("UPDATE records SET video = $1::text WHERE id = $2", video, self.id) @@ -220,7 +225,7 @@ impl FullRecord { let requirement = demon.requirement(connection).await?; if self.progress < requirement { - return Err(DemonlistError::InvalidProgress { requirement }) + return Err(DemonlistError::InvalidProgress { requirement }); } self.ensure_invariants(self.player.id, self.demon.id, connection).await?; @@ -238,9 +243,11 @@ impl FullRecord { /// /// If the new player has a record that would stand in conflict with this one, this records /// takes precedence and overrides the existing one. + /// + /// If this record is approved, updates the score of the old holder. pub async fn set_player(&mut self, player: DatabasePlayer, connection: &mut PgConnection) -> Result<()> { if player.banned && self.status != RecordStatus::Rejected { - return Err(DemonlistError::PlayerBanned) + return Err(DemonlistError::PlayerBanned); } info!("Setting player of record {} to {}", self, player); @@ -248,9 +255,11 @@ impl FullRecord { self.ensure_invariants(player.id, self.demon.id, connection).await?; sqlx::query!("UPDATE records SET player = $1 WHERE id = $2", player.id, self.id) - .execute(connection) + .execute(&mut *connection) .await?; + self.player.update_score(connection).await?; + self.player = player; Ok(()) @@ -345,7 +354,7 @@ impl FullRecord { let requirement = self.demon.requirement(&mut *connection).await?; if progress > 100 || progress < requirement { - return Err(DemonlistError::InvalidProgress { requirement }) + return Err(DemonlistError::InvalidProgress { requirement }); } if self.status == RecordStatus::Approved { diff --git a/pointercrate-demonlist/src/record/post.rs b/pointercrate-demonlist/src/record/post.rs index a3c8cf73d..2f7ad6e75 100644 --- a/pointercrate-demonlist/src/record/post.rs +++ b/pointercrate-demonlist/src/record/post.rs @@ -92,25 +92,25 @@ impl NormalizedSubmission { pub async fn validate(self, connection: &mut PgConnection) -> Result { // Banned player can't have records on the list if self.player.banned { - return Err(DemonlistError::PlayerBanned) + return Err(DemonlistError::PlayerBanned); } // Cannot submit records for the legacy list (it is possible to directly add them for list mods) if self.demon.position > crate::config::extended_list_size() && self.status == RecordStatus::Submitted { - return Err(DemonlistError::SubmitLegacy) + return Err(DemonlistError::SubmitLegacy); } // Can only submit 100% records for the extended list (it is possible to directly add them for list // mods) if self.demon.position > crate::config::list_size() && self.progress != 100 && self.status == RecordStatus::Submitted { - return Err(DemonlistError::Non100Extended) + return Err(DemonlistError::Non100Extended); } let requirement = self.demon.requirement(&mut *connection).await?; // Check if the record meets the record requirement for this demon if self.progress > 100 || self.progress < requirement { - return Err(DemonlistError::InvalidProgress { requirement }) + return Err(DemonlistError::InvalidProgress { requirement }); } debug!("Submission is valid, checking for duplicates!"); @@ -126,7 +126,7 @@ impl NormalizedSubmission { return Err(DemonlistError::SubmissionExists { existing: row.id, status: RecordStatus::from_sql(&row.status_), - }) + }); } } @@ -144,7 +144,7 @@ impl NormalizedSubmission { return Err(DemonlistError::SubmissionExists { existing: row.id, status: RecordStatus::from_sql(&row.status_), - }) + }); } match self.raw_footage { @@ -153,7 +153,7 @@ impl NormalizedSubmission { }, None if self.status == RecordStatus::Submitted => { // list mods can submit without raw - return Err(DemonlistError::RawRequired) + return Err(DemonlistError::RawRequired); }, _ => (), } @@ -219,6 +219,10 @@ impl ValidatedSubmission { .await?; } + if self.status != RecordStatus::Submitted { + record.player.update_score(connection).await?; + } + Ok(record) } } diff --git a/pointercrate-demonlist/src/submitter/get.rs b/pointercrate-demonlist/src/submitter/get.rs index 55985a839..8323ec658 100644 --- a/pointercrate-demonlist/src/submitter/get.rs +++ b/pointercrate-demonlist/src/submitter/get.rs @@ -25,11 +25,9 @@ impl Submitter { ) .fetch_optional(&mut *connection) .await? - .map(|row| { - Submitter { - id: row.submitter_id, - banned: row.banned, - } + .map(|row| Submitter { + id: row.submitter_id, + banned: row.banned, })) } } diff --git a/pointercrate-demonlist/src/submitter/mod.rs b/pointercrate-demonlist/src/submitter/mod.rs index df78463ce..a784ee5f6 100644 --- a/pointercrate-demonlist/src/submitter/mod.rs +++ b/pointercrate-demonlist/src/submitter/mod.rs @@ -1,7 +1,6 @@ -use crate::error::Result; use derive_more::Display; +use serde::Deserialize; use serde::Serialize; -use sqlx::PgConnection; pub use paginate::SubmitterPagination; pub use patch::PatchSubmitter; @@ -12,7 +11,7 @@ mod paginate; mod patch; mod post; -#[derive(Debug, Serialize, Hash, Display, Copy, Clone)] +#[derive(Debug, Deserialize, Serialize, Hash, Display, Copy, Clone, PartialEq, Eq)] #[display(fmt = "{} (Banned: {})", id, banned)] pub struct Submitter { pub id: i32, @@ -20,17 +19,3 @@ pub struct Submitter { } impl Taggable for Submitter {} - -impl Submitter { - /// Gets the maximal and minimal submitter id currently in use - /// - /// The returned tuple is of the form (max, min) - pub async fn extremal_submitter_ids(connection: &mut PgConnection) -> Result<(i32, i32)> { - let row = sqlx::query!( - r#"SELECT COALESCE(MAX(submitter_id), 0) AS "max_id!: i32", COALESCE(MIN(submitter_id), 0) AS "min_id!: i32" FROM submitters"# - ) - .fetch_one(connection) - .await?; // FIXME: crashes on empty table - Ok((row.max_id, row.min_id)) - } -} diff --git a/pointercrate-demonlist/src/submitter/paginate.rs b/pointercrate-demonlist/src/submitter/paginate.rs index fcf2cce7b..06cfd56b1 100644 --- a/pointercrate-demonlist/src/submitter/paginate.rs +++ b/pointercrate-demonlist/src/submitter/paginate.rs @@ -1,51 +1,48 @@ -use crate::{error::Result, submitter::Submitter}; +use crate::submitter::Submitter; use futures::StreamExt; -use pointercrate_core::{error::CoreError, util::non_nullable}; +use pointercrate_core::{ + first_and_last, + pagination::{PageContext, Paginatable, PaginationParameters, PaginationQuery, __pagination_compat}, + util::non_nullable, +}; use serde::{Deserialize, Serialize}; use sqlx::{PgConnection, Row}; -#[derive(Deserialize, Debug, Clone, Serialize)] +#[derive(Deserialize, Debug, Clone, Copy, Serialize)] pub struct SubmitterPagination { - #[serde(rename = "before", default, deserialize_with = "non_nullable")] - pub before_id: Option, - - #[serde(rename = "after", default, deserialize_with = "non_nullable")] - pub after_id: Option, - - #[serde(default, deserialize_with = "non_nullable")] - pub limit: Option, + #[serde(flatten)] + pub params: PaginationParameters, #[serde(default, deserialize_with = "non_nullable")] banned: Option, } -impl SubmitterPagination { - pub async fn page(&self, connection: &mut PgConnection) -> Result> { - if let Some(limit) = self.limit { - if !(1..=100).contains(&limit) { - return Err(CoreError::InvalidPaginationLimit.into()) - } - } +impl PaginationQuery for SubmitterPagination { + fn parameters(&self) -> PaginationParameters { + self.params + } - if let (Some(after), Some(before)) = (self.before_id, self.after_id) { - if after < before { - return Err(CoreError::AfterSmallerBefore.into()) - } + fn with_parameters(&self, parameters: PaginationParameters) -> Self { + Self { + params: parameters, + ..*self } + } +} + +impl Paginatable for Submitter { + first_and_last!("submitters", "submitter_id"); + + async fn page(query: &SubmitterPagination, connection: &mut PgConnection) -> Result<(Vec, PageContext), sqlx::Error> { + let order = query.params.order(); - let query = if self.before_id.is_some() && self.after_id.is_none() { - "SELECT submitter_id, banned FROM submitters WHERE (submitter_id < $1 OR $1 IS NULL) AND (submitter_id > $2 OR $2 IS NULL) AND \ - (banned = $3 OR $3 IS NULL) ORDER BY submitter_id DESC LIMIT $4 " - } else { - "SELECT submitter_id, banned FROM submitters WHERE (submitter_id < $1 OR $1 IS NULL) AND (submitter_id > $2 OR $2 IS NULL) AND \ - (banned = $3 OR $3 IS NULL) ORDER BY submitter_id ASC LIMIT $4 " - }; + let sql_query = format!("SELECT submitter_id, banned FROM submitters WHERE (submitter_id < $1 OR $1 IS NULL) AND (submitter_id > $2 OR $2 IS NULL) AND (banned = $3 OR $3 IS NULL) ORDER BY submitter_id {} LIMIT $4", order); - let mut stream = sqlx::query(query) - .bind(self.before_id) - .bind(self.after_id) - .bind(self.banned) - .bind(self.limit.unwrap_or(50) as i32 + 1) + let mut stream = sqlx::query(&sql_query) + .bind(query.params.before) + .bind(query.params.after) + .bind(query.banned) + .bind(query.params.limit + 1) .fetch(connection); let mut submitters = Vec::new(); @@ -59,6 +56,10 @@ impl SubmitterPagination { }) } - Ok(submitters) + Ok(__pagination_compat(&query.params, submitters)) + } + + fn pagination_id(&self) -> i32 { + self.id } } diff --git a/pointercrate-demonlist/src/video.rs b/pointercrate-demonlist/src/video.rs index c2bd2fce4..81f20daeb 100644 --- a/pointercrate-demonlist/src/video.rs +++ b/pointercrate-demonlist/src/video.rs @@ -19,11 +19,11 @@ pub fn validate(url: &str) -> Result { let url = Url::parse(url).map_err(|_| DemonlistError::MalformedVideoUrl)?; if !SCHEMES.contains(&url.scheme()) { - return Err(CoreError::InvalidUrlScheme.into()) + return Err(CoreError::InvalidUrlScheme.into()); } if !url.username().is_empty() || url.password().is_some() { - return Err(CoreError::UrlAuthenticated.into()) + return Err(CoreError::UrlAuthenticated.into()); } if let Some(host) = url.domain() { @@ -37,26 +37,26 @@ pub fn validate(url: &str) -> Result { return Ok(format!( "https://www.youtube.com/watch?v={}", video_id.chars().take(11).collect::() - )) + )); } } Err(CoreError::InvalidUrlFormat { expected: YOUTUBE_FORMAT }.into()) }, - "youtu.be" => + "youtu.be" => { if let Some(path_segments) = url.path_segments() { match &path_segments.collect::>()[..] { - [video_id] => - Ok(format!( - "https://www.youtube.com/watch?v={}", - video_id.chars().take(11).collect::() - )), + [video_id] => Ok(format!( + "https://www.youtube.com/watch?v={}", + video_id.chars().take(11).collect::() + )), _ => Err(CoreError::InvalidUrlFormat { expected: YOUTUBE_FORMAT }.into()), } } else { Err(CoreError::InvalidUrlFormat { expected: YOUTUBE_FORMAT }.into()) - }, - "www.twitch.tv" | "twitch.tv" => + } + }, + "www.twitch.tv" | "twitch.tv" => { if let Some(path_segments) = url.path_segments() { match &path_segments.collect::>()[..] { ["videos", video_id] => Ok(format!("https://www.twitch.tv/videos/{}", video_id)), @@ -65,24 +65,25 @@ pub fn validate(url: &str) -> Result { } } else { Err(CoreError::InvalidUrlFormat { expected: TWITCH_FORMAT }.into()) - }, - "everyplay.com" | "www.everyplay.com" => + } + }, + "everyplay.com" | "www.everyplay.com" => { if let Some(path_segments) = url.path_segments() { match &path_segments.collect::>()[..] { ["videos", video_id] => Ok(format!("https://everyplay.com/videos/{}", video_id)), - _ => - Err(CoreError::InvalidUrlFormat { - expected: EVERYPLAY_FORMAT, - } - .into()), + _ => Err(CoreError::InvalidUrlFormat { + expected: EVERYPLAY_FORMAT, + } + .into()), } } else { Err(CoreError::InvalidUrlFormat { expected: EVERYPLAY_FORMAT, } .into()) - }, - "www.bilibili.com" | "bilibili.com" => + } + }, + "www.bilibili.com" | "bilibili.com" => { if let Some(path_segments) = url.path_segments() { match &path_segments.collect::>()[..] { ["video", video_id] => Ok(format!("https://www.bilibili.com/video/{}", video_id)), @@ -90,8 +91,9 @@ pub fn validate(url: &str) -> Result { } } else { Err(CoreError::InvalidUrlFormat { expected: BILIBILI_FORMAT }.into()) - }, - "vimeo.com" | "www.vimeo.com" => + } + }, + "vimeo.com" | "www.vimeo.com" => { if let Some(path_segments) = url.path_segments() { match &path_segments.collect::>()[..] { [video_id] => Ok(format!("https://vimeo.com/{}", video_id)), @@ -99,7 +101,8 @@ pub fn validate(url: &str) -> Result { } } else { Err(CoreError::InvalidUrlFormat { expected: VIMEO_FORMAT }.into()) - }, + } + }, _ => Err(DemonlistError::UnsupportedVideoHost), } } else { diff --git a/pointercrate-example/.env.sample b/pointercrate-example/.env.sample new file mode 100644 index 000000000..380abb202 --- /dev/null +++ b/pointercrate-example/.env.sample @@ -0,0 +1,14 @@ +# A connection string to the postgresql database you are using. See https://www.postgresql.org/docs/current/libpq-connect.html#LIBPQ-CONNSTRING +DATABASE_URL=... + +# The size of the "main" part of your list (e.g. the part where non-100% records are accepted) +LIST_SIZE=75 + +# The size of the "extended" part of your list (e.g. the part where only 100% records can be submitted) +EXTENDED_LIST_SIZE=150 + +# The port on which rocket should list for incoming HTTP requests +ROCKET_PORT=1971 + +# Google Analytics tag. If google analytics is not desired, please delete pointercrate_core_pages::google_analytics_tag() and all its call-sites from your pointercrate clone! +ANALYTICS_TAG=... \ No newline at end of file diff --git a/pointercrate-example/Cargo.toml b/pointercrate-example/Cargo.toml new file mode 100644 index 000000000..61cbd9feb --- /dev/null +++ b/pointercrate-example/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "pointercrate-example" +version = "0.1.0" +authors.workspace = true +edition.workspace = true + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +dotenv = "0.15.0" +maud = "0.26.0" +pointercrate-core = { version = "0.1.0", path = "../pointercrate-core" } +pointercrate-core-api = { version = "0.1.0", path = "../pointercrate-core-api" } +pointercrate-core-pages = { version = "0.1.0", path = "../pointercrate-core-pages" } +pointercrate-demonlist = { version = "0.1.0", path = "../pointercrate-demonlist" } +pointercrate-demonlist-api = { version = "0.1.0", path = "../pointercrate-demonlist-api" } +pointercrate-demonlist-pages = { version = "0.1.0", path = "../pointercrate-demonlist-pages" } +pointercrate-user = { version = "0.1.0", path = "../pointercrate-user" } +pointercrate-user-api = { version = "0.1.0", path = "../pointercrate-user-api" } +pointercrate-user-pages = { version = "0.1.0", path = "../pointercrate-user-pages" } +rocket = "0.5.1" diff --git a/pointercrate-example/src/main.rs b/pointercrate-example/src/main.rs new file mode 100644 index 000000000..d4e79760d --- /dev/null +++ b/pointercrate-example/src/main.rs @@ -0,0 +1,184 @@ +use maud::html; +use pointercrate_core::error::CoreError; +use pointercrate_core::pool::PointercratePool; +use pointercrate_core_api::{error::ErrorResponder, maintenance::MaintenanceFairing}; +use pointercrate_core_pages::{ + footer::{Footer, FooterColumn, Link}, + navigation::{NavigationBar, TopLevelNavigationBarItem}, + PageConfiguration, +}; +use pointercrate_demonlist::LIST_ADMINISTRATOR; +use pointercrate_demonlist_pages::account::{ + demons::DemonsTab, list_integration::ListIntegrationTab, players::PlayersPage, records::RecordsPage, +}; +use pointercrate_user::MODERATOR; +use pointercrate_user_pages::account::{profile::ProfileTab, users::UsersTab, AccountPageConfig}; +use rocket::{fs::FileServer, response::Redirect, uri}; + +/// A catcher for 404 errors (e.g. when a user tried to navigate to a URL that +/// does not exist) +/// +/// An [`ErrorResponder`] will return either a JSON or an HTML error page, +/// depending on what `Accept` headers are set on the request. +#[rocket::catch(404)] +fn catch_404() -> ErrorResponder { + // `CoreError` contains various generic error conditions that might happen + // anywhere across the website. `CoreError::NotFound` is a generic 404 NOT FOUND + // error with code 40400. + CoreError::NotFound.into() +} + +/// We do not have a home page, so have the website root simply redirect to the demonlist +#[rocket::get("/")] +fn home() -> Redirect { + Redirect::to(uri!("/demonlist/")) +} + +#[rocket::launch] +async fn rocket() -> _ { + // Load the configuration from your .env file + dotenv::dotenv().unwrap(); + + // Initialize a database connection pool to the database specified by the + // DATABASE_URL environment variable + let pool = PointercratePool::init().await; + + // Set up the HTTP server + let rocket = rocket::build() + // Tell it about the connection pool to use (individual handlers can get hold of this pool by declaring an argument of type `&State`) + .manage(pool) + // Tell pointercrate's core components about navigation bar and footers, so that it knows how to render the website + .manage(page_configuration()) + // Register our 404 catcher + .register("/", rocket::catchers![catch_404]) + // Register our home page + .mount("/", rocket::routes![home]); + + // Define the permissions in use on our website. We just use the default setup + // from `pointercrate_user` and `pointercrate_demonlist`, but if you for example + // do not want list administrators to be able to promote helpers to moderators + // in autonomy, you could use a custom [`PermissionsManager`] where the + // `LIST_ADMINISTRATOR` permission does not assign `LIST_MODERATOR` and + // `LIST_HELPER`. For more information on pointercrate' permissions system, see + // the documentation of the [`PermissionsManager`] structure. + let mut permissions_manager = pointercrate_user::default_permissions_manager(); + permissions_manager.merge_with(pointercrate_demonlist::default_permissions_manager()); + + let rocket = rocket.manage(permissions_manager); + + // Set up which tabs can show up in the "user area" of your website. Anything + // that implements the [`AccountPageTab`] trait can be displayed here. Note that + // tabs will only be visible for users for which + // [`AccountPageTab::should_display_for`] returns `true`. + let account_page_config = AccountPageConfig::default() + // Tab where users can modify their own accounts + .with_page(ProfileTab) + // Tab where users can initiate player claims and manage their claimed players + .with_page(ListIntegrationTab("https://discord.gg/tMBzYP77ag")) + // Tab where website moderators can manage permissions. + // The vector below specified which permissions a user needs to have for the tab to be displayed. + .with_page(UsersTab(vec![MODERATOR, LIST_ADMINISTRATOR])) + // Tab where list helpers can manage demons + .with_page(DemonsTab) + // Tab where list helpers can manage players + .with_page(PlayersPage) + // Tab where list helpers can manage records + .with_page(RecordsPage); + + let rocket = rocket.manage(account_page_config); + + // Changing `false` to `true` here will put your website into "maintenance mode", which will disable all mutating request handlers and always return 503 SERVICE UNAVAILABLE responses for non-GET requests. + let rocket = rocket.attach(MaintenanceFairing::new(false)); + + // Register all the endpoints related to the demonlist to our server (this is + // optional, but without registering the demonlist related endpoint your website + // will just be User Account Simulator 2024). + let rocket = pointercrate_demonlist_api::setup(rocket); + + // Register all the endpoints related to the user account system to our server + let rocket = pointercrate_user_api::setup(rocket); + + // Let rocket serve static files (e.g. CSS, JavaScript, images, etc.). In a + // production environment, you will not want rocket to be responsible for this + // and instead use a web server such as nginx as a reverse proxy to serve your + // static files. + let rocket = rocket + .mount("/static/core", FileServer::from("pointercrate-core-pages/static")) + .mount("/static/demonlist", FileServer::from("pointercrate-demonlist-pages/static")) + .mount("/static/user", FileServer::from("pointercrate-user-pages/static")); + + rocket +} + +/// Constructs a [`PageConfiguration`] for your site. +/// +/// A `PageConfiguration` object is a description of your websites general +/// look-and-feel. It defines the navigation bar and footer layouts (e.g. what +/// links to include) and various metadata without you needing to worry (much) +/// about styling and layout. +fn page_configuration() -> PageConfiguration { + // Define a navigation bar with only two items, a link to the user account page, + // and a link to your demonlist. + let nav_bar = NavigationBar::new("/static/images/path/to/your/logo.png") + .with_item( + TopLevelNavigationBarItem::new( + "/demonlist/", + // Pointercrate uses the "maud" create as its templating engine. + // It allows you to describe HTML via Rust macros that allow you to dynamically generate content using + // a Rust-like syntax and by interpolating and Rust variables from surrounding scopes (as long as the + // implement the `Render` trait). See https://maud.lambda.xyz/ for details. + html! { + span { + "Demonlist" + } + }, + ) + // Add a drop down to the demonlist item, just like on pointercrate.com + .with_sub_item("/demonlist/statsviewer/", html! {"Stats Viewer"}) + .with_sub_item("/demonlist/?submitter=true", html! {"Record Submitter"}) + .with_sub_item("/demonlist/?timemachine=true", html! {"Time Machine"}), + ) + .with_item(TopLevelNavigationBarItem::new( + "/login/", + html! { + span { + "User Area" + } + }, + )); + + // A footer consists of a copyright notice, an arbitrary amount of columns + // displayed below it, side-by-side, and potentially some social media links to + // your team + let footer = Footer::new(html! { + "© Copyright " + br; + "All rights reserved" + br; + " and are in no way affiliated with RobTopGamesAB ® or pointercrate.com" + }, html! { "No terms of use" }) + // Add a column with links for various list-related highlights + .with_column(FooterColumn::LinkList { + heading: "Demonlist", + links: vec![ + Link::new("/demonlist/1/", "Current Top Demon"), + Link::new( + format!("/demonlist/{}/", pointercrate_demonlist::config::list_size() + 1), + "Extended List", + ), + Link::new( + format!("/demonlist/{}/", pointercrate_demonlist::config::extended_list_size() + 1), + "Legacy List", + ), + ], + }) + // Some links to social media, for example your twitter + .with_link("https://twitter.com/stadust1971", "Pointercrate Developer"); + + // Stitching it all together into a page configuration + PageConfiguration::new("", nav_bar, footer) + // Used for the HTML "author" meta tag + .author("your name") + // Used for the HTML "keywords" meta tag + .keywords("Your SEO keywords here") +} diff --git a/pointercrate-integrate/Cargo.toml b/pointercrate-integrate/Cargo.toml index 8fffb7b44..dc3e61640 100644 --- a/pointercrate-integrate/Cargo.toml +++ b/pointercrate-integrate/Cargo.toml @@ -1,19 +1,24 @@ [package] name = "pointercrate-integrate" version = "0.1.0" -edition = "2021" +authors.workspace = true +edition.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] - -sqlx = { version = "0.6", default-features = false, features = [ "runtime-tokio-native-tls", "macros", "postgres", "chrono", "offline" ] } +sqlx = { version = "0.7", default-features = false, features = [ "runtime-tokio-native-tls", "macros", "postgres", "chrono" ] } bincode = "1.3.1" -reqwest = "0.11.*" +reqwest = "0.12.*" futures = "0.3.8" -log = "0.4.11" -chrono = "0.4.19" -tokio = {version = "1.20.4", features = ["rt"]} +log = "0.4.22" +chrono = "0.4.38" +tokio = {version = "1.38.0", features = ["rt"]} +pointercrate-demonlist = { path = "../pointercrate-demonlist" } +pointercrate-core = { path = "../pointercrate-core" } +governor = "0.6.3" +nonzero_ext = "0.3.0" [dependencies.dash-rs] git = "https://github.com/qimiko/dash-rs" +rev = "21b8e86aa3ffe9ba4ba4bfd147b94abf2afa50ef" diff --git a/pointercrate-integrate/src/gd.rs b/pointercrate-integrate/src/gd.rs index cfb7e3e49..597de036b 100644 --- a/pointercrate-integrate/src/gd.rs +++ b/pointercrate-integrate/src/gd.rs @@ -1,408 +1,196 @@ -use chrono::{DateTime, Duration, NaiveDateTime, Utc}; use dash_rs::{ model::{ creator::Creator, - level::{Featured, Level, LevelData, LevelLength, ListedLevel, Objects, Password}, + level::{Featured, Level, LevelData, LevelLength, Password}, song::{MainSong, NewgroundsSong}, GameVersion, }, request::level::{LevelRequest, LevelRequestType, LevelsRequest, SearchFilters}, - response::ResponseError, - Base64Decoded, PercentDecoded, ProcessError, ThunkContent, + response::{parse_download_gj_level_response, parse_get_gj_levels_response}, }; -use futures::{FutureExt, StreamExt}; -use log::{error, info, trace}; +use log::{error, trace}; +use pointercrate_core::ratelimits; +use pointercrate_demonlist::demon::Demon; use reqwest::{header::CONTENT_TYPE, Client}; -use sqlx::{Error, Pool, Postgres}; -use std::{ - borrow::Cow, - collections::hash_map::DefaultHasher, - hash::{Hash, Hasher}, -}; +use sqlx::{Pool, Postgres}; +use std::{borrow::Cow, sync::Arc}; pub use dash_rs::{ model::level::{DemonRating, LevelRating}, Thunk, }; use reqwest::header::HeaderMap; -use std::cmp::Ordering; +ratelimits! { + IntegrationRatelimits { + demon_refresh[1u32 per 86400 per i32] => "Only one refresh per day per demon", + throttle[1u32 per 60] => "Wait at least 1 minute between level requests", + throttle_throttle[1u32 per 600 per i32] => "Only hit the global throttle rate limit once per 10 minutes per demon", + } +} + +/// Struct encoding cached level data. Like [LevelData] but with more pre-processing #[derive(Debug)] -pub enum GDIntegrationResult { - Success(Level<'static, ()>, CachedLevelData, Option>), - DemonNotFoundByName, - DemonNotYetCached, - LevelDataNotFound, - LevelDataNotCached, +pub struct CachedLevelData { + pub password: Password, + pub length: i32, + pub object_count: i32, } -impl PgCache { - pub async fn data_for_demon( - &self, http_client: Client, level_id: Option, name: String, demon_id: i32, - ) -> Result { - trace!("Retrieving data for demon {:?}", name); - - match level_id { - None => { - info!("Data for demon {} not cached, trying to download!", demon_id); - - let request = LevelsRequest::default() - .request_type(LevelRequestType::MostLiked) - .search(&name) - .with_rating(LevelRating::Demon(DemonRating::Hard)) - .search_filters(SearchFilters::default().rated()); - - let entry = match self.lookup_levels_request(&request).await { - Err(CacheError::Db(_err)) => return Err(()), - Err(_) => return Err(()), // shouldn't be reachable - Ok(entry) => entry, - }; - - match entry { - CacheEntry::Absent => Ok(GDIntegrationResult::DemonNotFoundByName), - // Okay we _could_ do something more elaborate here if we dont have a "Missing" variant, but honestly I dont care - _ => { - tokio::spawn(self.clone().find_demon(http_client, name.clone(), demon_id).map(|_| ())); - - Ok(GDIntegrationResult::DemonNotYetCached) - }, - } - }, - Some(level_id) => { - let entry = match self.lookup_level(level_id).await { - Err(CacheError::Db(_err)) => return Err(()), - Err(_) => return Err(()), // shouldn't be reachable - Ok(entry) => entry, - }; - - trace!( - "Lookup of level with id {} (associated with demon '{}') yielded: {:?}", - level_id, - name, - entry - ); - - let level = match entry { - // Something went very wrong - CacheEntry::Missing => return Ok(GDIntegrationResult::LevelDataNotCached), - CacheEntry::Absent => return Ok(GDIntegrationResult::LevelDataNotFound), - CacheEntry::Expired(level, _) => { - tokio::spawn(self.clone().find_demon(http_client.clone(), name.clone(), demon_id).map(|_| ())); - - level - }, - CacheEntry::Live(level, _) => level, - }; - - let level_data = match self.lookup_level_data(level_id).await { - Err(CacheError::Db(_err)) => return Err(()), - Err(_) => return Err(()), // shouldn't be reachable - Ok(CacheEntry::Missing) => { - // force a new download on missing - // in the future, there should probably be a way to track errors - tokio::spawn( - self.clone() - .download_demon(http_client, level.level_id.into(), demon_id) - .map(|_| ()), - ); - - return Ok(GDIntegrationResult::DemonNotYetCached) - }, - Ok(CacheEntry::Absent) => return Ok(GDIntegrationResult::LevelDataNotFound), - Ok(CacheEntry::Expired(level_data, _)) => { - tokio::spawn( - self.clone() - .download_demon(http_client, level.level_id.into(), demon_id) - .map(|_| ()), - ); - - level_data - }, - Ok(CacheEntry::Live(level_data, _)) => level_data, - }; - - let song = match level.custom_song { - Some(id) => - self.lookup_newgrounds_song(id).await.ok().and_then(|entry| { - match entry { - CacheEntry::Expired(song, _) | CacheEntry::Live(song, _) => Some(song), - _ => None, - } - }), - None => None, - }; - - Ok(GDIntegrationResult::Success(level, level_data, song)) - }, +pub type IntegrationLevel = Level<'static, CachedLevelData, Option>>; + +impl GeometryDashConnector { + /// Attempts to pull the Geometry Dash level data for the given [`Demon`] from the database + /// + /// If the last time the data for this demon was sought on the Geomeetry Dash servers was over 24h ago, + /// re-query them for updated data. + pub async fn load_level_for_demon(&self, demon: &Demon) -> Option { + if self.ratelimits.throttle_throttle(demon.base.id).is_ok() { + if self.ratelimits.throttle().is_ok() && self.ratelimits.demon_refresh(demon.base.id).is_ok() { + tokio::spawn(self.clone().refresh_demon_data(demon.base.name.clone(), demon.base.id)); + } + } + + if let Some(level_id) = demon.level_id { + let level = self + .lookup_level(level_id) + .await? + .with_data(self.lookup_level_data(level_id).await?); + + let song = match level.custom_song { + Some(id) => self.lookup_newgrounds_song(id).await, + None => None, + }; + + return Some(level.with_custom_song(song)); } + + None } - async fn find_demon(self, http_client: Client, demon_name: String, demon: i32) -> Result<(), ()> { + pub async fn refresh_demon_data(self, name: String, demon_id: i32) { + // Lookup demon by name let request = LevelsRequest::default() + // Heuristic: list level have a lot of likes .request_type(LevelRequestType::MostLiked) - .search(&demon_name) + .search(&name) + // passing any `LevelRating::Demon` variant here will result in filtering by arbitrary demon difficulty .with_rating(LevelRating::Demon(DemonRating::Hard)) .search_filters(SearchFilters::default().rated()); - trace!("Trying to find demon {} via request {:?}", demon_name, request); + let Ok(response) = self.make_request(request.to_url(), request.to_string()).await else { + return; + }; + let Ok(demons) = parse_get_gj_levels_response(&response) else { + return; + }; + let Some(mut hardest) = demons + .into_iter() + // Geometry Dash servers only do a substring match, so we have to ensure the name is equal to what we're looking for + .filter(|demon| demon.name.trim().eq_ignore_ascii_case(name.trim())) + .max_by(|x, y| x.difficulty.cmp(&y.difficulty)) + else { + return; + }; - let request_result = http_client - .post(&request.to_url()) - .headers(HeaderMap::new()) // boomlings.com rejects any request with a User-Agent header set, so make sure reqwest doesn't "helpfully" add one - .body(request.to_string()) - .header(CONTENT_TYPE, "application/x-www-form-urlencoded") - .send() - .await; + let request = LevelRequest::new(hardest.level_id); + let Ok(response) = self.make_request(request.to_url(), request.to_string()).await else { + return; + }; + let Ok(mut level) = parse_download_gj_level_response(&response) else { + return; + }; - trace!("Request result is {:?}", request_result); - - match request_result { - Ok(response) => - match response.text().await { - Ok(text) => - match dash_rs::response::parse_get_gj_levels_response(&text[..]) { - Err(ResponseError::NotFound) => - self.mark_levels_request_result_as_absent(&request) - .await - .map_err(|err| error!("Error marking result to {:?} as absent: {:?}", request, err)) - .map(|_| ()), - Ok(demons) => - if demons.is_empty() { - self.mark_levels_request_result_as_absent(&request) - .await - .map_err(|err| error!("Error marking result to {:?} as absent: {:?}", request, err)) - .map(|_| ()) - } else { - trace!("Request to find demon {} yielded result {:?}", demon_name, demons); - - self.store_levels_request(&request, &demons) - .await - .map_err(|err| error!("Error storing levels request result: {:?}", err))?; - - let hardest = demons - .into_iter() - .filter(|demon| demon.name.to_lowercase().trim() == demon_name.to_lowercase().trim()) - .max_by(|x, y| x.difficulty.cmp(&y.difficulty).then(Ordering::Greater)); - - match hardest { - Some(hardest) => { - trace!("The hardest demon I could find with name '{}' is {:?}", demon_name, hardest); - - self.download_demon(http_client, hardest.level_id.into(), demon).await - }, - None => { - error!("Could not find a level whose name matches '{}'", demon_name); - - Err(()) - }, - } - }, - Err(err) => Err(error!("Error processing response to request {:?}: {:?}", request, err)), - }, - Err(error) => Err(error!("Error reading server response: {:?}", error)), - }, - Err(error) => Err(error!("Error making request: {:?}", error)), + if let Some(newgrounds_song) = &mut hardest.custom_song { + self.store_newgrounds_song(newgrounds_song).await; } - } - async fn download_demon(self, http_client: Client, request: LevelRequest<'static>, demon_id: i32) -> Result<(), ()> { - trace!("Downloading demon with id {}", request.level_id); + if let Some(creator) = &hardest.creator { + self.store_creator(creator).await; + } - let request_result = http_client - .post(&request.to_url()) - .headers(HeaderMap::new()) // boomlings.com rejects any request with a User-Agent header set, so make sure reqwest doesn't "helpfully" add one - .body(request.to_string()) + self.store_level(&mut hardest, level.creator, level.custom_song).await; + self.store_level_data(level.level_id, &mut level.level_data).await; + + let _ = sqlx::query!("UPDATE demons SET level_id = $1 WHERE id = $2", level.level_id as i64, demon_id) + .execute(&self.pool) + .await; + } + + async fn make_request(&self, url: String, body: String) -> Result { + let response = self.http_client + .post(url) + // boomlings.com rejects any request with a User-Agent header set, so make sure reqwest doesn't "helpfully" add one + .headers(HeaderMap::new()) + .body(body) .header(CONTENT_TYPE, "application/x-www-form-urlencoded") .send() - .await; + .await?; - match request_result { - Ok(response) => { - let content = response.text().await; - - match content { - Ok(text) => - match dash_rs::response::parse_download_gj_level_response(&text[..]) { - Ok(demon) => { - self.store_level_data(demon.level_id, &demon.level_data) - .await - .map_err(|err| error!("Error storing demon '{}': {:?}", demon.name, err))?; - - sqlx::query!("UPDATE demons SET level_id = $1 WHERE id = $2", request.level_id as i64, demon_id) - .execute(&self.pool) - .await - .map_err(|err| error!("Error updating level_id: {:?}", err))?; - - sqlx::query!("DELETE FROM download_lock WHERE level_id = $1", request.level_id as i64) - .execute(&self.pool) - .await - .map_err(|err| error!("Error freeing download lock: {:?}", err))?; - - Ok(info!("Successfully retrieved demon data!")) - }, - Err(ResponseError::NotFound) => - self.mark_level_data_as_absent(request.level_id) - .await - .map_err(|err| error!("Error marking level as absent: {:?}", err)) - .map(|_| ()), - Err(err) => Err(error!("Error processing response: {:?}", err)), - }, - Err(err) => Err(error!("Error making http request: {:?}", err)), - } - }, - Err(err) => Err(error!("Error making http request: {:?}", err)), - } + response.text().await } } // FIXME: Right now this implementation always stores processed data. In case of processing failure, // it refuses to store the object. In the future, we probably want to store the unprocessed data -// then with a special flag. However, this is not yet supported by dash-rs, as dash-rs doesnt -// support owned, unprocessed data. - -#[derive(Debug)] -pub struct CacheEntryMeta { - made: NaiveDateTime, - key: i64, - absent: bool, -} - -#[derive(Debug)] -pub enum CacheError { - Db(Error), - Malformed(ProcessError), - MalformedLevelData, -} - -impl From for CacheError { - fn from(err: Error) -> Self { - CacheError::Db(err) - } -} - -impl From for CacheError { - fn from(err: ProcessError) -> Self { - CacheError::Malformed(err) - } -} - -#[derive(Debug)] -pub enum CacheEntry { - Missing, - Absent, - Expired(T, CacheEntryMeta), - Live(T, CacheEntryMeta), -} +// then with a special flag. #[derive(Clone)] -pub struct PgCache { +pub struct GeometryDashConnector { pool: Pool, - expire_after: Duration, -} - -/// Struct encoding cached level data. Like [LevelData] but with more pre-processing -#[derive(Debug)] -pub struct CachedLevelData { - pub password: Password, - pub length: i32, - pub object_count: i32, + http_client: Client, + ratelimits: Arc, } -impl PgCache { - pub fn new(pool: Pool, expire_after: Duration) -> Self { - PgCache { pool, expire_after } - } - - fn make_cache_entry(&self, meta: CacheEntryMeta, t: T) -> CacheEntry { - if DateTime::::from_utc(meta.made, Utc) - Utc::now() < self.expire_after { - CacheEntry::Live(t, meta) - } else { - CacheEntry::Expired(t, meta) +impl GeometryDashConnector { + pub fn new(pool: Pool) -> Self { + GeometryDashConnector { + pool, + http_client: Client::new(), + ratelimits: Arc::new(IntegrationRatelimits::new()), } } - pub async fn lookup_creator(&self, user_id: u64) -> Result>, CacheError> { - let mut connection = self.pool.acquire().await?; - - let meta = sqlx::query_as!( - CacheEntryMeta, - "SELECT user_id AS key, cached_at AS made, absent FROM gj_creator_meta WHERE user_id = $1", - user_id as i64 - ) - .fetch_one(&mut *connection) - .await; - - let meta = match meta { - Err(sqlx::Error::RowNotFound) => return Ok(CacheEntry::Missing), - Err(err) => return Err(err.into()), - Ok(meta) if meta.absent => return Ok(CacheEntry::Absent), - Ok(meta) => meta, - }; + pub async fn lookup_creator(&self, user_id: u64) -> Option> { + let mut connection = self.pool.acquire().await.ok()?; let creator_row = sqlx::query!("SELECT * FROM gj_creator WHERE user_id = $1", user_id as i64) .fetch_one(&mut *connection) - .await?; + .await + .ok()?; - let creator = Creator { + Some(Creator { user_id: creator_row.user_id as u64, name: Cow::Owned(creator_row.name), account_id: creator_row.account_id.map(|id| id as u64), - }; - - Ok(self.make_cache_entry(meta, creator)) + }) } - pub async fn store_creator<'a>(&self, creator: &Creator<'a>) -> Result { - let mut connection = self.pool.begin().await?; + pub async fn store_creator<'a>(&self, creator: &Creator<'a>) { + let Ok(mut connection) = self.pool.begin().await else { return }; - let meta = sqlx::query_as!( - CacheEntryMeta, - "INSERT INTO gj_creator_meta (user_id, cached_at, absent) VALUES($1, $2, FALSE) ON CONFLICT (user_id) DO UPDATE SET cached_at \ - = EXCLUDED.cached_at, absent = FALSE RETURNING user_id AS key, cached_at AS made, absent", - creator.user_id as i64, - Utc::now().naive_utc() - ) - .fetch_one(&mut connection) - .await?; - - sqlx::query!( + let _ = sqlx::query!( "INSERT INTO gj_creator (user_id, name, account_id) VALUES ($1, $2, $3) ON CONFLICT (user_id) DO UPDATE SET name = \ EXCLUDED.name, account_id = EXCLUDED.account_id", creator.user_id as i64, creator.name.to_string(), // FIXME: figure out why it doesnt accept a reference creator.account_id.map(|id| id as i64) ) - .execute(&mut connection) - .await?; - - connection.commit().await?; + .execute(&mut *connection) + .await; - Ok(meta) + let _ = connection.commit().await; } - pub async fn lookup_newgrounds_song(&self, song_id: u64) -> Result>, CacheError> { - let mut connection = self.pool.acquire().await?; - - let meta = sqlx::query_as!( - CacheEntryMeta, - "SELECT song_id AS key, cached_at AS made, absent FROM gj_newgrounds_song_meta WHERE song_id = $1", - song_id as i64 - ) - .fetch_one(&mut *connection) - .await; - - let meta = match meta { - Err(sqlx::Error::RowNotFound) => return Ok(CacheEntry::Missing), - Err(err) => return Err(err.into()), - Ok(meta) if meta.absent => return Ok(CacheEntry::Absent), - Ok(meta) => meta, - }; + pub async fn lookup_newgrounds_song(&self, song_id: u64) -> Option> { + let mut connection = self.pool.acquire().await.ok()?; let song_row = sqlx::query!("SELECT * from gj_newgrounds_song WHERE song_id = $1", song_id as i64) .fetch_one(&mut *connection) - .await?; + .await + .ok()?; - let song = NewgroundsSong { + Some(NewgroundsSong { song_id, name: Cow::Owned(song_row.song_name), index_3: song_row.index_3 as u64, @@ -411,32 +199,17 @@ impl PgCache { index_6: song_row.index_6.map(Cow::Owned), index_7: song_row.index_7.map(Cow::Owned), index_8: Cow::Owned(song_row.index_8), - link: Thunk::Processed(PercentDecoded(Cow::Owned(song_row.song_link))), - }; - - Ok(self.make_cache_entry(meta, song)) + link: Thunk::Processed(Cow::Owned(song_row.song_link)), + }) } - pub async fn store_newgrounds_song<'a>(&self, song: &NewgroundsSong<'a>) -> Result { - let mut connection = self.pool.begin().await?; - - let meta = sqlx::query_as!( - CacheEntryMeta, - "INSERT INTO gj_newgrounds_song_meta (song_id, cached_at, absent) VALUES ($1, $2, FALSE) ON CONFLICT (song_id) DO UPDATE SET \ - cached_at = EXCLUDED.cached_at, absent = FALSE RETURNING song_id AS key, cached_at AS made, absent", - song.song_id as i64, - Utc::now().naive_utc() - ) - .fetch_one(&mut connection) - .await?; + pub async fn store_newgrounds_song<'a>(&self, song: &NewgroundsSong<'a>) { + let Ok(mut connection) = self.pool.begin().await else { return }; // FIXME: this - let song_link = match song.link { - Thunk::Unprocessed(unprocessed) => PercentDecoded::from_unprocessed(unprocessed)?.0.to_string(), - Thunk::Processed(ref link) => link.0.to_string(), - }; + let Ok(song_link) = song.link.as_processed() else { return }; - sqlx::query!( + let _ = sqlx::query!( "INSERT INTO gj_newgrounds_song (song_id, song_name, index_3, song_artist, filesize, index_6, index_7, index_8, song_link) \ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) ON CONFLICT (song_id) DO UPDATE SET song_id = $1, song_name = $2, index_3 = $3, \ song_artist = $4, filesize = $5, index_6 = $6, index_7 = $7, index_8 = $8, song_link = $9", @@ -448,57 +221,23 @@ impl PgCache { song.index_6.as_deref(), song.index_7.as_deref(), &song.index_8.as_ref(), - song_link + song_link.as_ref() ) - .execute(&mut connection) - .await?; - - connection.commit().await?; - - Ok(meta) - } - - pub async fn mark_level_data_as_absent<'a>(&self, level_id: u64) -> Result { - let mut connection = self.pool.begin().await?; - - let meta = sqlx::query_as!( - CacheEntryMeta, - "INSERT INTO gj_level_data_meta (level_id, cached_at, absent) VALUES ($1, $2, TRUE) ON CONFLICT (level_id) DO UPDATE SET \ - cached_at = EXCLUDED.cached_at, absent = TRUE RETURNING level_id AS key, cached_at AS made, absent", - level_id as i64, - Utc::now().naive_utc() - ) - .fetch_one(&mut connection) - .await?; - - connection.commit().await?; + .execute(&mut *connection) + .await; - Ok(meta) + let _ = connection.commit().await; } - pub async fn lookup_level_data<'a>(&self, level_id: u64) -> Result, CacheError> { - let mut connection = self.pool.acquire().await?; - - let meta = sqlx::query_as!( - CacheEntryMeta, - "SELECT level_id AS key, cached_at AS made, absent FROM gj_level_data_meta WHERE level_id = $1", - level_id as i64 - ) - .fetch_one(&mut *connection) - .await; - - let meta = match meta { - Err(sqlx::Error::RowNotFound) => return Ok(CacheEntry::Missing), - Err(err) => return Err(err.into()), - Ok(meta) if meta.absent => return Ok(CacheEntry::Absent), - Ok(meta) => meta, - }; + pub async fn lookup_level_data<'a>(&self, level_id: u64) -> Option { + let mut connection = self.pool.acquire().await.ok()?; let row = sqlx::query!("SELECT * FROM gj_level_data WHERE level_id = $1", level_id as i64) .fetch_one(&mut *connection) - .await?; + .await + .ok()?; - let level = CachedLevelData { + Some(CachedLevelData { password: match row.level_password { None => Password::NoCopy, Some(-1) => Password::FreeCopy, @@ -506,23 +245,11 @@ impl PgCache { }, length: row.level_length, object_count: row.level_objects_count, - }; - - Ok(self.make_cache_entry(meta, level)) + }) } - pub async fn store_level_data<'a>(&self, level_id: u64, data: &LevelData<'a>) -> Result { - let mut connection = self.pool.begin().await?; - - let meta = sqlx::query_as!( - CacheEntryMeta, - "INSERT INTO gj_level_data_meta (level_id, cached_at, absent) VALUES ($1, $2, FALSE) ON CONFLICT (level_id) DO UPDATE SET \ - cached_at = EXCLUDED.cached_at, absent = FALSE RETURNING level_id AS key, cached_at AS made, absent", - level_id as i64, - Utc::now().naive_utc() - ) - .fetch_one(&mut connection) - .await?; + pub async fn store_level_data<'a>(&self, level_id: u64, data: &mut LevelData<'a>) { + let Ok(mut connection) = self.pool.begin().await else { return }; // lol. struct LevelObjectMeta { @@ -532,197 +259,51 @@ impl PgCache { // FIXME: this trace!("Starting to parse level data"); - let objects = match data.level_data { - Thunk::Unprocessed(unprocessed) => { - let processed = Objects::from_unprocessed(unprocessed).map_err(|err| { - error!("Error processing level data: {:?}", err); - - CacheError::MalformedLevelData - })?; - - LevelObjectMeta { - object_count: processed.objects.len(), - length_in_seconds: processed.length_in_seconds(), - } - }, - Thunk::Processed(ref proc) => - LevelObjectMeta { - object_count: proc.objects.len(), - length_in_seconds: proc.length_in_seconds(), - }, + let Ok(processed) = data.level_data.process() else { + return error!("Error processing level data for {}", level_id); + }; + + let objects = LevelObjectMeta { + object_count: processed.objects.len(), + length_in_seconds: processed.length_in_seconds(), }; trace!("Finished parsing level data"); - sqlx::query!( - "INSERT INTO gj_level_data(level_id,level_password,level_length,level_objects_count) VALUES ($1,$2,$3,$4) ON \ - CONFLICT(level_id) DO UPDATE SET \ + let Ok(password) = data.password.process() else { return }; + + let _ = sqlx::query!( + "INSERT INTO gj_level_data(level_id,level_password,level_length,level_objects_count) VALUES \ + ($1,$2,$3,$4) ON CONFLICT(level_id) DO UPDATE SET \ level_id=EXCLUDED.level_id,level_password=EXCLUDED.level_password,level_length=EXCLUDED.level_length,\ level_objects_count=EXCLUDED.level_objects_count", level_id as i64, - match data.password { + match password { Password::NoCopy => None, Password::FreeCopy => Some(-1), - Password::PasswordCopy(pw) => Some(pw as i32), + Password::PasswordCopy(pw) => Some(*pw as i32), }, objects.length_in_seconds as i32, // specify the overflow behavior to return max instead of whatever i32::try_from(objects.object_count).unwrap_or(i32::MAX) ) .execute(&mut *connection) - .await?; - - connection.commit().await?; - - Ok(meta) - } - - pub async fn mark_levels_request_result_as_absent<'a>(&self, request: &LevelsRequest<'a>) -> Result { - trace!("Marking result of request {:?} as absent!", request); - - let hash = { - let mut hasher = DefaultHasher::new(); - request.hash(&mut hasher); - hasher.finish() as i64 - }; - - let mut connection = self.pool.begin().await?; - - let meta = sqlx::query_as!( - CacheEntryMeta, - "INSERT INTO gj_level_request_meta (request_hash, cached_at, absent) VALUES ($1, $2, TRUE) ON CONFLICT (request_hash) DO \ - UPDATE SET cached_at = EXCLUDED.cached_at, absent = TRUE RETURNING request_hash AS key, cached_at AS made, absent", - hash, - Utc::now().naive_utc() - ) - .fetch_one(&mut connection) - .await?; - - connection.commit().await?; - - Ok(meta) - } - - pub async fn lookup_levels_request<'a>( - &self, request: &LevelsRequest<'a>, - ) -> Result>>>, CacheError> { - let hash = { - let mut hasher = DefaultHasher::new(); - request.hash(&mut hasher); - hasher.finish() as i64 - }; - - let mut connection = self.pool.acquire().await?; - - let meta = sqlx::query_as!( - CacheEntryMeta, - "SELECT request_hash AS key, cached_at AS made, absent FROM gj_level_request_meta WHERE request_hash = $1", - hash - ) - .fetch_one(&mut *connection) .await; - let meta = match meta { - Err(sqlx::Error::RowNotFound) => return Ok(CacheEntry::Missing), - Err(err) => return Err(err.into()), - Ok(meta) if meta.absent => return Ok(CacheEntry::Absent), - Ok(meta) => meta, - }; - - let mut stream = - sqlx::query!("SELECT level_id from gj_level_request_results WHERE request_hash = $1", hash).fetch(&mut *connection); - let mut levels = Vec::new(); - - while let Some(row) = stream.next().await { - let level_id = row?.level_id as u64; - - levels.push(self.lookup_level(level_id).await?); - } - - Ok(self.make_cache_entry(meta, levels)) - } - - pub async fn store_levels_request<'a, 'b>( - &self, request: &LevelsRequest<'a>, levels: &Vec>, - ) -> Result { - let hash = { - let mut hasher = DefaultHasher::new(); - request.hash(&mut hasher); - hasher.finish() as i64 - }; - - let mut connection = self.pool.begin().await?; - - let meta = sqlx::query_as!( - CacheEntryMeta, - "INSERT INTO gj_level_request_meta (request_hash, cached_at, absent) VALUES ($1, $2, FALSE) ON CONFLICT (request_hash) DO \ - UPDATE SET cached_at = EXCLUDED.cached_at, absent = FALSE RETURNING request_hash AS key, cached_at AS made, absent", - hash, - Utc::now().naive_utc() - ) - .fetch_one(&mut connection) - .await?; - - for level in levels { - self.store_level( - level, - level.creator.as_ref().map(|c| c.user_id).unwrap_or(0), - level.custom_song.as_ref().map(|n| n.song_id), - ) - .await?; - - // FIXME: We do not correctly handle the case where newgrounds song/creator is absent. For - // pointercrate, this should not be a problem however. - - if let Some(ref creator) = level.creator { - self.store_creator(creator).await?; - } - - if let Some(ref song) = level.custom_song { - self.store_newgrounds_song(song).await?; - } - - sqlx::query!( - "INSERT INTO gj_level_request_results(level_id, request_hash) VALUES ($1, $2)", - level.level_id as i64, - hash - ) - .execute(&mut *connection) - .await?; - } - - connection.commit().await?; - - Ok(meta) + let _ = connection.commit().await; } - pub async fn lookup_level(&self, level_id: u64) -> Result>, CacheError> { - let mut connection = self.pool.acquire().await?; - - let meta = sqlx::query_as!( - CacheEntryMeta, - "SELECT level_id AS key, cached_at AS made, absent FROM gj_level_meta WHERE level_id = $1", - level_id as i64 - ) - .fetch_one(&mut *connection) - .await; - - let meta = match meta { - Err(sqlx::Error::RowNotFound) => return Ok(CacheEntry::Missing), - Err(err) => return Err(err.into()), - Ok(meta) if meta.absent => return Ok(CacheEntry::Absent), - Ok(meta) => meta, - }; + pub async fn lookup_level(&self, level_id: u64) -> Option> { + let mut connection = self.pool.acquire().await.ok()?; let row = sqlx::query!("SELECT * FROM gj_level WHERE level_id = $1", level_id as i64) - .fetch_one(&mut connection) - .await?; + .fetch_one(&mut *connection) + .await + .ok()?; - let level = Level { + Some(Level { level_id, name: Cow::Owned(row.level_name), - description: row - .description - .map(|description| Thunk::Processed(Base64Decoded(Cow::Owned(description)))), + description: row.description.map(|description| Thunk::Processed(Cow::Owned(description))), version: row.level_version as u32, creator: row.creator_id as u64, difficulty: i16_to_level_rating(row.difficulty, row.is_demon), @@ -744,28 +325,18 @@ impl PgCache { index_46: row.index_46.map(Cow::Owned), index_47: row.index_47.map(Cow::Owned), level_data: (), - }; - - Ok(self.make_cache_entry(meta, level)) + }) } // This must be the most horrifying piece of code I have ever written. - async fn store_level<'a, T, U, V>( - &self, level: &Level<'a, T, U, V>, creator_id: u64, custom_song_id: Option, - ) -> Result { - let mut connection = self.pool.begin().await?; - - let meta = sqlx::query_as!( - CacheEntryMeta, - "INSERT INTO gj_level_meta (level_id, cached_at, absent) VALUES ($1, $2, FALSE) ON CONFLICT (level_id) DO UPDATE SET \ - cached_at = EXCLUDED.cached_at, absent = FALSE RETURNING level_id AS key, cached_at AS made, absent", - level.level_id as i64, - Utc::now().naive_utc() - ) - .fetch_one(&mut connection) - .await?; + async fn store_level<'a, T, U, V>(&self, level: &Level<'a, T, U, V>, creator_id: u64, custom_song_id: Option) { + let Ok(mut connection) = self.pool.begin().await else { return }; - sqlx::query!( + let Ok(description) = level.description.as_ref().map(|thunk| thunk.as_processed()).transpose() else { + return; + }; + + let _ = sqlx::query!( "INSERT INTO \ gj_level(level_id,level_name,description,level_version,creator_id,difficulty,is_demon,downloads,main_song,gd_version,likes,\ level_length,stars,featured,copy_of,two_player,custom_song_id,coin_amount,coins_verified,stars_requested,is_epic,\ @@ -780,15 +351,7 @@ impl PgCache { index_46,index_47=EXCLUDED.index_47", level.level_id as i64, level.name.as_ref(), - match &level.description { - Some(thunk) => { - Some(match thunk { - Thunk::Processed(processed) => processed.0.to_string(), - Thunk::Unprocessed(unproc) => Base64Decoded::from_unprocessed(unproc)?.0.to_string(), - }) - }, - None => None, - }, + description.map(|cow| cow.to_string()), level.version as i32, creator_id as i64, level_rating_to_i16(level.difficulty), @@ -811,12 +374,10 @@ impl PgCache { level.index_46.as_deref(), level.index_47.as_deref() ) - .execute(&mut connection) - .await?; - - connection.commit().await?; + .execute(&mut *connection) + .await; - Ok(meta) + let _ = connection.commit().await; } } @@ -830,24 +391,23 @@ fn level_rating_to_i16(level_rating: LevelRating) -> i16 { LevelRating::Harder => 5, LevelRating::Insane => 6, LevelRating::NotAvailable => 7, - LevelRating::Demon(demon_rating) => - match demon_rating { - DemonRating::Unknown(unknown) => (unknown as i16) * 100, - DemonRating::Easy => 1, - DemonRating::Medium => 2, - DemonRating::Hard => 3, - DemonRating::Insane => 4, - DemonRating::Extreme => 5, - }, + LevelRating::Demon(demon_rating) => match demon_rating { + DemonRating::Unknown(unknown) => (unknown as i16) * 100, + DemonRating::Easy => 1, + DemonRating::Medium => 2, + DemonRating::Hard => 3, + DemonRating::Insane => 4, + DemonRating::Extreme => 5, + }, } } fn i16_to_level_rating(value: i16, is_demon: bool) -> LevelRating { if value.abs() >= 100 || value == 0 { if is_demon { - return LevelRating::Demon(DemonRating::Unknown((value / 100) as i32)) + return LevelRating::Demon(DemonRating::Unknown((value / 100) as i32)); } else { - return LevelRating::Unknown((value / 100) as i32) + return LevelRating::Unknown((value / 100) as i32); } } @@ -882,12 +442,13 @@ fn level_length_to_i16(length: LevelLength) -> i16 { LevelLength::Medium => 3, LevelLength::Long => 4, LevelLength::ExtraLong => 5, + LevelLength::Platformer => 6, } } fn i16_to_level_length(value: i16) -> LevelLength { if value.abs() >= 100 || value == 0 { - return LevelLength::Unknown((value / 100) as i32) + return LevelLength::Unknown((value / 100) as i32); } match value { @@ -896,6 +457,7 @@ fn i16_to_level_length(value: i16) -> LevelLength { 3 => LevelLength::Medium, 4 => LevelLength::Long, 5 => LevelLength::ExtraLong, + 6 => LevelLength::Platformer, _ => unreachable!(), } } diff --git a/pointercrate-test/Cargo.toml b/pointercrate-test/Cargo.toml index b5ebf446c..f6c7aa468 100644 --- a/pointercrate-test/Cargo.toml +++ b/pointercrate-test/Cargo.toml @@ -1,7 +1,8 @@ [package] name = "pointercrate-test" version = "0.1.0" -edition = "2021" +authors.workspace = true +edition.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -9,11 +10,13 @@ edition = "2021" pointercrate-demonlist = {path = "../pointercrate-demonlist"} pointercrate-demonlist-api = {path = "../pointercrate-demonlist-api"} pointercrate-core = {path = "../pointercrate-core"} +pointercrate-core-api = {path = "../pointercrate-core-api"} pointercrate-user = {path = "../pointercrate-user"} pointercrate-user-api = {path = "../pointercrate-user-api"} pointercrate-user-pages = {path = "../pointercrate-user-pages"} -serde = "1.0.152" -sqlx = { version = "0.6", default-features = false, features = [ "runtime-tokio-native-tls", "macros", "postgres", "chrono", "migrate", "offline" ] } -rocket = "0.5.0-rc.3" -serde_json = "1.0.91" +serde = "1.0.203" +sqlx = { version = "0.7", default-features = false, features = [ "runtime-tokio-native-tls", "macros", "postgres", "chrono", "migrate" ] } +rocket = "0.5.1" +serde_json = "1.0.118" dotenv = "0.15.0" +serde_urlencoded = "0.7.1" diff --git a/pointercrate-test/src/demonlist.rs b/pointercrate-test/src/demonlist.rs index 1c065d3ff..013695653 100644 --- a/pointercrate-test/src/demonlist.rs +++ b/pointercrate-test/src/demonlist.rs @@ -1,10 +1,16 @@ -use crate::TestClient; +use crate::{TestClient, TestRequest}; +use pointercrate_core::etag::Taggable; use pointercrate_core::{permission::PermissionsManager, pool::PointercratePool}; +use pointercrate_demonlist::demon::FullDemon; use pointercrate_demonlist::{ - player::claim::PlayerClaim, record::RecordStatus, submitter::Submitter, LIST_ADMINISTRATOR, LIST_HELPER, LIST_MODERATOR, + player::{claim::PlayerClaim, FullPlayer}, + record::RecordStatus, + submitter::Submitter, + LIST_ADMINISTRATOR, LIST_HELPER, LIST_MODERATOR, }; +use pointercrate_user::AuthenticatedUser; use pointercrate_user_pages::account::AccountPageConfig; -use rocket::local::asynchronous::Client; +use rocket::{http::Status, local::asynchronous::Client}; use sqlx::{pool::PoolConnection, PgConnection, Pool, Postgres}; use std::{net::IpAddr, str::FromStr}; @@ -23,7 +29,7 @@ pub async fn setup_rocket(pool: Pool) -> (TestClient, PoolConnection

TestRequest { + let player: FullPlayer = self + .get(format!("/api/v1/players/{}/", player_id)) + .expect_status(Status::Ok) + .get_success_result() + .await; + + self.patch(format!("/api/v1/players/{}/", player_id), &patch) + .authorize_as(&auth_context) + .header("If-Match", player.etag_string()) + .expect_status(Status::Ok) + } + + pub async fn add_demon( + &self, auth_context: &AuthenticatedUser, name: impl Into, position: i16, requirement: i16, verifier: impl Into, + publisher: impl Into, + ) -> FullDemon { + self.post("/api/v2/demons/", &serde_json::json!({"name": name.into(), "position": position, "requirement": requirement, "verifier": verifier.into(), "publisher": publisher.into(), "creators": []})) + .expect_status(Status::Created) + .authorize_as(&auth_context) + .get_success_result() + .await + } +} diff --git a/pointercrate-test/src/lib.rs b/pointercrate-test/src/lib.rs index 0f060c93c..cbf1eae0d 100644 --- a/pointercrate-test/src/lib.rs +++ b/pointercrate-test/src/lib.rs @@ -8,7 +8,7 @@ use rocket::{ }; use serde::{de::DeserializeOwned, Serialize}; -use std::collections::HashMap; +use std::{collections::HashMap, fmt::Debug}; pub mod demonlist; pub mod user; @@ -31,6 +31,14 @@ impl TestClient { pub fn post(&self, url: impl Into, body: &impl Serialize) -> TestRequest { TestRequest::new(self.0.post(url.into()).json(body)) } + + pub fn patch(&self, url: impl Into, body: &impl Serialize) -> TestRequest { + TestRequest::new(self.0.patch(url.into()).json(body)) + } + + pub fn delete(&self, url: impl Into) -> TestRequest { + TestRequest::new(self.0.delete(url.into())) + } } pub struct TestRequest<'c> { @@ -47,6 +55,7 @@ impl<'c> TestRequest<'c> { expected_headers: HashMap::new(), } .header("X-Real-Ip", "127.0.0.1") + .header("Accept", "application/json") } pub fn header(mut self, header_name: impl Into, header_value: impl Into) -> Self { @@ -68,15 +77,47 @@ impl<'c> TestRequest<'c> { self } - pub async fn get_result(self) -> Result { + pub async fn get_success_result(self) -> Result { + let json: serde_json::Value = self.get_result().await; + let data = &json["data"]; + + let deserialized = serde_json::from_str(&data.to_string()); + + assert!(deserialized.is_ok(), "{:?}: {:?}", deserialized.unwrap_err(), data); + + deserialized.unwrap() + } + + pub async fn get_result(self) -> Result { let body_text = self.execute().await.into_string().await.unwrap(); - serde_json::from_str(&body_text).unwrap() + + let deserialized = serde_json::from_str(&body_text); + + assert!(deserialized.is_ok(), "{:?}: {}", deserialized.unwrap_err(), body_text); + + deserialized.unwrap() + } + + pub async fn get_pagination_result(self) -> (Vec, String) { + let response = self.execute().await; + let links_header = response + .headers() + .get_one("Links") + .expect("'Links' header to be set on pagination responses") + .to_owned(); + let body_text = response.into_string().await.unwrap(); + + let deserialized = serde_json::from_str(&body_text); + + assert!(deserialized.is_ok(), "{:?}: {}", deserialized.unwrap_err(), body_text); + + (deserialized.unwrap(), links_header) } pub async fn execute(self) -> LocalResponse<'c> { let response = self.request.dispatch().await; - assert_eq!(response.status(), self.expected_status); + assert_eq!(response.status(), self.expected_status, "{:?}", response.into_string().await); for (name, value) in self.expected_headers { let header = response.headers().get_one(&name); diff --git a/pointercrate-test/tests/demonlist/claim.rs b/pointercrate-test/tests/demonlist/claim.rs index d14965cfa..b49ff0ac5 100644 --- a/pointercrate-test/tests/demonlist/claim.rs +++ b/pointercrate-test/tests/demonlist/claim.rs @@ -5,9 +5,12 @@ use sqlx::{Pool, Postgres}; #[sqlx::test(migrations = "../migrations")] async fn test_put_claim(pool: Pool) { let (client, mut connection) = pointercrate_test::demonlist::setup_rocket(pool).await; - let user = pointercrate_test::user::add_normal_user(&mut connection).await; + let user = pointercrate_test::user::add_normal_user(&mut *connection).await; - let player_id = DatabasePlayer::by_name_or_create("stardust1971", &mut connection).await.unwrap().id; + let player_id = DatabasePlayer::by_name_or_create("stardust1971", &mut *connection) + .await + .unwrap() + .id; let json: PlayerClaim = client .put(format!("/api/v1/players/{}/claims/", player_id)) @@ -20,10 +23,13 @@ async fn test_put_claim(pool: Pool) { .get_result() .await; - assert_eq!(json, PlayerClaim { - user_id: user.inner().id, - player_id, - verified: false, - lock_submissions: false - }); + assert_eq!( + json, + PlayerClaim { + user_id: user.inner().id, + player_id, + verified: false, + lock_submissions: false + } + ); } diff --git a/pointercrate-test/tests/demonlist/demon.rs b/pointercrate-test/tests/demonlist/demon.rs index a0106827f..87a4b2c1e 100644 --- a/pointercrate-test/tests/demonlist/demon.rs +++ b/pointercrate-test/tests/demonlist/demon.rs @@ -1,4 +1,10 @@ -use pointercrate_demonlist::LIST_MODERATOR; +use pointercrate_core::pagination::PaginationParameters; +use pointercrate_core_api::pagination::LinksBuilder; +use pointercrate_demonlist::{ + demon::{Demon, DemonPositionPagination}, + player::DatabasePlayer, + LIST_MODERATOR, +}; use rocket::http::Status; use sqlx::{Pool, Postgres}; @@ -8,7 +14,7 @@ const DEFAULT_THUMBNAIL: &str = "https://i.ytimg.com/vi/zebrafishes/mqdefault.jp async fn test_add_demon_ratelimits(pool: Pool) { let (clnt, mut connection) = pointercrate_test::demonlist::setup_rocket(pool).await; - let user = pointercrate_test::user::system_user_with_perms(LIST_MODERATOR, &mut connection).await; + let user = pointercrate_test::user::system_user_with_perms(LIST_MODERATOR, &mut *connection).await; let demon = serde_json::json! {{"name": "Bloodbath", "requirement": 90, "position": 1, "verifier": "Riot", "publisher": "Riot", "creators": []}}; @@ -34,7 +40,7 @@ async fn test_add_demon_ratelimits(pool: Pool) { async fn test_default_thumbnail_no_video(pool: Pool) { let (clnt, mut connection) = pointercrate_test::demonlist::setup_rocket(pool).await; - let user = pointercrate_test::user::system_user_with_perms(LIST_MODERATOR, &mut connection).await; + let user = pointercrate_test::user::system_user_with_perms(LIST_MODERATOR, &mut *connection).await; let demon = serde_json::json! {{"name": "Bloodbath", "requirement": 90, "position": 1, "verifier": "Riot", "publisher": "Riot", "creators": []}}; @@ -55,12 +61,12 @@ async fn test_default_thumbnail_no_video(pool: Pool) { async fn test_default_thumbnail_linked_banned(pool: Pool) { let (clnt, mut connection) = pointercrate_test::demonlist::setup_rocket(pool).await; - let user = pointercrate_test::user::system_user_with_perms(LIST_MODERATOR, &mut connection).await; + let user = pointercrate_test::user::system_user_with_perms(LIST_MODERATOR, &mut *connection).await; let demon = serde_json::json! {{"name": "Bloodbath", "requirement": 90, "position": 1, "verifier": "Riot", "publisher": "Riot", "creators": [], "video": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"}}; sqlx::query!("INSERT INTO players (name, link_banned) VALUES ('Riot', TRUE)") - .execute(&mut connection) + .execute(&mut *connection) .await .unwrap(); @@ -81,7 +87,7 @@ async fn test_default_thumbnail_linked_banned(pool: Pool) { async fn test_default_thumbnail_with_video(pool: Pool) { let (clnt, mut connection) = pointercrate_test::demonlist::setup_rocket(pool).await; - let user = pointercrate_test::user::system_user_with_perms(LIST_MODERATOR, &mut connection).await; + let user = pointercrate_test::user::system_user_with_perms(LIST_MODERATOR, &mut *connection).await; let demon = serde_json::json! {{"name": "Bloodbath", "requirement": 90, "position": 1, "verifier": "Riot", "publisher": "Riot", "creators": [], "video": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"}}; @@ -100,3 +106,152 @@ async fn test_default_thumbnail_with_video(pool: Pool) { Some("https://i.ytimg.com/vi/dQw4w9WgXcQ/mqdefault.jpg") ) } + +#[sqlx::test(migrations = "../migrations")] +async fn test_demon_pagination(pool: Pool) { + /// The URL of the endpoint we are testing + const URL: &str = "/api/v2/demons/listed/"; + + let (clnt, mut connection) = pointercrate_test::demonlist::setup_rocket(pool).await; + + let player = DatabasePlayer::by_name_or_create("stardust1971", &mut *connection).await.unwrap(); + + // Pagination on an empty table results in an empty response with empty links header. + // + // Regression test for #77 + let (demons, links) = clnt.get(URL).get_pagination_result::().await; + + assert!(demons.is_empty(), "{:?}", demons); + assert_eq!(links, LinksBuilder::new(URL).generate(&DemonPositionPagination::default()).unwrap()); + + // Let's add some data to the database and do actual tests! + let id1 = pointercrate_test::demonlist::add_demon("Bloodbath", 1, 100, player.id, player.id, &mut *connection).await; + let id2 = pointercrate_test::demonlist::add_demon("Bloodbath 2", 2, 100, player.id, player.id, &mut *connection).await; + let id3 = pointercrate_test::demonlist::add_demon("Bloodbath 3", 3, 100, player.id, player.id, &mut *connection).await; + + // Normal pagination: Get the demon at position 2 via after=1 and limit=1. We should get both "next" and "previous" pages + // for the first and third demons! Let's throw in a requirement=100 as well, to test that these parameters do indeed get propagated into the Links headers + let base = DemonPositionPagination { + requirement: Some(100), + params: PaginationParameters { + limit: 1, + after: Some(1), + ..Default::default() + }, + ..Default::default() + }; + let (demons, links) = clnt + .get(format!("{}?{}", URL, serde_urlencoded::to_string(&base).unwrap())) + .get_pagination_result::() + .await; + + assert_eq!(demons.len(), 1); + assert_eq!(demons[0].base.id, id2); + + let expected = LinksBuilder::new(URL).with_first(0).with_last(4).with_next(2).with_previous(2); + assert_eq!(links, expected.generate(&base).unwrap()); + + // The same, but in reverse Get the demon at position 2 via before=3 and limit=1. We should get both "next" and "previous" pages + // for the first and third demons! Let's throw in a requirement=100 as well, to test that these parameters do indeed get propagated into the Links headers + let base = DemonPositionPagination { + requirement: Some(100), + params: PaginationParameters { + limit: 1, + before: Some(3), + ..Default::default() + }, + ..Default::default() + }; + let (demons, links) = clnt + .get(format!("{}?{}", URL, serde_urlencoded::to_string(&base).unwrap())) + .get_pagination_result::() + .await; + + assert_eq!(demons.len(), 1); + assert_eq!(demons[0].base.id, id2); + + let expected = LinksBuilder::new(URL).with_first(0).with_last(4).with_next(2).with_previous(2); + assert_eq!(links, expected.generate(&base).unwrap()); + + // Query an empty page by only setting before=1. We should still get a "next" link, with after=0 (e.g. before minus one), + // but no "prev" link + let base = DemonPositionPagination { + params: PaginationParameters { + before: Some(1), + ..Default::default() + }, + ..Default::default() + }; + let (demons, links) = clnt + .get(format!("{}?{}", URL, serde_urlencoded::to_string(&base).unwrap())) + .get_pagination_result::() + .await; + + assert_eq!(demons.len(), 0); + + let expected = LinksBuilder::new(URL).with_first(0).with_last(4).with_next(0); + + assert_eq!(links, expected.generate(&base).unwrap()); + + // Query an empty page by setting "before" and "after" to an empty range. Should result in a response with only "first" and "last" headers set + let base = DemonPositionPagination { + params: PaginationParameters { + before: Some(2), + after: Some(1), + ..Default::default() + }, + ..Default::default() + }; + let (demons, links) = clnt + .get(format!("{}?{}", URL, serde_urlencoded::to_string(&base).unwrap())) + .get_pagination_result::() + .await; + + assert_eq!(demons.len(), 0); + + let expected = LinksBuilder::new(URL).with_first(0).with_last(4); + + assert_eq!(links, expected.generate(&base).unwrap()); + + // Query with limit=3, which should result in all three demons being returned, and only "first" and "last" headers set (since there are no other pages) + let base = DemonPositionPagination::default(); + let (demons, links) = clnt + .get(format!("{}?{}", URL, serde_urlencoded::to_string(&base).unwrap())) + .get_pagination_result::() + .await; + + assert_eq!(demons.len(), 3); + assert_eq!(demons[0].base.id, id1); + assert_eq!(demons[1].base.id, id2); + assert_eq!(demons[2].base.id, id3); + + let expected = LinksBuilder::new(URL).with_first(0).with_last(4); + + assert_eq!(links, expected.generate(&base).unwrap()); + + // Query with limit=2 and before=4, to test that we still return the results in ascending order + let base = DemonPositionPagination { + params: PaginationParameters { + before: Some(4), + limit: 2, + ..Default::default() + }, + ..Default::default() + }; + let (demons, links) = clnt + .get(format!("{}?{}", URL, serde_urlencoded::to_string(&base).unwrap())) + .get_pagination_result::() + .await; + + assert_eq!(demons.len(), 2); + assert_eq!(demons[0].base.id, id2); + assert_eq!(demons[1].base.id, id3); + + let expected = LinksBuilder::new(URL) + .with_first(0) + .with_last(4) + .with_next(3) // FIXME: This `next` link should not have been returned (currently we always return a "next" link if a `before` parameter is set though) + .with_previous(2); + + assert_eq!(links, expected.generate(&base).unwrap()); +} diff --git a/pointercrate-test/tests/demonlist/player.rs b/pointercrate-test/tests/demonlist/player.rs deleted file mode 100644 index 348152e63..000000000 --- a/pointercrate-test/tests/demonlist/player.rs +++ /dev/null @@ -1,64 +0,0 @@ -use pointercrate_demonlist::{ - player::{DatabasePlayer, Player}, - LIST_HELPER, -}; -use rocket::http::Status; -use sqlx::{PgConnection, Pool, Postgres}; - -async fn create_players(connection: &mut PgConnection) -> (DatabasePlayer, DatabasePlayer) { - let mut banned = DatabasePlayer::by_name_or_create("stardust1971", &mut *connection).await.unwrap(); - banned.ban(&mut *connection).await.unwrap(); - ( - banned, - DatabasePlayer::by_name_or_create("stardust1972", &mut *connection).await.unwrap(), - ) -} - -#[sqlx::test(migrations = "../migrations")] -async fn test_unauthenticated_pagination(pool: Pool) { - let (client, mut connection) = pointercrate_test::demonlist::setup_rocket(pool).await; - - let (_, unbanned) = create_players(&mut connection).await; - - let json: Vec = client.get("/api/v1/players").expect_status(Status::Ok).get_result().await; - - assert_eq!(json.len(), 1, "Pagination returned banned player"); - assert_eq!(json[0].base.id, unbanned.id); -} - -#[sqlx::test(migrations = "../migrations")] -async fn test_authenticated_pagination(pool: Pool) { - let (client, mut connection) = pointercrate_test::demonlist::setup_rocket(pool).await; - - let (_, unbanned) = create_players(&mut connection).await; - let user = pointercrate_test::user::add_normal_user(&mut connection).await; - - let json: Vec = client - .get("/api/v1/players") - .authorize_as(&user) - .expect_status(Status::Ok) - .get_result() - .await; - - assert_eq!(json.len(), 1, "Pagination returned banned player"); - assert_eq!(json[0].base.id, unbanned.id); -} - -#[sqlx::test(migrations = "../migrations")] -async fn test_list_helper_pagination(pool: Pool) { - let (client, mut connection) = pointercrate_test::demonlist::setup_rocket(pool).await; - - let (banned, unbanned) = create_players(&mut connection).await; - let user = pointercrate_test::user::system_user_with_perms(LIST_HELPER, &mut connection).await; - - let json: Vec = client - .get("/api/v1/players") - .authorize_as(&user) - .expect_status(Status::Ok) - .get_result() - .await; - - assert_eq!(json.len(), 2, "Pagination did not return banned player"); - assert_eq!(json[0].base.id, banned.id); - assert_eq!(json[1].base.id, unbanned.id); -} diff --git a/pointercrate-test/tests/demonlist/player/mod.rs b/pointercrate-test/tests/demonlist/player/mod.rs new file mode 100644 index 000000000..7096c1f27 --- /dev/null +++ b/pointercrate-test/tests/demonlist/player/mod.rs @@ -0,0 +1,170 @@ +use pointercrate_demonlist::{ + nationality::{Nationality, Subdivision}, + player::{DatabasePlayer, FullPlayer, Player}, + LIST_HELPER, +}; +use rocket::http::Status; +use sqlx::{PgConnection, Pool, Postgres}; + +mod score; + +async fn create_players(connection: &mut PgConnection) -> (DatabasePlayer, DatabasePlayer) { + let mut banned = DatabasePlayer::by_name_or_create("stardust1971", &mut *connection).await.unwrap(); + banned.ban(&mut *connection).await.unwrap(); + ( + banned, + DatabasePlayer::by_name_or_create("stardust1972", &mut *connection).await.unwrap(), + ) +} + +#[sqlx::test(migrations = "../migrations")] +async fn test_unauthenticated_pagination(pool: Pool) { + let (client, mut connection) = pointercrate_test::demonlist::setup_rocket(pool).await; + + let (_, unbanned) = create_players(&mut *connection).await; + + let json: Vec = client.get("/api/v1/players").expect_status(Status::Ok).get_result().await; + + assert_eq!(json.len(), 1, "Pagination returned banned player"); + assert_eq!(json[0].base.id, unbanned.id); +} + +#[sqlx::test(migrations = "../migrations")] +async fn test_authenticated_pagination(pool: Pool) { + let (client, mut connection) = pointercrate_test::demonlist::setup_rocket(pool).await; + + let (_, unbanned) = create_players(&mut *connection).await; + let user = pointercrate_test::user::add_normal_user(&mut *connection).await; + + let json: Vec = client + .get("/api/v1/players") + .authorize_as(&user) + .expect_status(Status::Ok) + .get_result() + .await; + + assert_eq!(json.len(), 1, "Pagination returned banned player"); + assert_eq!(json[0].base.id, unbanned.id); +} + +#[sqlx::test(migrations = "../migrations")] +async fn test_list_helper_pagination(pool: Pool) { + let (client, mut connection) = pointercrate_test::demonlist::setup_rocket(pool).await; + + let (banned, unbanned) = create_players(&mut *connection).await; + let user = pointercrate_test::user::system_user_with_perms(LIST_HELPER, &mut *connection).await; + + let json: Vec = client + .get("/api/v1/players") + .authorize_as(&user) + .expect_status(Status::Ok) + .get_result() + .await; + + assert_eq!(json.len(), 2, "Pagination did not return banned player"); + assert_eq!(json[0].base.id, banned.id); + assert_eq!(json[1].base.id, unbanned.id); +} + +#[sqlx::test(migrations = "../migrations")] +async fn test_patch_player_nationality(pool: Pool) { + let (client, mut connection) = pointercrate_test::demonlist::setup_rocket(pool).await; + let player = DatabasePlayer::by_name_or_create("stardust1971", &mut *connection).await.unwrap(); + let user = pointercrate_test::user::system_user_with_perms(LIST_HELPER, &mut *connection).await; + + // Try to set subdivision when no nation is set. Should fail. + let result: serde_json::Value = client + .patch_player(player.id, &user, serde_json::json!({"subdivision": "ENG"})) + .await + .expect_status(Status::Conflict) + .get_result() + .await; + + assert_eq!(result["code"], 40907); + + // Patch both nationality and subdivision + let patched_player: FullPlayer = client + .patch_player( + player.id, + &user, + serde_json::json!({"nationality": "United Kingdom", "subdivision": "ENG"}), + ) + .await + .get_success_result() + .await; + + assert_eq!( + patched_player.player.nationality, + Some(Nationality { + iso_country_code: "GB".into(), + nation: "United Kingdom".into(), + subdivision: Some(Subdivision { + iso_code: "ENG".into(), + name: "England".into() + }) + }) + ); + + // Patch only subdivision, nationality should remain untouched + let patched_player: FullPlayer = client + .patch_player( + player.id, + &user, + serde_json::json!({"nationality": "United Kingdom", "subdivision": "SCT"}), + ) + .await + .get_success_result() + .await; + + assert_eq!( + patched_player.player.nationality, + Some(Nationality { + iso_country_code: "GB".into(), + nation: "United Kingdom".into(), + subdivision: Some(Subdivision { + iso_code: "SCT".into(), + name: "Scotland".into() + }) + }) + ); + + // Patch nation, but to the one we already have. Shouldn't change anything. + client + .patch_player(player.id, &user, serde_json::json!({"nationality": "United Kingdom"})) + .await + .expect_status(Status::NotModified) + .execute() + .await; + + // Patch only nationality. Should reset subdivision + let patched_player: FullPlayer = client + .patch_player(player.id, &user, serde_json::json!({"nationality": "Germany"})) + .await + .get_success_result() + .await; + + assert_eq!( + patched_player.player.nationality, + Some(Nationality { + iso_country_code: "DE".into(), + nation: "Germany".into(), + subdivision: None + }) + ); + + // Nonsense nationality/subdivision combo should be rejected + let result: serde_json::Value = client + .patch_player( + player.id, + &user, + serde_json::json!({"nationality": "Belgium", "subdivision": "ENG"}), + ) + .await + .expect_status(Status::NotFound) + .get_result() + .await; + + assert_eq!(result["code"], 40401); + assert_eq!(result["data"]["nation_code"], "BE"); + assert_eq!(result["data"]["subdivision_code"], "ENG"); +} diff --git a/pointercrate-test/tests/demonlist/player/score.rs b/pointercrate-test/tests/demonlist/player/score.rs new file mode 100644 index 000000000..4b377bd61 --- /dev/null +++ b/pointercrate-test/tests/demonlist/player/score.rs @@ -0,0 +1,235 @@ +//! Module containing all score related test cases (because I suspect over time there will be quite a few) + +use pointercrate_core::etag::Taggable; +use pointercrate_demonlist::{ + player::{DatabasePlayer, FullPlayer}, + record::FullRecord, + LIST_MODERATOR, +}; +use rocket::http::Status; +use sqlx::{PgConnection, Pool, Postgres}; + +#[sqlx::test(migrations = "../migrations")] +pub async fn test_score_update_on_record_update(pool: Pool) { + let (clnt, mut connection) = pointercrate_test::demonlist::setup_rocket(pool).await; + + let helper = pointercrate_test::user::system_user_with_perms(LIST_MODERATOR, &mut *connection).await; + let player = DatabasePlayer::by_name_or_create("stardust1971", &mut *connection).await.unwrap(); + let demon = clnt.add_demon(&helper, "Bloodbath", 1, 100, "stardust1972", "stardust1972").await; + + let submission = serde_json::json! {{"progress": 100, "demon": demon.demon.base.id, "player": "stardust1971", "video": "https://youtube.com/watch?v=1234567890", "status": "Approved"}}; + + let record = clnt + .post("/api/v1/records", &submission) + .authorize_as(&helper) + .expect_status(Status::Ok) + .get_success_result::() + .await; + + let player: FullPlayer = clnt + .get(format!("/api/v1/players/{}", player.id)) + .expect_status(Status::Ok) + .get_success_result() + .await; + + assert_ne!(player.player.score, 0.0f64, "Adding approved record failed to give player score"); + + clnt.patch( + format!("/api/v1/records/{}/", record.id), + &serde_json::json!({"status": "Rejected"}), + ) + .authorize_as(&helper) + .header("If-Match", record.etag_string()) + .expect_status(Status::Ok) + .execute() + .await; + + let player: FullPlayer = clnt + .get(format!("/api/v1/players/{}", player.player.base.id)) + .expect_status(Status::Ok) + .get_success_result() + .await; + + assert_eq!(player.player.score, 0.0f64, "Rejecting record failed to remove player score"); +} + +#[sqlx::test(migrations = "../migrations")] +pub async fn test_verifications_give_score(pool: Pool) { + let (clnt, mut connection) = pointercrate_test::demonlist::setup_rocket(pool).await; + + let helper = pointercrate_test::user::system_user_with_perms(LIST_MODERATOR, &mut *connection).await; + let demon = clnt.add_demon(&helper, "Bloodbath", 1, 100, "stardust1971", "stardust1971").await; + + let player: FullPlayer = clnt + .get(format!("/api/v1/players/{}", demon.demon.verifier.id)) + .expect_status(Status::Ok) + .get_success_result() + .await; + + assert_ne!(player.player.score, 0.0f64); +} + +async fn nationality_score(iso_country_code: &str, connection: &mut PgConnection) -> f64 { + sqlx::query!("SELECT score FROM nationalities WHERE iso_country_code = $1", iso_country_code) + .fetch_one(&mut *connection) + .await + .unwrap() + .score +} + +async fn subdivision_score(nation: &str, iso_code: &str, connection: &mut PgConnection) -> f64 { + sqlx::query!( + "SELECT score FROM subdivisions WHERE nation = $1 AND iso_code = $2", + nation, + iso_code + ) + .fetch_one(&mut *connection) + .await + .unwrap() + .score +} + +#[sqlx::test(migrations = "../migrations")] +pub async fn test_player_score_reflects_to_nationality(pool: Pool) { + let (clnt, mut connection) = pointercrate_test::demonlist::setup_rocket(pool).await; + + let helper = pointercrate_test::user::system_user_with_perms(LIST_MODERATOR, &mut *connection).await; + let demon = clnt.add_demon(&helper, "Bloodbath", 1, 100, "stardust1971", "stardust1971").await; + + clnt.patch_player( + demon.demon.verifier.id, + &helper, + serde_json::json!({"nationality": "GB", "subdivision": "ENG"}), + ) + .await + .execute() + .await; + + assert_ne!(nationality_score("GB", &mut connection).await, 0f64); + assert_ne!(subdivision_score("GB", "ENG", &mut connection).await, 0f64); + + clnt.patch_player(demon.demon.verifier.id, &helper, serde_json::json!({"subdivision": "SCT"})) + .await + .execute() + .await; + + assert_ne!(nationality_score("GB", &mut connection).await, 0f64); + assert_eq!(subdivision_score("GB", "ENG", &mut connection).await, 0f64); + assert_ne!(subdivision_score("GB", "SCT", &mut connection).await, 0f64); + + clnt.patch_player(demon.demon.verifier.id, &helper, serde_json::json!({"nationality": "DE"})) + .await + .execute() + .await; + + assert_eq!(nationality_score("GB", &mut connection).await, 0f64); + assert_eq!(subdivision_score("GB", "SCT", &mut connection).await, 0f64); + assert_ne!(nationality_score("DE", &mut connection).await, 0f64); +} + +#[sqlx::test(migrations = "../migrations")] +pub async fn test_extended_progress_records_give_no_score(pool: Pool) { + let (clnt, mut connection) = pointercrate_test::demonlist::setup_rocket(pool).await; + + let helper = pointercrate_test::user::system_user_with_perms(LIST_MODERATOR, &mut *connection).await; + let player = DatabasePlayer::by_name_or_create("stardust1971", &mut *connection).await.unwrap(); + + let list_size = std::env::var("LIST_SIZE").unwrap().parse::().unwrap(); + + let mut last_demon_id = 0; + + for position in 1..=(list_size + 1) { + last_demon_id = sqlx::query!( + "INSERT INTO demons (name, position, requirement, verifier, publisher) VALUES ('Bloodbath', $2, 98, $1, $1) RETURNING id", + player.id, + position + ) + .fetch_one(&mut *connection) + .await + .unwrap() + .id; + } + + let submission = serde_json::json! {{"progress": 99, "demon": last_demon_id, "player": "stardust1972", "video": "https://youtube.com/watch?v=1234567890", "status": "Approved"}}; + let record = clnt + .post("/api/v1/records", &submission) + .authorize_as(&helper) + .expect_status(Status::Ok) + .get_success_result::() + .await; + + let player: FullPlayer = clnt + .get(format!("/api/v1/players/{}", record.player.id)) + .expect_status(Status::Ok) + .get_success_result() + .await; + + assert_eq!(player.player.score, 0.0f64, "Progress record on extended list demon is given score"); +} + +#[sqlx::test(migrations = "../migrations")] +pub async fn test_score_resets_if_last_record_removed(pool: Pool) { + let (clnt, mut connection) = pointercrate_test::demonlist::setup_rocket(pool).await; + + let helper = pointercrate_test::user::system_user_with_perms(LIST_MODERATOR, &mut *connection).await; + let player = DatabasePlayer::by_name_or_create("stardust1971", &mut *connection).await.unwrap(); + + let list_size = std::env::var("LIST_SIZE").unwrap().parse::().unwrap(); + + let mut last_demon_id = 0; + + for position in 1..=list_size { + last_demon_id = sqlx::query!( + "INSERT INTO demons (name, position, requirement, verifier, publisher) VALUES ('Bloodbath', $2, 98, $1, $1) RETURNING id", + player.id, + position + ) + .fetch_one(&mut *connection) + .await + .unwrap() + .id; + } + + let submission = serde_json::json! {{"progress": 99, "demon": last_demon_id, "player": "stardust1972", "video": "https://youtube.com/watch?v=1234567890", "status": "Approved"}}; + let record = clnt + .post("/api/v1/records", &submission) + .authorize_as(&helper) + .expect_status(Status::Ok) + .get_success_result::() + .await; + + assert_eq!(record.demon.position, 75); + + let player: FullPlayer = clnt + .get(format!("/api/v1/players/{}", record.player.id)) + .expect_status(Status::Ok) + .get_success_result() + .await; + + assert_ne!( + player.player.score, 0.0f64, + "Progress record on final main list demon not giving score" + ); + + // Shift everything down. The demon on which the player has a progress record is now no longer main list, so his score should be updated to 0 now. + let _ = clnt.add_demon(&helper, "Bloodbath", 1, 100, "stardust1971", "stardust1971").await; + + let record: FullRecord = clnt + .get(format!("/api/v1/records/{}", record.id)) + .expect_status(Status::Ok) + .get_success_result() + .await; + + assert_eq!(record.demon.position, 76); + + let player: FullPlayer = clnt + .get(format!("/api/v1/players/{}", record.player.id)) + .expect_status(Status::Ok) + .get_success_result() + .await; + + assert_eq!( + player.player.score, 0.0f64, + "Removal of player's last record did not reset their score to 0" + ); +} diff --git a/pointercrate-test/tests/demonlist/record.rs b/pointercrate-test/tests/demonlist/record.rs index d3cfadfc7..636db1727 100644 --- a/pointercrate-test/tests/demonlist/record.rs +++ b/pointercrate-test/tests/demonlist/record.rs @@ -1,5 +1,12 @@ use pointercrate_core::error::PointercrateError; -use pointercrate_demonlist::{error::DemonlistError, player::DatabasePlayer, record::RecordStatus}; +use pointercrate_core::etag::Taggable; +use pointercrate_demonlist::{ + error::DemonlistError, + player::{DatabasePlayer, FullPlayer}, + record::{note::Note, FullRecord, RecordStatus}, + LIST_HELPER, LIST_MODERATOR, +}; +use pointercrate_test::{demonlist::add_simple_record, user::system_user_with_perms}; use rocket::http::Status; use sqlx::{PgConnection, Pool, Postgres}; @@ -7,7 +14,7 @@ use sqlx::{PgConnection, Pool, Postgres}; async fn paginate_records_unauthorized(pool: Pool) { let (clnt, mut connection) = pointercrate_test::demonlist::setup_rocket(pool).await; - let (p1, r1, _r2, _r3) = setup_pagination_tests(&mut connection).await; + let (p1, r1, _r2, _r3) = setup_pagination_tests(&mut *connection).await; let json: Vec = clnt .get(format!("/api/v1/records/?player={}", p1)) @@ -23,10 +30,10 @@ async fn paginate_records_unauthorized(pool: Pool) { async fn paginate_records_with_verified_claim(pool: Pool) { let (clnt, mut connection) = pointercrate_test::demonlist::setup_rocket(pool).await; - let (p1, r1, r2, _r3) = setup_pagination_tests(&mut connection).await; - let user = pointercrate_test::user::add_normal_user(&mut connection).await; + let (p1, r1, r2, _r3) = setup_pagination_tests(&mut *connection).await; + let user = pointercrate_test::user::add_normal_user(&mut *connection).await; - pointercrate_test::demonlist::put_claim(user.inner().id, p1, true, false, &mut connection).await; + pointercrate_test::demonlist::put_claim(user.inner().id, p1, true, false, &mut *connection).await; let json: Vec = clnt .get(format!("/api/v1/records/?player={}", p1)) @@ -43,10 +50,10 @@ async fn paginate_records_with_verified_claim(pool: Pool) { async fn paginate_records_with_unverified_claim(pool: Pool) { let (clnt, mut connection) = pointercrate_test::demonlist::setup_rocket(pool).await; - let (p1, r1, _r2, _r3) = setup_pagination_tests(&mut connection).await; - let user = pointercrate_test::user::add_normal_user(&mut connection).await; + let (p1, r1, _r2, _r3) = setup_pagination_tests(&mut *connection).await; + let user = pointercrate_test::user::add_normal_user(&mut *connection).await; - pointercrate_test::demonlist::put_claim(user.inner().id, p1, false, false, &mut connection).await; + pointercrate_test::demonlist::put_claim(user.inner().id, p1, false, false, &mut *connection).await; let json: Vec = clnt .get(format!("/api/v1/records/?player={}", p1)) @@ -62,10 +69,10 @@ async fn paginate_records_with_unverified_claim(pool: Pool) { async fn paginate_records_with_verified_claim_wrong_player(pool: Pool) { let (clnt, mut connection) = pointercrate_test::demonlist::setup_rocket(pool).await; - let (p1, _r1, _r2, _r3) = setup_pagination_tests(&mut connection).await; - let user = pointercrate_test::user::add_normal_user(&mut connection).await; + let (p1, _r1, _r2, _r3) = setup_pagination_tests(&mut *connection).await; + let user = pointercrate_test::user::add_normal_user(&mut *connection).await; - pointercrate_test::demonlist::put_claim(user.inner().id, p1, true, false, &mut connection).await; + pointercrate_test::demonlist::put_claim(user.inner().id, p1, true, false, &mut *connection).await; let json: Vec = clnt.get("/api/v1/records/?player=2").authorize_as(&user).get_result().await; @@ -90,11 +97,11 @@ async fn setup_pagination_tests(connection: &mut PgConnection) -> (i32, i32, i32 async fn unauthed_submit_for_player_with_locked_submission(pool: Pool) { let (clnt, mut connection) = pointercrate_test::demonlist::setup_rocket(pool).await; - let user = pointercrate_test::user::add_normal_user(&mut connection).await; - let player1 = DatabasePlayer::by_name_or_create("stardust1971", &mut connection).await.unwrap(); - let demon1 = pointercrate_test::demonlist::add_demon("Bloodbath", 1, 87, player1.id, player1.id, &mut connection).await; + let user = pointercrate_test::user::add_normal_user(&mut *connection).await; + let player1 = DatabasePlayer::by_name_or_create("stardust1971", &mut *connection).await.unwrap(); + let demon1 = pointercrate_test::demonlist::add_demon("Bloodbath", 1, 87, player1.id, player1.id, &mut *connection).await; - pointercrate_test::demonlist::put_claim(user.inner().id, player1.id, true, true, &mut connection).await; + pointercrate_test::demonlist::put_claim(user.inner().id, player1.id, true, true, &mut *connection).await; let submission = serde_json::json! {{"progress": 100, "demon": demon1, "player": "stardust1971", "video": "https://youtube.com/watch?v=1234567890"}}; @@ -115,12 +122,11 @@ async fn unauthed_submit_for_player_with_locked_submission(pool: Pool) async fn submit_existing_record(pool: Pool) { let (clnt, mut connection) = pointercrate_test::demonlist::setup_rocket(pool).await; - let player1 = DatabasePlayer::by_name_or_create("stardust1971", &mut connection).await.unwrap(); - let demon1 = pointercrate_test::demonlist::add_demon("Bloodbath", 1, 50, player1.id, player1.id, &mut connection).await; - let existing = pointercrate_test::demonlist::add_simple_record(70, player1.id, demon1, RecordStatus::Approved, &mut connection).await; + let player1 = DatabasePlayer::by_name_or_create("stardust1971", &mut *connection).await.unwrap(); + let demon1 = pointercrate_test::demonlist::add_demon("Bloodbath", 1, 50, player1.id, player1.id, &mut *connection).await; + let existing = pointercrate_test::demonlist::add_simple_record(70, player1.id, demon1, RecordStatus::Approved, &mut *connection).await; - let submission = - serde_json::json! {{"progress": 60, "demon": demon1, "player": "stardust1971", "video": "https://youtube.com/watch?v=1234567890"}}; + let submission = serde_json::json! {{"progress": 60, "demon": demon1, "player": "stardust1971", "video": "https://youtube.com/watch?v=1234567890", "raw_footage": "https://pointercrate.com"}}; let json: serde_json::Value = clnt .post("/api/v1/records/", &submission) @@ -131,3 +137,90 @@ async fn submit_existing_record(pool: Pool) { assert_eq!(json["code"].as_i64(), Some(42217i64)); assert_eq!(json["data"]["existing"].as_i64(), Some(existing as i64)); } + +#[sqlx::test(migrations = "../migrations")] +async fn test_no_submitter_info_on_unauthed_get(pool: Pool) { + let (clnt, mut connection) = pointercrate_test::demonlist::setup_rocket(pool).await; + + let player1 = DatabasePlayer::by_name_or_create("stardust1971", &mut *connection).await.unwrap(); + let demon1 = pointercrate_test::demonlist::add_demon("Bloodbath", 1, 50, player1.id, player1.id, &mut *connection).await; + let existing = pointercrate_test::demonlist::add_simple_record(70, player1.id, demon1, RecordStatus::Approved, &mut *connection).await; + + let record: FullRecord = clnt.get(format!("/api/v1/records/{}", existing)).get_success_result().await; + + assert_eq!(record.submitter, None); +} + +#[sqlx::test(migrations = "../migrations")] +async fn test_record_note_creation_and_deletion(pool: Pool) { + let (clnt, mut connection) = pointercrate_test::demonlist::setup_rocket(pool).await; + + let helper = system_user_with_perms(LIST_HELPER, &mut *connection).await; + let player1 = DatabasePlayer::by_name_or_create("stardust1971", &mut *connection).await.unwrap(); + let demon1 = pointercrate_test::demonlist::add_demon("Bloodbath", 1, 50, player1.id, player1.id, &mut *connection).await; + let record = add_simple_record(100, player1.id, demon1, RecordStatus::Approved, &mut *connection).await; + + // Create a record note whose author is `helper`. + let note: Note = clnt + .post( + format!("/api/v1/records/{}/notes", record), + &serde_json::json! {{ + "content": "My Note", + "is_public": false, + }}, + ) + .authorize_as(&helper) + .expect_status(Status::Created) + .get_success_result() + .await; + + // Check that the author was set correctly. + assert_eq!(note.author.as_ref(), Some(&helper.inner().name)); + + clnt.delete(format!("/api/v1/records/{}/notes/{}", record, note.id)) + .authorize_as(&helper) + .expect_status(Status::NoContent) + .execute() + .await; +} + +#[sqlx::test(migrations = "../migrations")] +async fn test_record_deletion_updates_player_score(pool: Pool) { + let (clnt, mut connection) = pointercrate_test::demonlist::setup_rocket(pool).await; + + let helper = pointercrate_test::user::system_user_with_perms(LIST_MODERATOR, &mut *connection).await; + let player = DatabasePlayer::by_name_or_create("stardust1971", &mut *connection).await.unwrap(); + let demon = clnt.add_demon(&helper, "Bloodbath", 1, 100, "stardust1972", "stardust1972").await; + + let submission = serde_json::json! {{"progress": 100, "demon": demon.demon.base.id, "player": "stardust1971", "video": "https://youtube.com/watch?v=1234567890", "status": "Approved"}}; + + let record = clnt + .post("/api/v1/records", &submission) + .authorize_as(&helper) + .expect_status(Status::Ok) + .get_success_result::() + .await; + + let player: FullPlayer = clnt + .get(format!("/api/v1/players/{}", player.id)) + .expect_status(Status::Ok) + .get_success_result() + .await; + + assert_ne!(player.player.score, 0.0f64, "Adding approved record failed to give player score"); + + clnt.delete(format!("/api/v1/records/{}/", record.id)) + .authorize_as(&helper) + .header("If-Match", record.etag_string()) + .expect_status(Status::NoContent) + .execute() + .await; + + let player: FullPlayer = clnt + .get(format!("/api/v1/players/{}", player.player.base.id)) + .expect_status(Status::Ok) + .get_success_result() + .await; + + assert_eq!(player.player.score, 0.0f64, "Deleting approved record failed to lower player score"); +} diff --git a/pointercrate-test/tests/user/login.rs b/pointercrate-test/tests/user/login.rs index 0555c37c9..8fd4d5ec4 100644 --- a/pointercrate-test/tests/user/login.rs +++ b/pointercrate-test/tests/user/login.rs @@ -3,21 +3,42 @@ use rocket::http::Status; use sqlx::{Pool, Postgres}; #[sqlx::test(migrations = "../migrations")] -pub async fn test_login(pool: Pool) { +pub async fn test_login_with_ratelimit(pool: Pool) { let (client, mut connection) = pointercrate_test::user::setup_rocket(pool).await; - let user = pointercrate_test::user::system_user_with_perms(ADMINISTRATOR, &mut connection).await; + let mut user = pointercrate_test::user::system_user_with_perms(ADMINISTRATOR, &mut *connection).await; - let response: serde_json::Value = client + for _ in 0..3 { + let response: serde_json::Value = client + .post("/api/v1/auth/", &()) + .header("Authorization", "Basic UGF0cmljazpiYWQgcGFzc3dvcmQ=") + .header("X-Real-IP", "127.0.0.1") + .expect_status(Status::Ok) + .get_result() + .await; + + assert_eq!(user.inner().id as i64, response["data"]["id"].as_i64().unwrap()); + + // validate_access_token takes ownership, but it gives back the object if verification is successfuly + user = user.validate_access_token(response["token"].as_str().unwrap()).unwrap(); + } + + // After 3 requests, both valid and invalid requests should just return a 429 response + client .post("/api/v1/auth/", &()) .header("Authorization", "Basic UGF0cmljazpiYWQgcGFzc3dvcmQ=") .header("X-Real-IP", "127.0.0.1") - .expect_status(Status::Ok) - .get_result() + .expect_status(Status::TooManyRequests) + .execute() .await; - assert_eq!(user.inner().id as i64, response["data"]["id"].as_i64().unwrap()); - assert!(user.validate_access_token(response["token"].as_str().unwrap()).is_ok()); + client + .post("/api/v1/auth/", &()) + .header("Authorization", "Basic kjföldsa") + .header("X-Real-IP", "127.0.0.1") + .expect_status(Status::TooManyRequests) + .execute() + .await; } #[sqlx::test(migrations = "../migrations")] @@ -38,7 +59,7 @@ pub async fn test_login_wrong_password(pool: Pool) { let (client, mut connection) = pointercrate_test::user::setup_rocket(pool).await; // Make sure the user we're trying to log in to exists - let _ = pointercrate_test::user::system_user_with_perms(ADMINISTRATOR, &mut connection).await; + let _ = pointercrate_test::user::system_user_with_perms(ADMINISTRATOR, &mut *connection).await; client .post("/api/v1/auth/", &()) diff --git a/pointercrate-test/tests/user/register.rs b/pointercrate-test/tests/user/register.rs index 8d5a35a88..4f1ef063e 100644 --- a/pointercrate-test/tests/user/register.rs +++ b/pointercrate-test/tests/user/register.rs @@ -7,10 +7,13 @@ pub async fn register_new(pool: Pool) { let (client, _) = pointercrate_test::user::setup_rocket(pool).await; client - .post("/api/v1/auth/register/", &Registration { - name: "Patrick".to_string(), - password: "bad password".to_string(), - }) + .post( + "/api/v1/auth/register/", + &Registration { + name: "Patrick".to_string(), + password: "bad password".to_string(), + }, + ) .header("X-Real-IP", "127.0.0.1") .expect_status(Status::Created) .execute() @@ -21,13 +24,16 @@ pub async fn register_new(pool: Pool) { pub async fn register_taken_username(pool: Pool) { let (client, mut connection) = pointercrate_test::user::setup_rocket(pool).await; - let _ = pointercrate_test::user::add_normal_user(&mut connection).await; + let _ = pointercrate_test::user::add_normal_user(&mut *connection).await; let _response = client - .post("/api/v1/auth/register/", &Registration { - name: "Patrick".to_string(), - password: "bad password".to_string(), - }) + .post( + "/api/v1/auth/register/", + &Registration { + name: "Patrick".to_string(), + password: "bad password".to_string(), + }, + ) .header("X-Real-IP", "127.0.0.1") .expect_status(Status::Conflict) .execute() diff --git a/pointercrate-user-api/Cargo.toml b/pointercrate-user-api/Cargo.toml index 8b017caa8..c93c20506 100644 --- a/pointercrate-user-api/Cargo.toml +++ b/pointercrate-user-api/Cargo.toml @@ -1,20 +1,21 @@ [package] name = "pointercrate-user-api" version = "0.1.0" -edition = "2021" +authors.workspace = true +edition.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -rocket = {version = "0.5.0-rc.3", features = ["json"]} -sqlx = { version = "0.6", default-features = false, features = [ "runtime-tokio-native-tls", "macros", "postgres", "chrono", "migrate", "offline" ] } +rocket = {version = "0.5.1", features = ["json"]} +sqlx = { version = "0.7", default-features = false, features = [ "runtime-tokio-native-tls", "macros", "postgres", "chrono", "migrate" ] } pointercrate-user = {path = "../pointercrate-user"} pointercrate-user-pages = {path = "../pointercrate-user-pages"} pointercrate-core = {path = "../pointercrate-core"} pointercrate-core-api = {path = "../pointercrate-core-api"} pointercrate-core-pages = {path = "../pointercrate-core-pages"} -log = "0.4.11" -base64 = "0.21.0" +log = "0.4.22" +base64 = "0.22.1" nonzero_ext = "0.3.0" serde_urlencoded = "0.7.0" -governor = "0.5.1" +governor = "0.6.0" diff --git a/pointercrate-user-api/src/auth.rs b/pointercrate-user-api/src/auth.rs index 9fb35151e..158134e47 100644 --- a/pointercrate-user-api/src/auth.rs +++ b/pointercrate-user-api/src/auth.rs @@ -1,4 +1,5 @@ -use log::{debug, error, warn}; +use base64::{engine::general_purpose::STANDARD, Engine}; +use log::{debug, warn}; use pointercrate_core::{ error::{CoreError, PointercrateError}, permission::{Permission, PermissionsManager}, @@ -13,6 +14,7 @@ use rocket::{ use sqlx::{Postgres, Transaction}; use std::collections::HashSet; +#[allow(non_upper_case_globals)] pub struct Auth { pub user: AuthenticatedUser, pub connection: Transaction<'static, Postgres>, @@ -22,6 +24,7 @@ pub struct Auth { pub(crate) secret: String, } +#[allow(non_upper_case_globals)] impl Auth { pub async fn commit(self) -> Result<(), UserError> { self.connection.commit().await.map_err(UserError::from) @@ -49,7 +52,7 @@ macro_rules! try_outcome { ($outcome:expr) => { match $outcome { Ok(success) => success, - Err(error) => return Outcome::Failure((Status::from_code(error.status_code()).unwrap(), error.into())), + Err(error) => return Outcome::Error((Status::from_code(error.status_code()).unwrap(), error.into())), } }; } @@ -59,53 +62,46 @@ impl<'r> FromRequest<'r> for Auth { type Error = UserError; async fn from_request(request: &'r Request<'_>) -> Outcome { - // No auth header set, forward to the request handler that doesnt require authorization + // No auth header set, forward to the request handler that doesnt require authorization (if one exists) if request.headers().get_one("Authorization").is_none() && request.cookies().get("access_token").is_none() { - return Outcome::Forward(()) + return Outcome::Forward(Status::NotFound); } let pool = request.guard::<&State>().await; let permission_manager = match request.guard::<&State>().await { Outcome::Success(manager) => manager.inner().clone(), - Outcome::Failure(err) => - return Outcome::Failure(( + Outcome::Error(err) => { + return Outcome::Error(( Status::InternalServerError, - CoreError::InternalServerError { - message: format!("PermissionManager not retrievable from rocket state: {:?}", err), - } - .into(), - )), + CoreError::internal_server_error(format!("PermissionsManager not retrievable from rocket state: {:?}", err)).into(), + )) + }, Outcome::Forward(_) => unreachable!(), // by impl FromRequest for State }; let mut connection = match pool { Outcome::Success(pool) => try_outcome!(pool.transaction().await), - Outcome::Failure(err) => { - error!("Could not retrieve database pool from shared state. Did you correctly configure rocket state?"); - - return Outcome::Failure(( + Outcome::Error(err) => { + return Outcome::Error(( Status::InternalServerError, - CoreError::InternalServerError { - message: format!("PointercratePool not retrievable from rocket state: {:?}", err), - } - .into(), - )) + CoreError::internal_server_error(format!("PointercratePool not retrievable from rocket state: {:?}", err)).into(), + )); }, Outcome::Forward(_) => unreachable!(), // by impl FromRequest for State }; for authorization in request.headers().get("Authorization") { if let ["Bearer", token] = authorization.split(' ').collect::>()[..] { - let user = try_outcome!(AuthenticatedUser::token_auth(token, None, &mut connection).await); + let user = try_outcome!(AuthenticatedUser::token_auth(token, None, &mut *connection).await); - try_outcome!(audit_connection(&mut connection, user.inner().id).await); + try_outcome!(audit_connection(&mut *connection, user.inner().id).await); return Outcome::Success(Auth { user, connection, permissions: permission_manager, secret: token.to_string(), - }) + }); } } @@ -116,16 +112,16 @@ impl<'r> FromRequest<'r> for Auth { if request.method() == Method::Get { debug!("GET request, the cookie is enough"); - let user = try_outcome!(AuthenticatedUser::token_auth(access_token, None, &mut connection).await); + let user = try_outcome!(AuthenticatedUser::token_auth(access_token, None, &mut *connection).await); - try_outcome!(audit_connection(&mut connection, user.inner().id).await); + try_outcome!(audit_connection(&mut *connection, user.inner().id).await); return Outcome::Success(Auth { user, connection, permissions: permission_manager, secret: access_token.to_string(), - }) + }); } debug!("Non-GET request, testing X-CSRF-TOKEN header"); @@ -134,22 +130,22 @@ impl<'r> FromRequest<'r> for Auth { // :tm: if let Some(csrf_token) = request.headers().get_one("X-CSRF-TOKEN") { - let user = try_outcome!(AuthenticatedUser::token_auth(access_token, Some(csrf_token), &mut connection).await); + let user = try_outcome!(AuthenticatedUser::token_auth(access_token, Some(csrf_token), &mut *connection).await); - try_outcome!(audit_connection(&mut connection, user.inner().id).await); + try_outcome!(audit_connection(&mut *connection, user.inner().id).await); return Outcome::Success(Auth { user, connection, permissions: permission_manager, secret: access_token.to_string(), - }) + }); } else { warn!("Cookie based authentication was used, but no CSRF-token was provided. This might be a CSRF attack!"); } } - Outcome::Failure((Status::Unauthorized, CoreError::Unauthorized.into())) + Outcome::Error((Status::Unauthorized, CoreError::Unauthorized.into())) } } @@ -158,44 +154,38 @@ impl<'r> FromRequest<'r> for Auth { type Error = UserError; async fn from_request(request: &'r Request<'_>) -> Outcome { - // No auth header set, forward to the request handler that doesnt require authorization + // No auth header set, forward to the request handler that doesnt require authorization (if one exists) if request.headers().get_one("Authorization").is_none() { - return Outcome::Forward(()) + return Outcome::Forward(Status::NotFound); } let pool = request.guard::<&State>().await; let permission_manager = match request.guard::<&State>().await { Outcome::Success(manager) => manager.inner().clone(), - Outcome::Failure(err) => - return Outcome::Failure(( + Outcome::Error(err) => { + return Outcome::Error(( Status::InternalServerError, - CoreError::InternalServerError { - message: format!("PermissionManager not retrievable from rocket state: {:?}", err), - } - .into(), - )), + CoreError::internal_server_error(format!("PermissionsManager not retrievable from rocket state: {:?}", err)).into(), + )) + }, Outcome::Forward(_) => unreachable!(), // by impl FromRequest for State }; let mut connection = match pool { Outcome::Success(pool) => try_outcome!(pool.transaction().await), - Outcome::Failure(err) => { - error!("Could not retrieve database pool from shared state. Did you correctly configure rocket state?"); - - return Outcome::Failure(( + Outcome::Error(err) => { + return Outcome::Error(( Status::InternalServerError, - CoreError::InternalServerError { - message: format!("PointercratePool not retrievable from rocket state: {:?}", err), - } - .into(), - )) + CoreError::internal_server_error(format!("PointercratePool not retrievable from rocket state: {:?}", err)).into(), + )); }, Outcome::Forward(_) => unreachable!(), // by impl FromRequest for State }; for authorization in request.headers().get("Authorization") { if let ["Basic", basic_auth] = authorization.split(' ').collect::>()[..] { - let decoded = try_outcome!(base64::decode(basic_auth) + let decoded = try_outcome!(STANDARD + .decode(basic_auth) .map_err(|_| ()) .and_then(|bytes| String::from_utf8(bytes).map_err(|_| ())) .map_err(|_| { @@ -205,20 +195,20 @@ impl<'r> FromRequest<'r> for Auth { })); if let [username, password] = &decoded.splitn(2, ':').collect::>()[..] { - let user = try_outcome!(AuthenticatedUser::basic_auth(username, password, &mut connection).await); + let user = try_outcome!(AuthenticatedUser::basic_auth(username, password, &mut *connection).await); - try_outcome!(audit_connection(&mut connection, user.inner().id).await); + try_outcome!(audit_connection(&mut *connection, user.inner().id).await); return Outcome::Success(Auth { user, connection, permissions: permission_manager, secret: password.to_string(), - }) + }); } } } - Outcome::Failure((Status::Unauthorized, CoreError::Unauthorized.into())) + Outcome::Error((Status::Unauthorized, CoreError::Unauthorized.into())) } } diff --git a/pointercrate-user-api/src/endpoints/auth.rs b/pointercrate-user-api/src/endpoints/auth.rs index 79c192467..95b4d0461 100644 --- a/pointercrate-user-api/src/endpoints/auth.rs +++ b/pointercrate-user-api/src/endpoints/auth.rs @@ -27,9 +27,9 @@ pub async fn register( AuthenticatedUser::validate_password(&body.password)?; User::validate_name(&body.name)?; - ratelimits.registrations(ip)?; + let user = AuthenticatedUser::register(body.0, &mut *connection).await?; - let user = AuthenticatedUser::register(body.0, &mut connection).await?; + ratelimits.registrations(ip)?; connection.commit().await.map_err(UserError::from)?; diff --git a/pointercrate-user-api/src/endpoints/user.rs b/pointercrate-user-api/src/endpoints/user.rs index 32e32efec..b29dba18c 100644 --- a/pointercrate-user-api/src/endpoints/user.rs +++ b/pointercrate-user-api/src/endpoints/user.rs @@ -4,7 +4,7 @@ use pointercrate_core::error::CoreError; use pointercrate_core_api::{ error::Result, etag::{Precondition, Tagged}, - pagination_response, + pagination::pagination_response, query::Query, response::Response2, }; @@ -18,7 +18,7 @@ pub async fn paginate(mut auth: TokenAuth, data: Query) -> Resul // permissions if auth.assignable_permissions().is_empty() { - return Err(CoreError::Forbidden.into()) + return Err(CoreError::Forbidden.into()); } // Pointercrate staff need to be able to see all users, not only those whose permissions they can @@ -32,11 +32,7 @@ pub async fn paginate(mut auth: TokenAuth, data: Query) -> Resul } } - let mut users = pagination.page(&mut auth.connection).await?; - - let (max_id, min_id) = User::extremal_member_ids(&mut auth.connection).await?; - - pagination_response!("/api/v1/users/", users, pagination, min_id, max_id, before_id, after_id, id) + Ok(pagination_response("/api/v1/users", pagination, &mut auth.connection).await?) } #[rocket::get("/")] @@ -49,7 +45,7 @@ pub async fn get_user(mut auth: TokenAuth, user_id: i32) -> Result> if !can_assign_any { // don't leak information about what users exist - return Err(UserError::UserNotFound { user_id }.into()) + return Err(UserError::UserNotFound { user_id }.into()); } } @@ -65,7 +61,7 @@ pub async fn patch_user(mut auth: TokenAuth, precondition: Precondition, user_id if !can_assign_any { // don't leak information about what users exist - return Err(UserError::UserNotFound { user_id }.into()) + return Err(UserError::UserNotFound { user_id }.into()); } } @@ -82,7 +78,7 @@ pub async fn patch_user(mut auth: TokenAuth, precondition: Precondition, user_id return Err(UserError::PermissionNotAssignable { non_assignable: auth.permissions.bits_to_permissions(unassignable_permissions), } - .into()) + .into()); } info!("assignable permissions are {:b}", assignable_bitmask); @@ -96,7 +92,7 @@ pub async fn patch_user(mut auth: TokenAuth, precondition: Precondition, user_id } if user_id == auth.user.inner().id { - return Err(UserError::PatchSelf.into()) + return Err(UserError::PatchSelf.into()); } precondition.require_etag_match(&user)?; @@ -113,7 +109,7 @@ pub async fn delete_user(mut auth: TokenAuth, precondition: Precondition, user_i auth.require_permission(ADMINISTRATOR)?; if user_id == auth.user.inner().id { - return Err(UserError::DeleteSelf.into()) + return Err(UserError::DeleteSelf.into()); } let to_delete = User::by_id(user_id, &mut auth.connection).await?; diff --git a/pointercrate-user-api/src/lib.rs b/pointercrate-user-api/src/lib.rs index 3e8306d5f..ec00b06d6 100644 --- a/pointercrate-user-api/src/lib.rs +++ b/pointercrate-user-api/src/lib.rs @@ -12,25 +12,29 @@ pub fn setup(rocket: Rocket) -> Rocket { rocket .manage(ratelimits) - .mount("/api/v1/auth/", rocket::routes![ - endpoints::auth::register, - endpoints::auth::login, - endpoints::auth::invalidate, - endpoints::auth::get_me, - endpoints::auth::patch_me, - endpoints::auth::delete_me, - endpoints::auth::verify_email, - ]) - .mount("/api/v1/users/", rocket::routes![ - endpoints::user::paginate, - endpoints::user::get_user, - endpoints::user::patch_user, - endpoints::user::delete_user - ]) - .mount("/", rocket::routes![ - pages::login_page, - pages::account_page, - pages::login, - pages::register - ]) + .mount( + "/api/v1/auth/", + rocket::routes![ + endpoints::auth::register, + endpoints::auth::login, + endpoints::auth::invalidate, + endpoints::auth::get_me, + endpoints::auth::patch_me, + endpoints::auth::delete_me, + endpoints::auth::verify_email, + ], + ) + .mount( + "/api/v1/users/", + rocket::routes![ + endpoints::user::paginate, + endpoints::user::get_user, + endpoints::user::patch_user, + endpoints::user::delete_user + ], + ) + .mount( + "/", + rocket::routes![pages::login_page, pages::account_page, pages::login, pages::register], + ) } diff --git a/pointercrate-user-api/src/pages.rs b/pointercrate-user-api/src/pages.rs index 01221528f..4fbce219c 100644 --- a/pointercrate-user-api/src/pages.rs +++ b/pointercrate-user-api/src/pages.rs @@ -29,7 +29,7 @@ pub async fn login( let auth = auth?; - let mut cookie = Cookie::build("access_token", auth.user.generate_access_token()) + let mut cookie = Cookie::build(("access_token", auth.user.generate_access_token())) .http_only(true) .same_site(SameSite::Strict) .path("/"); @@ -38,7 +38,7 @@ pub async fn login( cookie = cookie.secure(true) } - cookies.add(cookie.finish()); + cookies.add(cookie); Ok(Status::NoContent) } @@ -57,11 +57,11 @@ pub async fn register( ratelimits.registrations(ip)?; - let user = AuthenticatedUser::register(registration.0, &mut connection).await?; + let user = AuthenticatedUser::register(registration.0, &mut *connection).await?; connection.commit().await.map_err(UserError::from)?; - let mut cookie = Cookie::build("access_token", user.generate_access_token()) + let mut cookie = Cookie::build(("access_token", user.generate_access_token())) .http_only(true) .same_site(SameSite::Strict) .path("/"); @@ -70,7 +70,7 @@ pub async fn register( cookie = cookie.secure(true) } - cookies.add(cookie.finish()); + cookies.add(cookie); Ok(Status::Created) } diff --git a/pointercrate-user-api/src/ratelimits.rs b/pointercrate-user-api/src/ratelimits.rs index c815323ad..869c44f8a 100644 --- a/pointercrate-user-api/src/ratelimits.rs +++ b/pointercrate-user-api/src/ratelimits.rs @@ -1,10 +1,12 @@ +use std::net::IpAddr; + use pointercrate_core::ratelimits; ratelimits! { UserRatelimits { - registrations[1u32 per 86400 per ip] => "Too many registrations!", - soft_registrations[5u32 per 21600 per ip] => "Too many failed registration attempts!", - login_attempts[3u32 per 1800 per ip] => "Too many login attempts!", - change_email[1u32 per 2592000 per ip] => "Too many attempted email changes", + registrations[1u32 per 86400 per IpAddr] => "Too many registrations!", + soft_registrations[5u32 per 21600 per IpAddr] => "Too many failed registration attempts!", + login_attempts[3u32 per 1800 per IpAddr] => "Too many login attempts!", + change_email[1u32 per 2592000 per IpAddr] => "Too many attempted email changes", } } diff --git a/pointercrate-user-pages/Cargo.toml b/pointercrate-user-pages/Cargo.toml index 6f7cdaec9..9bf5fc50c 100644 --- a/pointercrate-user-pages/Cargo.toml +++ b/pointercrate-user-pages/Cargo.toml @@ -1,13 +1,14 @@ [package] name = "pointercrate-user-pages" version = "0.1.0" -edition = "2021" +authors.workspace = true +edition.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -maud = "0.25" +maud = "0.26.0" pointercrate-core = {path = "../pointercrate-core"} pointercrate-user = {path = "../pointercrate-user"} pointercrate-core-pages = {path = "../pointercrate-core-pages"} -async-trait = "0.1.42" +async-trait = "0.1.80" diff --git a/pointercrate-user-pages/src/account/users.rs b/pointercrate-user-pages/src/account/users.rs index 64c3780f4..9373795b5 100644 --- a/pointercrate-user-pages/src/account/users.rs +++ b/pointercrate-user-pages/src/account/users.rs @@ -11,7 +11,7 @@ impl AccountPageTab for UsersTab { fn should_display_for(&self, permissions_we_have: u16, permissions: &PermissionsManager) -> bool { for perm in &self.0 { if permissions.require_permission(permissions_we_have, *perm).is_ok() { - return true + return true; } } @@ -93,7 +93,6 @@ impl AccountPageTab for UsersTab { @let name_in_snake_case = permission.name().to_lowercase().replace(' ', "-"); label.cb-container.form-input #(name_in_snake_case) for = (name_in_snake_case) data-bit = (permission.bit()) { - input type = "checkbox" name = (name_in_snake_case); i { (permission.name()) } diff --git a/pointercrate-user/Cargo.toml b/pointercrate-user/Cargo.toml index 2f16ed504..c4f8bc0c1 100644 --- a/pointercrate-user/Cargo.toml +++ b/pointercrate-user/Cargo.toml @@ -1,20 +1,21 @@ [package] name = "pointercrate-user" version = "0.1.0" -edition = "2021" +authors.workspace = true +edition.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] pointercrate-core = {path = "../pointercrate-core"} -serde = "1.0.118" -derive_more = "0.99.11" -sqlx = { version = "0.6", default-features = false, features = [ "runtime-tokio-native-tls", "macros", "postgres", "chrono", "offline" ] } -jsonwebtoken = "7.2.0" -log = "0.4.11" +serde = "1.0.203" +derive_more = "0.99.18" +sqlx = { version = "0.7", default-features = false, features = [ "runtime-tokio-native-tls", "macros", "postgres", "chrono" ] } +jsonwebtoken = "9.3.0" +log = "0.4.22" futures = "0.3.8" -base64 = "0.13.0" -lazy_static = "1.4.0" -bcrypt = "0.9.0" -url = "2.2.0" -serde_json = "1.0.60" +base64 = "0.22.1" +lazy_static = "1.5.0" +bcrypt = "0.15.1" +url = "2.5.2" +serde_json = "1.0.118" diff --git a/pointercrate-user/src/auth/get.rs b/pointercrate-user/src/auth/get.rs index 859f80ccd..2eee7f9c3 100644 --- a/pointercrate-user/src/auth/get.rs +++ b/pointercrate-user/src/auth/get.rs @@ -1,8 +1,11 @@ +use std::collections::HashSet; + use crate::{ auth::{AccessClaims, AuthenticatedUser}, error::Result, User, }; +use jsonwebtoken::{DecodingKey, Validation}; use log::{debug, info}; use pointercrate_core::error::CoreError; use sqlx::{Error, PgConnection}; @@ -20,7 +23,12 @@ impl AuthenticatedUser { // Well this is reassuring. Also we directly deconstruct it and only save the ID // so we don't accidentally use unsafe values later on - let AccessClaims { id, .. } = jsonwebtoken::dangerous_insecure_decode::(access_token) + let mut no_validation = Validation::default(); + no_validation.insecure_disable_signature_validation(); + no_validation.validate_exp = false; + no_validation.required_spec_claims = HashSet::new(); + + let AccessClaims { id, .. } = jsonwebtoken::decode(access_token, &DecodingKey::from_secret(b""), &no_validation) .map_err(|_| CoreError::Unauthorized)? .claims; @@ -49,12 +57,11 @@ impl AuthenticatedUser { match row { Err(Error::RowNotFound) => Err(CoreError::Unauthorized.into()), Err(err) => Err(err.into()), - Ok(row) => - Ok(AuthenticatedUser { - user: construct_from_row!(row), - password_hash: row.password_hash, - email_address: row.email_address, - }), + Ok(row) => Ok(AuthenticatedUser { + user: construct_from_row!(row), + password_hash: row.password_hash, + email_address: row.email_address, + }), } } @@ -69,12 +76,11 @@ impl AuthenticatedUser { match row { Err(Error::RowNotFound) => Err(CoreError::Unauthorized.into()), Err(err) => Err(err.into()), - Ok(row) => - Ok(AuthenticatedUser { - user: construct_from_row!(row), - password_hash: row.password_hash, - email_address: row.email_address, - }), + Ok(row) => Ok(AuthenticatedUser { + user: construct_from_row!(row), + password_hash: row.password_hash, + email_address: row.email_address, + }), } } } diff --git a/pointercrate-user/src/auth/mod.rs b/pointercrate-user/src/auth/mod.rs index 96b45daa0..600769533 100644 --- a/pointercrate-user/src/auth/mod.rs +++ b/pointercrate-user/src/auth/mod.rs @@ -14,7 +14,10 @@ use jsonwebtoken::{DecodingKey, EncodingKey}; use log::{debug, warn}; use pointercrate_core::error::CoreError; use serde::{Deserialize, Serialize}; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use std::{ + collections::HashSet, + time::{Duration, SystemTime, UNIX_EPOCH}, +}; mod delete; mod get; @@ -62,7 +65,7 @@ impl AuthenticatedUser { pub fn validate_password(password: &str) -> Result<()> { if password.len() < 10 { - return Err(UserError::InvalidPassword) + return Err(UserError::InvalidPassword); } Ok(()) @@ -85,10 +88,9 @@ impl AuthenticatedUser { pub fn validate_access_token(self, token: &str) -> Result { // TODO: maybe one day do something with this - let validation = jsonwebtoken::Validation { - validate_exp: false, - ..Default::default() - }; + let mut validation = jsonwebtoken::Validation::default(); + validation.validate_exp = false; + validation.required_spec_claims = HashSet::default(); jsonwebtoken::decode::(token, &DecodingKey::from_secret(&self.jwt_secret()), &validation) .map_err(|err| { @@ -181,28 +183,28 @@ impl AuthenticatedUser { } pub fn validate_csrf_token(&self, token: &str) -> Result<()> { - jsonwebtoken::decode::( - token, - &DecodingKey::from_secret(&pointercrate_core::config::secret()), - &jsonwebtoken::Validation::default(), - ) - .map_err(|err| { - warn!("Access token validation FAILED for account {}: {}", self.user, err); + let mut validation = jsonwebtoken::Validation::default(); + validation.validate_exp = false; + validation.required_spec_claims = HashSet::new(); - CoreError::Unauthorized.into() - }) - .and_then(|token_data| { - if token_data.claims.id != self.user.id { - warn!( - "User {} attempt to authenticate using CSRF token generated for user {}", - self.user, token_data.claims.id - ); + jsonwebtoken::decode::(token, &DecodingKey::from_secret(&pointercrate_core::config::secret()), &validation) + .map_err(|err| { + warn!("Access token validation FAILED for account {}: {}", self.user, err); - Err(CoreError::Unauthorized.into()) - } else { - Ok(()) - } - }) + CoreError::Unauthorized.into() + }) + .and_then(|token_data| { + if token_data.claims.id != self.user.id { + warn!( + "User {} attempt to authenticate using CSRF token generated for user {}", + self.user, token_data.claims.id + ); + + Err(CoreError::Unauthorized.into()) + } else { + Ok(()) + } + }) } fn password_salt(&self) -> Vec { @@ -323,6 +325,7 @@ mod tests { mod b64 { use std::collections::HashMap; + use base64::{engine::general_purpose::STANDARD, Engine}; use lazy_static::lazy_static; // Decoding table from bcrypt base64 to standard base64 and standard -> bcrypt @@ -417,6 +420,6 @@ mod b64 { } // if we had non standard chars, it would have errored before - base64::decode(&res).unwrap() + STANDARD.decode(&res).unwrap() } } diff --git a/pointercrate-user/src/get.rs b/pointercrate-user/src/get.rs index fc34d0d24..33a258240 100644 --- a/pointercrate-user/src/get.rs +++ b/pointercrate-user/src/get.rs @@ -41,10 +41,9 @@ impl User { .await; match row { - Err(Error::RowNotFound) => - Err(UserError::UserNotFoundName { - user_name: name.to_string(), - }), + Err(Error::RowNotFound) => Err(UserError::UserNotFoundName { + user_name: name.to_string(), + }), Err(err) => Err(err.into()), Ok(row) => Ok(construct_from_row!(row)), } diff --git a/pointercrate-user/src/lib.rs b/pointercrate-user/src/lib.rs index 094354642..063507352 100644 --- a/pointercrate-user/src/lib.rs +++ b/pointercrate-user/src/lib.rs @@ -11,10 +11,12 @@ pub use self::{ patch::PatchUser, }; use crate::error::{Result, UserError}; -use pointercrate_core::{etag::Taggable, permission::Permission}; +use pointercrate_core::{ + etag::Taggable, + permission::{Permission, PermissionsManager}, +}; use serde::Serialize; pub use sqlx; -use sqlx::PgConnection; use std::{ fmt::{Display, Formatter}, hash::Hash, @@ -32,6 +34,12 @@ mod video; pub const ADMINISTRATOR: Permission = Permission::new("Administrator", 0x4000); pub const MODERATOR: Permission = Permission::new("Moderator", 0x2000); +pub fn default_permissions_manager() -> PermissionsManager { + PermissionsManager::new(vec![ADMINISTRATOR, MODERATOR]) + .assigns(ADMINISTRATOR, MODERATOR) + .implies(ADMINISTRATOR, MODERATOR) +} + /// Model representing a user in the database #[derive(Debug, Serialize, Hash, Eq, PartialEq)] pub struct User { @@ -80,7 +88,7 @@ impl User { pub fn validate_name(name: &str) -> Result<()> { if name.len() < 3 || name != name.trim() { - return Err(UserError::InvalidUsername) + return Err(UserError::InvalidUsername); } Ok(()) @@ -92,16 +100,4 @@ impl User { None => self.name.as_ref(), } } - - /// Gets the maximal and minimal member id currently in use - /// - /// The returned tuple is of the form (max, min) - pub async fn extremal_member_ids(connection: &mut PgConnection) -> Result<(i32, i32)> { - let row = sqlx::query!( - r#"SELECT COALESCE(MAX(member_id), 0) AS "max_id!: i32", COALESCE(MIN(member_id), 0) AS "min_id!: i32" FROM members"# - ) - .fetch_one(connection) - .await?; // FIXME: crashes on empty table - Ok((row.max_id, row.min_id)) - } } diff --git a/pointercrate-user/src/paginate.rs b/pointercrate-user/src/paginate.rs index 5b611540f..ac92974bf 100644 --- a/pointercrate-user/src/paginate.rs +++ b/pointercrate-user/src/paginate.rs @@ -1,7 +1,8 @@ use crate::{error::Result, User}; use futures::StreamExt; use pointercrate_core::{ - error::CoreError, + first_and_last, + pagination::{PageContext, Paginatable, PaginationParameters, PaginationQuery, __pagination_compat}, permission::Permission, util::{non_nullable, nullable}, }; @@ -10,14 +11,8 @@ use sqlx::{postgres::PgRow, PgConnection, Row}; #[derive(Deserialize, Debug, Clone, Serialize)] pub struct UserPagination { - #[serde(rename = "before", default, deserialize_with = "non_nullable")] - pub before_id: Option, - - #[serde(rename = "after", default, deserialize_with = "non_nullable")] - pub after_id: Option, - - #[serde(default, deserialize_with = "non_nullable")] - pub limit: Option, + #[serde(flatten)] + pub params: PaginationParameters, #[serde(default, deserialize_with = "non_nullable")] pub name: Option, @@ -35,38 +30,37 @@ pub struct UserPagination { pub any_permissions: Option, } -impl UserPagination { - pub async fn page(&self, connection: &mut PgConnection) -> Result> { - if let Some(limit) = self.limit { - if !(1..=100).contains(&limit) { - return Err(CoreError::InvalidPaginationLimit.into()) - } - } +impl PaginationQuery for UserPagination { + fn parameters(&self) -> PaginationParameters { + self.params + } - if let (Some(after), Some(before)) = (self.before_id, self.after_id) { - if after < before { - return Err(CoreError::AfterSmallerBefore.into()) - } + fn with_parameters(&self, parameters: PaginationParameters) -> Self { + Self { + params: parameters, + ..self.clone() } + } +} - let order = if self.after_id.is_none() && self.before_id.is_some() { - "DESC" - } else { - "ASC" - }; - - let query = format!(include_str!("../sql/paginate_users.sql"), order); - - let mut stream = sqlx::query(&query) - .bind(self.before_id) - .bind(self.after_id) - .bind(self.name.as_ref()) - .bind(self.display_name.as_ref()) - .bind(self.display_name == Some(None)) - .bind(self.has_permissions.map(|p| p as i32)) - .bind(self.any_permissions.map(|p| p as i32)) - .bind(self.name_contains.as_ref()) - .bind(self.limit.unwrap_or(50) as i32 + 1) +impl Paginatable for User { + first_and_last!("members", "member_id"); + + async fn page(query: &UserPagination, connection: &mut PgConnection) -> std::result::Result<(Vec, PageContext), sqlx::Error> { + let order = query.params.order(); + + let sql_query = format!(include_str!("../sql/paginate_users.sql"), order); + + let mut stream = sqlx::query(&sql_query) + .bind(query.params.before) + .bind(query.params.after) + .bind(query.name.as_ref()) + .bind(query.display_name.as_ref()) + .bind(query.display_name == Some(None)) + .bind(query.has_permissions.map(|p| p as i32)) + .bind(query.any_permissions.map(|p| p as i32)) + .bind(query.name_contains.as_ref()) + .bind(query.params.limit + 1) .fetch(connection); let mut users = Vec::new(); @@ -85,7 +79,11 @@ impl UserPagination { }) } - Ok(users) + Ok(__pagination_compat(&query.params, users)) + } + + fn pagination_id(&self) -> i32 { + self.id } } diff --git a/pointercrate-user/src/video.rs b/pointercrate-user/src/video.rs index 66da9dd77..20bb024d0 100644 --- a/pointercrate-user/src/video.rs +++ b/pointercrate-user/src/video.rs @@ -10,20 +10,20 @@ pub fn validate_channel(url: &str) -> Result { let url = Url::parse(url).map_err(|_| UserError::MalformedChannelUrl)?; if !SCHEMES.contains(&url.scheme()) { - return Err(CoreError::InvalidUrlScheme.into()) + return Err(CoreError::InvalidUrlScheme.into()); } if !url.username().is_empty() || url.password().is_some() { - return Err(CoreError::UrlAuthenticated.into()) + return Err(CoreError::UrlAuthenticated.into()); } if let Some(host) = url.domain() { match host { - "www.youtube.com" | "youtube.com" => + "www.youtube.com" | "youtube.com" => { if let Some(path_segments) = url.path_segments() { match &path_segments.collect::>()[..] { ["channel", _] | ["user", _] | ["c", _] => Ok(url.to_string()), - [handle] => + [handle] => { if handle.starts_with("@") { Ok(url.to_string()) } else { @@ -31,19 +31,20 @@ pub fn validate_channel(url: &str) -> Result { expected: YOUTUBE_CHANNEL_FORMAT, } .into()) - }, - _ => - Err(CoreError::InvalidUrlFormat { - expected: YOUTUBE_CHANNEL_FORMAT, } - .into()), + }, + _ => Err(CoreError::InvalidUrlFormat { + expected: YOUTUBE_CHANNEL_FORMAT, + } + .into()), } } else { Err(CoreError::InvalidUrlFormat { expected: YOUTUBE_CHANNEL_FORMAT, } .into()) - }, + } + }, _ => Err(UserError::NotYouTube), } } else { diff --git a/rustfmt.toml b/rustfmt.toml index ee68af825..9a2ab38f1 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,23 +1,7 @@ -combine_control_expr = false -comment_width = 100 -condense_wildcard_suffixes = true edition = "2021" -fn_args_layout = "Compressed" -format_code_in_doc_comments = true -format_macro_matchers = true -format_strings = true -force_multiline_blocks = true -imports_granularity = "Crate" -match_arm_blocks = false +fn_params_layout = "Compressed" match_block_trailing_comma = true max_width = 140 newline_style = "Unix" -#normalize_comments = true -overflow_delimited_expr = true -reorder_impl_items = true -report_todo = "Always" -report_fixme = "Always" -trailing_semicolon = false use_field_init_shorthand = true use_try_shorthand = true -wrap_comments = true