diff --git a/.env b/.env new file mode 100644 index 000000000..39c1206ee --- /dev/null +++ b/.env @@ -0,0 +1 @@ +DATABASE_URL="postgresql://pointercrate:asdf@localhost/pointercrate" \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/flag-request.md b/.github/ISSUE_TEMPLATE/flag-request.md deleted file mode 100644 index a969674df..000000000 --- a/.github/ISSUE_TEMPLATE/flag-request.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -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 deleted file mode 100644 index 8d75ff209..000000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,14 +0,0 @@ -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/workflows/check_all_features.yml b/.github/workflows/check_all_features.yml index 223e4bdef..29a70952d 100644 --- a/.github/workflows/check_all_features.yml +++ b/.github/workflows/check_all_features.yml @@ -1,5 +1,7 @@ name: Check all feature permutations -on: pull_request +on: + pull_request: + workflow_dispatch: jobs: # Label of the container job @@ -26,7 +28,6 @@ jobs: --health-interval 10s --health-timeout 5s --health-retries 5 - steps: # Downloads a copy of the code in your repository before running CI tests - name: Check out repository code @@ -50,7 +51,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: sqlx - args: migrate run + args: migrate run --source pointercrate-example/sample/migrations/_new/ env: DATABASE_URL: postgresql://pointercrate:postgres@localhost/postgres @@ -59,4 +60,4 @@ jobs: with: command: check-all-features env: - DATABASE_URL: postgresql://pointercrate:postgres@localhost/postgres + DATABASE_URL: postgresql://pointercrate:postgres@localhost/postgres \ No newline at end of file diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 000000000..9006bf12e --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,52 @@ +name: Shuttle Deploy + +on: + push: + branches: + - main + workflow_dispatch: + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + # Check out the repository + - uses: actions/checkout@v4 + + # Generate secrets to .env + - name: Generate secrets to .env + run: | + echo "DATABASE_URL=${{ secrets.database }}" > .env + echo "ABSTRACT_API_KEY='${{ secrets.abstract }}'" >> .env + echo "DISCORD_WEBHOOK= '${{ secrets.DISCORD_WEBHOOK }}'" >> .env + + - name: Generate auth secret + run: | + echo ${{ secrets.SECRET_THING }} > .secret + + # Install cargo-binstall + - name: Install cargo-binstall + run: curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash + shell: bash + + # Install cargo-shuttle (specific version if specified) + - name: Install cargo-shuttle + run: | + cargo binstall -y --locked cargo-shuttle + shell: bash + + # Restart project (optional) + - name: Restart project + run: | + cargo shuttle project restart --idle-minutes 0 --name cscl + env: + SHUTTLE_API_KEY: ${{ secrets.SHUTTLE_API_KEY }} + shell: bash + + # Deploy to Shuttle + - name: Deploy to Shuttle + run: | + cargo shuttle deploy --name cscl --allow-dirty --nt + env: + SHUTTLE_API_KEY: ${{ secrets.SHUTTLE_API_KEY }} + shell: bash diff --git a/.github/workflows/rustfmt.yml b/.github/workflows/rustfmt.yml index 1c5c51335..4d04f2d5b 100644 --- a/.github/workflows/rustfmt.yml +++ b/.github/workflows/rustfmt.yml @@ -1,17 +1,10 @@ name: Rustfmt -# Runs `cargo fmt` whenever code is pushed or pr'd into master. - -on: - push: - branches: - - master - pull_request: - branches: - - master - types: - - closed +# Runs `cargo fmt` on dispatch +on: + workflow_dispatch: + jobs: format: name: Rustfmt @@ -40,4 +33,4 @@ jobs: - name: Push changes uses: ad-m/github-push-action@master with: - github_token: ${{ secrets.GITHUB_TOKEN }} + github_token: ${{ secrets.GIT_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 07ee3c471..f313a6b2d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,9 +1,7 @@ name: Tests on: pull_request: - push: - branches: - - master + workflow_dispatch: jobs: # Label of the container job @@ -54,7 +52,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: sqlx - args: migrate run + args: migrate run --source pointercrate-example/sample/migrations/_new/ env: DATABASE_URL: postgresql://pointercrate:postgres@localhost/postgres @@ -69,19 +67,3 @@ jobs: 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 deleted file mode 100644 index e2993f728..000000000 --- a/.gitignore +++ /dev/null @@ -1,11 +0,0 @@ -**/target/ -.env -.secret -.idea -.vscode - -# random stuff I have because of enabling eslint in vscode -node_modules -eslint.config.mjs -package-lock.json -package.json \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 000000000..8e23ee363 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,20 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "local shuttle", + "type": "shell", + "command": "cargo shuttle run", + "problemMatcher": [], + "group": { + "kind": "build", + "isDefault": true + }, + "options": { + "cwd": "${workspaceFolder}" + } + } + ] +} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index ee3e545df..7c5ca3cba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -59,6 +59,12 @@ dependencies = [ "libc", ] +[[package]] +name = "anyhow" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" + [[package]] name = "async-stream" version = "0.3.6" @@ -78,7 +84,7 @@ checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.79", ] [[package]] @@ -89,7 +95,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.79", ] [[package]] @@ -122,6 +128,51 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "axum" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +dependencies = [ + "async-trait", + "axum-core", + "bitflags 1.3.2", + "bytes", + "futures-util", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.30", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper 0.1.2", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http 0.2.12", + "http-body 0.4.6", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + [[package]] name = "backtrace" version = "0.3.74" @@ -137,6 +188,12 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + [[package]] name = "base64" version = "0.21.7" @@ -281,6 +338,47 @@ dependencies = [ "inout", ] +[[package]] +name = "cloudflare" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b19c12f95c05549854043364bce0e65cb12bd545a62128af2810a01bf1843161" +dependencies = [ + "chrono", + "http 0.2.12", + "percent-encoding", + "reqwest 0.11.27", + "serde", + "serde_json", + "serde_urlencoded", + "serde_with", + "thiserror", + "url", + "uuid", +] + +[[package]] +name = "colored" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" +dependencies = [ + "lazy_static", + "windows-sys 0.48.0", +] + +[[package]] +name = "comfy-table" +version = "6.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e959d788268e3bf9d35ace83e81b124190378e4c91c9067524675e33394b8ba" +dependencies = [ + "crossterm 0.26.1", + "strum 0.24.1", + "strum_macros 0.24.3", + "unicode-width", +] + [[package]] name = "concurrent-queue" version = "2.5.0" @@ -290,18 +388,67 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "config" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7328b20597b53c2454f0b1919720c25c7339051c02b72b7e05409e00b14132be" +dependencies = [ + "async-trait", + "convert_case 0.6.0", + "json5", + "lazy_static", + "nom", + "pathdiff", + "ron", + "rust-ini", + "serde", + "serde_json", + "toml", + "yaml-rust", +] + [[package]] name = "const-oid" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom", + "once_cell", + "tiny-keccak", +] + [[package]] name = "convert_case" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "cookie" version = "0.18.1" @@ -362,6 +509,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-queue" version = "0.3.11" @@ -377,6 +533,53 @@ version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +[[package]] +name = "crossterm" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a84cda67535339806297f1b331d6dd6320470d2a0fe65381e79ee9e156dd3d13" +dependencies = [ + "bitflags 1.3.2", + "crossterm_winapi", + "libc", + "mio 0.8.11", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" +dependencies = [ + "bitflags 2.4.1", + "crossterm_winapi", + "libc", + "mio 0.8.11", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-common" version = "0.1.6" @@ -387,6 +590,62 @@ dependencies = [ "typenum", ] +[[package]] +name = "cscl" +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", + "shuttle-rocket", + "shuttle-runtime 0.48.0", + "tokio", +] + +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.79", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.79", +] + [[package]] name = "dash-rs" version = "0.1.0" @@ -412,7 +671,7 @@ source = "git+https://github.com/stadust/dash-rs?rev=8cdbb85625b7575b8775e6e28ce dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.79", ] [[package]] @@ -428,6 +687,12 @@ dependencies = [ "parking_lot_core", ] +[[package]] +name = "data-encoding" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" + [[package]] name = "der" version = "0.7.9" @@ -446,6 +711,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", + "serde", ] [[package]] @@ -454,11 +720,11 @@ version = "0.99.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" dependencies = [ - "convert_case", + "convert_case 0.4.0", "proc-macro2", "quote", "rustc_version", - "syn", + "syn 2.0.79", ] [[package]] @@ -491,7 +757,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn", + "syn 2.0.79", ] [[package]] @@ -506,6 +772,15 @@ dependencies = [ "subtle", ] +[[package]] +name = "dlv-list" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" +dependencies = [ + "const-random", +] + [[package]] name = "dotenv" version = "0.15.0" @@ -719,7 +994,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.79", ] [[package]] @@ -838,7 +1113,7 @@ dependencies = [ "futures-sink", "futures-util", "http 0.2.12", - "indexmap", + "indexmap 2.6.0", "slab", "tokio", "tokio-util", @@ -857,13 +1132,25 @@ dependencies = [ "futures-sink", "futures-util", "http 1.1.0", - "indexmap", + "indexmap 2.6.0", "slab", "tokio", "tokio-util", "tracing", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" + [[package]] name = "hashbrown" version = "0.14.5" @@ -889,6 +1176,36 @@ dependencies = [ "hashbrown 0.14.5", ] +[[package]] +name = "headers" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" +dependencies = [ + "base64 0.21.7", + "bytes", + "headers-core", + "http 0.2.12", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http 0.2.12", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "heck" version = "0.5.0" @@ -1052,6 +1369,20 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" +dependencies = [ + "futures-util", + "http 0.2.12", + "hyper 0.14.30", + "rustls 0.21.12", + "tokio", + "tokio-rustls 0.24.1", +] + [[package]] name = "hyper-rustls" version = "0.27.3" @@ -1062,13 +1393,38 @@ dependencies = [ "http 1.1.0", "hyper 1.4.0", "hyper-util", - "rustls", + "rustls 0.23.14", "rustls-pki-types", "tokio", - "tokio-rustls", + "tokio-rustls 0.26.0", "tower-service", ] +[[package]] +name = "hyper-timeout" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" +dependencies = [ + "hyper 0.14.30", + "pin-project-lite", + "tokio", + "tokio-io-timeout", +] + +[[package]] +name = "hyper-tls" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" +dependencies = [ + "bytes", + "hyper 0.14.30", + "native-tls", + "tokio", + "tokio-native-tls", +] + [[package]] name = "hyper-tls" version = "0.6.0" @@ -1127,6 +1483,12 @@ dependencies = [ "cc", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "0.5.0" @@ -1137,6 +1499,17 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + [[package]] name = "indexmap" version = "2.6.0" @@ -1180,6 +1553,15 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -1195,6 +1577,17 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "json5" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" +dependencies = [ + "pest", + "pest_derive", + "serde", +] + [[package]] name = "jsonwebtoken" version = "9.3.0" @@ -1253,6 +1646,12 @@ dependencies = [ "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.4.14" @@ -1299,6 +1698,12 @@ dependencies = [ "regex-automata 0.1.10", ] +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + [[package]] name = "maud" version = "0.26.0" @@ -1318,7 +1723,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 2.0.79", ] [[package]] @@ -1343,6 +1748,16 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -1358,6 +1773,18 @@ dependencies = [ "adler2", ] +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.48.0", +] + [[package]] name = "mio" version = "1.0.2" @@ -1549,7 +1976,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.79", ] [[package]] @@ -1570,6 +1997,73 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "opentelemetry" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e32339a5dc40459130b3bd269e9892439f55b33e772d2a9d402a789baaf4e8a" +dependencies = [ + "futures-core", + "futures-sink", + "indexmap 2.6.0", + "js-sys", + "once_cell", + "pin-project-lite", + "thiserror", + "urlencoding", +] + +[[package]] +name = "opentelemetry-http" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f51189ce8be654f9b5f7e70e49967ed894e84a06fc35c6c042e64ac1fc5399e" +dependencies = [ + "async-trait", + "bytes", + "http 0.2.12", + "opentelemetry", +] + +[[package]] +name = "opentelemetry_sdk" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f16aec8a98a457a52664d69e0091bac3a0abd18ead9b641cb00202ba4e0efe4" +dependencies = [ + "async-trait", + "crossbeam-channel", + "futures-channel", + "futures-executor", + "futures-util", + "glob", + "once_cell", + "opentelemetry", + "ordered-float", + "percent-encoding", + "rand", + "thiserror", +] + +[[package]] +name = "ordered-float" +version = "4.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a91171844676f8c7990ce64959210cd2eaef32c2612c50f9fae9f8aaa6065a6" +dependencies = [ + "num-traits", +] + +[[package]] +name = "ordered-multimap" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ed8acf08e98e744e5384c8bc63ceb0364e68a6854187221c18df61c4797690e" +dependencies = [ + "dlv-list", + "hashbrown 0.13.2", +] + [[package]] name = "overload" version = "0.1.1" @@ -1611,6 +2105,12 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pathdiff" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d61c5ce1153ab5b689d0c074c4e7fc613e942dfb7dd9eea5ab202d2ad91fe361" + [[package]] name = "pear" version = "0.2.9" @@ -1631,7 +2131,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn", + "syn 2.0.79", ] [[package]] @@ -1659,6 +2159,71 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "pest" +version = "2.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d214365f632b123a47fd913301e14c946c61d1c183ee245fa76eb752e59a02dd" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb55586734301717aea2ac313f50b2eb8f60d2fc3dc01d190eefa2e625f60c4e" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "pest_meta" +version = "2.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b75da2a70cf4d9cb76833c990ac9cd3923c9a8905a8929789ce347c84564d03d" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + +[[package]] +name = "pin-project" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf123a161dde1e524adf36f90bc5d8d3462824a9c43553ad07a8183161189ec" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4502d8515ca9f32f1fb543d987f63d95a14934883db45bdb48060b6b69257f8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.79", +] + [[package]] name = "pin-project-lite" version = "0.2.14" @@ -1703,11 +2268,15 @@ name = "pointercrate-core" version = "0.1.0" dependencies = [ "chrono", + "config", "derive_more", + "dotenv", "getrandom", "log", "serde", + "shuttle-runtime 0.47.0", "sqlx", + "toml", ] [[package]] @@ -1767,7 +2336,7 @@ dependencies = [ "pointercrate-user", "pointercrate-user-api", "rand", - "reqwest", + "reqwest 0.12.8", "rocket", "serde", "serde_json", @@ -1797,6 +2366,7 @@ dependencies = [ name = "pointercrate-example" version = "0.1.0" dependencies = [ + "cloudflare", "dotenv", "maud", "pointercrate-core", @@ -1809,6 +2379,8 @@ dependencies = [ "pointercrate-user-api", "pointercrate-user-pages", "rocket", + "shuttle-rocket", + "shuttle-runtime 0.48.0", ] [[package]] @@ -1824,7 +2396,7 @@ dependencies = [ "nonzero_ext", "pointercrate-core", "pointercrate-demonlist", - "reqwest", + "reqwest 0.12.8", "sqlx", "tokio", ] @@ -1926,6 +2498,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", + "syn 1.0.109", "version_check", ] @@ -1957,11 +2530,43 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.79", "version_check", "yansi", ] +[[package]] +name = "prost" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-derive" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "prost-types" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" +dependencies = [ + "prost", +] + [[package]] name = "quanta" version = "0.12.3" @@ -2051,7 +2656,7 @@ checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.79", ] [[package]] @@ -2098,6 +2703,51 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "reqwest" +version = "0.11.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +dependencies = [ + "base64 0.21.7", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.30", + "hyper-rustls 0.24.2", + "hyper-tls 0.5.0", + "ipnet", + "js-sys", + "log", + "mime", + "mime_guess", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls 0.21.12", + "rustls-pemfile 1.0.4", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 0.1.2", + "system-configuration 0.5.1", + "tokio", + "tokio-native-tls", + "tokio-rustls 0.24.1", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", + "winreg", +] + [[package]] name = "reqwest" version = "0.12.8" @@ -2114,8 +2764,8 @@ dependencies = [ "http-body 1.0.0", "http-body-util", "hyper 1.4.0", - "hyper-rustls", - "hyper-tls", + "hyper-rustls 0.27.3", + "hyper-tls 0.6.0", "hyper-util", "ipnet", "js-sys", @@ -2125,12 +2775,12 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls-pemfile", + "rustls-pemfile 2.2.0", "serde", "serde_json", "serde_urlencoded", - "sync_wrapper", - "system-configuration", + "sync_wrapper 1.0.1", + "system-configuration 0.6.1", "tokio", "tokio-native-tls", "tower-service", @@ -2141,6 +2791,21 @@ dependencies = [ "windows-registry", ] +[[package]] +name = "reqwest-middleware" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a735987236a8e238bf0296c7e351b999c188ccc11477f311b82b55c93984216" +dependencies = [ + "anyhow", + "async-trait", + "http 0.2.12", + "reqwest 0.11.27", + "serde", + "task-local-extensions", + "thiserror", +] + [[package]] name = "ring" version = "0.17.8" @@ -2156,6 +2821,28 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rmp" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "228ed7c16fa39782c3b3468e974aec2795e9089153cd08ee2e9aefb3613334c4" +dependencies = [ + "byteorder", + "num-traits", + "paste", +] + +[[package]] +name = "rmp-serde" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52e599a477cf9840e92f2cde9a7189e67b42c57532749bf90aea6ec10facd4db" +dependencies = [ + "byteorder", + "rmp", + "serde", +] + [[package]] name = "rocket" version = "0.5.1" @@ -2170,7 +2857,7 @@ dependencies = [ "either", "figment", "futures", - "indexmap", + "indexmap 2.6.0", "log", "memchr", "multer", @@ -2202,11 +2889,11 @@ checksum = "575d32d7ec1a9770108c879fc7c47815a80073f96ca07ff9525a94fcede1dd46" dependencies = [ "devise", "glob", - "indexmap", + "indexmap 2.6.0", "proc-macro2", "quote", "rocket_http", - "syn", + "syn 2.0.79", "unicode-xid", "version_check", ] @@ -2222,7 +2909,7 @@ dependencies = [ "futures", "http 0.2.12", "hyper 0.14.30", - "indexmap", + "indexmap 2.6.0", "log", "memchr", "pear", @@ -2238,6 +2925,18 @@ dependencies = [ "uncased", ] +[[package]] +name = "ron" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91f7eff05f748767f183df4320a63d6936e9c6107d97c9e6bdd9784f4289c94" +dependencies = [ + "base64 0.21.7", + "bitflags 2.4.1", + "serde", + "serde_derive", +] + [[package]] name = "rsa" version = "0.9.6" @@ -2258,6 +2957,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "rust-ini" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e2a3bcec1f113553ef1c88aae6c020a369d03d55b58de9869a0908930385091" +dependencies = [ + "cfg-if", + "ordered-multimap", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -2286,6 +2995,18 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rustls" +version = "0.21.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" +dependencies = [ + "log", + "ring", + "rustls-webpki 0.101.7", + "sct", +] + [[package]] name = "rustls" version = "0.23.14" @@ -2294,11 +3015,20 @@ checksum = "415d9944693cb90382053259f89fbb077ea730ad7273047ec63b19bc9b160ba8" dependencies = [ "once_cell", "rustls-pki-types", - "rustls-webpki", + "rustls-webpki 0.102.8", "subtle", "zeroize", ] +[[package]] +name = "rustls-pemfile" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" +dependencies = [ + "base64 0.21.7", +] + [[package]] name = "rustls-pemfile" version = "2.2.0" @@ -2314,6 +3044,16 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e696e35370c65c9c541198af4543ccd580cf17fc25d8e05c5a242b202488c55" +[[package]] +name = "rustls-webpki" +version = "0.101.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "rustls-webpki" version = "0.102.8" @@ -2358,6 +3098,16 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sct" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "security-framework" version = "2.10.0" @@ -2386,6 +3136,9 @@ name = "semver" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +dependencies = [ + "serde", +] [[package]] name = "serde" @@ -2404,7 +3157,7 @@ checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.79", ] [[package]] @@ -2441,12 +3194,40 @@ dependencies = [ ] [[package]] -name = "serde_yaml" -version = "0.9.34+deprecated" +name = "serde_with" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07ff71d2c147a7b57362cead5e22f772cd52f6ab31cfcd9edcd7f6aeb2a0afbe" +dependencies = [ + "base64 0.13.1", + "chrono", + "hex", + "indexmap 1.9.3", + "serde", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "881b6f881b17d13214e5d494c939ebab463d01264ce1811e9d4ac3a882e7695f" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap", + "indexmap 2.6.0", "itoa", "ryu", "serde", @@ -2490,6 +3271,245 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "shuttle-api-client" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "166fbe3022a11ff3f9f771b673f9e39578dec9274a406ff278ed9ee620272e0a" +dependencies = [ + "anyhow", + "async-trait", + "headers", + "http 0.2.12", + "percent-encoding", + "reqwest 0.11.27", + "reqwest-middleware", + "rmp-serde", + "serde", + "serde_json", + "shuttle-common 0.48.0", + "tokio", + "tokio-tungstenite", + "url", + "uuid", +] + +[[package]] +name = "shuttle-codegen" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d0e472cdad882debb65474b618f1a2e1504460b0803f84a0ca18b84493ffd2b" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "shuttle-codegen" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e60a2db8b8548a2a725316ae5d7a04b4dad680a4746372d1b7172f2065f9c345" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.79", +] + +[[package]] +name = "shuttle-common" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31f26e99a8921bb0824d0cb5fa7c2fe077d7b0476634c9b3c99f775ba94b468d" +dependencies = [ + "anyhow", + "chrono", + "comfy-table", + "crossterm 0.27.0", + "http 0.2.12", + "opentelemetry", + "opentelemetry-http", + "pin-project", + "semver", + "serde", + "serde_json", + "strum 0.26.3", + "thiserror", + "tower", + "tracing", + "tracing-opentelemetry", + "tracing-subscriber", + "url", + "uuid", + "zeroize", +] + +[[package]] +name = "shuttle-common" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89c694264eba4a286c85f9cc27f0059232fb4863cb668a11286e6b93e879a4d5" +dependencies = [ + "anyhow", + "async-trait", + "chrono", + "comfy-table", + "crossterm 0.27.0", + "http 0.2.12", + "opentelemetry", + "opentelemetry-http", + "pin-project", + "reqwest 0.11.27", + "semver", + "serde", + "serde_json", + "strum 0.26.3", + "thiserror", + "tower", + "tracing", + "tracing-opentelemetry", + "tracing-subscriber", + "typeshare", + "url", + "uuid", + "zeroize", +] + +[[package]] +name = "shuttle-proto" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12d68790de14e4048738be652b1c5803a1563aef1a20cc00979855c2e497ec5d" +dependencies = [ + "futures-core", + "prost", + "prost-types", + "shuttle-common 0.47.0", + "tonic", +] + +[[package]] +name = "shuttle-proto" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d322a55314553aad71cd506451b817bec1d2ef5459d939f5642b472870c1e9" +dependencies = [ + "futures-core", + "prost", + "prost-types", + "shuttle-common 0.48.0", + "tonic", +] + +[[package]] +name = "shuttle-rocket" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d692712b0d3f7792a7b0d330e6989ebbdfe7709a960e7e44e541d6e2faf9776f" +dependencies = [ + "rocket", + "shuttle-runtime 0.48.0", +] + +[[package]] +name = "shuttle-runtime" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07c18036c9640c0e74c0ce4a67cc11e4e6603179305f74558035fe5a033ec77f" +dependencies = [ + "anyhow", + "async-trait", + "colored", + "serde", + "serde_json", + "shuttle-codegen 0.47.0", + "shuttle-common 0.47.0", + "shuttle-proto 0.47.0", + "shuttle-service 0.47.0", + "strfmt", + "tokio", + "tokio-stream", + "tokio-util", + "tonic", + "tracing-subscriber", +] + +[[package]] +name = "shuttle-runtime" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20628ddf00446da52c448ef39b987321227195f6393dc6a5066a12fc22491bd3" +dependencies = [ + "anyhow", + "async-trait", + "hyper 0.14.30", + "serde", + "serde_json", + "shuttle-api-client", + "shuttle-codegen 0.48.0", + "shuttle-common 0.48.0", + "shuttle-proto 0.48.0", + "shuttle-service 0.48.0", + "strfmt", + "tokio", + "tokio-stream", + "tokio-util", + "tonic", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "shuttle-service" +version = "0.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff6f3011b6766b3aad6b93f16aaf9beb5fe9e600e85e409a5077d8e2f0b055c4" +dependencies = [ + "anyhow", + "async-trait", + "serde", + "shuttle-common 0.47.0", + "strfmt", + "thiserror", +] + +[[package]] +name = "shuttle-service" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57770033649fa028aa9cc76702c66eb6c93b47a103f5ad15e2739c6404689bba" +dependencies = [ + "anyhow", + "async-trait", + "serde", + "shuttle-common 0.48.0", + "strfmt", + "thiserror", +] + +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" +dependencies = [ + "libc", + "mio 0.8.11", + "signal-hook", +] + [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -2622,7 +3642,7 @@ dependencies = [ "hashbrown 0.14.5", "hashlink", "hex", - "indexmap", + "indexmap 2.6.0", "log", "memchr", "native-tls", @@ -2651,7 +3671,7 @@ dependencies = [ "quote", "sqlx-core", "sqlx-macros-core", - "syn", + "syn 2.0.79", ] [[package]] @@ -2662,7 +3682,7 @@ checksum = "1804e8a7c7865599c9c79be146dc8a9fd8cc86935fa641d3ea58e5f0688abaa5" dependencies = [ "dotenvy", "either", - "heck", + "heck 0.5.0", "hex", "once_cell", "proc-macro2", @@ -2674,7 +3694,7 @@ dependencies = [ "sqlx-mysql", "sqlx-postgres", "sqlx-sqlite", - "syn", + "syn 2.0.79", "tempfile", "tokio", "url", @@ -2804,6 +3824,12 @@ dependencies = [ "loom", ] +[[package]] +name = "strfmt" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a8348af2d9fc3258c8733b8d9d8db2e56f54b2363a4b5b81585c7875ed65e65" + [[package]] name = "stringprep" version = "0.1.5" @@ -2815,12 +3841,70 @@ dependencies = [ "unicode-properties", ] +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros 0.26.4", +] + +[[package]] +name = "strum_macros" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "rustversion", + "syn 1.0.109", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.79", +] + [[package]] name = "subtle" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.79" @@ -2832,6 +3916,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "sync_wrapper" version = "1.0.1" @@ -2841,6 +3931,17 @@ dependencies = [ "futures-core", ] +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys 0.5.0", +] + [[package]] name = "system-configuration" version = "0.6.1" @@ -2849,7 +3950,17 @@ checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ "bitflags 2.4.1", "core-foundation", - "system-configuration-sys", + "system-configuration-sys 0.6.0", +] + +[[package]] +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]] @@ -2862,6 +3973,15 @@ dependencies = [ "libc", ] +[[package]] +name = "task-local-extensions" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba323866e5d033818e3240feeb9f7db2c4296674e4d9e16b97b7bf8f490434e8" +dependencies = [ + "pin-utils", +] + [[package]] name = "tempfile" version = "3.13.0" @@ -2892,7 +4012,7 @@ checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.79", ] [[package]] @@ -2936,6 +4056,15 @@ dependencies = [ "time-core", ] +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tinyvec" version = "1.8.0" @@ -2960,7 +4089,8 @@ dependencies = [ "backtrace", "bytes", "libc", - "mio", + "mio 1.0.2", + "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", @@ -2968,6 +4098,16 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "tokio-io-timeout" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf" +dependencies = [ + "pin-project-lite", + "tokio", +] + [[package]] name = "tokio-macros" version = "2.4.0" @@ -2976,7 +4116,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.79", ] [[package]] @@ -2989,13 +4129,23 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +dependencies = [ + "rustls 0.21.12", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ - "rustls", + "rustls 0.23.14", "rustls-pki-types", "tokio", ] @@ -3011,6 +4161,21 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-tungstenite" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212d5dcb2a1ce06d81107c3d0ffa3121fe974b73f068c8282cb1c32328113b6c" +dependencies = [ + "futures-util", + "log", + "rustls 0.21.12", + "tokio", + "tokio-rustls 0.24.1", + "tungstenite", + "webpki-roots", +] + [[package]] name = "tokio-util" version = "0.7.12" @@ -3051,13 +4216,66 @@ version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ - "indexmap", + "indexmap 2.6.0", "serde", "serde_spanned", "toml_datetime", "winnow", ] +[[package]] +name = "tonic" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d560933a0de61cf715926b9cac824d4c883c2c43142f787595e48280c40a1d0e" +dependencies = [ + "async-stream", + "async-trait", + "axum", + "base64 0.21.7", + "bytes", + "h2 0.3.26", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.30", + "hyper-timeout", + "percent-encoding", + "pin-project", + "prost", + "tokio", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "indexmap 1.9.3", + "pin-project", + "pin-project-lite", + "rand", + "slab", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + [[package]] name = "tower-service" version = "0.3.3" @@ -3084,7 +4302,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.79", ] [[package]] @@ -3108,6 +4326,34 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-opentelemetry" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c67ac25c5407e7b961fafc6f7e9aa5958fd297aada2d20fa2ae1737357e55596" +dependencies = [ + "js-sys", + "once_cell", + "opentelemetry", + "opentelemetry_sdk", + "smallvec", + "tracing", + "tracing-core", + "tracing-log", + "tracing-subscriber", + "web-time", +] + +[[package]] +name = "tracing-serde" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6b213177105856957181934e4920de57730fc69bf42c37ee5bb664d406d9e1" +dependencies = [ + "serde", + "tracing-core", +] + [[package]] name = "tracing-subscriber" version = "0.3.18" @@ -3118,12 +4364,15 @@ dependencies = [ "nu-ansi-term", "once_cell", "regex", + "serde", + "serde_json", "sharded-slab", "smallvec", "thread_local", "tracing", "tracing-core", "tracing-log", + "tracing-serde", ] [[package]] @@ -3132,12 +4381,54 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "tungstenite" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e3dac10fd62eaf6617d3a904ae222845979aec67c615d1c842b4002c7666fb9" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 0.2.12", + "httparse", + "log", + "rand", + "rustls 0.21.12", + "sha1", + "thiserror", + "url", + "utf-8", +] + [[package]] name = "typenum" version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +[[package]] +name = "typeshare" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04f17399b76c2e743d58eac0635d7686e9c00f48cd4776f00695d9882a7d3187" +dependencies = [ + "chrono", + "serde", + "serde_json", + "typeshare-annotation", +] + +[[package]] +name = "typeshare-annotation" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a615d6c2764852a2e88a4f16e9ce1ea49bb776b5872956309e170d63a042a34f" +dependencies = [ + "quote", + "syn 2.0.79", +] + [[package]] name = "ubyte" version = "0.10.4" @@ -3147,6 +4438,12 @@ dependencies = [ "serde", ] +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + [[package]] name = "uncased" version = "0.9.10" @@ -3157,6 +4454,15 @@ dependencies = [ "version_check", ] +[[package]] +name = "unicase" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", +] + [[package]] name = "unicode-bidi" version = "0.3.17" @@ -3184,6 +4490,18 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + [[package]] name = "unicode-xid" version = "0.2.6" @@ -3217,6 +4535,29 @@ dependencies = [ "form_urlencoded", "idna", "percent-encoding", + "serde", +] + +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "uuid" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +dependencies = [ + "getrandom", + "serde", ] [[package]] @@ -3232,7 +4573,7 @@ source = "git+https://github.com/stadust/variant-partial-eq#bdccf434b502b36b77f2 dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.79", ] [[package]] @@ -3290,7 +4631,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.79", "wasm-bindgen-shared", ] @@ -3324,7 +4665,7 @@ checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.79", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -3345,6 +4686,22 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa30049b1c872b72c89866d458eae9f20380ab280ffd1b1e18df2d3e2d98cfe0" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki-roots" +version = "0.25.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" + [[package]] name = "whoami" version = "1.5.2" @@ -3582,6 +4939,25 @@ dependencies = [ "memchr", ] +[[package]] +name = "winreg" +version = "0.50.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] + [[package]] name = "yansi" version = "1.0.1" @@ -3609,7 +4985,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.79", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 36cae776e..c6083132f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,4 +24,30 @@ 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 +repository = "https://github.com/stadust/pointercrate" + +[package] +name = "cscl" +version = "0.1.0" +edition = "2021" + +[dependencies] +rocket = "0.5.0" +shuttle-rocket = "0.48.0" +shuttle-runtime = "0.48.0" +tokio = "1.40.0" +dotenv = "0.15.0" +maud = "0.26.0" +pointercrate-core = { path = "pointercrate-core/" } +pointercrate-core-api = { path = "pointercrate-core-api/" } +pointercrate-core-pages = { path = "pointercrate-core-pages/" } +pointercrate-demonlist = { path = "pointercrate-demonlist/" } +pointercrate-demonlist-api = { path = "pointercrate-demonlist-api" } +pointercrate-demonlist-pages = { path = "pointercrate-demonlist-pages/" } +pointercrate-user = { path = "pointercrate-user/" } +pointercrate-user-api = { path = "pointercrate-user-api/" } +pointercrate-user-pages = { path = "pointercrate-user-pages/" } + +[[bin]] +name = "cscl" +path = "pointercrate-example/src/main.rs" diff --git a/README.md b/README.md index dfb9ace28..4649dcf65 100644 --- a/README.md +++ b/README.md @@ -1,189 +1,44 @@ -# Pointercrate ![build](https://github.com/stadust/pointercrate/actions/workflows/test.yml/badge.svg) [![codecov](https://codecov.io/gh/stadust/pointercrate/branch/master/graph/badge.svg?token=C7B5LU2IF5)](https://codecov.io/gh/stadust/pointercrate) - - -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. - -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. - -## Getting Started (Linux) - -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 the latest stable one using [`rustup`](https://rustup.rs): - -```bash -curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -. "$HOME/.cargo/env" -``` - -### 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 -# you might need your distrbutions equivalent of the libssl-dev and gcc packages -cargo install sqlx-cli --no-default-features --features native-tls,postgres -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 - -The following people have helped with development of pointercrate, either through code contributions or other things: - -- [cos8o](https://github.com/cos8o): Reverse engineered parts of the Geometry Dash source that allows pointercrate to display accurate object counts and level lengths -- [zmx](https://github.com/kyurime) and [mgostIH](https://github.com/mgostIH) and everyone else over in my discord server -- [Nimbus](https://github.com/NimbusGD): Development of various discord bots integrating with the pointercrate API -- Aquatias, Deltablu and Moosh: My trusty admins that click checkboxes for me (love you guys) -- rSteel, zMarc, Stiluetto and Zipi: My beloved staff -- and of course the developers of all the dependencies pointercrate uses +# Hey!!! + +This is the code for the new CSCL site. It took a lot of work (probably more than it should have) to get this up and running while hiding some sensitive info lol. Feel free to report bugs, request features, or help out with development here! If you are curious about anything, open an issue or ask me on Discord. (i probably won't know tho lol) + +# How to use this respository to experiment with the site + +Firstly, do NOT use this guide as a way to set up your own pointercrate list. Refer to the [official Pointercrate repo readme](https://github.com/stadust/pointercrate) instead. If you need any help with following that guide, join the [Pointercrate Central discord](https://discord.gg/sQewUEB). + +# ----- + +To begin, start by downloading a .zip of this repo (or clone it with github desktop) and unzip it. + +Next, you need to download a few things: + +[rustup](https://rustup.rs), + +[Postgres](https://www.enterprisedb.com/downloads/postgres-postgresql-downloads) (if prompted, set the password to "asdf". the security of this password won't matter since you're hosting this database locally, meaning only you can access it.), + +Shuttle (open command prompt and type `cargo install cargo-shuttle`), + +and sqlx (open command prompt and type `cargo install sqlx-cli --no-default-features --features native-tls,postgres`. + +# ----- + +Now search for the program "pgAdmin 4" which should be installed with Postgres, open it, and make sure there is a server called "Postgres" or something. If there isn't, right click on `servers > register > server > name it whatever you want`, then go to the `definition` tab, set `Host name/address` to "`localhost`", set the username to "`postgres`" and the password to "`asdf`", and press save. + +Now we need to create a new role and database. You must make the role before the database. To make a role, right click on `Login/Group Roles` and create a Login/Group role. Name it "pointercratetest", and in the Definition tab make the password "asdf". Next, in the Privileges tab toggle the "Can login?" and "Superuser?" settings on and press Save. Next, right click "Databases" on the left and create a new one. Name it "pointercrate" and set the owner to the "pointercratetest" role you just created and press save. + +# ----- + +Next, we need to apply the database schema Pointercrate uses. You can think of a schema as the "files and folders" the site stores its data in. + +First, open the repository folder on your computer, then go to /pointercrate-example/sample/migrations/_new , select all files in the folder, and move them to the migrations folder in the root (TheClicksyncChallengeList-main/migrations/), and replace the files if prompted. +(side note i know this sucks but i'm working on it lol....,...) + +Next, open the command prompt/shell and navigate to the folder you downloaded this repository to via "cd". The command line usually drops you in your user folder (C:/Users/username), so to if the folder is on your Desktop, for example, type `cd desktop/[folder name]`. Next, run `cargo sqlx migrate run`. This should automatically apply the schema the database needs. + +The last thing to do is actually compile and run the site. To do this, run `cargo shuttle run` and wait for all the libraries to install and compile. At the end, it should give you a link (probably https://127.0.0.1:8001 ) to access the site in your browser! + +# Next steps + +If you need to be able to add data to the site, you need to give yourself administrator permissions. To do this, create a pointercrate account if you haven't already (user area -> register), go into pgadmin 4, right click on the "pointercrate" database and click "PSQL Tool". Then paste in `UPDATE members SET permissions = '0100000000001000'::BIT(16) WHERE member_id = 1;` and press enter, then refresh the site. You should be able to see all moderator tabs in the user area! + +if you know what you're doing, i would love some help with development! i wrote most of the above text like a month ago, and i've since gotten """"""""better""""""" at coding in general, but still lol. thank you! diff --git a/Shuttle.toml b/Shuttle.toml new file mode 100644 index 000000000..edb759e9f --- /dev/null +++ b/Shuttle.toml @@ -0,0 +1,2 @@ +[project] +name = "cscl" \ No newline at end of file diff --git a/darkmode.css b/darkmode.css new file mode 100644 index 000000000..c4196e473 --- /dev/null +++ b/darkmode.css @@ -0,0 +1,307 @@ +:root { + --white: #ffffff; + --black: #000000; + --grayDark: #202020; + --boxCol: #303030; +} + +html { + visibility: visable !important; +} + +div.tooltiptext.fade { + background-color: var(--boxCol) !important; + color: var(--white) !important; +} + +.hover white { + background-color: var(--black) !important; +} + +body { + background-color: var(--grayDark) !important; +} + +section { + background-color: #080808 !important; +} + +header { + background-color: var(--black) !important; +} + +#editors { + background-color: var(--boxCol) !important; + color: var(--white) !important; + border-radius: 25px !important; + border-style: none !important; + box-shadow: 2px 2px 15px rgba(0, 0, 0, 0.5) !important; +} + +#rules { + background-color: var(--boxCol) !important; + color: var(--white) !important; + border-radius: 25px !important; + border-style: none !important; + box-shadow: 2px 2px 15px rgba(0, 0, 0, 0.5) !important; +} + +#submit { + background-color: var(--boxCol) !important; + color: var(--white) !important; + border-radius: 25px !important; + border-style: none !important; + box-shadow: 2px 2px 15px rgba(0, 0, 0, 0.5) !important; +} + +#stats { + background-color: var(--boxCol) !important; + color: var(--white) !important; + border-radius: 25px !important; + border-style: none !important; + box-shadow: 2px 2px 15px rgba(0, 0, 0, 0.5) !important; +} + +#discord { + background-color: var(--boxCol) !important; + color: var(--white) !important; + border-radius: 25px !important; + border-style: none !important; + box-shadow: 2px 2px 15px rgba(0, 0, 0, 0.5) !important; +} + +a { + color: var(--white) !important; +} + +.left { + color: var(--black) !important; +} + +section[class="panel fade"] { + background-color: #282828 !important; + color: var(--white) !important; + border-style: none !important; + border-radius: 15px !important; + box-shadow: 2px 2px 15px rgba(0, 0, 0, 0.5) !important; + transition: opacity 0.8s ease !important; +} + +section[class="panel fade"]:hover { + background-color: #363636 !important; + color: var(--white) !important; + border-style: none !important; + border-radius: 15px !important; + box-shadow: 2px 2px 15px rgba(0, 0, 0, 0.5) !important; + transition: opacity 0.8s ease !important; +} + +nav[class="center collapse underlined see-through"], +[class="nav-item hover white"] { + background-color: #222222 !important; + color: var(--white) !important; + border-style: none !important; +} + +.overlay { + display: none !important; + position: fixed !important; + top: 0 !important; + left: 0 !important; + right: 0 !important; + bottom: 0 !important; + background-color: rgba(0, 0, 0, 0.8) !important; + z-index: 1083458237 !important; +} + +.hover, +.hover:hover:not(.disabled):not([disabled]) { + transition: opacity 0.8s ease !important; +} + +#statsviewers { + background-color: var(--boxCol) !important; + border-style: none !important; + border-radius: 20px !important; +} + +#stats-viewer-pagination li i { + padding-left: 5px !important; + color: var(--white) !important; + font-size: 70% !important; + font-variant: small-caps !important; +} + +#lists { + background-color: var(--boxCol) !important; + border-style: none !important; + border-radius: 20px !important; +} + +a[class="blue hover button"], +[class="blue hover button js-scroll"] { + background-color: rgb(4, 90, 55) !important; + color: var(--white) !important; +} + +.white { + background-color: var(--grayDark) !important; + color: var(--white) !important; +} + +.white:hover { + background-color: var(--boxCol) !important; + color: var(--white) !important; +} + +body > div:first-child { + background-image: url("https://raw.githubusercontent.com/PokeyYapper1991/PointercratePlus/main/assets/squares3.png") !important; +} + +section[class="panel fade js-scroll-anim"] { + background-color: #282828 !important; + color: var(--white) !important; + border-style: none !important; + border-radius: 15px !important; + box-shadow: 2px 2px 15px rgba(0, 0, 0, 0.5) !important; +} + +div[class="panel fade js-scroll-anim js-collapse"], +[class="panel fade js-scroll-anim js-collapse active"] { + background-color: #282828 !important; + background: #282828 !important; + color: var(--white) !important; + border-style: none !important; + border-radius: 15px !important; + box-shadow: 2px 2px 15px rgba(0, 0, 0, 0.5) !important; +} + +.info-yellow, +.info-green { + font-family: montserrat, sans-serif !important; + text-align: center !important; + background: #353535 !important; + border-style: none !important; + border-radius: 15px !important; + padding: 5px 15px !important; + color: var(--white) !important; + margin: 25px 0 !important; + flex-grow: 1 !important; + box-shadow: 2px 2px 15px rgba(0, 0, 0, 0.5) !important; +} + +.panel { + background: #282828 !important; + color: var(--white) !important; + border-style: none !important; + border-radius: 15px !important; + box-shadow: 2px 2px 15px rgba(0, 0, 0, 0.5) !important; +} + +.arrow { + color: var(--white) !important; +} + +.arrow:before, +.arrow:after { + position: absolute !important; + content: "" !important; + display: inline-block !important; + width: 12px !important; + height: 3px !important; + background-color: var(--white) !important; + transition: 0.4s ease !important; +} + +.blue { + background-color: rgb(4, 90, 55) !important; + color: var(--white) !important; +} + +.blue.hover:not(.disabled):not([disabled]):hover, +.blue.active { + background-color: rgb(10, 113, 71) !important; + color: var(--white) !important; +} + +.blue.hover:not(.disabled):not([disabled]):active { + background-color: rgb(10, 113, 71) !important; + color: var(--white) !important; +} + +code { + background: rgba(0, 0, 0, 0.2) !important; + border: none !important; +} + +th { + background: #222222 !important; +} + +tr { + background: #252525 !important; +} + +tr:nth-child(even) { + background: #232323 !important; +} + +tr:nth-child(odd) { + background: #292929 !important; +} + +a.link { + color: rgb(180, 85, 0) !important; +} + +a.link:hover { + color: rgb(255, 120, 0) !important; +} + +#history-table tr { + background: rgb(32, 32, 32) !important; + color: var(--white) !important; +} + +#history-table tr.moved-up { + background: #074d07 !important; + color: #dfd !important; +} + +#history-table tr.moved-down { + background: #4d0707 !important; + color: #fdd !important; +} + +/* Front Page */ + +.information-banner > * { + background: #282828 !important; + padding: 50px 20px !important; + max-width: 1072px !important; + display: flex !important; + justify-content: space-between !important; + align-items: center !important; + box-shadow: 0 0 7px 5px #282828 !important; + color: var(--white) !important; +} + +.information-banner { + background-color: rgba(0, 0, 0, 0) !important; + display: flex !important; + justify-content: center !important; + font-size: 110% !important; +} + +.information-stripe { + background: rgb(0, 73, 43) !important; + background-color: rgb(0, 73, 43) !important; + text-align: center !important; + color: var(--white) !important; + font-weight: 700 !important; + position: relative !important; +} + +.search.seperated input { + color: var(--white) !important; +} diff --git a/migrations/00000000000000_diesel_initial_setup.down.sql b/migrations/00000000000000_diesel_initial_setup.down.sql index a9f526091..83230d6c3 100644 --- a/migrations/00000000000000_diesel_initial_setup.down.sql +++ b/migrations/00000000000000_diesel_initial_setup.down.sql @@ -1,6 +1,7 @@ --- This file was automatically created by Diesel to setup helper functions +/* -- This file was automatically created by Diesel to setup helper functions -- and other internal bookkeeping. This file is safe to edit, any future -- changes will be added to existing projects as new migrations. DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass); DROP FUNCTION IF EXISTS diesel_set_updated_at(); + */ \ No newline at end of file diff --git a/migrations/20180918150231_initial.down.sql b/migrations/20180918150231_initial.down.sql index f1bc7918e..67cb0c35e 100644 --- a/migrations/20180918150231_initial.down.sql +++ b/migrations/20180918150231_initial.down.sql @@ -1,4 +1,4 @@ --- This file should undo anything in `up.sql` +/* -- This file should undo anything in `up.sql` -- Drop everything in reverse order @@ -12,4 +12,4 @@ DROP TABLE players; DROP TYPE AUDIT_OPERATION; DROP TYPE RECORD_STATUS; -DROP EXTENSION CITEXT; \ No newline at end of file +DROP EXTENSION CITEXT; */ \ No newline at end of file diff --git a/migrations/20180918150231_initial.up.sql b/migrations/20180918150231_initial.up.sql index 0ee0661b6..9218c9060 100644 --- a/migrations/20180918150231_initial.up.sql +++ b/migrations/20180918150231_initial.up.sql @@ -1,4 +1,4 @@ --- Your SQL goes here +/* -- Your SQL goes here -- This is a workaround -- To get the new backend to work, we need to run all migrations against the existing database @@ -67,4 +67,4 @@ CREATE TABLE IF NOT EXISTS creators ( ); GRANT TRIGGER, REFERENCES, SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO pointercrate; -GRANT USAGE ON ALL SEQUENCES IN SCHEMA public TO pointercrate; \ No newline at end of file +GRANT USAGE ON ALL SEQUENCES IN SCHEMA public TO pointercrate; */ \ No newline at end of file diff --git a/migrations/20181229171817_demon_view.down.sql b/migrations/20181229171817_demon_view.down.sql index 60593db51..fc4b34b5a 100644 --- a/migrations/20181229171817_demon_view.down.sql +++ b/migrations/20181229171817_demon_view.down.sql @@ -1,3 +1,3 @@ --- This file should undo anything in `up.sql` +/* -- This file should undo anything in `up.sql` -DROP VIEW demon_publisher_verifier_join; \ No newline at end of file +DROP VIEW demon_publisher_verifier_join; */ \ No newline at end of file diff --git a/migrations/20181229171817_demon_view.up.sql b/migrations/20181229171817_demon_view.up.sql index 61e69d66a..fde470dbd 100644 --- a/migrations/20181229171817_demon_view.up.sql +++ b/migrations/20181229171817_demon_view.up.sql @@ -1,4 +1,4 @@ --- Your SQL goes here +/* -- Your SQL goes here CREATE OR REPLACE VIEW demon_verifier_publisher_join AS SELECT p1.name AS vname, p1.id AS vid, p1.banned AS vbanned, p2.name AS pname, p2.id AS pid, p2.banned AS pbanned @@ -6,4 +6,4 @@ FROM demons INNER JOIN players AS p1 ON demons.verifier = p1.id INNER JOIN players AS p2 -ON demons.publisher = p2.id \ No newline at end of file +ON demons.publisher = p2.id */ \ No newline at end of file diff --git a/migrations/20190105154218_new_audit_log.down.sql b/migrations/20190105154218_new_audit_log.down.sql index 042954d34..d312a0dfb 100644 --- a/migrations/20190105154218_new_audit_log.down.sql +++ b/migrations/20190105154218_new_audit_log.down.sql @@ -1,4 +1,4 @@ --- This file should undo anything in `up.sql` +/* -- This file should undo anything in `up.sql` DROP TABLE demon_additions; DROP FUNCTION audit_demon_addition() CASCADE; @@ -44,4 +44,4 @@ DROP FUNCTION audit_user_deletion() CASCADE; DROP TABLE audit_log2; -DROP TABLE active_user; \ No newline at end of file +DROP TABLE active_user; */ \ No newline at end of file diff --git a/migrations/20190105154218_new_audit_log.up.sql b/migrations/20190105154218_new_audit_log.up.sql index 20ec73d0b..50165e662 100644 --- a/migrations/20190105154218_new_audit_log.up.sql +++ b/migrations/20190105154218_new_audit_log.up.sql @@ -1,4 +1,4 @@ --- Alright, audit_log overhault +/* -- Alright, audit_log overhault -- In audit log entries we cannot use foreign keys because that would force us to delete log entries -- when objects are removed from the db, or disallow the deletion of objects -- Thus, we need to make sure we references database objects by a non-modifiable primary key, which @@ -367,3 +367,4 @@ CREATE FUNCTION audit_user_deletion() RETURNS trigger AS $user_deletion_trigger$ $user_deletion_trigger$ LANGUAGE plpgsql; CREATE TRIGGER user_deletion_trigger AFTER DELETE ON members FOR EACH ROW EXECUTE PROCEDURE audit_user_deletion(); + */ \ No newline at end of file diff --git a/migrations/20190222212203_list_functions.down.sql b/migrations/20190222212203_list_functions.down.sql index 15b7152ce..93978e767 100644 --- a/migrations/20190222212203_list_functions.down.sql +++ b/migrations/20190222212203_list_functions.down.sql @@ -1,2 +1,2 @@ --- This file should undo anything in `up.sql` -DROP FUNCTION record_score(progress FLOAT, demon FLOAT, list_size FLOAT); \ No newline at end of file +/* -- This file should undo anything in `up.sql` +DROP FUNCTION record_score(progress FLOAT, demon FLOAT, list_size FLOAT); */ \ No newline at end of file diff --git a/migrations/20190222212203_list_functions.up.sql b/migrations/20190222212203_list_functions.up.sql index c9e15072b..d456f83a7 100644 --- a/migrations/20190222212203_list_functions.up.sql +++ b/migrations/20190222212203_list_functions.up.sql @@ -1,7 +1,7 @@ --- Your SQL goes here +/* -- Your SQL goes here CREATE FUNCTION record_score(progress FLOAT, demon FLOAT, list_size FLOAT) RETURNS FLOAT AS $record_score$ SELECT (progress / 100.0) ^ demon * list_size / (1.0 + (list_size - 1.0) * EXP(-4.0 * (list_size - demon) * LN(list_size - 1.0)/(3.0 * list_size))); $record_score$ -LANGUAGE SQL IMMUTABLE; \ No newline at end of file +LANGUAGE SQL IMMUTABLE; */ \ No newline at end of file diff --git a/migrations/20190311185303_remove_old_columns.down.sql b/migrations/20190311185303_remove_old_columns.down.sql index 1378b9a3e..db1af47e4 100644 --- a/migrations/20190311185303_remove_old_columns.down.sql +++ b/migrations/20190311185303_remove_old_columns.down.sql @@ -1,8 +1,8 @@ --- This file should undo anything in `up.sql` +/* -- This file should undo anything in `up.sql` ALTER TABLE demons ADD COLUMN notes TEXT NULL; ALTER TABLE demons ADD COLUMN description TEXT NULL; ALTER TABLE members ADD COLUMN password_salt BYTEA NOT NULL DEFAULT E'\\000'; ALTER TABLE members ALTER COLUMN display_name TYPE CITEXT; -ALTER TABLE members ALTER COLUMN name TYPE CITEXT; \ No newline at end of file +ALTER TABLE members ALTER COLUMN name TYPE CITEXT; */ \ No newline at end of file diff --git a/migrations/20190311185303_remove_old_columns.up.sql b/migrations/20190311185303_remove_old_columns.up.sql index 6b098c890..2add9e7b0 100644 --- a/migrations/20190311185303_remove_old_columns.up.sql +++ b/migrations/20190311185303_remove_old_columns.up.sql @@ -1,8 +1,8 @@ --- Your SQL goes here +/* -- Your SQL goes here ALTER TABLE demons DROP COLUMN notes; ALTER TABLE demons DROP COLUMN description; ALTER TABLE members DROP COLUMN password_salt; ALTER TABLE members ALTER COLUMN display_name TYPE TEXT; -ALTER TABLE members ALTER COLUMN name TYPE TEXT; \ No newline at end of file +ALTER TABLE members ALTER COLUMN name TYPE TEXT; */ \ No newline at end of file diff --git a/migrations/20190311200729_hash_as_string.down.sql b/migrations/20190311200729_hash_as_string.down.sql index 443a8e174..44e9a7f4f 100644 --- a/migrations/20190311200729_hash_as_string.down.sql +++ b/migrations/20190311200729_hash_as_string.down.sql @@ -1,3 +1,4 @@ --- This file should undo anything in `up.sql` +/* -- This file should undo anything in `up.sql` alter table members alter column password_hash type bytea using decode(password_hash, 'escape'); + */ \ No newline at end of file diff --git a/migrations/20190311200729_hash_as_string.up.sql b/migrations/20190311200729_hash_as_string.up.sql index 4b30fd3b1..3c9ee0323 100644 --- a/migrations/20190311200729_hash_as_string.up.sql +++ b/migrations/20190311200729_hash_as_string.up.sql @@ -1,3 +1,4 @@ --- Your SQL goes here +/* -- Your SQL goes here alter table members alter column password_hash type text using encode(password_hash, 'escape'); + */ \ No newline at end of file diff --git a/migrations/20190312131253_nationality.down.sql b/migrations/20190312131253_nationality.down.sql index 928a7b7c9..bcc191868 100644 --- a/migrations/20190312131253_nationality.down.sql +++ b/migrations/20190312131253_nationality.down.sql @@ -1,6 +1,7 @@ --- This file should undo anything in `up.sql` +/* -- This file should undo anything in `up.sql` ALTER TABLE players DROP COLUMN nationality; ALTER TABLE members DROP COLUMN nationality; DROP TABLE nationalities; + */ \ No newline at end of file diff --git a/migrations/20190312131253_nationality.up.sql b/migrations/20190312131253_nationality.up.sql index 16b799f67..2fc8c9919 100644 --- a/migrations/20190312131253_nationality.up.sql +++ b/migrations/20190312131253_nationality.up.sql @@ -1,4 +1,4 @@ --- Your SQL goes here +/* -- Your SQL goes here CREATE TABLE nationalities ( iso_country_code VARCHAR(2) PRIMARY KEY, @@ -259,3 +259,4 @@ VALUES ('Afghanistan','AF'), ALTER TABLE players ADD COLUMN nationality VARCHAR(2) NULL DEFAULT NULL REFERENCES nationalities(iso_country_code); ALTER TABLE members ADD COLUMN nationality VARCHAR(2) NULL DEFAULT NULL REFERENCES nationalities(iso_country_code); + */ \ No newline at end of file diff --git a/migrations/20190314134153_score_view.down.sql b/migrations/20190314134153_score_view.down.sql index 6db267318..9583f83c5 100644 --- a/migrations/20190314134153_score_view.down.sql +++ b/migrations/20190314134153_score_view.down.sql @@ -1,3 +1,3 @@ --- This file should undo anything in `up.sql` +/* -- This file should undo anything in `up.sql` -DROP VIEW players_with_score; \ No newline at end of file +DROP VIEW players_with_score; */ \ No newline at end of file diff --git a/migrations/20190314134153_score_view.up.sql b/migrations/20190314134153_score_view.up.sql index ea67d28b5..6a8375b33 100644 --- a/migrations/20190314134153_score_view.up.sql +++ b/migrations/20190314134153_score_view.up.sql @@ -1,4 +1,4 @@ --- Your SQL goes here +/* -- Your SQL goes here -- I call this query Frank @@ -50,4 +50,4 @@ CREATE VIEW players_with_score AS ON scores.player = players.id LEFT OUTER JOIN nationalities ON players.nationality = nationalities.iso_country_code - WHERE NOT players.banned; \ No newline at end of file + WHERE NOT players.banned; */ \ No newline at end of file diff --git a/migrations/20190708102450_formula_update.down.sql b/migrations/20190708102450_formula_update.down.sql index 9ad2a1bc5..6873c1455 100644 --- a/migrations/20190708102450_formula_update.down.sql +++ b/migrations/20190708102450_formula_update.down.sql @@ -1,4 +1,4 @@ --- This file should undo anything in `up.sql` +/* -- This file should undo anything in `up.sql` -- Your SQL goes here @@ -60,4 +60,4 @@ CREATE VIEW players_with_score AS ON scores.player = players.id LEFT OUTER JOIN nationalities ON players.nationality = nationalities.iso_country_code - WHERE NOT players.banned; \ No newline at end of file + WHERE NOT players.banned; */ \ No newline at end of file diff --git a/migrations/20190708102450_formula_update.up.sql b/migrations/20190708102450_formula_update.up.sql index 1342b9576..c1e20a746 100644 --- a/migrations/20190708102450_formula_update.up.sql +++ b/migrations/20190708102450_formula_update.up.sql @@ -1,4 +1,4 @@ --- Your SQL goes here +/* -- Your SQL goes here DROP VIEW players_with_score; DROP FUNCTION record_score(FLOAT, FLOAT, FLOAT); @@ -69,4 +69,4 @@ CREATE VIEW players_with_score AS ON scores.player = players.id LEFT OUTER JOIN nationalities ON players.nationality = nationalities.iso_country_code - WHERE NOT players.banned; \ No newline at end of file + WHERE NOT players.banned; */ \ No newline at end of file diff --git a/migrations/20191002080414_fix_database_design.down.sql b/migrations/20191002080414_fix_database_design.down.sql index ac0fa0275..5e06f53b7 100644 --- a/migrations/20191002080414_fix_database_design.down.sql +++ b/migrations/20191002080414_fix_database_design.down.sql @@ -1,4 +1,4 @@ --- This file should undo anything in `up.sql` +/* -- This file should undo anything in `up.sql` DROP VIEW demons_pv; DROP VIEW demons_p; DROP VIEW records_pds; @@ -13,4 +13,4 @@ FROM demons INNER JOIN players AS p1 ON demons.verifier = p1.id INNER JOIN players AS p2 -ON demons.publisher = p2.id \ No newline at end of file +ON demons.publisher = p2.id */ \ No newline at end of file diff --git a/migrations/20191002080414_fix_database_design.up.sql b/migrations/20191002080414_fix_database_design.up.sql index 5b67a31d8..a10e452b3 100644 --- a/migrations/20191002080414_fix_database_design.up.sql +++ b/migrations/20191002080414_fix_database_design.up.sql @@ -1,4 +1,4 @@ --- Your SQL goes here +/* -- Your SQL goes here CREATE VIEW demons_pv AS -- demons with publisher and verifier SELECT demons.position, demons.name, demons.requirement, demons.video, @@ -60,4 +60,4 @@ CREATE VIEW players_n AS -- players with nationality LEFT OUTER JOIN nationalities ON players.nationality = nationalities.iso_country_code; -DROP VIEW IF EXISTS demon_verifier_publisher_join; \ No newline at end of file +DROP VIEW IF EXISTS demon_verifier_publisher_join; */ \ No newline at end of file diff --git a/migrations/20191004164504_demon_ids.down.sql b/migrations/20191004164504_demon_ids.down.sql index 291a97c5c..42b9a52c6 100644 --- a/migrations/20191004164504_demon_ids.down.sql +++ b/migrations/20191004164504_demon_ids.down.sql @@ -1 +1 @@ --- This file should undo anything in `up.sql` \ No newline at end of file +/* -- This file should undo anything in `up.sql` */ \ No newline at end of file diff --git a/migrations/20191004164504_demon_ids.up.sql b/migrations/20191004164504_demon_ids.up.sql index 3e21614ae..f5b3b609e 100644 --- a/migrations/20191004164504_demon_ids.up.sql +++ b/migrations/20191004164504_demon_ids.up.sql @@ -1,4 +1,4 @@ --- Your SQL goes here +/* -- Your SQL goes here -- Create the ID column ALTER TABLE demons ADD COLUMN id SERIAL; @@ -305,3 +305,4 @@ ALTER TABLE records ADD CONSTRAINT records_demon_fkey FOREIGN KEY (demon) REFERE -- set up the primary key of the creators table again ALTER TABLE creators ADD CONSTRAINT creators_pkey PRIMARY KEY (demon, creator); + */ \ No newline at end of file diff --git a/migrations/20191009080542_record_notes.down.sql b/migrations/20191009080542_record_notes.down.sql index dc84838f6..18121d942 100644 --- a/migrations/20191009080542_record_notes.down.sql +++ b/migrations/20191009080542_record_notes.down.sql @@ -1,4 +1,4 @@ --- This file should undo anything in `up.sql` +/* -- This file should undo anything in `up.sql` -- Fix up the views over the records table DROP VIEW records_pds; @@ -11,4 +11,4 @@ CREATE VIEW records_pds AS -- records with player, demon and submitter INNER JOIN submitters ON records_pd.submitter_id = submitters.submitter_id; -ALTER TABLE records DROP COLUMN notes; \ No newline at end of file +ALTER TABLE records DROP COLUMN notes; */ \ No newline at end of file diff --git a/migrations/20191009080542_record_notes.up.sql b/migrations/20191009080542_record_notes.up.sql index 8a83454ae..ec2b885a7 100644 --- a/migrations/20191009080542_record_notes.up.sql +++ b/migrations/20191009080542_record_notes.up.sql @@ -1,4 +1,4 @@ --- Your SQL goes here +/* -- Your SQL goes here ALTER TABLE records ADD COLUMN notes TEXT; @@ -17,3 +17,4 @@ CREATE VIEW records_pds AS -- records with player, demon and submitter ON records.player = players.id INNER JOIN demons ON demons.id = records.demon; + */ \ No newline at end of file diff --git a/migrations/20200227174850_view_cleanup.down.sql b/migrations/20200227174850_view_cleanup.down.sql index 455914768..ef8d20078 100644 --- a/migrations/20200227174850_view_cleanup.down.sql +++ b/migrations/20200227174850_view_cleanup.down.sql @@ -1,4 +1,4 @@ --- This file should undo anything in `up.sql` +/* -- This file should undo anything in `up.sql` CREATE VIEW records_pds AS -- records with player, demon and submitter @@ -63,4 +63,4 @@ CREATE VIEW players_n AS -- players with nationality nationalities.iso_country_code, nationalities.nation FROM players LEFT OUTER JOIN nationalities - ON players.nationality = nationalities.iso_country_code; \ No newline at end of file + ON players.nationality = nationalities.iso_country_code; */ \ No newline at end of file diff --git a/migrations/20200227174850_view_cleanup.up.sql b/migrations/20200227174850_view_cleanup.up.sql index fc2b4de52..1f76f9681 100644 --- a/migrations/20200227174850_view_cleanup.up.sql +++ b/migrations/20200227174850_view_cleanup.up.sql @@ -1,4 +1,4 @@ --- Your SQL goes here +/* -- Your SQL goes here DROP VIEW players_n; DROP VIEW records_d; @@ -6,4 +6,4 @@ DROP VIEW records_p; DROP VIEW records_pd; DROP VIEW records_pds; DROP VIEW demons_p; -DROP VIEW demons_pv; \ No newline at end of file +DROP VIEW demons_pv; */ \ No newline at end of file diff --git a/migrations/20200227185921_record_notes.down.sql b/migrations/20200227185921_record_notes.down.sql index 6a980736e..c8bfd34d2 100644 --- a/migrations/20200227185921_record_notes.down.sql +++ b/migrations/20200227185921_record_notes.down.sql @@ -1,4 +1,4 @@ --- This file should undo anything in `up.sql` +/* -- This file should undo anything in `up.sql` ALTER TABLE records ADD COLUMN notes TEXT; @@ -21,4 +21,4 @@ DROP TABLE record_notes_modifications; DROP FUNCTION audit_record_notes_modification() CASCADE; DROP TABLE record_notes_deletions; -DROP FUNCTION audit_record_notes_deletion() CASCADE; \ No newline at end of file +DROP FUNCTION audit_record_notes_deletion() CASCADE; */ \ No newline at end of file diff --git a/migrations/20200227185921_record_notes.up.sql b/migrations/20200227185921_record_notes.up.sql index be5f9f577..b8bbddd24 100644 --- a/migrations/20200227185921_record_notes.up.sql +++ b/migrations/20200227185921_record_notes.up.sql @@ -1,4 +1,4 @@ --- Your SQL goes here +/* -- Your SQL goes here CREATE TABLE record_notes ( id SERIAL PRIMARY KEY, record INTEGER REFERENCES records(id) NOT NULL, @@ -68,4 +68,4 @@ $record_notes_deletion_trigger$ LANGUAGE plpgsql; CREATE TRIGGER record_note_addition_trigger AFTER INSERT ON record_notes FOR EACH ROW EXECUTE PROCEDURE audit_record_notes_addition(); CREATE TRIGGER record_note_modification_trigger AFTER UPDATE ON record_notes FOR EACH ROW EXECUTE PROCEDURE audit_record_notes_modification(); -CREATE TRIGGER record_note_deletion_trigger AFTER DELETE ON record_notes FOR EACH ROW EXECUTE PROCEDURE audit_record_notes_modification(); \ No newline at end of file +CREATE TRIGGER record_note_deletion_trigger AFTER DELETE ON record_notes FOR EACH ROW EXECUTE PROCEDURE audit_record_notes_modification(); */ \ No newline at end of file diff --git a/migrations/20200302225154_cascade_note_deletion.down.sql b/migrations/20200302225154_cascade_note_deletion.down.sql index 852b6f896..4ff02d54f 100644 --- a/migrations/20200302225154_cascade_note_deletion.down.sql +++ b/migrations/20200302225154_cascade_note_deletion.down.sql @@ -1,7 +1,7 @@ --- This file should undo anything in `up.sql` +/* -- This file should undo anything in `up.sql` ALTER TABLE record_notes DROP CONSTRAINT record_notes_record_fkey, ADD CONSTRAINT record_notes_record_fkey FOREIGN KEY (record) - REFERENCES records(id) \ No newline at end of file + REFERENCES records(id) */ \ No newline at end of file diff --git a/migrations/20200302225154_cascade_note_deletion.up.sql b/migrations/20200302225154_cascade_note_deletion.up.sql index d9f300bd7..18216acc6 100644 --- a/migrations/20200302225154_cascade_note_deletion.up.sql +++ b/migrations/20200302225154_cascade_note_deletion.up.sql @@ -1,8 +1,8 @@ --- Your SQL goes here +/* -- Your SQL goes here ALTER TABLE record_notes DROP CONSTRAINT record_notes_record_fkey, ADD CONSTRAINT record_notes_record_fkey FOREIGN KEY (record) REFERENCES records(id) - ON DELETE CASCADE; \ No newline at end of file + ON DELETE CASCADE; */ \ No newline at end of file diff --git a/migrations/20200321014223_delete_old_procedures.down.sql b/migrations/20200321014223_delete_old_procedures.down.sql index 3943b3f77..7e3c852cb 100644 --- a/migrations/20200321014223_delete_old_procedures.down.sql +++ b/migrations/20200321014223_delete_old_procedures.down.sql @@ -1,2 +1,2 @@ --- This file should undo anything in `up.sql` -SELECT 1 \ No newline at end of file +/* -- This file should undo anything in `up.sql` +SELECT 1 */ \ No newline at end of file diff --git a/migrations/20200321014223_delete_old_procedures.up.sql b/migrations/20200321014223_delete_old_procedures.up.sql index 972eadbc6..8768aa405 100644 --- a/migrations/20200321014223_delete_old_procedures.up.sql +++ b/migrations/20200321014223_delete_old_procedures.up.sql @@ -1,7 +1,7 @@ --- Your SQL goes here +/* -- Your SQL goes here DROP FUNCTION IF EXISTS ban_player; DROP FUNCTION IF EXISTS remove_submitted_records_on_user_ban; DROP FUNCTION IF EXISTS ban_user CASCADE; DROP FUNCTION IF EXISTS remove_invalid_records CASCADE; DROP FUNCTION IF EXISTS unban_player; -DROP FUNCTION IF EXISTS player_score; \ No newline at end of file +DROP FUNCTION IF EXISTS player_score; */ \ No newline at end of file diff --git a/migrations/20200404074854_shadow_ban.down.sql b/migrations/20200404074854_shadow_ban.down.sql index d7c39eb91..1d0fb85aa 100644 --- a/migrations/20200404074854_shadow_ban.down.sql +++ b/migrations/20200404074854_shadow_ban.down.sql @@ -1,4 +1,4 @@ --- This file should undo anything in `up.sql` +/* -- This file should undo anything in `up.sql` ALTER TABLE players -DROP COLUMN link_banned; \ No newline at end of file +DROP COLUMN link_banned; */ \ No newline at end of file diff --git a/migrations/20200404074854_shadow_ban.up.sql b/migrations/20200404074854_shadow_ban.up.sql index 01efa2732..20256f8e1 100644 --- a/migrations/20200404074854_shadow_ban.up.sql +++ b/migrations/20200404074854_shadow_ban.up.sql @@ -1,4 +1,4 @@ --- Your SQL goes here +/* -- Your SQL goes here ALTER TABLE players -ADD COLUMN link_banned BOOL DEFAULT FALSE; \ No newline at end of file +ADD COLUMN link_banned BOOL DEFAULT FALSE; */ \ No newline at end of file diff --git a/migrations/20200515125343_new_formula.down.sql b/migrations/20200515125343_new_formula.down.sql index ee33a9d29..c07348349 100644 --- a/migrations/20200515125343_new_formula.down.sql +++ b/migrations/20200515125343_new_formula.down.sql @@ -1,4 +1,4 @@ --- This file should undo anything in `up.sql` +/* -- This file should undo anything in `up.sql` -- Your SQL goes here DROP VIEW players_with_score; @@ -70,4 +70,4 @@ CREATE OR REPLACE VIEW players_with_score AS ON scores.player = players.id LEFT OUTER JOIN nationalities ON players.nationality = nationalities.iso_country_code - WHERE NOT players.banned; \ No newline at end of file + WHERE NOT players.banned; */ \ No newline at end of file diff --git a/migrations/20200515125343_new_formula.up.sql b/migrations/20200515125343_new_formula.up.sql index 26e74d19b..cece1e36f 100644 --- a/migrations/20200515125343_new_formula.up.sql +++ b/migrations/20200515125343_new_formula.up.sql @@ -1,4 +1,4 @@ --- Your SQL goes here +/* -- Your SQL goes here DROP VIEW players_with_score; DROP FUNCTION record_score(FLOAT, FLOAT, FLOAT, FLOAT); @@ -72,4 +72,4 @@ CREATE OR REPLACE VIEW players_with_score AS ON scores.player = players.id LEFT OUTER JOIN nationalities ON players.nationality = nationalities.iso_country_code - WHERE NOT players.banned; \ No newline at end of file + WHERE NOT players.banned; */ \ No newline at end of file diff --git a/migrations/20200521101927_new_formulaagain.down.sql b/migrations/20200521101927_new_formulaagain.down.sql index 64e508bea..6c3793e2d 100644 --- a/migrations/20200521101927_new_formulaagain.down.sql +++ b/migrations/20200521101927_new_formulaagain.down.sql @@ -1,4 +1,4 @@ --- This file should undo anything in `up.sql` +/* -- This file should undo anything in `up.sql` DROP VIEW players_with_score; DROP FUNCTION record_score(FLOAT, FLOAT, FLOAT, FLOAT); @@ -72,4 +72,4 @@ CREATE OR REPLACE VIEW players_with_score AS ON scores.player = players.id LEFT OUTER JOIN nationalities ON players.nationality = nationalities.iso_country_code - WHERE NOT players.banned; \ No newline at end of file + WHERE NOT players.banned; */ \ No newline at end of file diff --git a/migrations/20200521101927_new_formulaagain.up.sql b/migrations/20200521101927_new_formulaagain.up.sql index c6f2ac563..bdc1645b0 100644 --- a/migrations/20200521101927_new_formulaagain.up.sql +++ b/migrations/20200521101927_new_formulaagain.up.sql @@ -1,4 +1,4 @@ --- Your SQL goes here +/* -- Your SQL goes here DROP VIEW players_with_score; DROP FUNCTION record_score(FLOAT, FLOAT, FLOAT, FLOAT); @@ -69,3 +69,4 @@ CREATE OR REPLACE VIEW players_with_score AS LEFT OUTER JOIN nationalities ON players.nationality = nationalities.iso_country_code WHERE NOT players.banned AND players.id != 1534; + */ \ No newline at end of file diff --git a/migrations/20200725072253_fix_smallint.down.sql b/migrations/20200725072253_fix_smallint.down.sql index 390b74757..e62d786f1 100644 --- a/migrations/20200725072253_fix_smallint.down.sql +++ b/migrations/20200725072253_fix_smallint.down.sql @@ -1,4 +1,4 @@ --- This file should undo anything in `up.sql` +/* -- This file should undo anything in `up.sql` CREATE OR REPLACE FUNCTION audit_demon_modification() RETURNS trigger AS $demon_modification_trigger$ @@ -40,4 +40,4 @@ CREATE OR REPLACE FUNCTION audit_demon_modification() RETURNS trigger AS $demon_ RETURN NEW; END; -$demon_modification_trigger$ LANGUAGE plpgsql; \ No newline at end of file +$demon_modification_trigger$ LANGUAGE plpgsql; */ \ No newline at end of file diff --git a/migrations/20200725072253_fix_smallint.up.sql b/migrations/20200725072253_fix_smallint.up.sql index 7d076cdbf..4d062129d 100644 --- a/migrations/20200725072253_fix_smallint.up.sql +++ b/migrations/20200725072253_fix_smallint.up.sql @@ -1,4 +1,4 @@ --- Your SQL goes here +/* -- Your SQL goes here CREATE OR REPLACE FUNCTION audit_demon_modification() RETURNS trigger AS $demon_modification_trigger$ @@ -40,4 +40,4 @@ CREATE OR REPLACE FUNCTION audit_demon_modification() RETURNS trigger AS $demon_ RETURN NEW; END; -$demon_modification_trigger$ LANGUAGE plpgsql; \ No newline at end of file +$demon_modification_trigger$ LANGUAGE plpgsql; */ \ No newline at end of file diff --git a/migrations/20200813055728_dash_rs.down.sql b/migrations/20200813055728_dash_rs.down.sql index 7c9fa7b96..8415cc24a 100644 --- a/migrations/20200813055728_dash_rs.down.sql +++ b/migrations/20200813055728_dash_rs.down.sql @@ -1,4 +1,4 @@ --- This file should undo anything in `up.sql` +/* -- This file should undo anything in `up.sql` ALTER TABLE demons DROP COLUMN level_id; @@ -16,4 +16,4 @@ DROP TABLE gj_level_request_meta; DROP TABLE gj_newgrounds_song; DROP TABLE gj_newgrounds_song_meta; -DROP TABLE download_lock; \ No newline at end of file +DROP TABLE download_lock; */ \ No newline at end of file diff --git a/migrations/20200813055728_dash_rs.up.sql b/migrations/20200813055728_dash_rs.up.sql index 336d274a9..87846a78e 100644 --- a/migrations/20200813055728_dash_rs.up.sql +++ b/migrations/20200813055728_dash_rs.up.sql @@ -1,4 +1,4 @@ --- Your SQL goes here +/* -- Your SQL goes here CREATE TABLE gj_creator ( user_id bigint PRIMARY KEY NOT NULL, @@ -94,4 +94,4 @@ CREATE TABLE download_lock( level_id bigint not null ); -ALTER TABLE demons ADD COLUMN level_id INT8 NULL UNIQUE REFERENCES gj_level(level_id); \ No newline at end of file +ALTER TABLE demons ADD COLUMN level_id INT8 NULL UNIQUE REFERENCES gj_level(level_id); */ \ No newline at end of file diff --git a/migrations/20201221091225_no_ext_progress_point.down.sql b/migrations/20201221091225_no_ext_progress_point.down.sql index 95dd14915..f0b5eb1d7 100644 --- a/migrations/20201221091225_no_ext_progress_point.down.sql +++ b/migrations/20201221091225_no_ext_progress_point.down.sql @@ -1,4 +1,4 @@ --- Your SQL goes here +/* -- Your SQL goes here DROP VIEW players_with_score; DROP FUNCTION record_score(FLOAT, FLOAT, FLOAT, FLOAT); @@ -69,3 +69,4 @@ FROM LEFT OUTER JOIN nationalities ON players.nationality = nationalities.iso_country_code WHERE NOT players.banned AND players.id != 1534; + */ \ No newline at end of file diff --git a/migrations/20201221091225_no_ext_progress_point.up.sql b/migrations/20201221091225_no_ext_progress_point.up.sql index e6252901c..ad3e351ae 100644 --- a/migrations/20201221091225_no_ext_progress_point.up.sql +++ b/migrations/20201221091225_no_ext_progress_point.up.sql @@ -1,4 +1,4 @@ --- Your SQL goes here +/* -- Your SQL goes here DROP VIEW players_with_score; DROP FUNCTION record_score(FLOAT, FLOAT, FLOAT, FLOAT); @@ -69,3 +69,4 @@ FROM LEFT OUTER JOIN nationalities ON players.nationality = nationalities.iso_country_code WHERE NOT players.banned AND players.id != 1534; + */ \ No newline at end of file diff --git a/migrations/20210224123230_new_formula_once_again.down.sql b/migrations/20210224123230_new_formula_once_again.down.sql index e6252901c..ad3e351ae 100644 --- a/migrations/20210224123230_new_formula_once_again.down.sql +++ b/migrations/20210224123230_new_formula_once_again.down.sql @@ -1,4 +1,4 @@ --- Your SQL goes here +/* -- Your SQL goes here DROP VIEW players_with_score; DROP FUNCTION record_score(FLOAT, FLOAT, FLOAT, FLOAT); @@ -69,3 +69,4 @@ FROM LEFT OUTER JOIN nationalities ON players.nationality = nationalities.iso_country_code WHERE NOT players.banned AND players.id != 1534; + */ \ No newline at end of file diff --git a/migrations/20210224123230_new_formula_once_again.up.sql b/migrations/20210224123230_new_formula_once_again.up.sql index 84c8e6e48..27adf5fcf 100644 --- a/migrations/20210224123230_new_formula_once_again.up.sql +++ b/migrations/20210224123230_new_formula_once_again.up.sql @@ -1,4 +1,4 @@ --- Your SQL goes here +/* -- Your SQL goes here DROP VIEW players_with_score; DROP FUNCTION record_score(FLOAT, FLOAT, FLOAT, FLOAT); @@ -92,3 +92,4 @@ FROM LEFT OUTER JOIN nationalities ON players.nationality = nationalities.iso_country_code WHERE NOT players.banned AND players.id != 1534; + */ \ No newline at end of file diff --git a/migrations/20210327024242_timemachine.down.sql b/migrations/20210327024242_timemachine.down.sql index 643da30d5..0fd0a6df5 100644 --- a/migrations/20210327024242_timemachine.down.sql +++ b/migrations/20210327024242_timemachine.down.sql @@ -1,3 +1,3 @@ --- This file should undo anything in `up.sql` +/* -- This file should undo anything in `up.sql` -DROP FUNCTION list_at(TIMESTAMP WITHOUT TIME ZONE); \ No newline at end of file +DROP FUNCTION list_at(TIMESTAMP WITHOUT TIME ZONE); */ \ No newline at end of file diff --git a/migrations/20210327024242_timemachine.up.sql b/migrations/20210327024242_timemachine.up.sql index c0b3e47fc..cba689efe 100644 --- a/migrations/20210327024242_timemachine.up.sql +++ b/migrations/20210327024242_timemachine.up.sql @@ -1,4 +1,4 @@ --- Your SQL goes here +/* -- Your SQL goes here CREATE FUNCTION list_at(TIMESTAMP WITHOUT TIME ZONE) RETURNS TABLE ( @@ -25,4 +25,4 @@ AS $$ WHERE NOT EXISTS (SELECT 1 FROM demon_additions WHERE demon_additions.id = demons.id AND time >= $1) $$ LANGUAGE SQL -STABLE; \ No newline at end of file +STABLE; */ \ No newline at end of file diff --git a/migrations/20210419002933_nationalityupdate.down.sql b/migrations/20210419002933_nationalityupdate.down.sql index 39ba1fd5e..fb4f2999f 100644 --- a/migrations/20210419002933_nationalityupdate.down.sql +++ b/migrations/20210419002933_nationalityupdate.down.sql @@ -1,4 +1,4 @@ --- This file should undo anything in `up.sql` +/* -- This file should undo anything in `up.sql` -- Undo audit log related changes @@ -99,4 +99,4 @@ ALTER TABLE nationalities DROP COLUMN continent; DROP TABLE subdivisions; DROP TYPE continent; -ALTER TABLE players DROP COLUMN subdivision; \ No newline at end of file +ALTER TABLE players DROP COLUMN subdivision; */ \ No newline at end of file diff --git a/migrations/20210419002933_nationalityupdate.up.sql b/migrations/20210419002933_nationalityupdate.up.sql index af03f5af4..546256191 100644 --- a/migrations/20210419002933_nationalityupdate.up.sql +++ b/migrations/20210419002933_nationalityupdate.up.sql @@ -1,4 +1,4 @@ -CREATE TABLE subdivisions ( +/* CREATE TABLE subdivisions ( iso_code VARCHAR(3), name CITEXT UNIQUE NOT NULL, nation VARCHAR(2) REFERENCES nationalities(iso_country_code), @@ -426,3 +426,4 @@ FROM LEFT OUTER JOIN nationalities ON players.nationality = nationalities.iso_country_code WHERE NOT players.banned AND players.id != 1534; + */ \ No newline at end of file diff --git a/migrations/20210725221543_player_claims.down.sql b/migrations/20210725221543_player_claims.down.sql index 1f7c18d0c..46badf93d 100644 --- a/migrations/20210725221543_player_claims.down.sql +++ b/migrations/20210725221543_player_claims.down.sql @@ -1,4 +1,4 @@ --- This file should undo anything in `up.sql` +/* -- This file should undo anything in `up.sql` ALTER TABLE players DROP COLUMN claimed_by; -ALTER TABLE members DROP COLUMN claimed_player; \ No newline at end of file +ALTER TABLE members DROP COLUMN claimed_player; */ \ No newline at end of file diff --git a/migrations/20210725221543_player_claims.up.sql b/migrations/20210725221543_player_claims.up.sql index 5f8c3166c..ebb126ccf 100644 --- a/migrations/20210725221543_player_claims.up.sql +++ b/migrations/20210725221543_player_claims.up.sql @@ -1,4 +1,5 @@ --- Your SQL goes here +/* -- Your SQL goes here ALTER TABLE members ADD COLUMN claimed_player INTEGER REFERENCES players(id) ON DELETE SET NULL; ALTER TABLE players ADD COLUMN claimed_by INTEGER REFERENCES members(member_id) ON DELETE SET NULL; + */ \ No newline at end of file diff --git a/migrations/20210726174613_nation_ranking.down.sql b/migrations/20210726174613_nation_ranking.down.sql index 5ae126c97..ae8cd452a 100644 --- a/migrations/20210726174613_nation_ranking.down.sql +++ b/migrations/20210726174613_nation_ranking.down.sql @@ -1,4 +1,4 @@ --- This file should undo anything in `up.sql` +/* -- This file should undo anything in `up.sql` DROP VIEW nations_with_score; -DROP FUNCTION best_records_in(country VARCHAR(2)); \ No newline at end of file +DROP FUNCTION best_records_in(country VARCHAR(2)); */ \ No newline at end of file diff --git a/migrations/20210726174613_nation_ranking.up.sql b/migrations/20210726174613_nation_ranking.up.sql index 8783b1d45..6df1fda97 100644 --- a/migrations/20210726174613_nation_ranking.up.sql +++ b/migrations/20210726174613_nation_ranking.up.sql @@ -1,4 +1,4 @@ --- Your SQL goes here +/* -- Your SQL goes here CREATE OR REPLACE FUNCTION best_records_in(country VARCHAR(2)) RETURNS TABLE (LIKE records) @@ -57,3 +57,4 @@ CREATE OR REPLACE VIEW nations_with_score AS ) scores INNER JOIN nationalities ON nationalities.iso_country_code = scores.nationality; + */ \ No newline at end of file diff --git a/migrations/20210825182933_new_claim_system.down.sql b/migrations/20210825182933_new_claim_system.down.sql index f41a075c5..8ca40e2ee 100644 --- a/migrations/20210825182933_new_claim_system.down.sql +++ b/migrations/20210825182933_new_claim_system.down.sql @@ -1,8 +1,8 @@ --- This file should undo anything in `up.sql` +/* -- This file should undo anything in `up.sql` ALTER TABLE members ADD COLUMN claimed_player INTEGER REFERENCES players(id) ON DELETE SET NULL; ALTER TABLE players ADD COLUMN claimed_by INTEGER REFERENCES members(member_id) ON DELETE SET NULL; UPDATE members SET claimed_player = (select player_id from player_claims where player_claims.member_id = members.member_id limit 1); -DROP TABLE player_claims; \ No newline at end of file +DROP TABLE player_claims; */ \ No newline at end of file diff --git a/migrations/20210825182933_new_claim_system.up.sql b/migrations/20210825182933_new_claim_system.up.sql index 1028ea7f8..e6d6d47d1 100644 --- a/migrations/20210825182933_new_claim_system.up.sql +++ b/migrations/20210825182933_new_claim_system.up.sql @@ -1,4 +1,4 @@ --- Your SQL goes here +/* -- Your SQL goes here CREATE TABLE player_claims( id SERIAL PRIMARY KEY, -- only used for pagination @@ -11,4 +11,4 @@ INSERT INTO player_claims (member_id, player_id) SELECT member_id, claimed_player FROM members WHERE claimed_player IS NOT NULL; ALTER TABLE members DROP COLUMN claimed_player; -ALTER TABLE players DROP COLUMN claimed_by; \ No newline at end of file +ALTER TABLE players DROP COLUMN claimed_by; */ \ No newline at end of file diff --git a/migrations/20210903174349_subdivision_ranking.down.sql b/migrations/20210903174349_subdivision_ranking.down.sql index f75a05944..640024681 100644 --- a/migrations/20210903174349_subdivision_ranking.down.sql +++ b/migrations/20210903174349_subdivision_ranking.down.sql @@ -1,4 +1,4 @@ --- This file should undo anything in `up.sql` +/* -- This file should undo anything in `up.sql` DROP FUNCTION subdivision_ranking_of(country varchar(2)); -DROP FUNCTION best_records_local(country VARCHAR(2), the_subdivision VARCHAR(3)); \ No newline at end of file +DROP FUNCTION best_records_local(country VARCHAR(2), the_subdivision VARCHAR(3)); */ \ No newline at end of file diff --git a/migrations/20210903174349_subdivision_ranking.up.sql b/migrations/20210903174349_subdivision_ranking.up.sql index f6ab6c3db..d8481a232 100644 --- a/migrations/20210903174349_subdivision_ranking.up.sql +++ b/migrations/20210903174349_subdivision_ranking.up.sql @@ -1,4 +1,4 @@ --- Your SQL goes here +/* -- Your SQL goes here CREATE OR REPLACE FUNCTION best_records_local(country VARCHAR(2), the_subdivision VARCHAR(3)) RETURNS TABLE (LIKE records) @@ -66,3 +66,4 @@ AS $body$ LANGUAGE SQL; + */ \ No newline at end of file diff --git a/migrations/20220323105850_member_add_email_column.down.sql b/migrations/20220323105850_member_add_email_column.down.sql index 4f191b0be..90118748e 100644 --- a/migrations/20220323105850_member_add_email_column.down.sql +++ b/migrations/20220323105850_member_add_email_column.down.sql @@ -1,4 +1,4 @@ --- This file should undo anything in `up.sql` +/* -- This file should undo anything in `up.sql` ALTER TABLE members DROP COLUMN email_address; -DROP DOMAIN EMAIL; \ No newline at end of file +DROP DOMAIN EMAIL; */ \ No newline at end of file diff --git a/migrations/20220323105850_member_add_email_column.up.sql b/migrations/20220323105850_member_add_email_column.up.sql index 5cb7b748d..b346bbb08 100644 --- a/migrations/20220323105850_member_add_email_column.up.sql +++ b/migrations/20220323105850_member_add_email_column.up.sql @@ -1,7 +1,7 @@ --- Your SQL goes here +/* -- Your SQL goes here -- https://dba.stackexchange.com/questions/68266/what-is-the-best-way-to-store-an-email-address-in-postgresql CREATE DOMAIN EMAIL AS CITEXT CHECK ( value ~ '^[a-zA-Z0-9.!#$%&''*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$' ); -ALTER TABLE members ADD COLUMN email_address EMAIL; \ No newline at end of file +ALTER TABLE members ADD COLUMN email_address EMAIL; */ \ No newline at end of file diff --git a/migrations/20220324104659_lock_submissions.down.sql b/migrations/20220324104659_lock_submissions.down.sql index 31410e91f..5249b28bd 100644 --- a/migrations/20220324104659_lock_submissions.down.sql +++ b/migrations/20220324104659_lock_submissions.down.sql @@ -1,3 +1,3 @@ --- This file should undo anything in `up.sql` +/* -- This file should undo anything in `up.sql` -ALTER TABLE player_claims DROP COLUMN lock_submissions; \ No newline at end of file +ALTER TABLE player_claims DROP COLUMN lock_submissions; */ \ No newline at end of file diff --git a/migrations/20220324104659_lock_submissions.up.sql b/migrations/20220324104659_lock_submissions.up.sql index 2390f40e4..ece808aae 100644 --- a/migrations/20220324104659_lock_submissions.up.sql +++ b/migrations/20220324104659_lock_submissions.up.sql @@ -1,3 +1,3 @@ --- Your SQL goes here +/* -- Your SQL goes here -ALTER TABLE player_claims ADD COLUMN lock_submissions BOOL NOT NULL DEFAULT FALSE; \ No newline at end of file +ALTER TABLE player_claims ADD COLUMN lock_submissions BOOL NOT NULL DEFAULT FALSE; */ \ No newline at end of file diff --git a/migrations/20220412171531_public_notes.down.sql b/migrations/20220412171531_public_notes.down.sql index 1e86f220e..600250452 100644 --- a/migrations/20220412171531_public_notes.down.sql +++ b/migrations/20220412171531_public_notes.down.sql @@ -1,3 +1,3 @@ --- This file should undo anything in `up.sql` +/* -- This file should undo anything in `up.sql` -ALTER TABLE record_notes DROP COLUMN is_public; \ No newline at end of file +ALTER TABLE record_notes DROP COLUMN is_public; */ \ No newline at end of file diff --git a/migrations/20220412171531_public_notes.up.sql b/migrations/20220412171531_public_notes.up.sql index e7eeec03c..055b0d147 100644 --- a/migrations/20220412171531_public_notes.up.sql +++ b/migrations/20220412171531_public_notes.up.sql @@ -1,3 +1,3 @@ --- Your SQL goes here +/* -- Your SQL goes here -ALTER TABLE record_notes ADD COLUMN is_public BOOLEAN NOT NULL DEFAULT FALSE; \ No newline at end of file +ALTER TABLE record_notes ADD COLUMN is_public BOOLEAN NOT NULL DEFAULT FALSE; */ \ No newline at end of file diff --git a/migrations/20220601192100_extended_list_buff.down.sql b/migrations/20220601192100_extended_list_buff.down.sql index 4b7697428..eb87e63cf 100644 --- a/migrations/20220601192100_extended_list_buff.down.sql +++ b/migrations/20220601192100_extended_list_buff.down.sql @@ -1,4 +1,4 @@ --- Your SQL goes here +/* -- Your SQL goes here CREATE OR REPLACE FUNCTION record_score(progress FLOAT, demon FLOAT, list_size FLOAT, requirement FLOAT) RETURNS FLOAT AS $record_score$ @@ -36,3 +36,4 @@ SELECT CASE $record_score$ LANGUAGE SQL IMMUTABLE; + */ \ No newline at end of file diff --git a/migrations/20220601192100_extended_list_buff.up.sql b/migrations/20220601192100_extended_list_buff.up.sql index c2ff3b8da..6df3d382e 100644 --- a/migrations/20220601192100_extended_list_buff.up.sql +++ b/migrations/20220601192100_extended_list_buff.up.sql @@ -1,4 +1,4 @@ --- Your SQL goes here +/* -- Your SQL goes here CREATE OR REPLACE FUNCTION record_score(progress FLOAT, demon FLOAT, list_size FLOAT, requirement FLOAT) RETURNS FLOAT AS $record_score$ @@ -35,3 +35,4 @@ SELECT CASE END; $record_score$ LANGUAGE SQL IMMUTABLE; + */ \ No newline at end of file diff --git a/migrations/20220805215800_fix_youtube_channel_audit.down.sql b/migrations/20220805215800_fix_youtube_channel_audit.down.sql index 147f03494..1dbba5561 100644 --- a/migrations/20220805215800_fix_youtube_channel_audit.down.sql +++ b/migrations/20220805215800_fix_youtube_channel_audit.down.sql @@ -1,4 +1,4 @@ --- This file should undo anything in `up.sql` +/* -- This file should undo anything in `up.sql` CREATE OR REPLACE FUNCTION audit_user_modification() RETURNS trigger as $user_modification_trigger$ DECLARE display_name_change CITEXT; @@ -22,4 +22,4 @@ BEGIN RETURN NEW; END; -$user_modification_trigger$ LANGUAGE plpgsql; \ No newline at end of file +$user_modification_trigger$ LANGUAGE plpgsql; */ \ No newline at end of file diff --git a/migrations/20220805215800_fix_youtube_channel_audit.up.sql b/migrations/20220805215800_fix_youtube_channel_audit.up.sql index 88344efb2..054c62c57 100644 --- a/migrations/20220805215800_fix_youtube_channel_audit.up.sql +++ b/migrations/20220805215800_fix_youtube_channel_audit.up.sql @@ -1,4 +1,4 @@ --- Your SQL goes here +/* -- Your SQL goes here CREATE OR REPLACE FUNCTION audit_user_modification() RETURNS trigger as $user_modification_trigger$ DECLARE display_name_change CITEXT; @@ -22,4 +22,4 @@ BEGIN RETURN NEW; END; -$user_modification_trigger$ LANGUAGE plpgsql; \ No newline at end of file +$user_modification_trigger$ LANGUAGE plpgsql; */ \ No newline at end of file diff --git a/migrations/20221009140916_thumbnails.down.sql b/migrations/20221009140916_thumbnails.down.sql index 30dd42db3..9f44571db 100644 --- a/migrations/20221009140916_thumbnails.down.sql +++ b/migrations/20221009140916_thumbnails.down.sql @@ -1,4 +1,4 @@ --- This file should undo anything in `up.sql` +/* -- This file should undo anything in `up.sql` DROP TRIGGER demons_insert_set_thumbnail ON demons; DROP FUNCTION set_initial_thumbnail; @@ -74,4 +74,4 @@ FROM demons WHERE NOT EXISTS (SELECT 1 FROM demon_additions WHERE demon_additions.id = demons.id AND time >= $1) $$ LANGUAGE SQL - STABLE; \ No newline at end of file + STABLE; */ \ No newline at end of file diff --git a/migrations/20221009140916_thumbnails.up.sql b/migrations/20221009140916_thumbnails.up.sql index 123c08003..b519b6693 100644 --- a/migrations/20221009140916_thumbnails.up.sql +++ b/migrations/20221009140916_thumbnails.up.sql @@ -1,4 +1,4 @@ --- Your SQL goes here +/* -- Your SQL goes here ALTER TABLE demons ADD COLUMN thumbnail TEXT NOT NULL DEFAULT 'https://i.ytimg.com/vi/zebrafishes/mqdefault.jpg'; @@ -97,4 +97,4 @@ FROM demons WHERE NOT EXISTS (SELECT 1 FROM demon_additions WHERE demon_additions.id = demons.id AND time >= $1) $$ LANGUAGE SQL - STABLE; \ No newline at end of file + STABLE; */ \ No newline at end of file diff --git a/migrations/20240328150521_simplify_gd_integration.down.sql b/migrations/20240328150521_simplify_gd_integration.down.sql index 93479c830..a2b70365f 100644 --- a/migrations/20240328150521_simplify_gd_integration.down.sql +++ b/migrations/20240328150521_simplify_gd_integration.down.sql @@ -1,4 +1,4 @@ --- Add down migration script here +/* -- 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! @@ -39,4 +39,4 @@ CREATE TABLE gj_newgrounds_song_meta ( CREATE TABLE download_lock( level_id bigint not null -); \ No newline at end of file +); */ \ No newline at end of file diff --git a/migrations/20240328150521_simplify_gd_integration.up.sql b/migrations/20240328150521_simplify_gd_integration.up.sql index f220f7492..f14a8904d 100644 --- a/migrations/20240328150521_simplify_gd_integration.up.sql +++ b/migrations/20240328150521_simplify_gd_integration.up.sql @@ -1,4 +1,4 @@ --- Add up migration script here +/* -- Add up migration script here DROP TABLE download_lock; DROP TABLE gj_newgrounds_song_meta; @@ -6,4 +6,4 @@ 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 +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 index 814293862..667830167 100644 --- a/migrations/20240404084539_nationalityupdate_again.down.sql +++ b/migrations/20240404084539_nationalityupdate_again.down.sql @@ -1,4 +1,4 @@ --- Add down migration script here +/* -- Add down migration script here DELETE FROM subdivisions; INSERT INTO subdivisions (iso_code, name, nation) @@ -78,4 +78,4 @@ VALUES ('QLD', 'Queensland', 'AU'), ('NSW', 'New South Wales', 'AU'), ('VIC', 'Victoria', 'AU'), - ('SA', 'South Australia', 'AU'); \ No newline at end of file + ('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 index 627e29d8a..ecc0a9561 100644 --- a/migrations/20240404084539_nationalityupdate_again.up.sql +++ b/migrations/20240404084539_nationalityupdate_again.up.sql @@ -1,4 +1,4 @@ --- Add up migration script here +/* -- Add up migration script here INSERT INTO subdivisions (iso_code, name, nation) VALUES ('V', 'Provincia de Tierra del Fuego', 'AR'), @@ -426,3 +426,4 @@ VALUES ('48', 'Gyeongsangnam-do', 'KR'), ('49', 'Jeju-do', 'KR'), ('50', 'Sejong', 'KR'); + */ \ No newline at end of file diff --git a/migrations/20240418192100_top_10_buff.down.sql b/migrations/20240418192100_top_10_buff.down.sql index c2ff3b8da..6df3d382e 100644 --- a/migrations/20240418192100_top_10_buff.down.sql +++ b/migrations/20240418192100_top_10_buff.down.sql @@ -1,4 +1,4 @@ --- Your SQL goes here +/* -- Your SQL goes here CREATE OR REPLACE FUNCTION record_score(progress FLOAT, demon FLOAT, list_size FLOAT, requirement FLOAT) RETURNS FLOAT AS $record_score$ @@ -35,3 +35,4 @@ SELECT CASE END; $record_score$ LANGUAGE SQL IMMUTABLE; + */ \ No newline at end of file diff --git a/migrations/20240418192100_top_10_buff.up.sql b/migrations/20240418192100_top_10_buff.up.sql index 3bcd66692..c25cbd5f9 100644 --- a/migrations/20240418192100_top_10_buff.up.sql +++ b/migrations/20240418192100_top_10_buff.up.sql @@ -1,4 +1,4 @@ -CREATE OR REPLACE FUNCTION record_score(progress FLOAT, demon FLOAT, list_size FLOAT, requirement FLOAT) RETURNS FLOAT AS +/* CREATE OR REPLACE FUNCTION record_score(progress FLOAT, demon FLOAT, list_size FLOAT, requirement FLOAT) RETURNS FLOAT AS $record_score$ SELECT CASE WHEN progress = 100 THEN @@ -32,3 +32,4 @@ SELECT CASE END; $record_score$ LANGUAGE SQL IMMUTABLE; + */ \ No newline at end of file diff --git a/migrations/20240504194923_cached_points.down.sql b/migrations/20240504194923_cached_points.down.sql index 12af63e14..9699955ba 100644 --- a/migrations/20240504194923_cached_points.down.sql +++ b/migrations/20240504194923_cached_points.down.sql @@ -1,4 +1,4 @@ --- Add down migration script here +/* -- Add down migration script here DROP VIEW ranked_players; DROP VIEW ranked_nations; @@ -181,3 +181,4 @@ AS $body$ LANGUAGE SQL; + */ \ No newline at end of file diff --git a/migrations/20240504194923_cached_points.up.sql b/migrations/20240504194923_cached_points.up.sql index 306aa9af5..68e3b4edc 100644 --- a/migrations/20240504194923_cached_points.up.sql +++ b/migrations/20240504194923_cached_points.up.sql @@ -1,4 +1,4 @@ --- Add up migration script here +/* -- Add up migration script here ALTER TABLE players ADD COLUMN score DOUBLE PRECISION DEFAULT 0 NOT NULL; @@ -142,4 +142,4 @@ 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 +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 index 8f33ab529..67614627d 100644 --- a/migrations/20240518154803_no_extended_progress_points.down.sql +++ b/migrations/20240518154803_no_extended_progress_points.down.sql @@ -1,4 +1,4 @@ --- Add down migration script here +/* -- Add down migration script here CREATE OR REPLACE VIEW score_giving AS SELECT records.progress, demons.position, demons.requirement, records.player @@ -14,4 +14,4 @@ CREATE OR REPLACE VIEW score_giving AS SELECT recompute_player_scores(); SELECT recompute_nation_scores(); -SELECT recompute_subdivision_scores(); \ No newline at end of file +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 index 8efed0766..d0d6aab45 100644 --- a/migrations/20240518154803_no_extended_progress_points.up.sql +++ b/migrations/20240518154803_no_extended_progress_points.up.sql @@ -1,4 +1,4 @@ --- Add up migration script here +/* -- Add up migration script here CREATE OR REPLACE VIEW score_giving AS SELECT records.progress, demons.position, demons.requirement, records.player FROM records @@ -14,4 +14,4 @@ CREATE OR REPLACE VIEW score_giving AS SELECT recompute_player_scores(); SELECT recompute_nation_scores(); -SELECT recompute_subdivision_scores(); \ No newline at end of file +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 index f689e4bf7..a875894f2 100644 --- a/migrations/20240518161548_properly_zero_scores.down.sql +++ b/migrations/20240518161548_properly_zero_scores.down.sql @@ -1,4 +1,4 @@ --- Add down migration script here +/* -- Add down migration script here CREATE OR REPLACE FUNCTION recompute_player_scores() RETURNS void AS $$ UPDATE players @@ -50,4 +50,4 @@ $$ LANGUAGE SQL; SELECT recompute_player_scores(); SELECT recompute_nation_scores(); -SELECT recompute_subdivision_scores(); \ No newline at end of file +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 index dedf68fbf..e055cd4b1 100644 --- a/migrations/20240518161548_properly_zero_scores.up.sql +++ b/migrations/20240518161548_properly_zero_scores.up.sql @@ -1,4 +1,4 @@ --- Add up migration script here +/* -- 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 @@ -59,4 +59,4 @@ $$ LANGUAGE SQL; SELECT recompute_player_scores(); SELECT recompute_nation_scores(); -SELECT recompute_subdivision_scores(); \ No newline at end of file +SELECT recompute_subdivision_scores(); */ \ No newline at end of file diff --git a/migrations/20240725201300_better_raw_footage.down.sql b/migrations/20240725201300_better_raw_footage.down.sql index 935f603bc..bc8d8fc43 100644 --- a/migrations/20240725201300_better_raw_footage.down.sql +++ b/migrations/20240725201300_better_raw_footage.down.sql @@ -1,4 +1,4 @@ -ALTER TABLE records DROP COLUMN raw_footage; +/* ALTER TABLE records DROP COLUMN raw_footage; DROP FUNCTION best_records_in(VARCHAR(2)); @@ -19,3 +19,4 @@ $body$ WHERE rk = 1; $body$ LANGUAGE SQL; + */ \ No newline at end of file diff --git a/migrations/20240725201300_better_raw_footage.up.sql b/migrations/20240725201300_better_raw_footage.up.sql index ba8a5a0c6..c36cba836 100644 --- a/migrations/20240725201300_better_raw_footage.up.sql +++ b/migrations/20240725201300_better_raw_footage.up.sql @@ -1 +1 @@ -ALTER TABLE records ADD COLUMN raw_footage TEXT; +/* ALTER TABLE records ADD COLUMN raw_footage TEXT; */ \ No newline at end of file diff --git a/migrations/20240804194818_raw_footage_fixup.down.sql b/migrations/20240804194818_raw_footage_fixup.down.sql index 2457913de..e438348fb 100644 --- a/migrations/20240804194818_raw_footage_fixup.down.sql +++ b/migrations/20240804194818_raw_footage_fixup.down.sql @@ -1,3 +1,4 @@ --- Add down migration script here +/* -- Add down migration script here -- This migration is undone by the previous one, as it is impossible to restore the partially broken state --- introduced by only applying 20240725201300_better_raw_footage.up.sql (function with wrong return type). \ No newline at end of file +-- introduced by only applying 20240725201300_better_raw_footage.up.sql (function with wrong return type). + */ \ No newline at end of file diff --git a/migrations/20240804194818_raw_footage_fixup.up.sql b/migrations/20240804194818_raw_footage_fixup.up.sql index 7cc67e5ec..c518a9604 100644 --- a/migrations/20240804194818_raw_footage_fixup.up.sql +++ b/migrations/20240804194818_raw_footage_fixup.up.sql @@ -1,5 +1,4 @@ --- Add up migration script here -DROP FUNCTION best_records_in(VARCHAR(2)); +/* DROP FUNCTION best_records_in(VARCHAR(2)); CREATE FUNCTION best_records_in(country VARCHAR(2)) RETURNS TABLE ( @@ -25,4 +24,5 @@ $body$ FROM grp WHERE rk = 1; $body$ -LANGUAGE SQL; \ No newline at end of file +LANGUAGE SQL; + */ \ No newline at end of file diff --git a/migrations/20240907133101_resync_database.down.sql b/migrations/20240907133101_resync_database.down.sql new file mode 100644 index 000000000..e006f0800 --- /dev/null +++ b/migrations/20240907133101_resync_database.down.sql @@ -0,0 +1,11 @@ +/* ALTER TABLE IF EXISTS public.demons + ADD CONSTRAINT demons_level_id_fkey FOREIGN KEY (level_id) + REFERENCES public.gj_level (level_id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE NO ACTION; + + + +ALTER TABLE IF EXISTS public.records + ADD CONSTRAINT records_video_key UNIQUE (video); + */ \ No newline at end of file diff --git a/migrations/20240907133101_resync_database.up.sql b/migrations/20240907133101_resync_database.up.sql new file mode 100644 index 000000000..226514ea9 --- /dev/null +++ b/migrations/20240907133101_resync_database.up.sql @@ -0,0 +1,9 @@ +/* ALTER TABLE IF EXISTS public.demons + DROP CONSTRAINT IF EXISTS demons_level_id_fkey; + + + + +ALTER TABLE IF EXISTS public.records + DROP CONSTRAINT IF EXISTS records_video_key; + */ \ No newline at end of file diff --git a/migrations/20240913151142_backup_records_on_delete.down.sql b/migrations/20240913151142_backup_records_on_delete.down.sql new file mode 100644 index 000000000..c910d1624 --- /dev/null +++ b/migrations/20240913151142_backup_records_on_delete.down.sql @@ -0,0 +1 @@ +/* DROP TABLE rec_backup */ \ No newline at end of file diff --git a/migrations/20240913151142_backup_records_on_delete.up.sql b/migrations/20240913151142_backup_records_on_delete.up.sql new file mode 100644 index 000000000..5e36ac216 --- /dev/null +++ b/migrations/20240913151142_backup_records_on_delete.up.sql @@ -0,0 +1,19 @@ +/* CREATE TABLE public.rec_backup +( + id integer DEFAULT nextval('records_id_seq'::regclass), + progress smallint, + video character varying(200), + status_ record_status, + player integer, + submitter integer, + demon integer, + raw_footage text, + demon_name citext, + PRIMARY KEY (id) +) + +USING heap +TABLESPACE pg_default; + +ALTER TABLE IF EXISTS public.rec_backup + OWNER to pointercrate; */ \ No newline at end of file diff --git a/migrations/20240921125640_add_enjoyment_ratings.down.sql b/migrations/20240921125640_add_enjoyment_ratings.down.sql new file mode 100644 index 000000000..28b7ab916 --- /dev/null +++ b/migrations/20240921125640_add_enjoyment_ratings.down.sql @@ -0,0 +1,6 @@ +/* ALTER TABLE IF EXISTS public.records + DROP COLUMN enjoyment; + +ALTER TABLE IF EXISTS public.rec_backup + DROP COLUMN enjoyment; + */ \ No newline at end of file diff --git a/migrations/20240921125640_add_enjoyment_ratings.up.sql b/migrations/20240921125640_add_enjoyment_ratings.up.sql new file mode 100644 index 000000000..3c4ee3603 --- /dev/null +++ b/migrations/20240921125640_add_enjoyment_ratings.up.sql @@ -0,0 +1,13 @@ +/* ALTER TABLE IF EXISTS public.records + ADD COLUMN enjoyment integer; + +ALTER TABLE IF EXISTS public.records + ALTER COLUMN enjoyment SET STORAGE PLAIN; + +ALTER TABLE IF EXISTS public.rec_backup + ADD COLUMN enjoyment integer; + +ALTER TABLE IF EXISTS public.rec_backup + ALTER COLUMN enjoyment SET STORAGE PLAIN; + + */ \ No newline at end of file diff --git a/migrations/20241006175654_member_generation.down.sql b/migrations/20241006175654_member_generation.down.sql index 8c7d65d94..0b6462848 100644 --- a/migrations/20241006175654_member_generation.down.sql +++ b/migrations/20241006175654_member_generation.down.sql @@ -1,2 +1,2 @@ --- Add down migration script here -ALTER TABLE members DROP COLUMN generation; \ No newline at end of file +/* -- Add down migration script here +ALTER TABLE members DROP COLUMN generation; */ \ No newline at end of file diff --git a/migrations/20241006175654_member_generation.up.sql b/migrations/20241006175654_member_generation.up.sql index ae654f065..893b2a0a5 100644 --- a/migrations/20241006175654_member_generation.up.sql +++ b/migrations/20241006175654_member_generation.up.sql @@ -1,2 +1,2 @@ --- Add up migration script here -ALTER TABLE members ADD COLUMN generation BIGINT NOT NULL DEFAULT 0; \ No newline at end of file +/* -- Add up migration script here +ALTER TABLE members ADD COLUMN generation BIGINT NOT NULL DEFAULT 0; */ \ No newline at end of file diff --git a/migrations/_notice.md b/migrations/_notice.md new file mode 100644 index 000000000..be9857a3d --- /dev/null +++ b/migrations/_notice.md @@ -0,0 +1,7 @@ +# These migrations will not work! Set the source directory to /pointercrate-example/sample/migrations/_new/ + +the reason these will not work is.. i'm an idiot! past me decided to make changes to the database schema however i pleased without adding migrations. lol! when i open sourced the site, i was gettign errors saying the migrations were run but changed afterward. idk, but commenting out all these migrations and re-running them seemed to work. so now i just need to live with this, unless i think of a solution. + +as instructed in the readme, you should run the migrations in the _new folder to get a clone of the database, and add any new ones there if youre making a pull request or something. + +let me know if you have an idea on how i can fix this! \ No newline at end of file diff --git a/pointercrate-core-pages/src/config.rs b/pointercrate-core-pages/src/config.rs index dff56d878..8b1378917 100644 --- a/pointercrate-core-pages/src/config.rs +++ b/pointercrate-core-pages/src/config.rs @@ -1,4 +1 @@ -pub fn google_analytics_tag() -> String { - std::env::var("ANALYTICS_TAG") - .expect("No google analytics tag configured. Please remove all google analytics code from your custom copy of pointercrate") -} + diff --git a/pointercrate-core-pages/src/footer.rs b/pointercrate-core-pages/src/footer.rs index 12e10bf9b..9cb849073 100644 --- a/pointercrate-core-pages/src/footer.rs +++ b/pointercrate-core-pages/src/footer.rs @@ -90,8 +90,7 @@ impl Render for Footer { } } div style="display: flex; justify-content: center; align-items: center" { - i class = "fab fa-twitter fa-2x" {} - (PreEscaped("  Tweet Us:")) + (PreEscaped("  Socials:")) @for link in &self.twitter_links { (PreEscaped("          ")) a href=(link.href) target="_blank" style = "color:#666" {(link.text)} diff --git a/pointercrate-core-pages/src/lib.rs b/pointercrate-core-pages/src/lib.rs index ede6ba8af..941c7477f 100644 --- a/pointercrate-core-pages/src/lib.rs +++ b/pointercrate-core-pages/src/lib.rs @@ -3,7 +3,7 @@ use crate::{ head::{Head, HeadLike}, navigation::NavigationBar, }; -use maud::{html, Markup, PreEscaped, Render, DOCTYPE}; +use maud::{html, Markup, Render, DOCTYPE}; pub mod config; pub mod error; @@ -27,17 +27,6 @@ impl HeadLike for PageConfiguration { impl PageConfiguration { pub fn new(site_name: impl Into, nav_bar: NavigationBar, footer: Footer) -> Self { let default_head_html = html! { - (PreEscaped(format!(r#" - - - - "#, config::google_analytics_tag()))); meta http-equiv="Content-Type" content = "text/html; charset=utf-8"; meta http-equiv="Content-Style-Type" content="text/css"; diff --git a/pointercrate-core-pages/src/navigation.rs b/pointercrate-core-pages/src/navigation.rs index f85fd2a58..c9b198a52 100644 --- a/pointercrate-core-pages/src/navigation.rs +++ b/pointercrate-core-pages/src/navigation.rs @@ -71,7 +71,7 @@ impl Render for NavigationBar { nav.center.collapse.underlined.see-through { div.nav-icon style = "margin-right: auto" { a href = "/" aria-label = "Go to homepage" { - img src = (self.logo_path) style="height:15px" alt="Logo"; + img src = (self.logo_path) style="height:1.3rem" alt="Logo"; } } @for item in &self.items { diff --git a/pointercrate-core-pages/src/util.rs b/pointercrate-core-pages/src/util.rs index 39709b6d8..d75038295 100644 --- a/pointercrate-core-pages/src/util.rs +++ b/pointercrate-core-pages/src/util.rs @@ -41,7 +41,7 @@ pub fn dropdown(default_entry: &str, default_item: Markup, filter_items: impl It html! { div.dropdown-menu.js-search.no-stretch { div { - input type="text" data-default=(default_entry) autocomplete="off" style = "color: #444446; font-weight: bold;"; + input type="text" data-default=(default_entry) autocomplete="off" class="click-to-select"; } div.menu { ul { @@ -61,10 +61,10 @@ pub fn simple_dropdown(dropdown_id: &str, default: Option, item div { @match default { Some(ref default) => { - input type="text" autocomplete="off" data-default=(default) style = "color: #444446; font-weight: bold;"; + input type="text" autocomplete="off" data-default=(default) class = "click-to-select"; } None => { - input type="text" autocomplete="off" style = "color: #444446; font-weight: bold;"; + input type="text" autocomplete="off" class = "click-to-select"; } } } diff --git a/pointercrate-core-pages/static/css/core.css b/pointercrate-core-pages/static/css/core.css index 045d93b9d..89dd58522 100644 --- a/pointercrate-core-pages/static/css/core.css +++ b/pointercrate-core-pages/static/css/core.css @@ -164,6 +164,68 @@ background: #5ba324; } +.logo-css { + height: 1.3rem; + content: url("/static/core/thecscl.png"); +} + +/* dark mode */ +@media (prefers-color-scheme: dark) { + body { + background-color: #292929; + color: #e6e6e6; + } + + .fade { + box-shadow: 0px 0px 20px 0px rgba(0, 0, 0, 0.1); + border: 1px #999999 dashed; + } + + @media (min-width: 1072px) { + .fade-hide { + box-shadow: 0px 0px 20px 0px rgba(0, 0, 0, 0.1); + } + } + + /* color modifying styles */ + + .blue { + background-color: #0A95E6; + color: white; + } + + .white { + background-color: #444446; + color: #FFFFFF; + } + + .white.hover:not(.disabled):not([disabled]):hover, + .white.active { + background-color: #616161; + color: #fff; + } + + .white.hover:not(.disabled):not([disabled]):active { + background-color: #5a5a5a; + color: #f0eeee; + } + + .red { + background: #fc426d; + color: white; + } + + .red.hover:not(.disabled):not([disabled]):hover, + .red.active { + background: #e22b56; + } + + .red.hover:not(.disabled):not([disabled]):active, + .red.active { + background: #c7274c; + } +} + /* size modifying styles */ .large { diff --git a/pointercrate-core-pages/static/css/icon.css b/pointercrate-core-pages/static/css/icon.css index b087c2846..7d3832738 100644 --- a/pointercrate-core-pages/static/css/icon.css +++ b/pointercrate-core-pages/static/css/icon.css @@ -168,4 +168,19 @@ i.fa.hover:hover { Flag styles, taken from https://github.com/lipis/flag-icon-css */ -.flag-icon-background{background-size:contain;background-position:50%;background-repeat:no-repeat}.flag-icon{background-size:contain;background-position:50%;background-repeat:no-repeat;position:relative;display:inline-block;width:1.33333333em;line-height:1em}.flag-icon:before{content:'\00a0'} \ No newline at end of file + .flag-icon-background{background-size:contain;background-position:50%;background-repeat:no-repeat}.flag-icon{background-size:contain;background-position:50%;background-repeat:no-repeat;position:relative;display:inline-block;width:1.33333333em;line-height:1em}.flag-icon:before{content:'\00a0'} + + /* dark mode */ + @media (prefers-color-scheme: dark) { + .arrow:before, .arrow:after, .hamburger span, .plus:before, .plus:after { + background-color: #e6e6e6 !important; + } + + .arrow.hover { + opacity: 1; + } + + .arrow.hover:hover { + opacity: 0.5 !important; + } + } \ No newline at end of file diff --git a/pointercrate-core-pages/static/css/main.css b/pointercrate-core-pages/static/css/main.css index 04e944646..49b6df3c8 100644 --- a/pointercrate-core-pages/static/css/main.css +++ b/pointercrate-core-pages/static/css/main.css @@ -47,3 +47,10 @@ input { height: calc(90px + 0.6em); /* fixed header height*/ margin-top: calc(-90px - 0.6em); /* negative fixed header height */ } + +/* dark mode */ +@media (prefers-color-scheme: dark) { + body { + color: #444446; + } +} \ No newline at end of file diff --git a/pointercrate-core-pages/static/css/nav.css b/pointercrate-core-pages/static/css/nav.css index 2a42510bf..431980b68 100644 --- a/pointercrate-core-pages/static/css/nav.css +++ b/pointercrate-core-pages/static/css/nav.css @@ -236,3 +236,24 @@ footer * { flex-wrap: wrap; } } + +/* dark mode */ +@media (prefers-color-scheme: dark) { + header nav { + background: #444446; + } + + /* + * Things for on hover sub-menus + */ + header nav .nav-group .nav-hover-dropdown { + border: 1px solid rgba(211, 211, 211, 1); + } + + /* --------- smartphone ----------- */ + @media (min-width: 1072px) { + header nav .seperator { + border-right: 1px solid #999; + } + } +} \ No newline at end of file diff --git a/pointercrate-core-pages/static/css/ui.css b/pointercrate-core-pages/static/css/ui.css index 9481cb4fb..6ed0f9a2d 100644 --- a/pointercrate-core-pages/static/css/ui.css +++ b/pointercrate-core-pages/static/css/ui.css @@ -441,10 +441,6 @@ textarea:invalid { } @media (min-width: 768px) { - body > div:first-child{ - background-image: url(/static/images/squares3.png); - } - .viewer .paginator { max-width: 35%; } @@ -738,3 +734,200 @@ ul.selection-list li { .tab-active { color: #0881c6; } +.click-to-select { + color: #444446; font-weight: bold; +} + +.approved-info-label { + background-color: #E9FAE3; +} + +.unchecked-info-label { + background-color: #F7F7E0; +} + +.rejected-info-label { + background-color: #F8DCE4; +} + +.under-consideration-info-label { + background-color: #d8eff3; +} + + +/* dark mode */ +@media (prefers-color-scheme: dark) { + body { + scrollbar-color: #363636 #525252; + } + + /* Link styles */ + + a.link { + color: #9b9bff; + } + + /* Misc element styles */ + + a { + color: #f0f0f0; + } + + input, textarea { + color: #e6e6e6 !important; + } + + + + /* Flex layout classes */ + + .overlay { + background-color: rgba(85, 85, 85, 0.8); + } + + .dialog { + background-color: #414141; + box-shadow: 0px 0px 20px 0px rgba(0, 0, 0, 0.3); + border: 1px lightgray dashed; + } + + /* Panel styles */ + + .panel { + background: #414141; + } + + /* General Input Styles */ + .form-input .error { + font-size: 60% !important; + color: coral; + } + + /* When the checkbox is checked, add a blue background */ + .cb-container input:checked ~ .checkmark { + background-color: #0A95E6; + } + + /* Search UI Element */ + + .search { + border: 1px solid #999; + } + + .search.seperated input { + border-left: 1px solid #888; + border-right: 1px solid #888; + } + + /* Dropdown selection menu thingy styles */ + + .dropdown-menu .menu { + background: #4e4e4e; + border: 1px solid #bbb; + box-shadow: 0px 8px 10px #4e4e4e, -10px 8px 15px #4e4e4e, 10px 8px 15px #4e4e4e; + } + + .dropdown-menu > div { + border-bottom: 1px dotted #e6e6e6; + } + + .dropdown-menu div:focus-within { + border: 1px solid #bbb; + box-shadow: 0 0 10px 5px #6d6d6d; + } + + /* + * Tooltip stuff + */ + + .tooltip .tooltiptext { + border: 3px solid #696969; + background: #414141; + } + + /* (Under)lines */ + .underlined { + border-bottom: 1px solid #767676; + } + + .leftlined { + border-left: 1px solid #e6e6e6; + } + + .rightlined { + border-right: 1px solid #e6e6e6; + } + + .overlined { + border-top: 1px solid #e6e6e6; + } + + .menu .underlined { + border-bottom: 1px solid #525252; + } + + .menu ul { + background-color: #414141; + } + + .info-green { + background: #3a653a; + border: 1px solid #6ba15e; + color: #d2e6d2; + } + + .info-green a { + color: #68bf0b !important; + } + + .info-yellow { + background: #fffadd; + border: 1px solid #e0e37b; + color: #4d4707; + } + + .info-red { + background: #805454; + border: 1px solid #bd7878; + color: #d2d2d2; + } + + ul.selection-list { + border: 1px solid #999; + } + + /* Tab panel stuff */ + .tab-active { + color: #0881c6; + } + + .approved-info-label { + background-color: rgba(198, 255, 161, 0.3); + } + + .unchecked-info-label { + background-color: rgba(255, 255, 161, 0.3); + } + + .rejected-info-label { + background-color: rgba(255, 161, 174, 0.3); + } + + .under-consideration-info-label { + background-color: rgba(142, 230, 230, 0.3); + } + + input[type="text"], + input[type="number"], + input[type="url"], + input[type="password"], + input[type="datetime-local"], + textarea { + min-width: 0; /* Firefox and Egde need this */ + + padding: 0px 5px; + + background: rgba(0, 0, 0, 0); + box-sizing: border-box; + } +} \ No newline at end of file diff --git a/pointercrate-core-pages/static/favicon.ico b/pointercrate-core-pages/static/favicon.ico new file mode 100644 index 000000000..442ea26b9 Binary files /dev/null and b/pointercrate-core-pages/static/favicon.ico differ diff --git a/pointercrate-core-pages/static/js/modules/form.js b/pointercrate-core-pages/static/js/modules/form.js index 5b3642256..48414caf4 100644 --- a/pointercrate-core-pages/static/js/modules/form.js +++ b/pointercrate-core-pages/static/js/modules/form.js @@ -1365,4 +1365,4 @@ function parseHeaders(xhr) { result[name] = value; return result; }, {}); -} +} \ No newline at end of file diff --git a/pointercrate-core-pages/static/thecscl.png b/pointercrate-core-pages/static/thecscl.png new file mode 100644 index 000000000..3c37ae562 Binary files /dev/null and b/pointercrate-core-pages/static/thecscl.png differ diff --git a/pointercrate-core/Cargo.toml b/pointercrate-core/Cargo.toml index 065061d21..31b743b7e 100644 --- a/pointercrate-core/Cargo.toml +++ b/pointercrate-core/Cargo.toml @@ -12,4 +12,8 @@ derive_more = "0.99.18" sqlx = { version = "0.8", default-features = false, features = [ "runtime-tokio-native-tls", "macros", "postgres", "chrono", "migrate"] } log = "0.4.22" chrono = {version = "0.4.38", features = ["serde"]} -getrandom = "0.2.15" +dotenv = "0.15.0" +toml = "0.8" +shuttle-runtime = "0.47.0" +config = "0.14.0" +getrandom = "0.2.15" \ No newline at end of file diff --git a/pointercrate-demonlist-api/src/endpoints/demon.rs b/pointercrate-demonlist-api/src/endpoints/demon.rs index 324c81535..10d0fe232 100644 --- a/pointercrate-demonlist-api/src/endpoints/demon.rs +++ b/pointercrate-demonlist-api/src/endpoints/demon.rs @@ -14,7 +14,7 @@ use pointercrate_demonlist::{ Demon, DemonIdPagination, DemonPositionPagination, FullDemon, PatchDemon, PostDemon, }, error::DemonlistError, - player::DatabasePlayer, + player::{recompute_scores, DatabasePlayer}, LIST_ADMINISTRATOR, LIST_MODERATOR, }; use pointercrate_user::auth::ApiToken; @@ -131,3 +131,25 @@ pub async fn delete_creator(demon_id: i32, player_id: i32, mut auth: Auth")] + +/// delete all creators and records from a demon, and delete the demon itself +pub async fn delete_demon_data(demon_id: i32, mut auth: Auth) -> Result { + auth.require_permission(LIST_MODERATOR)?; + + // pass in the id of the demon we're trying to delete + let demon = FullDemon::by_id(demon_id, &mut auth.connection).await?; + + // we don't really need fulldemon here but oh well + FullDemon::by_id(demon.demon.base.id, &mut auth.connection) + .await? + .delete_demon(&mut auth.connection) + .await?; + + recompute_scores(&mut auth.connection).await?; + + auth.commit().await?; + + Ok(Status::NoContent) +} diff --git a/pointercrate-demonlist-api/src/endpoints/record.rs b/pointercrate-demonlist-api/src/endpoints/record.rs index 95352023e..e83b7b923 100644 --- a/pointercrate-demonlist-api/src/endpoints/record.rs +++ b/pointercrate-demonlist-api/src/endpoints/record.rs @@ -383,8 +383,8 @@ fn webhook_embed(record: &FullRecord) -> serde_json::Value { "embeds": [ { "type": "rich", - "title": format!("{}% on {}", record.progress, record.demon.name), - "description": format!("{} just got {}% on {}! Go add their record!", record.player.name, record.progress, record.demon.name), + "title": record.demon.name, + "description": format!("{} just beat {}!", record.player.name, record.demon.name), "footer": { "text": format!("This record has been submitted by submitter #{}", record.submitter.map(|s|s.id).unwrap_or(1)) }, @@ -393,7 +393,7 @@ fn webhook_embed(record: &FullRecord) -> serde_json::Value { "url": record.video }, "thumbnail": { - "url": "https://cdn.discordapp.com/avatars/277391246035648512/b03c85d94dc02084c413a7fdbe2cea79.webp?size=1024" + "url": "https://images-ext-1.discordapp.net/external/N3dQA0vBRLKMt6YDut0IfDFu3mnWr7ov1eqZiN8jPAM/%3Fsize%3D4096/https/cdn.discordapp.com/icons/1236218202829230211/2704cd7c7315665885bc56fd4db9a7b6.png?format=webpheight=1024" }, } ] diff --git a/pointercrate-demonlist-api/src/lib.rs b/pointercrate-demonlist-api/src/lib.rs index 74799c522..362148004 100644 --- a/pointercrate-demonlist-api/src/lib.rs +++ b/pointercrate-demonlist-api/src/lib.rs @@ -73,11 +73,12 @@ pub fn setup(rocket: Rocket) -> Rocket { endpoints::demon::patch, endpoints::demon::post, endpoints::demon::post_creator, - endpoints::demon::delete_creator + endpoints::demon::delete_creator, + endpoints::demon::delete_demon_data ], ) .mount( - "/demonlist/", + "/list/", rocket::routes![ pages::overview, pages::stats_viewer_redirect, diff --git a/pointercrate-demonlist-api/src/pages.rs b/pointercrate-demonlist-api/src/pages.rs index 2eb5949bd..a8ce22102 100644 --- a/pointercrate-demonlist-api/src/pages.rs +++ b/pointercrate-demonlist-api/src/pages.rs @@ -92,7 +92,7 @@ pub async fn demon_permalink(demon_id: i32, pool: &State) -> R let position = MinimalDemon::by_id(demon_id, &mut *connection).await?.position; - Ok(Redirect::to(rocket::uri!("/demonlist", demon_page(position)))) + Ok(Redirect::to(rocket::uri!("/list", demon_page(position)))) } #[rocket::get("/")] diff --git a/pointercrate-demonlist-api/src/ratelimits.rs b/pointercrate-demonlist-api/src/ratelimits.rs index 3d74ea6ae..9f4351aa2 100644 --- a/pointercrate-demonlist-api/src/ratelimits.rs +++ b/pointercrate-demonlist-api/src/ratelimits.rs @@ -4,15 +4,15 @@ use pointercrate_core::ratelimits; ratelimits! { DemonlistRatelimits { - record_submission[3u32 per 1200 per IpAddr] => "You're submitting too many records too fast!", + record_submission[3u32 per 10 per IpAddr] => "You're submitting too many records too fast!", - record_submission_global[20u32 per 3600] => "Too many records are being submitted right now!", + record_submission_global[20u32 per 3600] => "Too many records are being submitted right now! Complain to sphericle if you need this fixed!", new_submitters[7u32 per 3600] => "DDoS protection ratelimit", 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", + add_demon[1u32 per 20] => "Spam Detected, Loser!", } } diff --git a/pointercrate-demonlist-pages/src/account/demons.rs b/pointercrate-demonlist-pages/src/account/demons.rs index a5f0ca422..4fcd13cf2 100644 --- a/pointercrate-demonlist-pages/src/account/demons.rs +++ b/pointercrate-demonlist-pages/src/account/demons.rs @@ -25,10 +25,10 @@ impl AccountPageTab for DemonsTab { fn tab(&self) -> Markup { html! { - i class = "fa fa-shower fa-2x" aria-hidden="true" {} + i class = "fa fa-solid fa-shapes fa-2x" aria-hidden="true" {} (PreEscaped("  ")) b { - "Demons" + "Levels" } } } @@ -41,22 +41,24 @@ impl AccountPageTab for DemonsTab { (demon_submitter()) div.panel.fade { h2.underlined.pad { - "Demon Manager" + "Level Manager" } div.flex.viewer { (filtered_paginator("demon-pagination", "/api/v2/demons/listed/")) p.viewer-welcome { - "Click on a demon on the left to get started!" + "Click on a level on the left to get started!" } div.viewer-content { div.flex.col{ + h3 style = "font-size:1.1em; margin: 10px 0" { - "Demon #" + "Level #" i #demon-demon-id {} " - " i.fa.fa-pencil-alt.clickable #demon-name-pen aria-hidden = "true" {} (PreEscaped(" ")) i #demon-demon-name {} } + span.plus.cross.hover #demon_x {} iframe."ratio-16-9"#demon-video style="width:90%; margin: 15px 5%" allowfullscreen="" {"Verification Video"} p.info-red.output style = "margin: 10px" {} @@ -87,13 +89,7 @@ impl AccountPageTab for DemonsTab { br; span #demon-position {} } - span{ - b { - i.fa.fa-pencil-alt.clickable #demon-requirement-pen aria-hidden = "true" {} " Requirement:" - } - br; - span #demon-requirement {} - } + } div.stats-container.flex.space { span{ @@ -120,6 +116,13 @@ impl AccountPageTab for DemonsTab { span #demon-creators {} } + span{ + b { + i.fa.fa-pencil-alt.clickable #demon-level_id-pen aria-hidden = "true" {} " Level ID:" + } + br; + span #demon-level_id {} + } } } } @@ -132,12 +135,12 @@ impl AccountPageTab for DemonsTab { } (change_name_dialog()) (change_position_dialog()) - (change_requirement_dialog()) (change_video_dialog()) (change_thumbnail_dialog()) (change_verifier_dialog()) (change_publisher_dialog()) (add_creator_dialog()) + (change_level_id_dialog()) } } } @@ -147,11 +150,11 @@ pub(super) fn submit_panel() -> Markup { section.panel.fade.js-scroll-anim data-anim = "fade" { div.underlined { h2 { - "Add Demon:" + "Add level:" } } a.blue.hover.button.js-scroll data-destination = "demon-submitter" data-reveal = "true" { - "Add a demon!" + "Add a level!" } } } @@ -183,32 +186,6 @@ fn change_name_dialog() -> Markup { } } -fn change_requirement_dialog() -> Markup { - html! { - div.overlay.closable { - div.dialog #demon-requirement-dialog { - span.plus.cross.hover {} - h2.underlined.pad { - "Change demon requirement:" - } - p style = "max-width: 400px"{ - "Change the record requirement for this demon. Has be lie between 0 and and 100 (inclusive)." - } - form.flex.col novalidate = "" { - p.info-red.output {} - p.info-green.output {} - span.form-input #demon-requirement-edit { - label for = "requirement" {"Requirement:"} - input name = "requirement" type = "number" min = "0" max="100" required = ""; - p.error {} - } - input.button.blue.hover type = "submit" style = "margin: 15px auto 0px;" value = "Edit"; - } - } - } - } -} - fn change_position_dialog() -> Markup { html! { div.overlay.closable { @@ -323,6 +300,31 @@ fn add_creator_dialog() -> Markup { "creator" ) } +fn change_level_id_dialog() -> Markup { + html! { + div.overlay.closable { + div.dialog #demon-level_id-dialog { + span.plus.cross.hover {} + h2.underlined.pad { + "Change Level ID:" + } + p style = "max-width: 400px"{ + "Change the ID for this level." + } + form.flex.col novalidate = "" { + p.info-red.output {} + p.info-green.output {} + span.form-input #demon-level_id-edit { + label for = "level_id" {"ID:"} + input required="" name = "level_id" type = "number"; + p.error {} + } + input.button.blue.hover type = "submit" style = "margin: 15px auto 0px;" value = "Edit"; + } + } + } + } +} fn demon_submitter() -> Markup { html! { @@ -331,20 +333,20 @@ fn demon_submitter() -> Markup { div.flex { form #demon-submission-form novalidate = "" { div.underlined { - h2 {"Add demon:"} + h2 {"Add level:"} } p.info-red.output {} p.info-green.output {} span.form-input.flex.col #demon-add-name { label for = "name" { - "Demon name:" + "Name:" } input type = "text" name = "name" required=""; p.error {} } span.form-input.flex.col #demon-add-level-id { label for = "level_id" { - "Geometry Dash Level ID:" + "Level ID:" } input type = "number" name = "level_id" required min = "1"; p.error {} @@ -356,11 +358,18 @@ fn demon_submitter() -> Markup { input type = "number" name = "position" required="" min="1"; p.error {} } - span.form-input.flex.col #demon-add-requirement { + span.form-input.flex.col style="display: none" #demon-add-requirement { label for = "requirement" { "Requirement:" } - input type = "number" name = "requirement" required="" min="0" max = "100"; + input type = "number" name = "requirement" required="" value="100" min="100" max = "100"; + p.error {} + } + span.form-input.flex.col #demon-add-video { + label for = "video" { + "Verification Video:" + } + input type = "url" required = "" name = "video"; p.error {} } span.form-input.flex.col data-type = "dropdown" { @@ -375,20 +384,15 @@ fn demon_submitter() -> Markup { (player_selection_dropdown("demon-add-publisher", "/api/v1/players/", "name", "publisher")) p.error {} } - 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 {} } - input.button.blue.hover type = "submit" style = "margin: 15px auto 0px;" value="Add Demon"; + + input.button.blue.hover type = "submit" style = "margin: 15px auto 0px;" value="Add"; } } } diff --git a/pointercrate-demonlist-pages/src/account/list_integration.rs b/pointercrate-demonlist-pages/src/account/list_integration.rs index 956878947..ea6f017e0 100644 --- a/pointercrate-demonlist-pages/src/account/list_integration.rs +++ b/pointercrate-demonlist-pages/src/account/list_integration.rs @@ -85,7 +85,6 @@ impl AccountPageTab for ListIntegrationTab { i style="margin-right: 15px;" { "Verified" } - span.arrow.hover {} }, Some(_) => i{"Unverified"}, _ => {} @@ -94,10 +93,11 @@ impl AccountPageTab for ListIntegrationTab { } @if let Some(ref claim) = player_claim { @if claim.verified { - div.overlined.pad.js-collapse-content #claims-claim-panel style="display:none" { + div.overlined.pad.js-collapse-content #claims-claim-panel { p.info-red.output style = "margin: 10px 0" {} p.info-green.output style = "margin: 10px 0" {} - div.flex.no-stretch style="justify-content: space-between; align-items: center" { + div.flex.no-stretch style="justify-content: space-between; align-items: center; display: none;" { + b { "Geolocate statsviewer flag:" } @@ -105,11 +105,12 @@ impl AccountPageTab for ListIntegrationTab { "Go" } } - p { - "Clicking the above button let's you set your claimed player's statsviewer flag via IP Geolocation. To offer this functionality, pointercrate uses " + p style = "display: none;" { + "Clicking the above button lets you set your claimed player's statsviewer flag via IP Geolocation. To offer this functionality, pointercrate uses " a.link href = "https://www.abstractapi.com/ip-geolocation-api" { "abstract's IP geolocation API"} ". Clicking the above button also counts as your consent for pointercrate to send your IP to abstract." } + div.cb-container.flex.no-stretch style="justify-content: space-between; align-items: center" { b { "Lock Submissions:" @@ -125,6 +126,10 @@ impl AccountPageTab for ListIntegrationTab { p { "Whether submissions for your claimed player should be locked, meaning only you will be able to submit records for your claimed player (and only while logged in to this account holding the verified claim)" } + + p { + "To set your player nationality, join the " a.link href = {"https://discord.com/invite/W7Eqqj8NG2"} {"Discord server"} " and create a thread in " a.link href = {"https://discord.com/channels/1236218202829230211/1260745449040777338/1260745449040777338"} {"#website-help."} " Geolocation coming soon!" + } } } } @@ -135,12 +140,12 @@ impl AccountPageTab for ListIntegrationTab { h2.pad.underlined { "Your claimed player's records" } - p { + p #claimed-records-info { "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 background color of each record tells you whether the record is " - span style = "background-color: #E9FAE3" { "Approved" } ", " - span style = "background-color: #F7F7E0" { "Unchecked" } ", " - span style = "background-color: #F8DCE4" { "Rejected" } " or " - span style = "background-color: #D8EFF3" { "Under Consideration" } "." + span class = "approved-info-label" { "Approved" } ", " + span class = "unchecked-info-label" { "Unchecked" } ", " + span class = "rejected-info-label" { "Rejected" } " or " + span class = "under-consideration-info-label" { "Under Consideration" } "." } (paginator("claims-record-pagination", "/api/v1/records/")) } @@ -179,9 +184,9 @@ impl AccountPageTab for ListIntegrationTab { "Claiming 101" } p { - "Player claiming is the process of associated a demonlist player with a pointercrate user account. A verified claim allows you to to modify some of the player's properties, such as nationality. " + "Player claiming is the process of associated a list player with a pointercrate user account. A verified claim allows you to to modify some of the player's properties, such as nationality. " br; - "To initiate a claim, click the pen left of the 'Claimed Player' heading. Once initiated, you have an unverified claim on a player. These claims will then be manually verified by members of the pointercrate team. You can request verification in " a.link href=(self.0) {"this discord server"} "." + "To initiate a claim, click the pen left of the 'Claimed Player' heading. Once initiated, you have an unverified claim on a player. These claims will then be manually verified by members of the pointercrate team. You can request verification in " a.link href=(self.0) {"our discord server"} "." br; "You cannot initiate a claim on a player that already has a verified claim by a different user on it. " } diff --git a/pointercrate-demonlist-pages/src/account/players.rs b/pointercrate-demonlist-pages/src/account/players.rs index f9050f1c7..3a3d1f54d 100644 --- a/pointercrate-demonlist-pages/src/account/players.rs +++ b/pointercrate-demonlist-pages/src/account/players.rs @@ -79,7 +79,7 @@ impl AccountPageTab for PlayersPage { br; div.dropdown-menu.js-search #edit-player-banned style = "max-width: 50px" { div { - input type="text" style = "color: #444446; font-weight: bold;"; + input type="text" class = "click-to-select"; } div.menu { ul { @@ -99,7 +99,7 @@ impl AccountPageTab for PlayersPage { } div.dropdown-menu.js-search #edit-player-nationality data-default = "None" { div { - input type="text" style = "color: #444446; font-weight: bold;"; + input type="text" class = "click-to-select"; } div.menu { ul { diff --git a/pointercrate-demonlist-pages/src/account/records.rs b/pointercrate-demonlist-pages/src/account/records.rs index 9a7e26158..f88c7eecf 100644 --- a/pointercrate-demonlist-pages/src/account/records.rs +++ b/pointercrate-demonlist-pages/src/account/records.rs @@ -76,8 +76,9 @@ impl AccountPageTab for RecordsPage { (player_selector()) (submit_panel()) } - (change_progress_dialog()) + (change_video_dialog()) + (change_enjoyment_dialog()) (change_holder_dialog()) (change_demon_dialog(&demons[..])) } @@ -91,7 +92,7 @@ fn record_manager(demons: &[Demon]) -> Markup { "Record Manager - " (dropdown("All", html! { li.white.hover.underlined data-value = "All" - {"All Demons"} + {"All levels"} }, demons.iter().map(|demon| html!(li.white.hover data-value = (demon.base.id) data-display = (demon.base.name) {b{"#"(demon.base.position) " - " (demon.base.name)} br; {"by "(demon.publisher.name)}})))) } div.flex.viewer { @@ -133,6 +134,7 @@ fn record_manager(demons: &[Demon]) -> Markup { a.link #record-video-link target = "_blank" {} } } + div.stats-container.flex.space { span { b { "Raw Footage:" } @@ -143,7 +145,7 @@ fn record_manager(demons: &[Demon]) -> Markup { div.stats-container.flex.space { span { b { - i.fa.fa-pencil-alt.clickable #record-demon-pen aria-hidden = "true" {} " Demon:" + i.fa.fa-pencil-alt.clickable #record-demon-pen aria-hidden = "true" {} " Level:" } br; span #record-demon {} @@ -159,24 +161,24 @@ fn record_manager(demons: &[Demon]) -> Markup { div.stats-container.flex.space { span { b { - i.fa.fa-pencil-alt.clickable #record-progress-pen aria-hidden = "true" {} " Progress:" + "Submitter ID:" } br; - span #record-progress {} + span #record-submitter {} } span { b { - "Submitter ID:" + i.fa.fa-pencil-alt.clickable #record-enjoyment-pen aria-hidden = "true" {} " Enjoyment:" } br; - span #record-submitter {} + span #record-enjoyment {} } } - span.button.red.hover #record-delete style = "margin: 15px auto 0px" {"Delete Record"}; } + span.button.red.hover #record-delete style = "margin: 15px auto 0px" {"Delete Record"}; } - } + } } } @@ -188,10 +190,10 @@ fn manager_help() -> Markup { "Manage Records" } p { - "Use the list on the left to select records for editing/viewing. Use the panel on the right to filter the record list by status, player, etc.. Clicking the 'All Demons' field at the top allows to filter by demon." + "Use the list on the left to select records for editing/viewing. Use the panel on the right to filter the record list by status, player, etc.. Clicking the 'All levels' field at the top allows to filter by level." } p { - "There are four possible record states a record can be in: " i { "'rejected', 'approved', 'submitted'" } " and " i { "'under consideration'" } ". For simplicity of explanation we will assume that 'Bob' is a player and 'Cataclysm' is a demon he has a record on." + "There are four possible record states a record can be in: " i { "'rejected', 'approved', 'submitted'" } " and " i { "'under consideration'" } ". For simplicity of explanation we will assume that 'Bob' is a player and 'Cataclysm' is a level he has a record on." ul { li { b{"Rejected: "} "If the record is 'rejected', it means that Bob has no other record in other states on Cataclysm and no submissions for Bob on Cataclysm are possible. Conversely, this means if Bob has a record on Catalysm that's not rejected, we immediately know that no rejected record for Bob on Cataclysm exists. " @@ -219,6 +221,10 @@ fn manager_help() -> Markup { b { "Note: " } "Banning a submitter will delete all their submissions that still have the status 'Submitted'. Records submitted by them that were already accepted/rejected will not be affected" } + p { + b { "Note: " } + "The 'Submit a Record' button on this page will automatically set the record's status to 'approved'." + } } } } @@ -262,13 +268,13 @@ fn player_selector() -> Markup { "Filter by player" } p { - "Players can be uniquely identified by name and ID. Entering either in the appropriate place below will filter the view on the left. Reset by clicking \"Find ...\" when the text field is empty." + "Players can be uniquely identified by name and ID. Entering either in the appropriate place below will filter the view on the left. Click 'Find' while the text box is empty to reset the filter." } form.flex.col.underlined.pad #record-filter-by-player-id-form novalidate = "" { p.info-red.output {} span.form-input #record-player-id { label for = "id" {"Player ID:"} - input required = "" type = "number" name = "id" min = "0" style="width:93%"; // FIXME: I have no clue why the input thinks it's a special snowflake and fucks up its width, but I dont have the time to fix it + input required = "" type = "number" name = "id" min = "0" style="width:93%"; // FIXME: I have no clue why the input thinks it's a special snowflake and fucks up its width, but I dont have the time to fix it --- hey stadust what the fuck are you talking about -sphericle p.error {} } input.button.blue.hover type = "submit" style = "margin: 15px auto 0px;" value="Find by ID"; @@ -330,32 +336,6 @@ fn note_adder() -> Markup { } } -fn change_progress_dialog() -> Markup { - html! { - div.overlay.closable { - div.dialog #record-progress-dialog { - span.plus.cross.hover {} - h2.underlined.pad { - "Change record progress:" - } - p style = "max-width: 400px"{ - "Change the progress value of this record. Has to be between the demon's record requirement and 100 (inclusive)." - } - form.flex.col novalidate = "" { - p.info-red.output {} - p.info-green.output {} - span.form-input #record-progress-edit { - label for = "progress" {"Progress:"} - input name = "progress" type = "number" min = "0" max="100" required = ""; - p.error {} - } - input.button.blue.hover type = "submit" style = "margin: 15px auto 0px;" value = "Edit"; - } - } - } - } -} - fn change_video_dialog() -> Markup { html! { div.overlay.closable { @@ -393,17 +373,40 @@ fn change_holder_dialog() -> Markup { ) } +fn change_enjoyment_dialog() -> Markup { + html! { + div.overlay.closable { + div.dialog #record-enjoyment-dialog { + span.plus.cross.hover {} + h2.underlined.pad { + "Change enjoyment:" + } + form.flex.col novalidate = "" { + p.info-red.output {} + p.info-green.output {} + span.form-input #record-enjoyment-edit { + label for = "enjoyment" {"Enjoyment:"} + input name = "enjoyment" type = "number"; + p.error {} + } + input.button.blue.hover type = "submit" style = "margin: 15px auto 0px;" value = "Edit"; + } + } + } + } +} + fn change_demon_dialog(demons: &[Demon]) -> Markup { html! { div.overlay.closable { div.dialog #record-demon-dialog style="overflow: initial;" { span.plus.cross.hover {} h2.underlined.pad { - "Change record demon:" + "Change record level:" } div.flex.col { p { - "Change the demon associated with this record. Search up the demon this record should be associated with below. Then click it to modify the record" + "Change the level associated with this record. Search up the level this record should be associated with below. Then click it to modify the record" } (demon_dropdown("edit-demon-record", demons.iter())) } diff --git a/pointercrate-demonlist-pages/src/components/submitter.rs b/pointercrate-demonlist-pages/src/components/submitter.rs index 4dfdce0c7..44ae11a63 100644 --- a/pointercrate-demonlist-pages/src/components/submitter.rs +++ b/pointercrate-demonlist-pages/src/components/submitter.rs @@ -28,10 +28,11 @@ impl Render for RecordSubmitter<'_> { p.info-red.output {} p.info-green.output {} h3 { - "Demon:" + "Level:" } + // Only demons in the top " (config::extended_list_size()) " are accepted. This excludes legacy demons! p { - "The demon the record was made on. Only demons in the top " (config::extended_list_size()) " are accepted. This excludes legacy demons!" + "The level the record was made on." } span.form-input data-type = "dropdown" { (demon_dropdown("id_demon", self.demons.iter().filter(|demon| demon.base.position <= config::extended_list_size()))) @@ -47,44 +48,46 @@ impl Render for RecordSubmitter<'_> { (player_selection_dropdown("id_player", "/api/v1/players/", "name", "player")) p.error {} } + span.form-input.flex.col style="display: none" #id_progress { + input type = "number" name = "progress" required="" value="100" placeholder = "e. g. '50', '98'" min="0" max="100"; + p.error {} + } h3 { - "Progress:" + "(Optional) Enjoyment:" } p { - "The progress made as percentage. Only values greater than or equal to the demons record requirement and smaller than or equal to 100 are accepted!" + "A rating out of 10 based on how much you enjoyed this level. If it was an 8 out of 10, for example, write 8." } - span.form-input.flex.col #id_progress { - input type = "number" name = "progress" required="" placeholder = "e. g. '50', '98'" min="0" max="100"; + span.form-input.flex.col #id_enjoyment { + input type = "number" name = "enjoyment" placeholder = "e.g. 8 = 8/10" min="1" max="10"; p.error {} } h3 { "Video: " } p { - "A proof video of the legitimacy of the given record. If the record was achieved on stream, but wasn't uploaded anywhere else, please provide a twitch link to that stream." + "The video to be public on the site. This video can be edited if you also submit the unedited version below." br {} i { "Note: " } "Please pay attention to only submit well-formed URLs!" } span.form-input.flex.col #id_video { - input type = "url" name = "video" required = "" placeholder = "e.g. 'https://youtu.be/cHEGAqOgddA'" ; + input type = "url" name = "video" required = "" placeholder = "e.g. 'https://youtu.be/EUBtwD-e2R0'" ; p.error {} } h3 { - "Raw footage: " + "(Optional) Unedited completion: " } p { - "The unedited and untrimmed video for this completion, uploaded to a non-compressing (e.g. not YouTube) file-sharing service such as google drive. If the record was achieved on stream (meaning there is no recording), please provide a link to the stream VOD" + "If you want to edit your record's video, you must submit the unedited completion for review. The 'Video' field above will be the video public on the site, however." } p { - "Any personal information possibly contained within raw footage (e.g. names, sensitive conversations) will be kept strictly confidential and will not be shared outside of the demonlist team. Conversely, you acknowledge that you might inadvertently share such information by providing raw footage. You have the right to request deletion of your record note by contacting a list administrator." - } - p { - i {"Note: "} "This is required for every record submitted to the list!" + "Any personal information possibly contained within your unedited video (e.g. names, sensitive conversations) will be kept strictly confidential and will not be shared outside of the list team. Conversely, you acknowledge that you might inadvertently share such information. You have the right to request deletion of this video by contacting a list administrator." } + span.form-input.flex.col #submit-raw-footage { - input type = "url" name = "raw_footage" required = "" placeholder = "https://drive.google.com/file/d/.../view?usp=sharing" {} + input type = "url" name = "raw_footage" placeholder = "This does not need to be a YouTube link!" {} p.error {} } h3 { @@ -94,11 +97,11 @@ impl Render for RecordSubmitter<'_> { "Provide any additional notes you'd like to pass on to the list moderator receiving your submission." } span.form-input.flex.col #submit-note { - textarea name = "note" placeholder = "Your dreams and hopes for this record... or something like that" {} + textarea name = "note" placeholder = "e.g. this level SUCKS and it should be removed I HATE THIS LEVEL" {} p.error {} } p { - "By submitting the record you acknowledge the " a.link href = "/guidelines" {"submission guidelines"} "." + "By submitting the record you acknowledge the " a.link href = "https://docs.google.com/document/d/1zW2tOWRi-qTxd2pM2FrParnVTzJjzRiGKIGGSJycKuI/edit?usp=sharing" {"submission guidelines"} "." } input.button.blue.hover type = "submit" style = "margin: 15px auto 0px;" value="Submit record"; } diff --git a/pointercrate-demonlist-pages/src/components/team.rs b/pointercrate-demonlist-pages/src/components/team.rs index 9fbf54172..cdf3a665c 100644 --- a/pointercrate-demonlist-pages/src/components/team.rs +++ b/pointercrate-demonlist-pages/src/components/team.rs @@ -26,12 +26,9 @@ impl Render for Team { section.panel.fade.js-scroll-anim #editors data-anim = "fade" { div.underlined { h2 { - "List Editors" + "List Team" } } - p { - "Contact any of these people if you have problems with the list or want to see a specific thing changed." - } ul style = "line-height: 30px" { @for admin in &self.admins { b { @@ -39,18 +36,10 @@ impl Render for Team { } } @for moderator in &self.moderators { - (maybe_link(moderator)) - } - } - div.underlined { - h2 { - "List Helpers" + b { + (maybe_link(moderator)) + } } - } - p { - "Contact these people if you have any questions regarding why a specific record was rejected. Do not needlessly bug them about checking submissions though!" - } - ul style = "line-height: 30px" { @for helper in &self.helpers { (maybe_link(helper)) } diff --git a/pointercrate-demonlist-pages/src/components/time_machine.rs b/pointercrate-demonlist-pages/src/components/time_machine.rs index 41d65b4fa..51d14a979 100644 --- a/pointercrate-demonlist-pages/src/components/time_machine.rs +++ b/pointercrate-demonlist-pages/src/components/time_machine.rs @@ -51,7 +51,7 @@ impl Render for Tardis { Tardis::Activated { destination, show_destination, ..} if *show_destination => { div.panel.fade.blue.flex style="align-items: center;" { span style = "text-align: end"{ - "You are currently looking at the demonlist how it was on" + "You are currently looking at the list how it was on" br; b { @match destination.day() { @@ -62,7 +62,7 @@ impl Render for Tardis { } } } - a.white.button href = "/demonlist/" onclick=r#"document.cookie = "when=""# style = "margin-left: 15px"{ b{"Go to present" }} + a.white.button href = "/list/" onclick=r#"document.cookie = "when=""# style = "margin-left: 15px"{ b{"Go to present" }} } }, _ => {} @@ -74,12 +74,12 @@ impl Render for Tardis { h2 {"Time Machine"} } p { - "Enter the date you want to view the demonlist at below. For technical reasons, the earliest possible date is January 4th 2017. Note however that data before August 4th 2017 is only provided on a best-effort basis and not guaranteed to be 100% accurate. Particularly data from before April 4th 2017 contains significant errors!" + "Enter the date you want to view the list at below. The earliest possible date is July 9th, 2024." } div.flex { span.form-input #time-machine-destination data-type = "datetime-local" { h3 {"Destination:"} - input name="time-machine-destination" type="datetime-local" min="2017-01-04T00:00" required; + input name="time-machine-destination" type="datetime-local" min="2024-01-04T00:00" required; p.error {} } } diff --git a/pointercrate-demonlist-pages/src/demon_page.rs b/pointercrate-demonlist-pages/src/demon_page.rs index 62cdecf35..884ad0941 100644 --- a/pointercrate-demonlist-pages/src/demon_page.rs +++ b/pointercrate-demonlist-pages/src/demon_page.rs @@ -47,7 +47,7 @@ impl From for PageFragment { impl DemonPage { fn title(&self) -> String { let mut title = format!( - "{} - Geometry Dash Demonlist", + "{} - Clicksync Challenge List", self.data.demon.base.name // FIXME: flatten the structs, holy shit ); @@ -80,21 +80,21 @@ impl DemonPage { "@type": "ListItem", "position": 1, "item": {{ - "@id": "https://pointercrate.com/", - "name": "pointercrate" + "@id": "https://cscl.shuttleapp.rs/", + "name": "cscl" }} }},{{ "@type": "ListItem", "position": 2, "item": {{ - "@id": "https://pointercrate.com/demonlist/", - "name": "demonlist" + "@id": "https://cscl.shuttleapp.rs/list/", + "name": "list" }} }},{{ "@type": "ListItem", "position": 3, "item": {{ - "@id": "https://pointercrate.com/demonlist/{0}/", + "@id": "https://cscl.shuttleapp.rs/list/{0}/", "name": "{1}" }} }} @@ -102,7 +102,7 @@ impl DemonPage { }}, "name": "#{0} - {1}", "description": "{2}", - "url": "https://pointercrate.com/demonlist/{0}/" + "url": "https://cscl.shuttleapp.rs/list/{0}/" }} "##, self.data.position(), self.data.name(), self.description()))) @@ -191,10 +191,10 @@ impl DemonPage { } aside.right { (self.team) + (super::nongs_panel()) (super::rules_panel()) (submit_panel()) (stats_viewer_panel()) - (super::discord_panel()) } } } @@ -205,27 +205,38 @@ impl DemonPage { let name = &self.data.demon.base.name; let score100 = self.data.demon.score(100); - let score_requirement = self.data.demon.score(self.data.demon.requirement); + + let mut total_enjoyment: i32 = 0; + let mut with_enjoyment = 0; + + for record in &self.data.records { + if let Some(ref enjoyment) = record.enjoyment { + total_enjoyment += *enjoyment; + with_enjoyment += 1; + } + } + + let avg_enjoyment: f32 = total_enjoyment as f32 / with_enjoyment as f32; html! { section.panel.fade.js-scroll-anim data-anim = "fade" { div.underlined { - h1 #demon-heading style = "overflow: hidden"{ + h1 #demon-heading style = "overflow: hidden;cursor: pointer;" aria-label = "Copy to clipboard" { @if self.data.demon.base.position != 1 { - a href=(format!("/demonlist/{:?}", self.data.demon.base.position - 1)) { + a href=(format!("/list/{:?}", self.data.demon.base.position - 1)) { i class="fa fa-chevron-left" style="padding-right: 5%" {} } } (name) @if position as usize != self.demonlist.len() { - a href=(format!("/demonlist/{:?}", position + 1)) { + a href=(format!("/list/{:?}", position + 1)) { i class="fa fa-chevron-right" style="padding-left: 5%" {} } } } (PreEscaped(format!(r#" "#, self.data.demon.base.id))) h3 { @@ -336,24 +347,75 @@ impl DemonPage { } } } - @if position <= list_config::extended_list_size() { + + span { + b { + "Points: " + } + br; + (format!("{:.2}", score100)) + } + + span { + b { + "Enjoyment: " + } + br; + @if avg_enjoyment > 0.0 { + (format!("{:?}/10", avg_enjoyment)) + + } + @else { + "N/A" + } + } + + @if self.data.demon.level_id.unwrap_or_default() == 0 { span { b { - "Demonlist score (100%): " + "Level ID:" } br; - (format!("{:.2}", score100)) + p { + "Missing, please contact staff" + } } } - @if position <= list_config::list_size(){ + + // Sync Fantasy + + @else if self.data.demon.level_id.unwrap_or_default() == 105823909 { span { b { - "Demonlist score (" (self.data.demon.requirement) "%): " + "GDShare File:" } br; - (format!("{:.2}", score_requirement)) + a href = {"https://drive.google.com/file/d/1ljuoN_Cs3Ommdh1OL9O0tS_lZmxVBxQg/view"} {"Download"} + } + } + + // X + + @else if self.data.demon.level_id.unwrap_or_default() == 106039651 { + span { + b { + "GDShare File:" + } + br; + a href = {"https://drive.google.com/file/d/1ByRGNWc3EGd-OcOBFE62g82Vo_vfPTfZ/view"} {"Download"} + } + } + + @else { + span { + b { + "Level ID:" + } + br; + (format!("{:?}", self.data.demon.level_id.unwrap_or_default())) } } + } } } @@ -370,24 +432,12 @@ impl DemonPage { h2 { "Records" } - @if position <= list_config::list_size() { - h3 { - (self.data.demon.requirement) "% or better required to qualify" - } - } - @else if position <= list_config::extended_list_size() { - h3 { - "100% required to qualify" - } - } + + @if !self.data.records.is_empty() { h4 { - @let records_registered_100_count = self.data.records.iter().filter(|record| record.progress == 100).count(); (self.data.records.len()) - " records registered, out of which " - (records_registered_100_count) - @if records_registered_100_count == 1 { " is" } @else { " are" } - " 100%" + " records registered." } } } @@ -410,7 +460,7 @@ impl DemonPage { "Record Holder" } th.blue { - "Progress" + "Enjoyment" } th.video-link.blue { "Video Proof" @@ -434,7 +484,9 @@ impl DemonPage { } } td { - (record.progress) "%" + @if let Some(ref enjoyment) = record.enjoyment { + (format!("{}/10", enjoyment)) + } } td.video-link { @if let Some(ref video) = record.video { diff --git a/pointercrate-demonlist-pages/src/lib.rs b/pointercrate-demonlist-pages/src/lib.rs index 26c0b5d37..ed9c5ba99 100644 --- a/pointercrate-demonlist-pages/src/lib.rs +++ b/pointercrate-demonlist-pages/src/lib.rs @@ -16,14 +16,13 @@ struct ListSection { } static MAIN_SECTION: ListSection = ListSection { - name: "Main List", - description: "The main section of the Demonlist. These demons are the hardest rated levels in the game. Records are accepted above a \ - given threshold and award a large amount of points!", + name: "Search for a challenge here!", + description: "", id: "mainlist", numbered: true, }; -static EXTENDED_SECTION: ListSection = ListSection { +/* static EXTENDED_SECTION: ListSection = ListSection { name: "Extended List", description: "These are demons that dont qualify for the main section of the list, but are still of high relevance. Only 100% records \ are accepted for these demons! Note that non-100% that were submitted/approved before a demon fell off the main list \ @@ -40,6 +39,7 @@ static LEGACY_SECTION: ListSection = ListSection { id: "legacy", numbered: false, }; +*/ fn dropdowns(all_demons: &[&Demon], current: Option<&Demon>) -> Markup { let (main, extended, legacy) = if all_demons.len() < config::list_size() as usize { @@ -59,12 +59,14 @@ fn dropdowns(all_demons: &[&Demon], current: Option<&Demon>) -> Markup { html! { nav.flex.wrap.m-center.fade #lists style="text-align: center;" { - // The drop down for the main list: + // The drop down for the main list: (dropdown(&MAIN_SECTION, main, current)) // The drop down for the extended list: + /* (dropdown(&EXTENDED_SECTION, extended, current)) // The drop down for the legacy list: (dropdown(&LEGACY_SECTION, legacy, current)) + */ } } } @@ -72,7 +74,7 @@ fn dropdowns(all_demons: &[&Demon], current: Option<&Demon>) -> Markup { fn dropdown(section: &ListSection, demons: &[&Demon], current: Option<&Demon>) -> Markup { let format = |demon: &Demon| -> Markup { html! { - a href = {"/demonlist/permalink/" (demon.base.id) "/"} { + a href = {"/list/permalink/" (demon.base.id) "/"} { @if section.numbered { {"#" (demon.base.position) " - " (demon.base.name)} br ; @@ -99,7 +101,7 @@ fn dropdown(section: &ListSection, demons: &[&Demon], current: Option<&Demon>) - div.see-through.fade.dropdown #(section.id) { div.search.js-search.seperated style = "margin: 10px" { - input placeholder = "Filter..." type = "text" {} + input type = "text" {} } p style = "margin: 10px" { (section.description) @@ -130,21 +132,26 @@ fn rules_panel() -> Markup { "Guidelines" } p { - "All demonlist operations are carried out in accordance to our guidelines. Be sure to check them before submitting a record to ensure a flawless experience!" + "Read this before submitting a challenge or record to ensure a flawless experience." } - a.blue.hover.button href = "/guidelines/" { + a.blue.hover.button href = "https://docs.google.com/document/d/1zW2tOWRi-qTxd2pM2FrParnVTzJjzRiGKIGGSJycKuI/edit?usp=sharing" { "Read the guidelines!" } } } } -fn discord_panel() -> Markup { +fn nongs_panel() -> Markup { html! { - section.panel.fade.js-scroll-anim #discord data-anim = "fade" { - iframe.js-delay-attr style = "width: 100%; height: 400px;" allowtransparency="true" frameborder = "0" data-attr = "src" data-attr-value = "https://discordapp.com/widget?id=395654171422097420&theme=light" {} + section #rules.panel.fade.js-scroll-anim data-anim = "fade" { + h2.underlined.pad.clickable { + "Nongs" + } p { - "Join the official Demonlist discord server, where you can get in touch with the demonlist team!" + "Some challenges have songs that aren't on " a.link href = {"https://www.newgrounds.com"} {"Newgrounds"} ", so you can find all NONG songs in this Google Drive folder." + } + a.blue.hover.button href = "https://drive.google.com/drive/folders/1_P5D7jKT8oUcjk_vzWt5riqouOnnwRqB?usp=sharing" { + "Find a nong!" } } } diff --git a/pointercrate-demonlist-pages/src/overview.rs b/pointercrate-demonlist-pages/src/overview.rs index f7f7966c1..1e6e51843 100644 --- a/pointercrate-demonlist-pages/src/overview.rs +++ b/pointercrate-demonlist-pages/src/overview.rs @@ -34,7 +34,7 @@ fn demon_panel(demon: &Demon, current_position: Option) -> Markup { } div style = "padding-left: 15px" { h2 style = "text-align: left; margin-bottom: 0px" { - a href = {"/demonlist/permalink/" (demon.base.id) "/"} { + a href = {"/list/permalink/" (demon.base.id) "/"} { "#" (demon.base.position) (PreEscaped(" – ")) (demon.base.name) } } @@ -60,7 +60,7 @@ fn demon_panel(demon: &Demon, current_position: Option) -> Markup { impl From for PageFragment { fn from(page: OverviewPage) -> Self { - PageFragment::new("Geometry Dash Demonlist", "The official pointercrate Demonlist!") + PageFragment::new("The CSCL", "The Clicksync Challenge List!") .module("/static/core/js/modules/form.js") .module("/static/demonlist/js/modules/demonlist.js") .module("/static/demonlist/js/demonlist.js") @@ -86,23 +86,23 @@ impl OverviewPage { "@type": "ListItem", "position": 1, "item": { - "@id": "https://pointercrate.com/", - "name": "pointercrate" + "@id": "https://cscl.shuttleapp.rs/", + "name": "cscl" } }, { "@type": "ListItem", "position": 2, "item": { - "@id": "https://pointercrate.com/demonlist/", - "name": "demonlist" + "@id": "https://cscl.shuttleapp.rs/list/", + "name": "list" } } ] }, - "name": "Geometry Dash Demonlist", - "description": "The official pointercrate Demonlist!", - "url": "https://pointercrate.com/demonlist/" + "name": "The CSCL", + "description": "The Clicksync Challenge List!", + "url": "https://cscl.shuttleapp.rs/list/" } "#)) @@ -113,7 +113,7 @@ impl OverviewPage { ", list_config::list_size(), list_config::extended_list_size()) )) // FIXME: abstract away - link ref = "canonical" href = "https://pointercrate.com/demonlist/"; + link ref = "canonical" href = "https://cscl.shuttleapp.rs/list/"; } } @@ -154,9 +154,10 @@ impl OverviewPage { aside.right { (self.team) (super::rules_panel()) + (super::nongs_panel()) (submit_panel()) (stats_viewer_panel()) - (super::discord_panel()) + } } } diff --git a/pointercrate-demonlist-pages/src/statsviewer/individual.rs b/pointercrate-demonlist-pages/src/statsviewer/individual.rs index 27e381004..ee9907b99 100644 --- a/pointercrate-demonlist-pages/src/statsviewer/individual.rs +++ b/pointercrate-demonlist-pages/src/statsviewer/individual.rs @@ -12,8 +12,7 @@ impl From for PageFragment { fn from(stats_viewer: IndividualStatsViewer) -> Self { PageFragment::new( "Individual Stats Viewer", - "The pointercrate individual stats viewer, a ranking of the worlds best Geometry Dash players. Now more local than ever, \ - allowing you to see who's the best in your state!", + "The pointercrate individual stats viewer, a ranking of the best players.", ) .module("/static/demonlist/js/modules/statsviewer.js") .module("/static/demonlist/js/statsviewer/individual.js") @@ -27,10 +26,10 @@ impl IndividualStatsViewer { fn body(&self) -> Markup { html! { nav.flex.wrap.m-center.fade #statsviewers style="text-align: center;" { - a.button.white.hover.no-shadow href="/demonlist/statsviewer/"{ + a.button.white.hover.no-shadow href="/list/statsviewer/"{ b {"Individual"} } - a.button.white.hover.no-shadow href="/demonlist/statsviewer/nations/" { + a.button.white.hover.no-shadow href="/list/statsviewer/nations/" { b {"Nations"} } } diff --git a/pointercrate-demonlist-pages/src/statsviewer/mod.rs b/pointercrate-demonlist-pages/src/statsviewer/mod.rs index 1cbccddda..15b3230a3 100644 --- a/pointercrate-demonlist-pages/src/statsviewer/mod.rs +++ b/pointercrate-demonlist-pages/src/statsviewer/mod.rs @@ -14,9 +14,9 @@ pub(crate) fn stats_viewer_panel() -> Markup { } } p { - "Get a detailed overview of who completed the most, created the most demons or beat the hardest demons! There is even a leaderboard to compare yourself to the very best!" + "Get a detailed overview of who completed the most, created the most challenges or beat the hardest challenges! There is even a leaderboard to compare yourself to the very best!" } - a.blue.hover.button #show-stats-viewer href = "/demonlist/statsviewer/ "{ + a.blue.hover.button #show-stats-viewer href = "/list/statsviewer/ "{ "Open the stats viewer!" } } @@ -59,15 +59,14 @@ struct StatsViewerRow(Vec<(&'static str, &'static str)>); fn standard_stats_viewer_rows() -> Vec { vec![ - StatsViewerRow(vec![("Demonlist rank", "rank"), ("Demonlist score", "score")]), - StatsViewerRow(vec![("Demonlist stats", "stats"), ("Hardest demon", "hardest")]), - StatsViewerRow(vec![("Demons completed", "beaten")]), + StatsViewerRow(vec![("Rank", "rank"), ("Score", "score")]), + StatsViewerRow(vec![("Records", "stats"), ("Hardest", "hardest")]), + StatsViewerRow(vec![("Levels completed", "beaten")]), StatsViewerRow(vec![ - ("Demons created", "created"), - ("Demons published", "published"), - ("Demons verified", "verified"), + ("Levels created", "created"), + ("Levels published", "published"), + ("Levels verified", "verified"), ]), - StatsViewerRow(vec![("Progress on", "progress")]), ] } diff --git a/pointercrate-demonlist-pages/src/statsviewer/national.rs b/pointercrate-demonlist-pages/src/statsviewer/national.rs index 7b11fff66..2c4b8e243 100644 --- a/pointercrate-demonlist-pages/src/statsviewer/national.rs +++ b/pointercrate-demonlist-pages/src/statsviewer/national.rs @@ -6,7 +6,7 @@ pub fn nation_based_stats_viewer() -> PageFragment { PageFragment::new( "Nation Stats Viewer", "The pointercrate nation stats viewer, ranking how well each nation's players are doing in their quest to collectively complete \ - the entire demonlist!", + the entire list!", ) .module("/static/demonlist/js/modules/statsviewer.js") .module("/static/demonlist/js/statsviewer/nation.js") @@ -19,14 +19,14 @@ fn nation_based_stats_viewer_html() -> Markup { let mut rows = super::standard_stats_viewer_rows(); rows[0].0.insert(1, ("Players", "players")); - rows.push(StatsViewerRow(vec![("Unbeaten demons", "unbeaten")])); + rows.push(StatsViewerRow(vec![("Uncompleted", "unbeaten")])); html! { nav.flex.wrap.m-center.fade #statsviewers style="text-align: center;" { - a.button.white.hover.no-shadow href="/demonlist/statsviewer/"{ + a.button.white.hover.no-shadow href="/list/statsviewer/"{ b {"Individual"} } - a.button.white.hover.no-shadow href="/demonlist/statsviewer/nations/" { + a.button.white.hover.no-shadow href="/list/statsviewer/nations/" { b {"Nations"} } } diff --git a/pointercrate-demonlist-pages/static/css/demonlist.css b/pointercrate-demonlist-pages/static/css/demonlist.css index 2d087c1e6..df7a5ef64 100644 --- a/pointercrate-demonlist-pages/static/css/demonlist.css +++ b/pointercrate-demonlist-pages/static/css/demonlist.css @@ -130,6 +130,7 @@ tr:nth-child(even) { } #level-info span { + width: 20%; margin: 5px 10px; } @@ -216,3 +217,18 @@ tr:nth-child(even) { .ct-series-a .ct-point { stroke: #0881c6; } + +/* dark mode */ +@media (prefers-color-scheme: dark) { + .dropdown { + background: #414141; + } + + tr { + background: #4f4f4f; + } + + tr:nth-child(even) { + background: #333333; + } +} \ No newline at end of file diff --git a/pointercrate-demonlist-pages/static/css/statsviewer.css b/pointercrate-demonlist-pages/static/css/statsviewer.css index a1f06b104..ce6a77483 100644 --- a/pointercrate-demonlist-pages/static/css/statsviewer.css +++ b/pointercrate-demonlist-pages/static/css/statsviewer.css @@ -53,4 +53,15 @@ .tooltip:hover .tooltiptext { opacity: 1 !important; +} + +/* dark mode */ +@media (prefers-color-scheme: dark) { + #stats-viewer-pagination li i { + color: #e6e6e6; + } + + #world-map-wrapper:after { + box-shadow: inset 0 -10px 23px 22px #292929; + } } \ No newline at end of file diff --git a/pointercrate-demonlist-pages/static/images/world.svg b/pointercrate-demonlist-pages/static/images/world.svg index 1577f8b7e..885cb15ef 100644 --- a/pointercrate-demonlist-pages/static/images/world.svg +++ b/pointercrate-demonlist-pages/static/images/world.svg @@ -57,6 +57,19 @@ opacity: .5; } +/* dark mode */ +@media (prefers-color-scheme: dark) { + .land path, .island path, .land-with-states .state, .land-with-states path { + stroke: #292929; + fill: #6b6b6b; + } + + .selectable .land:hover path, .selectable .island:hover path, .selectable .land-with-states .state:hover, .selectable .land-with-states:not(.subdivided):hover path:not(.state), + .selectable .land.selected path, .selectable .land-with-states.selected path:not(.state), .selectable .state.selected { + fill: #0a95e6 !important; + } +} + .selectable.continent { opacity: 1; }CubaBonaireJamaicaPuerto RicoDominican RepublicHaitiEl SalvadorGuatemalaHondurasNicaraguaPanamaCosta RicaMexicoJaliscoAguascalientesSan Luis PotosíNuevo LeónTamaulipasCoahuilaQuintana RooYucatánGuerreroCampecheChiapasOaxacaCiudad de MéxicoMorelosPueblaHidalgoMéxicoQueretaroMichoacanGuanajuatoTabascoVeracruzColimaZacatecasDurangoChihuahuaSinaloaBaja CaliforniaSonoraBaja California SurNayaritMontserratBritish Virgin IslandsUS Virgin IslandsSaint Kitts and NevisCayman IslandsAnguillaGrenadaSaint LuciaSaint Vincent and the GrenadinesTurks and Caicos IslandsBarbadosAntigua and BarbudaSint Maarten (Dutch Part)DominicaTrinidad and TobagoBahamasBelizeWashingtonDelawareMarylandWest VirginiaNew YorkNew JerseyPennsylvaniaVirginiaKentuckyOhioIndianaIllinoisMichiganWisconsinConnecticutRhode IslandVermontNew HampshireMassachusettsMaineAlabamaGeorgiaSouth CarolinaFloridaMississippiTennesseeNorth CarolinaTexasOklahomaNew MexicoNebraskaSouth DakotaKansasColoradoNorth DakotaArkansasMissouriLouisianaIowaMinnesotaArizonaNevadaCaliforniaUtahOregonMontanaIdahoWyomingHawaiiAlaskaWashington, District of ColumbiaUnited StatesManitobaNorthwest TerritoriesNewfoundland and LabradorNunavutQuebecBritish ColumbiaSaskatchewanAlbertaOntarioNew BrunswickNova ScotiaPrince Edward IslandYukonCanadaBermudaBrazilSão PauloRio de JaneiroEspírito SantoRio Grande do SulSanta CatarinaParanáMinas GeraisSergipeAlagoasPernambucoParaíbaRio Grande do NorteBahiaCearáPiauíMaranhãoMato Grosso do SulMato GrossoParáAmapáTocantinsAcreRondôniaAmazonasRoraimaGoiásDistrito FederalArica y ParinacotaAntofagastaTarapacáCoquimboO'HigginsBiobíoLos RíosAtacamaRegión Metropolitana de SantiagoValparaísoMauleÑubleAraucaníaAysénLos LagosMagallanesChileArgentinaProvincia de Santa CruzProvincia del ChubutCiudad Autónoma de Buenos AiresProvincia de Buenos AiresProvincia de NeuquénProvincia de Río NegroProvincia de La PampaProvincia de MendozaProvincia de San LuisProvincia de CórdobaProvincia de Santa FeProvincia del ChacoProvincia de FormosaProvincia de La RiojaProvincia de San JuanProvincia de CatamarcaProvincia de Santiago del EsteroProvincia de TucumánProvincia de SaltaProvincia de JujuyProvincia de Entre RíosProvincia de CorrientesProvincia de MisionesProvincia de Tierra del FuegoSurinameGuyanaVenezuela, Bolivarian Republic ofUruguayParaguayEcuadorColombiaDepartamento del AmazonasDepartamento del PutumayoDepartamento de NariñoDepartamento del CaquetáDepartamento de GuainíaDepartamento del VaupésDepartamento del GuaviareDepartamento del MetaDepartamento del VichadaDepartamento de CasanareDepartamento de AraucaDepartamento del CaucaDepartamento del HuilaBogotáDepartamento del TolimaDepartamento de SantanderDepartamento de CórdobaDepartamento de SucreDepartamento del Valle del CaucaDepartamento del QuindíoDepartamento del RisaraldaDepartamento de CaldasDepartamento de AntioquiaDepartamento del ChocóDepartamento de BolívarDepartamento del MagdalenaDepartamento del AtlánticoDepartamento de CundinamarcaDepartamento de BoyacáNorte de SantanderDepartamento del CesarDepartamento de La GuajiraSan Andrés y ProvidenciaPeruMadre de DiosUcayaliTacnaMoqueguaApurímacAyacuchoArequipaIcaHuancavelicaPunoCuzcoCajamarcaSan MartínPiuraTumbesLambayequeJunínPascoHuánucoÁncashDepartment of LimaLa LibertadAmazonasLoretoBolivia, Plurinational State ofArubaSouth Georgia and the South Sandwich IslandsFalkland Islands (Malvinas)CuracaoChadAlgeriaEgyptLibyaMoroccoWestern SaharaSudanTunisiaNigerMauritaniaMaliBurkina FasoEritreaSenegalGambiaGuinea-BissauGuineaSierra LeoneLiberiaCote d'IvoireGhanaTogoBeninCameroonCentral African RepublicSouth SudanEthiopiaDjiboutiSomaliaEquatorial GuineaGabonKenyaTanzania, United Republic ofUgandaBurundiRwandaCongo, the Democratic Republic of theCongoAngolaZambiaMozambiqueMalawiZimbabweNamibiaBotswanaSwazilandLesothoMadagascarNigeriaSouth AfricaSaint Helena, Ascension and Tristan Da CunhaSeychellesCape VerdeSao Tome and PrincipeMauritiusComorosFranceFranceFranceFranceFranceFranceFranceFranceFranceFranceFranceFranceFrancePolynésie FrançaiseWallis-et-FutunaNouvelle-CalédonieSaint-MartinSaint-BarthélemySaint-Pierre-et-MiquelonCorseNouvelle-AquitaineAuvergne-Rhône-AlpesCentre-Val de LoireBourgogne-Franche-ComtéBretagnePays de la LoireGrand EstNormandieÎle-de-FranceHauts-de-FranceProvence-Alpes-Côte d'AzurOccitanieLa RéunionMayotteMartiniqueGuadeloupeTerres Australes et Antarctiques FrançaisesGuyaneFranceSloveniaSerbiaKosovoMontenegroMacedonia, the Former Yugoslav Republic ofGreeceCroatiaBosnia and HerzegovinaAlbaniaHoly See (Vatican City State)San MarinoPugliaMoliseAbruzzoMarcheEmilia-RomagnaLombardiaLazioUmbriaVenetoTrentino-Alto AdigeBasilicataCampaniaToscanaLiguriaPiemonteCalabriaFriuli-Venezia GiuliaValle d'AostaSiciliaSardegnaItalySlovakiaRomaniaWojewództwo warmińsko-mazurskieWojewództwo podlaskieWojewództwo małopolskieWojewództwo śląskieWojewództwo opolskieWojewództwo dolnośląskieWojewództwo lubuskieWojewództwo podkarpackieWojewództwo świętokrzyskieWojewództwo łódzkieWojewództwo wielkopolskieWojewództwo zachodniopomorskieWojewództwo lubelskieWojewództwo mazowieckieWojewództwo kujawsko-pomorskieWojewództwo pomorskiePolandMoldova, Republic ofHungaryCzech RepublicBulgariaAustriaSwitzerlandGermanyBrandenburgThüringenSachsenSchleswig-HolsteinSachsen-AnhaltHamburgMecklenburg-VorpommernBayernHessenNiedersachsenBaden-WürttembergRheinland-PfalzNordrhein-WestfalenSaarlandBremenBerlinDenmarkNorwayVestlandTrøndelagRogalandAgderTelemarkBuskerudInnlandetAkershusOsloØstfoldVestfoldMøre og RomsdalSvalbard og Jan MayenNordlandTromsFinnmarkNorwaySwedenVarsinais-SuomiAhvenanmaaUusimaaSatakuntaKanta-HämePirkanmaaKeski-SuomiKymenlaaksoPäijät-HämePohjanmaaKeski-PohjanmaaEtelä-PohjanmaaEtelä-SavoPohjois-KarjalaKainuuEtelä-KarjalaPohjois-SavoPohjois-PohjanmaaLappiFinlandEstoniaLatviaLithuaniaBelarusNetherlandsZuid-HollandDrentheGelderlandLimburgZeelandNoord-BrabantUtrechtNoord-HollandFlevolandFrieslandGroningenOverijsselLuxembourgBelgiumUnited KingdomScotlandWalesEnglandNorthern IrelandIrelandIcelandSpainSpainSpainSpainSpainCantabriaAsturiasGaliciaLa RiojaNavarraComunidad de MadridAragónExtremaduraCastilla-La ManchaAndalucíaRegión de MurciaComunidad ValencianaCataluñaCanariasIslas BalearesPaís VascoCastilla y LeónCeutaMelillaPortugalCyprusTurkeyUkraineZakarpattia OblastTernopil OblastChernivtsi OblastIvano-Frankivsk OblastLviv OblastVolyn OblastMykolaiv OblastZaporizhzhia OblastCherkasy OblastKyivKyiv OblastVinnytsia OblastKhmelnytskyi OblastRivne OblastZhytomyr OblastKirovohrad OblastDonetsk OblastDnipropetrovsk OblastKharkiv OblastPoltava OblastLuhansk OblastChernihiv OblastSumy OblastSevastopolAutonomous Republic of CrimeaOdesa OblastKherson OblastIsle of ManMonacoGibraltarGuernseyJerseyLiechtensteinMaltaFaroe IslandsSri LankaTaiwanViet NamMyanmarCambodiaLao People's Democratic RepublicThailandPhilippinesMalaysiaOmanUnited Arab EmiratesYemenQatarKuwaitSaudi ArabiaIsraelLebanonSyrian Arab RepublicJordanIraqRussian FederationKrasnodar KraiKabardino-Balkaria, Republic ofIngushetia, Republic ofChechnya, Republic ofUlyanovsk OblastNizhny Novgorod OblastRyazan OblastMoscow OblastKaliningrad OblastKarachay-Cherkessia, Republic ofNorth Ossetia-Alania, Republic ofDagestan, Republic ofAstrakhan OblastVolgograd OblastPenza OblastTatarstan, Republic ofMari El, Republic ofUdmurtia, Republic ofKostroma OblastVladimir OblastTver OblastPskov OblastSaint PetersburgVologda OblastBelgorod OblastTambov OblastAdygea, Republic ofOryol OblastBryansk OblastKaluga OblastStavropol KraiKalmykia, Republic ofRostov OblastSaratov OblastSamara OblastMordovia, Republic ofChuvashia, Republic ofKirov OblastOrenburg OblastBashkortostan, Republic ofPerm KraiKomi, Republic ofIvanovo OblastYaroslavl OblastNovgorod OblastLeningrad OblastKarelia, Republic ofKurgan OblastChelyabinsk OblastSverdlovsk OblastKhabarovsk KraiSakha, Republic ofAmur OblastPrimorsky KraiMagadan OblastJewish Autonomous OblastChukotka Autonomous OkrugKamchatka KraiSakhalin OblastKrasnoyarsk KraiBuryatia, Republic ofZabaykalsky KraiKhakassia, Republic ofTuva, Republic ofIrkutsk OblastKemerovo OblastAltai, Republic ofAltai KraiNovosibirsk OblastOmsk OblastTomsk OblastTyumen OblastKhanty-Mansi Autonomous OkrugYamalo-Nenets Autonomous OkrugArkhangelsk OblastNenets Autonomous OkrugMurmansk OblastVoronezh OblastKursk OblastLipetsk OblastTula OblastSmolensk OblastMoscowAzerbaijanArmeniaGeorgiaKorea, Democratic People's Republic ofKorea, Republic ofJejudoIncheonDaeguBusanUlsanGyeongsangbuk-doGwangjuJeonnamGyeongsangnam-doJeonbukDaejeonSejongChungcheongbuk-doChungcheongnam-doSeoulGyeonggiGangwonJapanBangladeshBhutanNepalMongoliaAfghanistanPakistanKyrgyzstanIran, Islamic Republic ofTurkmenistanTajikistanUzbekistanIndiaKazakhstanChinaHong KongSingaporeMaldivesBahrainPalestine, State ofKiribatiNew ZealandBrunei DarussalamTimor-LesteIndonesiaPapua New GuineaAustralian Capital TerritoryTasmaniaNorthern TerritoryWestern AustraliaQueenslandNew South WalesVictoriaSouth AustraliaAustraliaTokelauNorfolk IslandGuamPitcairnNauruTuvaluMarshall IslandsAmerican SamoaCook IslandsNiueTongaPalauNorthern Mariana IslandsMicronesia, Federated States ofSamoaVanuatuFijiSolomon Islands \ No newline at end of file diff --git a/pointercrate-demonlist-pages/static/js/account/demon.js b/pointercrate-demonlist-pages/static/js/account/demon.js index b03658886..d14c56094 100644 --- a/pointercrate-demonlist-pages/static/js/account/demon.js +++ b/pointercrate-demonlist-pages/static/js/account/demon.js @@ -42,12 +42,12 @@ export class DemonManager extends FilteredPaginator { this._thumbnail_link = document.getElementById("demon-thumbnail-link"); this._position = document.getElementById("demon-position"); - this._requirement = document.getElementById("demon-requirement"); this._verifier = document.getElementById("demon-verifier"); this._publisher = document.getElementById("demon-publisher"); this._creators = document.getElementById("demon-creators"); + this._level_id = document.getElementById("demon-level_id"); let videoForm = setupFormDialogEditor( new PaginatorEditorBackend(this, false), @@ -74,24 +74,10 @@ export class DemonManager extends FilteredPaginator { videoForm.addErrorOverride(errorCode, "demon-video-edit"); } - let requirementForm = setupFormDialogEditor( - new PaginatorEditorBackend(this, false), - "demon-requirement-dialog", - "demon-requirement-pen", - this.output - ); + - requirementForm.addValidators({ - "demon-requirement-edit": { - "Record requirement cannot be negative": rangeUnderflow, - "Record requirement cannot be larger than 100%": rangeOverflow, - "Record requirement must be a valid integer": badInput, - "Record requirement mustn't be a decimal": stepMismatch, - "Please enter a requirement value": valueMissing, - }, - }); + - requirementForm.addErrorOverride(42212, "demon-requirement-edit"); let positionForm = setupFormDialogEditor( new PaginatorEditorBackend(this, true), @@ -123,6 +109,7 @@ export class DemonManager extends FilteredPaginator { "Please provide a name for the demon": valueMissing, }, }); + setupEditorDialog( new FormDialog("demon-verifier-dialog"), "demon-verifier-pen", @@ -135,6 +122,13 @@ export class DemonManager extends FilteredPaginator { new PaginatorEditorBackend(this, true), this.output ); + + setupFormDialogEditor( + new PaginatorEditorBackend(this, false), + "demon-level_id-dialog", + "demon-level_id-pen", + this.output + ); } onReceive(response) { @@ -147,7 +141,6 @@ export class DemonManager extends FilteredPaginator { this._id.innerText = this.currentObject.id; this._name.innerText = this.currentObject.name; this._position.innerText = this.currentObject.position; - this._requirement.innerText = this.currentObject.requirement; var embeddedVideo = embedVideo(this.currentObject.video); @@ -161,7 +154,7 @@ export class DemonManager extends FilteredPaginator { if (this.currentObject.video) { this._video_link.href = this.currentObject.video; - this._video_link.innerText = this.currentObject.video; + this._video_link.innerHTML = this.currentObject.video; } else { this._video_link.style.display = "none"; } @@ -169,12 +162,12 @@ export class DemonManager extends FilteredPaginator { this._thumbnail_link.href = this.currentObject.thumbnail; this._thumbnail_link.innerText = this.currentObject.thumbnail; - this._publisher.innerText = + this._publisher.innerHTML = this.currentObject.publisher.name + " (" + this.currentObject.publisher.id + ")"; - this._verifier.innerText = + this._verifier.innerHTML = this.currentObject.verifier.name + " (" + this.currentObject.verifier.id + @@ -187,6 +180,7 @@ export class DemonManager extends FilteredPaginator { for (let creator of this.currentObject.creators) { this.addCreator(creator); } + this._level_id.innerText = this.currentObject.level_id; } addCreator(creator) { @@ -201,11 +195,24 @@ export class DemonManager extends FilteredPaginator { ) .then(() => { this._creators.removeChild(html); - this.output.setSuccess("owo uwu owo"); + this.output.setSuccess("NO CREATOR?!?! OMG?!?!"); }) .catch(displayError(this.output)); }); - } + } +} + +function deleteDemon(demon_id) { + + if (confirm("Are you sure? This will irrevocably delete this level and all its records!")) { + demonManager.output.setSuccess("Deleting, please wait..."); + del("/api/v2/demons/" + demon_id + "/") + .then(() => { + demonManager.output.setSuccess("This demon has been deleted."); + demonManager.refresh(); + }) + .catch(displayError(output)) + } } function insertCreatorInto(creator, container) { @@ -219,6 +226,10 @@ function insertCreatorInto(creator, container) { return html; } + + + + function createCreatorHtml(creator) { let span = document.createElement("span"); @@ -284,7 +295,9 @@ function setupDemonAdditionForm() { post("/api/v2/demons/", {}, data) .then(() => { - form.setSuccess("Successfully added demon!"); + form.setSuccess( + `Successfully added demon!\n\n + ${data["name"]} by ${form.creators} has been placed at #${data["position"]}, above and below .`); demonManager.refresh(); form.clear(); }) @@ -323,8 +336,7 @@ export function initialize() { location.length - 1 ), }); - - demonManager.output.setSuccess("Successfully added creator"); + }) .catch(response => { displayError(creatorFormDialog.form)(response); @@ -350,4 +362,11 @@ export function initialize() { addDemonForm.creators.push(data.creator); }); }); -} + + let demon_x = document.getElementById("demon_x") + + demon_x.addEventListener("click", () => { + deleteDemon(demonManager.currentObject.id); + }); + +} \ No newline at end of file diff --git a/pointercrate-demonlist-pages/static/js/account/records.js b/pointercrate-demonlist-pages/static/js/account/records.js index 8f3de248e..8f06837d8 100644 --- a/pointercrate-demonlist-pages/static/js/account/records.js +++ b/pointercrate-demonlist-pages/static/js/account/records.js @@ -16,8 +16,8 @@ import { setupFormDialogEditor, Output, setupDropdownEditor, - PaginatorEditorBackend, - setupEditorDialog, + PaginatorEditorBackend, + setupEditorDialog, DropdownDialog, FormDialog, } from "/static/core/js/modules/form.js"; @@ -43,10 +43,10 @@ class RecordManager extends Paginator { this._video = document.getElementById("record-video"); this._video_link = document.getElementById("record-video-link"); this._raw_footage_link = document.getElementById("record-raw-footage-link"); + this._enjoyment = document.getElementById("record-enjoyment"); this._id = document.getElementById("record-id"); this._demon = document.getElementById("record-demon"); this._holder = document.getElementById("record-holder"); - this._progress = document.getElementById("record-progress"); this._submitter = document.getElementById("record-submitter"); this._notes = document.getElementById("record-notes"); @@ -74,7 +74,7 @@ class RecordManager extends Paginator { this.output ); - this.initProgressDialog(); + this.initVideoDialog(); setupEditorDialog( new FormDialog("record-holder-dialog"), @@ -82,63 +82,58 @@ class RecordManager extends Paginator { new PaginatorEditorBackend(this, true), this.output ); + this.initEnjoymentDialog(); + setupEditorDialog( + new FormDialog("record-enjoyment-dialog"), + "record-enjoyment-pen", + new PaginatorEditorBackend(this, true), + this.output + ) this.initDemonDialog(); - document - .getElementById("record-copy-info") - .addEventListener("click", () => { - navigator.clipboard - .writeText( - this.currentObject.id + - ", " + - this._holder.innerText + - ", " + - this.currentObject.video - ) - .then(() => - this.output.setSuccess("Copied record data to clipboard!") - ) + document.getElementById("record-copy-info").addEventListener('click', () => { + navigator.clipboard.writeText(this.currentObject.id + ", " + this._holder.innerText + ", " + this.currentObject.video) + .then(() => this.output.setSuccess("Copied record data to clipboard!")) .catch(() => this.output.setError("Error copying to clipboard")); - }); + }); } - initProgressDialog() { + + + initVideoDialog() { let form = setupFormDialogEditor( - new PaginatorEditorBackend(this, true), - "record-progress-dialog", - "record-progress-pen", + new PaginatorEditorBackend(this, false), + "record-video-dialog", + "record-video-pen", this.output ); form.addValidators({ - "record-progress-edit": { - "Record progress cannot be negative": rangeUnderflow, - "Record progress cannot be larger than 100%": rangeOverflow, - "Record progress must be a valid integer": badInput, - "Record progress mustn't be a decimal": stepMismatch, - "Please enter a progress value": valueMissing, + "record-video-edit": { + "Please enter a valid URL": typeMismatch, }, }); - form.addErrorOverride(42215, "record-progress-edit"); + for (let errorCode of [42222, 42223, 42224, 42225]) { + form.addErrorOverride(errorCode, "record-video-edit"); + } } - - initVideoDialog() { + initEnjoymentDialog() { let form = setupFormDialogEditor( new PaginatorEditorBackend(this, false), - "record-video-dialog", - "record-video-pen", + "record-enjoyment-dialog", + "record-enjoyment-pen", this.output ); form.addValidators({ - "record-video-edit": { - "Please enter a valid URL": typeMismatch, + "record-enjoyment-edit": { + "Please enter a valid enjoyment": typeMismatch, }, }); for (let errorCode of [42222, 42223, 42224, 42225]) { - form.addErrorOverride(errorCode, "record-video-edit"); + form.addErrorOverride(errorCode, "record-enjoyment-edit"); } } @@ -148,7 +143,7 @@ class RecordManager extends Paginator { "record-demon-pen", new PaginatorEditorBackend(this, true), this.output, - (demonId) => ({ demon_id: parseInt(demonId) }) + demonId => ({demon_id: parseInt(demonId)}) ); } @@ -168,49 +163,48 @@ class RecordManager extends Paginator { this._video.style.display = "none"; } - if (this.currentObject.video !== undefined) { + if(this.currentObject.video !== undefined) { this._video_link.href = this.currentObject.video; - this._video_link.innerText = this.currentObject.video; + this._video_link.innerHTML = this.currentObject.video; this._video_link.style.display = "initial"; } else { this._video_link.style.display = "none"; } - if (this.currentObject.raw_footage !== undefined) { + if(this.currentObject.raw_footage !== undefined) { this._raw_footage_link.href = this.currentObject.raw_footage; - this._raw_footage_link.innerText = this.currentObject.raw_footage; + this._raw_footage_link.innerHTML = this.currentObject.raw_footage; this._raw_footage_link.style.display = "initial"; } else { this._raw_footage_link.style.display = "none"; } - - this._id.innerText = this.currentObject.id; - this._demon.innerText = + + this._id.innerHTML = this.currentObject.id; + this._demon.innerHTML = this.currentObject.demon.name + " (" + this.currentObject.demon.id + ")"; - this._holder.innerText = + this._holder.innerHTML = this.currentObject.player.name + " (" + this.currentObject.player.id + ")"; this._status.selectSilently(this.currentObject.status); - this._progress.innerText = this.currentObject.progress + "%"; - this._submitter.innerText = this.currentObject.submitter.id; + this._submitter.innerHTML = this.currentObject.submitter.id; + this._enjoyment.innerHTML = this.currentObject.enjoyment; // this is introducing race conditions. Oh well. - return get("/api/v1/records/" + this.currentObject.id + "/notes").then( - (response) => { - // clear notes - while (this._notes.firstChild) { - this._notes.removeChild(this._notes.firstChild); - } - - for (let note of response.data) { - this._notes.appendChild(createNoteHtml(note)); - } - - $(this._notes.parentElement).show(300); // TODO: maybe via CSS transform? + return get("/api/v1/records/" + this.currentObject.id + "/notes").then(response => { + // clear notes + while (this._notes.firstChild) { + this._notes.removeChild(this._notes.firstChild); } - ); + + for (let note of response.data) { + this._notes.appendChild(createNoteHtml(note)); + } + + + $(this._notes.parentElement).show(300); // TODO: maybe via CSS transform? + }) } } @@ -246,36 +240,35 @@ function createNoteHtml(note) { } let b = document.createElement("b"); - b.innerText = "Record Note #" + note.id; + b.innerHTML = "Record Note #" + note.id; let i = document.createElement("i"); - i.innerText = note.content; + i.innerHTML = note.content; let furtherInfo = document.createElement("i"); furtherInfo.style.fontSize = "80%"; furtherInfo.style.textAlign = "right"; if (note.author === null) { - furtherInfo.innerText = + furtherInfo.innerHTML = "This note was left as a comment by the submitter. "; } else { - furtherInfo.innerText = "This note was left by " + note.author + ". "; + furtherInfo.innerHTML = "This note was left by " + note.author + ". "; } if (note.editors.length) { - furtherInfo.innerText += + furtherInfo.innerHTML += "This note was subsequently modified by: " + note.editors.join(", ") + ". "; } if (note.transferred) { - furtherInfo.innerText += - "This not was not originally left on this record. "; + furtherInfo.innerHTML += "This not was not originally left on this record. "; } - if (note.is_public) { - furtherInfo.innerText += "This note is public. "; + if(note.is_public) { + furtherInfo.innerHTML += "This note is public. "; } if (isAdmin) noteDiv.appendChild(closeX); @@ -323,11 +316,8 @@ function setupRecordFilterPlayerIdForm() { var playerId = recordFilterPlayerIdForm.input("record-player-id"); recordFilterPlayerIdForm.onSubmit(function () { - // Reset search filter if player ID field is empty - recordManager.updateQueryData( - "player", - valueMissing(playerId) ? playerId.value : undefined - ); + // reset search filter if player ID field is empty + recordManager.updateQueryData("player", valueMissing(playerId) ? playerId.value : undefined); }); } @@ -351,12 +341,16 @@ function setupRecordFilterPlayerNameForm() { ); var playerName = recordFilterPlayerNameForm.input("record-player-name"); + playerName.addValidators({ + "Player name required": valueMissing, + }); + recordFilterPlayerNameForm.onSubmit(function () { if (!valueMissing(playerName)) { // Player name field is empty, so reset search filter recordManager.updateQueryData("player", undefined); } else { - get("/api/v1/players/?name=" + playerName.value) + get("/api/v1/players/?name=" + playerName.value) .then((response) => { let json = response.data; @@ -367,7 +361,7 @@ function setupRecordFilterPlayerNameForm() { } }) .catch(displayError(recordFilterPlayerNameForm)); - } + } }); } diff --git a/pointercrate-demonlist-pages/static/js/modules/demonlist.js b/pointercrate-demonlist-pages/static/js/modules/demonlist.js index c48912912..2b2011607 100644 --- a/pointercrate-demonlist-pages/static/js/modules/demonlist.js +++ b/pointercrate-demonlist-pages/static/js/modules/demonlist.js @@ -64,7 +64,7 @@ export function initializeTimeMachine() { document.cookie = "when=" + when; gtag('event', 'time-machine-usage', {'event-category': 'demonlist', 'label': when}); - window.location = "/demonlist/"; + window.location = "/list/"; }) } @@ -76,10 +76,11 @@ export function initializeRecordSubmitter(submitApproved = false) { var progress = submissionForm.input("id_progress"); var video = submissionForm.input("id_video"); var rawFootage = submissionForm.input("submit-raw-footage"); + var enjoyment = submissionForm.input("id_enjoyment"); demon.addValidator(input => input.dropdown.selected !== undefined, "Please specify a demon"); demon.setTransform(parseInt); - + player.addValidator(input => input.value !== undefined, "Please specify a record holder"); player.addValidator( input => input.value === undefined || input.value.length <= 50, @@ -103,6 +104,15 @@ export function initializeRecordSubmitter(submitApproved = false) { rawFootage.addValidator(typeMismatch, "Please enter a valid URL"); + enjoyment.addValidator(valueMissing, "Please specify the record's enjoyment"); + enjoyment.addValidator(rangeUnderflow, "Record enjoyment cannot be negative"); + enjoyment.addValidator( + rangeOverflow, + "Record enjoyment cannot be larger than 10" + ); + enjoyment.addValidator(badInput, "Record enjoyment must be a valid integer"); + enjoyment.addValidator(stepMismatch, "Record enjoyment mustn't be a decimal"); + submissionForm.onInvalid(() => gtag('event', 'record-submit-failure-frontend', {'event-category': 'demonlist'})); submissionForm.onSubmit(function () { let data = submissionForm.serialize(); @@ -119,7 +129,6 @@ export function initializeRecordSubmitter(submitApproved = false) { submissionForm.setSuccess(`Record successfully submitted. It is #${queue_position} in the queue!`); else submissionForm.setSuccess("Record successfully submitted."); - submissionForm.clear(); gtag('event', 'record-submit-success', {'event-category': 'demonlist'}); }) .catch((response) => { @@ -130,7 +139,7 @@ export function initializeRecordSubmitter(submitApproved = false) { case 42218: player.errorText = response.data.message; break; - case 42215: + case 42215: case 42220: progress.errorText = response.data.message; break; @@ -277,9 +286,9 @@ export function generateRecord(record) { ); li.appendChild(document.createElement("br")); li.appendChild( - document.createTextNode(record.progress + "% on " + record.demon.name) + document.createTextNode(record.demon.name) ); li.appendChild(document.createElement("br")); return li; -} \ No newline at end of file +} diff --git a/pointercrate-demonlist-pages/static/js/modules/statsviewer.js b/pointercrate-demonlist-pages/static/js/modules/statsviewer.js index cf3b7d988..0732f205f 100644 --- a/pointercrate-demonlist-pages/static/js/modules/statsviewer.js +++ b/pointercrate-demonlist-pages/static/js/modules/statsviewer.js @@ -12,470 +12,421 @@ import { } from "/static/core/js/modules/form.js"; export class StatsViewer extends FilteredPaginator { - /** - * Constructs a new StatsViewer - * - * @param {HTMLElement} html The container element of this stats viewer instance - * @param statsviewerdata additional settings for this stats viewer - */ - constructor(html, statsviewerdata) { - super( - "stats-viewer-pagination", - statsviewerdata.entryGenerator, - "name_contains" - ); - - this.endpoint = statsviewerdata.rankingEndpoint; - // different from pagination endpoint here! - this.retrievalEndpoint = statsviewerdata.retrievalEndpoint; - this.currentLink = this.endpoint + "?" + $.param(this.queryData); - - this.html = html; - this.output = new Viewer( - html.getElementsByClassName("viewer-content")[0], - this - ); - - this._name = document.getElementById("player-name"); - this._created = document.getElementById("created"); - this._beaten = document.getElementById("beaten"); - this._verified = document.getElementById("verified"); - this._published = document.getElementById("published"); - this._hardest = document.getElementById("hardest"); - this._score = document.getElementById("score"); - this._rank = document.getElementById("rank"); - this._amountBeaten = document.getElementById("stats"); - this._welcome = html.getElementsByClassName("viewer-welcome")[0]; - this._progress = document.getElementById("progress"); - this._content = html.getElementsByClassName("viewer-content")[0]; - - let dropdownElement = html.getElementsByClassName("dropdown-menu")[0]; - - if (dropdownElement !== undefined) { - this.dropdown = new Dropdown(dropdownElement); - this.dropdown.addEventListener((selected) => { - if (selected === "International") { - this.updateQueryData("nation", undefined); - } else { - this.updateQueryData("nation", selected); + /** + * Constructs a new StatsViewer + * + * @param {HTMLElement} html The container element of this stats viewer instance + * @param statsviewerdata additional settings for this stats viewer + */ + constructor(html, statsviewerdata) { + super( + "stats-viewer-pagination", + statsviewerdata.entryGenerator, + "name_contains" + ); + + this.endpoint = statsviewerdata.rankingEndpoint; + // different from pagination endpoint here! + this.retrievalEndpoint = statsviewerdata.retrievalEndpoint; + this.currentLink = this.endpoint + "?" + $.param(this.queryData); + + this.html = html; + this.output = new Viewer( + html.getElementsByClassName("viewer-content")[0], + this + ); + + this._name = document.getElementById("player-name"); + this._created = document.getElementById("created"); + this._beaten = document.getElementById("beaten"); + this._verified = document.getElementById("verified"); + this._published = document.getElementById("published"); + this._hardest = document.getElementById("hardest"); + this._score = document.getElementById("score"); + this._rank = document.getElementById("rank"); + this._amountBeaten = document.getElementById("stats"); + this._welcome = html.getElementsByClassName("viewer-welcome")[0]; + this._progress = document.getElementById("progress"); + this._content = html.getElementsByClassName("viewer-content")[0]; + + let dropdownElement = html.getElementsByClassName("dropdown-menu")[0]; + + if(dropdownElement !== undefined) { + this.dropdown = new Dropdown(dropdownElement); + this.dropdown.addEventListener((selected) => { + if (selected === "International") { + this.updateQueryData("nation", undefined); + } else { + this.updateQueryData("nation", selected); + } + }); } - }); } - } - initialize() { - return get("/api/v1/list_information/").then((data) => { - this.list_size = data.data["list_size"]; - this.extended_list_size = data.data["extended_list_size"]; + initialize() { + return get("/api/v1/list_information/").then(data => { + this.list_size = data.data['list_size']; + this.extended_list_size = data.data['extended_list_size']; - super.initialize(); - }); - } + super.initialize() + }); + } - setName(name, nationality) { - if (nationality === null) { - this._name.textContent = name; - } else { - while (this._name.lastChild) { - this._name.removeChild(this._name.lastChild); - } - - let nameSpan = document.createElement("span"); - nameSpan.style.padding = "0 8px"; - nameSpan.innerText = name; - - this._name.appendChild( - getCountryFlag(nationality.nation, nationality.country_code) - ); - this._name.appendChild(nameSpan); - - if (nationality.subdivision !== null) { - this._name.appendChild( - getSubdivisionFlag( - nationality.subdivision.name, - nationality.country_code, - nationality.subdivision.iso_code - ) - ); - } else { - // needed for layout - this._name.appendChild(document.createElement("span")); - } + setName(name, nationality) { + if(nationality === null) { + this._name.textContent = name; + } else { + while (this._name.lastChild) { + this._name.removeChild(this._name.lastChild); + } + + let nameSpan = document.createElement("span"); + nameSpan.style.padding = "0 8px"; + nameSpan.innerText = name; + + this._name.appendChild(getCountryFlag(nationality.nation, nationality.country_code)); + this._name.appendChild(nameSpan); + + if (nationality.subdivision !== null) { + this._name.appendChild(getSubdivisionFlag(nationality.subdivision.name, nationality.country_code, nationality.subdivision.iso_code)); + } else { + // needed for layout + this._name.appendChild(document.createElement("span")); + } + } } - } - - setHardest(hardest) { - if (this._hardest.lastChild) - this._hardest.removeChild(this._hardest.lastChild); - this._hardest.appendChild( - hardest === undefined - ? document.createTextNode("None") - : this.formatDemon(hardest, "/demonlist/permalink/" + hardest.id + "/") - ); - } - - setCompletionNumber(main, extended, legacy) { - this._amountBeaten.textContent = - main + " Main, " + extended + " Extended, " + legacy + " Legacy "; - } - - onReceive(response) { - super.onReceive(response); - - // Using currentlySelected is O.K. here, as selection via clicking li-elements is the only possibility (well, not for the nation based one, but oh well)! - this._rank.innerText = this.currentlySelected.dataset.rank; - this._score.innerHTML = this.currentlySelected.getElementsByTagName( - "i" - )[0].innerHTML; - } - - formatDemon(demon, link) { - var element; - - if (demon.position <= this.list_size) { - element = document.createElement("b"); - } else if (demon.position <= this.extended_list_size) { - element = document.createElement("span"); - } else { - element = document.createElement("i"); - element.style.opacity = ".5"; + + setHardest(hardest) { + if(this._hardest.lastChild) + this._hardest.removeChild(this._hardest.lastChild); + this._hardest.appendChild(hardest === undefined ? document.createTextNode("None") : this.formatDemon(hardest, "/list/permalink/" + hardest.id + "/")); } - if (link) { - let a = document.createElement("a"); - a.href = link; - a.textContent = demon.name; + setCompletionNumber(main, extended, legacy) { + this._amountBeaten.textContent = main; + } - element.appendChild(a); - } else { - element.textContent = demon.name; + onReceive(response) { + super.onReceive(response); + + // Using currentlySelected is O.K. here, as selection via clicking li-elements is the only possibility (well, not for the nation based one, but oh well)! + this._rank.innerHTML = this.currentlySelected.dataset.rank; + this._score.innerHTML = this.currentlySelected.getElementsByTagName( + "i" + )[0].innerHTML; } - return element; - } + formatDemon(demon, link) { + var element; + + if (demon.position <= this.list_size) { + element = document.createElement("b"); + } else if (demon.position <= this.extended_list_size) { + element = document.createElement("span"); + } else { + element = document.createElement("i"); + element.style.opacity = ".5"; + } + + if (link) { + let a = document.createElement("a"); + a.href = link; + a.textContent = demon.name; + + element.appendChild(a); + } else { + element.textContent = demon.name; + } + + return element; + } } export function formatInto(parent, childs) { - while (parent.lastChild) { - parent.removeChild(parent.lastChild); - } - - if (childs.length) { - for (let child of childs) { - parent.appendChild(child); - parent.appendChild(document.createTextNode(" - ")); + while(parent.lastChild) { + parent.removeChild(parent.lastChild); } - // remove trailing dash - parent.removeChild(parent.lastChild); - } else { - parent.appendChild(document.createTextNode("None")); - } + if(childs.length) { + for(let child of childs) { + parent.appendChild(child); + parent.appendChild(document.createTextNode(" - ")); + } + + // remove trailing dash + parent.removeChild(parent.lastChild); + } else { + parent.appendChild(document.createTextNode("None")); + } } export class InteractiveWorldMap { - constructor() { - this.wrapper = document.getElementById("world-map-wrapper"); - this.map = document.getElementById("world-map"); - this.svg = this.map.contentDocument.children[0]; + constructor() { + this.wrapper = document.getElementById("world-map-wrapper"); + this.map = document.getElementById("world-map"); + this.svg = this.map.contentDocument.children[0]; - this.selectionListeners = []; - this.deselectionListeners = []; + this.selectionListeners = []; + this.deselectionListeners = []; - this.zoom = 1; - this.translate = { x: 0, y: 0 }; + this.zoom = 1; + this.translate = {x: 0, y: 0}; - this.isDragging = false; - this.dragDistance = 0; // approximate line integral of mouse movement + this.isDragging = false; + this.dragDistance = 0; // approximate line integral of mouse movement - this.relativeMousePosition = { x: 0, y: 0 }; - this.lastTouchPosition = { x: 0, y: 0 }; + this.relativeMousePosition = {x: 0, y: 0}; + this.lastTouchPosition = {x: 0, y: 0}; - this.setupTouchHandlers(); - this.setupMouseHandlers(); + this.setupTouchHandlers(); + this.setupMouseHandlers(); - this.currentlySelected = undefined; + this.currentlySelected = undefined; - for (let subdivision of this.map.contentDocument.querySelectorAll( - ".land-with-states .state" - )) { - subdivision.addEventListener("click", (event) => { - // states are overlaid over the .land-with-states. We need to stop propagation as otherwise the - // event handler on the .land-with-states is also run and it would select the entire country. - event.stopPropagation(); + for (let subdivision of this.map.contentDocument.querySelectorAll(".land-with-states .state")) { + subdivision.addEventListener('click', event => { + // states are overlaid over the .land-with-states. We need to stop propagation as otherwise the + // event handler on the .land-with-states is also run and it would select the entire country. + event.stopPropagation(); - if ( - !findParentWithClass(subdivision, "continent").classList.contains( - "selectable" - ) - ) - return; + if(!findParentWithClass(subdivision, "continent").classList.contains("selectable")) + return; - if (this.currentlySelected === subdivision) { - this._deselect(); - } else { - this._select(subdivision); + if (this.currentlySelected === subdivision) { + this._deselect(); + } else { + this._select(subdivision); + } + }); } - }); + + for (let clickable of this.map.contentDocument.querySelectorAll(".land, .island, .land-with-states")) { + clickable.addEventListener('click', () => { + if(!findParentWithClass(clickable, "continent").classList.contains("selectable")) + return; + + if(this.currentlySelected === clickable) { + this._deselect(); + } else { + this._select(clickable); + } + }) + } + } + + /** + * Adds a selection listener to be called when a country/subdivision is selected by clicking + * + * @param listener callback (object, object?) -> void taking a nation and optionally a subdivision (both as objects with 'name' and 'code' fields) + */ + addSelectionListener(listener) { + this.selectionListeners.push(listener); } - for (let clickable of this.map.contentDocument.querySelectorAll( - ".land, .island, .land-with-states" - )) { - clickable.addEventListener("click", () => { - if ( - !findParentWithClass(clickable, "continent").classList.contains( - "selectable" - ) - ) - return; - - if (this.currentlySelected === clickable) { - this._deselect(); + addDeselectionListener(listener) { + this.deselectionListeners.push(listener); + } + + highlightContinent(continentName) { + if(continentName === undefined) { + for(let continent of this.svg.getElementsByClassName("continent")) { + continent.classList.add("selectable"); + } } else { - this._select(clickable); + for(let continent of this.svg.getElementsByClassName("continent")) { + if(continent.id !== continentName.toLowerCase().replaceAll(' ', "-")) { + continent.classList.remove("selectable"); + } else { + continent.classList.add("selectable"); + } + } } - }); } - } - - /** - * Adds a selection listener to be called when a country/subdivision is selected by clicking - * - * @param listener callback (object, object?) -> void taking a nation and optionally a subdivision (both as objects with 'name' and 'code' fields) - */ - addSelectionListener(listener) { - this.selectionListeners.push(listener); - } - - addDeselectionListener(listener) { - this.deselectionListeners.push(listener); - } - - highlightContinent(continentName) { - if (continentName === undefined) { - for (let continent of this.svg.getElementsByClassName("continent")) { - continent.classList.add("selectable"); - } - } else { - for (let continent of this.svg.getElementsByClassName("continent")) { - if (continent.id !== continentName.toLowerCase().replaceAll(" ", "-")) { - continent.classList.remove("selectable"); - } else { - continent.classList.add("selectable"); + + resetContinentHighlight() { + this.highlightContinent(undefined); + } + + select(nation, subdivision) { + let elementId = nation.toUpperCase(); + + if(subdivision !== undefined) + elementId += "-" + subdivision.toUpperCase(); + + let element = this.svg.getElementById(elementId) || this.svg.getElementById(elementId.toLowerCase()); + + if(element !== undefined) + this._select(element, false); + } + + deselectSubdivision() { + if (this.currentlySelected === undefined || !this.currentlySelected.id.includes("-")) + return; + + this.select(this.currentlySelected.id.substring(0, 2)); + } + + deselect() { + this._deselect(false); + } + + showSubdivisions() { + for(let divided of this.map.contentDocument.querySelectorAll(".land-with-states")) { + divided.classList.add("subdivided"); } - } } - } - resetContinentHighlight() { - this.highlightContinent(undefined); - } + hideSubdivisions() { + for(let divided of this.map.contentDocument.querySelectorAll(".land-with-states.subdivided")) { + divided.classList.remove("subdivided"); + } + } + + // private + + _select(clicked, fireEvents = true) { + if(this.isDragging) + return; - select(nation, subdivision) { - let elementId = nation.toUpperCase(); + if (this.currentlySelected !== undefined) + this.currentlySelected.classList.remove("selected"); - if (subdivision !== undefined) elementId += "-" + subdivision.toUpperCase(); + this.currentlySelected = clicked; + this.currentlySelected.classList.add("selected"); - let element = - this.svg.getElementById(elementId) || - this.svg.getElementById(elementId.toLowerCase()); + let subdivisionCode = clicked.id.substring(3); + let countryCode = clicked.id.substring(0, 2); - if (element !== undefined) this._select(element, false); - } + if(fireEvents) + for(let listener of this.selectionListeners) + listener(countryCode.toUpperCase(), subdivisionCode === "" ? undefined : subdivisionCode.toUpperCase()); + } - deselectSubdivision() { - if ( - this.currentlySelected === undefined || - !this.currentlySelected.id.includes("-") - ) - return; + _deselect(fireEvents = true) { + if(this.isDragging) + return - this.select(this.currentlySelected.id.substring(0, 2)); - } + if(this.currentlySelected === undefined) + return; - deselect() { - this._deselect(false); - } + this.currentlySelected.classList.remove("selected"); + this.currentlySelected = undefined; - showSubdivisions() { - for (let divided of this.map.contentDocument.querySelectorAll( - ".land-with-states" - )) { - divided.classList.add("subdivided"); + if(fireEvents) + for(let listener of this.deselectionListeners) + listener(); } - } - hideSubdivisions() { - for (let divided of this.map.contentDocument.querySelectorAll( - ".land-with-states.subdivided" - )) { - divided.classList.remove("subdivided"); + setLastPosFromTouchEvent(event) { + this.lastTouchPosition.x = event.touches[0].pageX; + this.lastTouchPosition.y = event.touches[0].pageY; } - } - // private + doDrag(deltaX, deltaY) { + if(deltaX === undefined || deltaY === undefined) + return; + + this.translate.x += deltaX / this.zoom; + this.translate.y += deltaY / this.zoom; - _select(clicked, fireEvents = true) { - if (this.isDragging) return; + // TODO(patrick): pretty sure this is nonsense? + this.dragDistance += Math.sqrt(this.translate.x * this.translate.x + this.translate.y * this.translate.y); - if (this.currentlySelected !== undefined) - this.currentlySelected.classList.remove("selected"); + this.svg.style.transform = "scale(" + this.zoom + ") translate(" + this.translate.x + "px, " + this.translate.y + "px)"; + } - this.currentlySelected = clicked; - this.currentlySelected.classList.add("selected"); + setupTouchHandlers() { + this.svg.addEventListener("touchstart", event => { + this.isDragging = event.touches.length === 1; - let subdivisionCode = clicked.id.substring(3); - let countryCode = clicked.id.substring(0, 2); + if(this.isDragging) { + this.setLastPosFromTouchEvent(event); - if (fireEvents) - for (let listener of this.selectionListeners) - listener( - countryCode.toUpperCase(), - subdivisionCode === "" ? undefined : subdivisionCode.toUpperCase() - ); - } + event.preventDefault(); + } + }); - _deselect(fireEvents = true) { - if (this.isDragging) return; + this.svg.addEventListener("touchend", event => { + this.isDragging = event.touches.length !== 1; - if (this.currentlySelected === undefined) return; + if(this.isDragging) { + this.setLastPosFromTouchEvent(event); - this.currentlySelected.classList.remove("selected"); - this.currentlySelected = undefined; + event.preventDefault(); + } + }); - if (fireEvents) for (let listener of this.deselectionListeners) listener(); - } + this.svg.addEventListener("touchmove", event => { + if(this.isDragging) { + this.doDrag(event.touches[0].pageX - this.lastTouchPosition.x, event.touches[0].pageY - this.lastTouchPosition.y); - setLastPosFromTouchEvent(event) { - this.lastTouchPosition.x = event.touches[0].pageX; - this.lastTouchPosition.y = event.touches[0].pageY; - } + this.setLastPosFromTouchEvent(event); - doDrag(deltaX, deltaY) { - if (deltaX === undefined || deltaY === undefined) return; + event.preventDefault(); + } + }); + } - this.translate.x += deltaX / this.zoom; - this.translate.y += deltaY / this.zoom; + setupMouseHandlers() { + document.addEventListener('scroll', () => { + let scrollRatio = window.scrollY / this.wrapper.clientHeight; - // TODO(patrick): pretty sure this is nonsense? - this.dragDistance += Math.sqrt( - this.translate.x * this.translate.x + this.translate.y * this.translate.y - ); + this.wrapper.style.filter = "blur(" + (scrollRatio * .25) + "rem)"; + }); - this.svg.style.transform = - "scale(" + - this.zoom + - ") translate(" + - this.translate.x + - "px, " + - this.translate.y + - "px)"; - } + this.svg.addEventListener("mousedown", event => { + this.isDragging = true; + }); - setupTouchHandlers() { - this.svg.addEventListener("touchstart", (event) => { - this.isDragging = event.touches.length === 1; + this.svg.addEventListener("mousemove", event => { + if (this.isDragging) + this.doDrag(event.movementX, event.movementY); - if (this.isDragging) { - this.setLastPosFromTouchEvent(event); + this.relativeMousePosition.x = event.clientX - this.svg.getBoundingClientRect().left + this.translate.x * this.zoom; + this.relativeMousePosition.y = event.clientY - this.svg.getBoundingClientRect().top + this.translate.y * this.zoom; + }); - event.preventDefault(); - } - }); + this.svg.addEventListener("mouseleave", event => { + this.isDragging = false; + }); - this.svg.addEventListener("touchend", (event) => { - this.isDragging = event.touches.length !== 1; + this.svg.addEventListener("mouseup", event => { + this.isDragging = false; - if (this.isDragging) { - this.setLastPosFromTouchEvent(event); + if (this.dragDistance >= 5) { + let captureClick = event => { + event.stopPropagation(); + this.svg.removeEventListener('click', captureClick, true); + }; - event.preventDefault(); - } - }); + this.svg.addEventListener( + 'click', + captureClick, + true + ); + } - this.svg.addEventListener("touchmove", (event) => { - if (this.isDragging) { - this.doDrag( - event.touches[0].pageX - this.lastTouchPosition.x, - event.touches[0].pageY - this.lastTouchPosition.y - ); + this.dragDistance = 0; + }); + + this.svg.addEventListener('wheel', event => { + if (event.shiftKey) { + let unzoomedMouseX = this.relativeMousePosition.x / this.zoom; + let unzoomedMouseY = this.relativeMousePosition.y / this.zoom; + + if(this.zoom - event.deltaY / Math.abs(event.deltaY) * .1 < 0.2) + return; - this.setLastPosFromTouchEvent(event); - - event.preventDefault(); - } - }); - } - - setupMouseHandlers() { - document.addEventListener("scroll", () => { - let scrollRatio = window.scrollY / this.wrapper.clientHeight; - - this.wrapper.style.filter = "blur(" + scrollRatio * 0.25 + "rem)"; - }); - - this.svg.addEventListener("mousedown", (event) => { - this.isDragging = true; - }); - - this.svg.addEventListener("mousemove", (event) => { - if (this.isDragging) this.doDrag(event.movementX, event.movementY); - - this.relativeMousePosition.x = - event.clientX - - this.svg.getBoundingClientRect().left + - this.translate.x * this.zoom; - this.relativeMousePosition.y = - event.clientY - - this.svg.getBoundingClientRect().top + - this.translate.y * this.zoom; - }); - - this.svg.addEventListener("mouseleave", (event) => { - this.isDragging = false; - }); - - this.svg.addEventListener("mouseup", (event) => { - this.isDragging = false; - - if (this.dragDistance >= 5) { - let captureClick = (event) => { - event.stopPropagation(); - this.svg.removeEventListener("click", captureClick, true); - }; - - this.svg.addEventListener("click", captureClick, true); - } - - this.dragDistance = 0; - }); - - this.svg.addEventListener("wheel", (event) => { - if (event.shiftKey) { - let unzoomedMouseX = this.relativeMousePosition.x / this.zoom; - let unzoomedMouseY = this.relativeMousePosition.y / this.zoom; - - if (this.zoom - (event.deltaY / Math.abs(event.deltaY)) * 0.1 < 0.2) - return; - - this.zoom -= (event.deltaY / Math.abs(event.deltaY)) * 0.1; - - let rezoomedMouseX = this.relativeMousePosition.x / this.zoom; - let rezoomedMouseY = this.relativeMousePosition.y / this.zoom; - - this.translate.x += rezoomedMouseX - unzoomedMouseX; - this.translate.y += rezoomedMouseY - unzoomedMouseY; - - this.svg.style.transform = - "scale(" + - this.zoom + - ") translate(" + - this.translate.x + - "px, " + - this.translate.y + - "px)"; - } - }); - } + this.zoom -= event.deltaY / Math.abs(event.deltaY) * .1; + + let rezoomedMouseX = this.relativeMousePosition.x / this.zoom; + let rezoomedMouseY = this.relativeMousePosition.y / this.zoom; + + this.translate.x += (rezoomedMouseX - unzoomedMouseX); + this.translate.y += (rezoomedMouseY - unzoomedMouseY); + + this.svg.style.transform = "scale(" + this.zoom + ") translate(" + this.translate.x + "px, " + this.translate.y + "px)"; + } + }) + } } diff --git a/pointercrate-demonlist-pages/static/js/statsviewer/individual.js b/pointercrate-demonlist-pages/static/js/statsviewer/individual.js index a9a7fe490..1ed538cee 100644 --- a/pointercrate-demonlist-pages/static/js/statsviewer/individual.js +++ b/pointercrate-demonlist-pages/static/js/statsviewer/individual.js @@ -47,25 +47,17 @@ class IndividualStatsViewer extends StatsViewer { this.setHardest(hardest.name === "None" ? undefined : hardest); - let non100Records = playerData.records - .filter((record) => record.progress !== 100) - .sort((r1, r2) => r1.progress - r2.progress); - - this.formatRecordsInto(this._progress, non100Records); + } formatDemonsInto(element, demons) { - formatInto(element, demons.map(demon => this.formatDemon(demon, "/demonlist/permalink/" + demon.id + "/"))); + formatInto(element, demons.map(demon => this.formatDemon(demon, "/list/permalink/" + demon.id + "/"))); } formatRecordsInto(element, records) { formatInto(element, records.map(record => { - let demon = this.formatDemon(record.demon, record.video ?? ("/demonlist/permalink/" + record.demon.id + "/")); - if (record.progress !== 100) { - demon.appendChild( - document.createTextNode(" (" + record.progress + "%)") - ); - } + let demon = this.formatDemon(record.demon, record.video ?? ("/list/permalink/" + record.demon.id + "/")); + return demon; })); } diff --git a/pointercrate-demonlist-pages/static/js/statsviewer/nation.js b/pointercrate-demonlist-pages/static/js/statsviewer/nation.js index ba05deced..5eabccac8 100644 --- a/pointercrate-demonlist-pages/static/js/statsviewer/nation.js +++ b/pointercrate-demonlist-pages/static/js/statsviewer/nation.js @@ -23,7 +23,6 @@ class NationStatsViewer extends StatsViewer { this.setName(nationData.nation.nation, nationData.nation); let beaten = []; - let progress = []; let legacy = 0; let extended = 0; @@ -35,11 +34,8 @@ class NationStatsViewer extends StatsViewer { for(let record of nationData.records) { record.players.forEach(players.add, players); - if(record.progress !== 100) { - if (!nationData.verified.some(d => d.id === record.id)) - progress.push(record); - } - else { + + beaten.push(record); if(hardest === undefined || record.position < hardest.position) { @@ -51,7 +47,7 @@ class NationStatsViewer extends StatsViewer { ++extended; else ++legacy; - } + } let amountBeaten = beaten.length - extended - legacy; @@ -80,20 +76,18 @@ class NationStatsViewer extends StatsViewer { nationData.unbeaten.sort((r1, r2) => r1.name.localeCompare(r2.name)); beaten.sort((r1, r2) => r1.demon.localeCompare(r2.demon)); - progress.sort((r1, r2) => r2.progress - r1.progress); nationData.created.sort((r1, r2) => r1.demon.localeCompare(r2.demon)); - formatInto(this._unbeaten, nationData.unbeaten.map(demon => this.formatDemon(demon, "/demonlist/permalink/" + demon.id + "/"))) + formatInto(this._unbeaten, nationData.unbeaten.map(demon => this.formatDemon(demon, "/list/permalink/" + demon.id + "/"))) formatInto(this._beaten, beaten.map(record => this.formatDemonFromRecord(record))); - formatInto(this._progress, progress.map(record => this.formatDemonFromRecord(record))); formatInto(this._created, nationData.created.map(creation => { - return this.makeTooltip(this.formatDemon({name: creation.demon, position: creation.position}, "/demonlist/permalink/" + creation.id + "/"), "(Co)created by " + creation.players.length + " player" + (creation.players.length === 1 ? "" : "s") + " in this country: ", creation.players.join(", ")); + return this.makeTooltip(this.formatDemon({name: creation.demon, position: creation.position}, "/list/permalink/" + creation.id + "/"), "(Co)created by " + creation.players.length + " player" + (creation.players.length === 1 ? "" : "s") + " in this country: ", creation.players.join(", ")); })); formatInto(this._verified, nationData.verified.map(verification => { - return this.makeTooltip(this.formatDemon({name: verification.demon, position: verification.position}, "/demonlist/permalink/" + verification.id + "/"), "Verified by: ", verification.player); + return this.makeTooltip(this.formatDemon({name: verification.demon, position: verification.position}, "/list/permalink/" + verification.id + "/"), "Verified by: ", verification.player); })); formatInto(this._published, nationData.published.map(publication => { - return this.makeTooltip(this.formatDemon({name: publication.demon, position: publication.position}, "/demonlist/permalink/" + publication.id + "/"), "Published by: ", publication.player); + return this.makeTooltip(this.formatDemon({name: publication.demon, position: publication.position}, "/list/permalink/" + publication.id + "/"), "Published by: ", publication.player); })); } @@ -117,7 +111,7 @@ class NationStatsViewer extends StatsViewer { } formatDemonFromRecord(record) { - let baseElement = this.formatDemon({name: record.demon, position: record.position}, "/demonlist/permalink/" + record.id + "/"); + let baseElement = this.formatDemon({name: record.demon, position: record.position}, "/list/permalink/" + record.id + "/"); if(record.progress !== 100) baseElement.appendChild(document.createTextNode(" (" + record.progress + "%)")); diff --git a/pointercrate-demonlist/sql/paginate_records.sql b/pointercrate-demonlist/sql/paginate_records.sql index ea2170bbe..8e773b562 100644 --- a/pointercrate-demonlist/sql/paginate_records.sql +++ b/pointercrate-demonlist/sql/paginate_records.sql @@ -1,6 +1,7 @@ -SELECT records.id, progress, CASE WHEN players.link_banned THEN NULL ELSE records.video::text END, status_::text AS status, - players.id AS player_id, players.name::text AS player_name, players.banned AS player_banned, - demons.id AS demon_id, demons.name::text AS demon_name, demons.position +SELECT records.id, progress, CASE WHEN players.link_banned THEN NULL ELSE records.video::text END, + status_::text AS status, players.id AS player_id, players.name::text AS player_name, + players.banned AS player_banned, demons.id AS demon_id, demons.name::text AS demon_name, + demons.position, records.enjoyment FROM records INNER JOIN players ON records.player = players.id INNER JOIN demons ON records.demon = demons.id @@ -18,5 +19,6 @@ WHERE (records.id < $1 OR $1 IS NULL) AND (records.video = $12 OR (records.video IS NULL AND $13) OR ($12 IS NULL AND NOT $13)) AND (players.id = $14 OR $14 IS NULL) AND (records.submitter = $15 OR $15 IS NULL) + AND (records.enjoyment = $16 OR $16 IS NULL) ORDER BY id {} -LIMIT $16 +LIMIT $17 \ No newline at end of file diff --git a/pointercrate-demonlist/sql/record_by_id.sql b/pointercrate-demonlist/sql/record_by_id.sql index dc13ddd85..8dd0b5b19 100644 --- a/pointercrate-demonlist/sql/record_by_id.sql +++ b/pointercrate-demonlist/sql/record_by_id.sql @@ -4,9 +4,10 @@ SELECT progress, status_::text AS "status!: String" , players.id AS player_id, players.name AS "player_name: String", players.banned AS player_banned, demons.id AS demon_id, demons.name AS "demon_name: String", demons.position, - submitters.submitter_id AS submitter_id, submitters.banned AS submitter_banned + submitters.submitter_id AS submitter_id, submitters.banned AS submitter_banned, + enjoyment FROM records INNER JOIN players ON records.player = players.id INNER JOIN demons ON records.demon = demons.id INNER JOIN submitters ON records.submitter = submitters.submitter_id -WHERE records.id = $1 +WHERE records.id = $1 \ No newline at end of file diff --git a/pointercrate-demonlist/src/config.rs b/pointercrate-demonlist/src/config.rs index 4c7bafdba..bc346626f 100644 --- a/pointercrate-demonlist/src/config.rs +++ b/pointercrate-demonlist/src/config.rs @@ -1,9 +1,7 @@ -use pointercrate_core::util::from_env_or_default; - pub fn list_size() -> i16 { - from_env_or_default("LIST_SIZE", 50) + 100 } pub fn extended_list_size() -> i16 { - from_env_or_default("EXTENDED_LIST_SIZE", 100) + 200 } diff --git a/pointercrate-demonlist/src/demon/delete.rs b/pointercrate-demonlist/src/demon/delete.rs new file mode 100644 index 000000000..d95bdd26e --- /dev/null +++ b/pointercrate-demonlist/src/demon/delete.rs @@ -0,0 +1,47 @@ +use crate::{demon::FullDemon, error::Result}; +use log::info; +use sqlx::PgConnection; + +impl FullDemon { + pub async fn delete_demon(self, connection: &mut PgConnection) -> Result<()> { + info!("Deleting demon {}", self); + + FullDemon::delete_all_records(self.demon.base.id, &self.demon.base.name, connection).await?; + + // creator is stored separately from demons + FullDemon::delete_demon_data(self.demon.base.id, connection).await?; + + // prevent holes in the list of demons + FullDemon::shift_up(self.demon.base.position, connection).await?; + + Ok(()) + } + /// Delete all records on a demon + pub async fn delete_all_records(demon_id: i32, demon_name: &String, connection: &mut PgConnection) -> Result<()> { + // backup records before they're deleted + sqlx::query!(" + INSERT INTO rec_backup (id, progress, video, status_, player, submitter, demon, raw_footage, demon_name) SELECT id, progress, video, status_, player, submitter, demon, raw_footage, $1::text FROM records WHERE demon = $2::integer; + ", demon_name, demon_id) + .execute(&mut *connection) + .await?; + + // Delete records from records table + sqlx::query!("DELETE FROM records WHERE demon = $1", demon_id) + .execute(&mut *connection) + .await?; + + Ok(()) + } + /// Delete a demon from the database + pub async fn delete_demon_data(demon_id: i32, connection: &mut PgConnection) -> Result<()> { + sqlx::query!("DELETE FROM creators WHERE demon = $1", demon_id) + .execute(&mut *connection) + .await?; + + sqlx::query!("DELETE FROM demons WHERE id = $1", demon_id) + .execute(&mut *connection) + .await?; + + Ok(()) + } +} diff --git a/pointercrate-demonlist/src/demon/mod.rs b/pointercrate-demonlist/src/demon/mod.rs index ed40683f7..30f6dc8c9 100644 --- a/pointercrate-demonlist/src/demon/mod.rs +++ b/pointercrate-demonlist/src/demon/mod.rs @@ -22,6 +22,7 @@ use std::{ #[macro_use] mod get; pub mod audit; +mod delete; mod paginate; mod patch; mod post; @@ -53,9 +54,6 @@ pub struct Demon { pub verifier: DatabasePlayer, /// This ['Demons']'s Geometry Dash level ID - /// - /// This is automatically queried based on the level name, but can be manually overridden by a - /// list mod. pub level_id: Option, } @@ -87,7 +85,7 @@ pub struct FullDemon { #[serde(flatten)] pub demon: Demon, pub creators: Vec, - pub records: Vec, + pub records: Vec, //wtf } impl Taggable for FullDemon { @@ -143,13 +141,25 @@ impl FullDemon { if creator == verifier && creator == publisher { format!("by {}", creator) } else if creator != verifier && verifier == publisher { - format!("by {}, verified and published by {}", creator, verifier) + if verifier == "N/A" { + format!("by {}, published by {}", creator, publisher) + } else { + format!("by {}, verified and published by {}", creator, verifier) + } } else if creator != verifier && creator != publisher && publisher != verifier { - format!("by {}, verified by {}, published by {}", creator, verifier, publisher) + if verifier == "N/A" { + format!("by {}, published by {}", creator, publisher) + } else { + format!("by {}, verified by {}, published by {}", creator, verifier, publisher) + } } else if creator == verifier && creator != publisher { format!("by {}, published by {}", creator, publisher) } else if creator == publisher && creator != verifier { - format!("by {}, verified by {}", creator, verifier) + if verifier == "N/A" { + format!("by {}", creator) + } else { + format!("by {}, verified by {}", creator, verifier) + } } else { "If you're seeing this, file a bug report".to_string() } @@ -164,6 +174,17 @@ impl FullDemon { format!("published by {}, verified by {}", demon.publisher.name, demon.verifier.name) } } + /// Shifts all demons' positions by one, starting from the specified position. + /// This is used to prevent holes in the list when we delete a level. + async fn shift_up(starting_at: i16, connection: &mut PgConnection) -> Result<()> { + info!("Shifting up all demons, starting at {}", starting_at); + + sqlx::query!("UPDATE demons SET position = position - 1 WHERE position >= $1", starting_at) + .execute(connection) + .await?; + + Ok(()) + } } impl Demon { diff --git a/pointercrate-demonlist/src/demon/patch.rs b/pointercrate-demonlist/src/demon/patch.rs index c6cc16fce..019231146 100644 --- a/pointercrate-demonlist/src/demon/patch.rs +++ b/pointercrate-demonlist/src/demon/patch.rs @@ -30,6 +30,9 @@ pub struct PatchDemon { #[serde(default, deserialize_with = "non_nullable")] pub publisher: Option, + + #[serde(default, deserialize_with = "non_nullable")] + pub level_id: Option, } impl FullDemon { @@ -88,6 +91,9 @@ impl Demon { if let Some(requirement) = patch.requirement { self.set_requirement(requirement, connection).await?; } + if let Some(level_id) = patch.level_id { + self.set_level_id(level_id as i64, connection).await?; + } Ok(self) } @@ -167,6 +173,15 @@ impl Demon { self.thumbnail = thumbnail; + Ok(()) + } + pub async fn set_level_id(&mut self, level_id: i64, connection: &mut PgConnection) -> Result<()> { + sqlx::query!("UPDATE demons SET level_id = $1 WHERE id = $2", level_id, self.base.id) + .execute(connection) + .await?; + + self.level_id = Some(level_id as u64); + Ok(()) } } @@ -203,8 +218,8 @@ impl MinimalDemon { } // FIXME: Temporarily move the demon somewhere else because otherwise the unique constraints - // complains. I actually dont know why, its DEFERRABLE INITIALLY IMMEDIATE (whatever the - // fuck that means, it made it work in the python version) + // complains. I actually dont know why, its DEFERRABLE INITIALLY IMMEDIATE (whatever + // that means, it made it work in the python version of the demonlist) sqlx::query!("UPDATE demons SET position = -1 WHERE id = $1", self.id) .execute(&mut *connection) .await?; diff --git a/pointercrate-demonlist/src/demon/post.rs b/pointercrate-demonlist/src/demon/post.rs index f7bccbbe6..f74ffdbc9 100644 --- a/pointercrate-demonlist/src/demon/post.rs +++ b/pointercrate-demonlist/src/demon/post.rs @@ -41,7 +41,7 @@ impl FullDemon { Demon::shift_down(data.position, connection).await?; let created = sqlx::query!( - "INSERT INTO demons (name, position, requirement, video, verifier, publisher, level_id) VALUES ($1::text,$2,$3,$4::text,$5,$6, $7) \ + "INSERT INTO demons (name, position, requirement, video, verifier, publisher, level_id) VALUES ($1::text,$2,$3,$4::text,$5,$6,$7) \ RETURNING id, thumbnail", data.name.to_string(), data.position, diff --git a/pointercrate-demonlist/src/error.rs b/pointercrate-demonlist/src/error.rs index 95d3e3132..e003318b7 100644 --- a/pointercrate-demonlist/src/error.rs +++ b/pointercrate-demonlist/src/error.rs @@ -77,12 +77,6 @@ pub enum DemonlistError { #[display(fmt = "This player is already registered as a creator on this demon")] CreatorExists, - /// `409 CONFLICT` variant - /// - /// Error Code `40906` - #[display(fmt = "This video is already used by record #{}", id)] - DuplicateVideo { id: i32 }, - /// `409 CONFLICT` variant /// /// Error Code `40907` @@ -100,6 +94,7 @@ pub enum DemonlistError { /// requirements outside of [0, 100] /// /// Error Code `42212` + #[display(fmt = "Record requirement needs to be greater than -1 and smaller than 101")] InvalidRequirement, @@ -119,11 +114,13 @@ pub enum DemonlistError { /// `422 UNPROCESSABLE ENTITY` variant /// /// Error Code `42215` + #[display(fmt = "Record progress must lie between {} and 100%!", requirement)] InvalidProgress { /// The [`Demon`]'s record requirement requirement: i16, }, + /// `422 UNPROCESSABLE ENTITY` variant /// /// Error Code `42217` @@ -178,12 +175,6 @@ pub enum DemonlistError { #[display(fmt = "This player already have a verified claim associated with them")] AlreadyClaimed, - /// `422 UNPROCESSABLE ENTITY` variant - /// - /// Error Code `42232` - #[display(fmt = "Raw footage much be provided to submit this record")] - RawRequired, //hehe - /// `422 UNPROCESSABLE ENTITY` variant /// /// Error Code `42233` @@ -226,7 +217,6 @@ impl PointercrateError for DemonlistError { DemonNotFoundPosition { .. } => 40401, RecordNotFound { .. } => 40401, ClaimNotFound { .. } => 40401, - DuplicateVideo { .. } => 40906, NoNationSet => 40907, ConflictingClaims { .. } => 40908, InvalidProgress { .. } => 42215, @@ -237,7 +227,6 @@ impl PointercrateError for DemonlistError { UnsupportedVideoHost => 42224, DemonNameNotUnique { .. } => 42228, AlreadyClaimed => 42231, - RawRequired => 42232, MalformedRawUrl => 42233, InvalidLevelId => 42235, } diff --git a/pointercrate-demonlist/src/player/get.rs b/pointercrate-demonlist/src/player/get.rs index 5ce02f0b1..86e17fee8 100644 --- a/pointercrate-demonlist/src/player/get.rs +++ b/pointercrate-demonlist/src/player/get.rs @@ -71,7 +71,7 @@ impl DatabasePlayer { pub async fn by_name(name: &str, connection: &mut PgConnection) -> Result { let name = name.trim(); - let result = sqlx::query_as!(DatabasePlayer, "SELECT id, name, banned FROM players WHERE name = $1::CITEXT", name) + let result = sqlx::query_as!(DatabasePlayer, "SELECT id, name, banned FROM players WHERE name = $1", name) .fetch_one(connection) .await; @@ -97,16 +97,18 @@ impl DatabasePlayer { } pub async fn by_name_or_create(name: &str, connection: &mut PgConnection) -> Result { + let name = name.trim(); + match Self::by_name(name, connection).await { - Err(DemonlistError::PlayerNotFoundName { player_name }) => { - let id = sqlx::query!("INSERT INTO players (name) VALUES ($1) RETURNING id", player_name) + Err(DemonlistError::PlayerNotFoundName { .. }) => { + let id = sqlx::query!("INSERT INTO players (name) VALUES ($1) RETURNING id", name.to_string()) .fetch_one(connection) .await? .id; Ok(DatabasePlayer { id, - name: player_name, + name: name.to_owned(), banned: false, }) }, @@ -114,55 +116,3 @@ impl DatabasePlayer { } } } - -#[cfg(test)] -mod tests { - use sqlx::{pool::PoolConnection, Postgres}; - - use crate::{error::DemonlistError, player::DatabasePlayer}; - - #[sqlx::test(migrations = "../migrations")] - async fn test_by_name_or_create(mut conn: PoolConnection) { - // No players: return error - assert_eq!( - DatabasePlayer::by_name("PlasmaLust", &mut conn).await, - Err(DemonlistError::PlayerNotFoundName { - player_name: "PlasmaLust".to_string() - }) - ); - - // White spaces are trimmed, even in the error case - assert_eq!( - DatabasePlayer::by_name(" PlasmaLust ", &mut conn).await, - Err(DemonlistError::PlayerNotFoundName { - player_name: "PlasmaLust".to_string() - }) - ); - - // Create the player - let player = DatabasePlayer::by_name_or_create(" PlasmaLust", &mut conn).await.unwrap(); - // Whitespaces got stripped - assert_eq!(player.name, "PlasmaLust"); - - // Now `by_name` returns the player - assert_eq!(DatabasePlayer::by_name("PlasmaLust", &mut conn).await.as_ref(), Ok(&player)); - // Even with whitespaces stripped - assert_eq!(DatabasePlayer::by_name(" PlasmaLust ", &mut conn).await.as_ref(), Ok(&player)); - // And for different capitalization - assert_eq!(DatabasePlayer::by_name(" plAsmalust ", &mut conn).await.as_ref(), Ok(&player)); - - // Same thing for by_name_or_create - assert_eq!( - DatabasePlayer::by_name_or_create("PlasmaLust", &mut conn).await.as_ref(), - Ok(&player) - ); - assert_eq!( - DatabasePlayer::by_name_or_create(" PlasmaLust ", &mut conn).await.as_ref(), - Ok(&player) - ); - assert_eq!( - DatabasePlayer::by_name_or_create(" plAsmalust ", &mut conn).await.as_ref(), - Ok(&player) - ); - } -} diff --git a/pointercrate-demonlist/src/player/mod.rs b/pointercrate-demonlist/src/player/mod.rs index 35e8fc7fe..dd8331726 100644 --- a/pointercrate-demonlist/src/player/mod.rs +++ b/pointercrate-demonlist/src/player/mod.rs @@ -51,10 +51,12 @@ pub struct Player { /// * Record progress updated /// * Record holder updated /// * Record Added + /// * Record Removed /// - Demon updates /// * Demon movement/addition (recompute all scores) /// * Demon requirement updated (recompute all scores) /// * Demon verifier updated + /// * Demon removed /// - Player updates /// * Player banned /// * Player objects merged diff --git a/pointercrate-demonlist/src/record/get.rs b/pointercrate-demonlist/src/record/get.rs index a5bcb155a..2ec767aae 100644 --- a/pointercrate-demonlist/src/record/get.rs +++ b/pointercrate-demonlist/src/record/get.rs @@ -23,6 +23,7 @@ struct FetchedRecord { position: i16, submitter_id: i32, submitter_banned: bool, + enjoyment: Option, } impl FullRecord { @@ -37,6 +38,7 @@ impl FullRecord { progress: row.progress, video: row.video, raw_footage: row.raw_footage, + enjoyment: row.enjoyment, status: RecordStatus::from_sql(&row.status), player: DatabasePlayer { id: row.player_id, @@ -100,11 +102,12 @@ pub async fn approved_records_on(demon: &MinimalDemon, connection: &mut PgConnec banned: bool, nation: Option, iso_country_code: Option, + enjoyment: Option, } 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, + r#"SELECT records.id, progress, enjoyment, CASE WHEN players.link_banned THEN NULL ELSE video::text END, players.id AS player_id, 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 @@ -114,12 +117,13 @@ pub async fn approved_records_on(demon: &MinimalDemon, connection: &mut PgConnec let mut records = Vec::new(); while let Some(row) = stream.next().await { - let row = row?; + let row: Fetched = row?; records.push(MinimalRecordP { id: row.id, progress: row.progress, video: row.video, + enjoyment: row.enjoyment, status: RecordStatus::Approved, player: DatabasePlayer { id: row.player_id, diff --git a/pointercrate-demonlist/src/record/mod.rs b/pointercrate-demonlist/src/record/mod.rs index bd2e975fd..ac4e848c6 100644 --- a/pointercrate-demonlist/src/record/mod.rs +++ b/pointercrate-demonlist/src/record/mod.rs @@ -123,7 +123,7 @@ impl<'de> Deserialize<'de> for RecordStatus { } #[derive(Debug, Deserialize, Serialize, Display, Hash)] -#[display(fmt = "{} {}% on {} (ID: {})", player, progress, demon, id)] +#[display(fmt = "{} {} (ID: {})", player, demon, id)] pub struct FullRecord { pub id: i32, pub progress: i16, @@ -133,6 +133,7 @@ pub struct FullRecord { pub demon: MinimalDemon, pub submitter: Option, pub raw_footage: Option, + pub enjoyment: Option, } impl Taggable for FullRecord { @@ -152,10 +153,11 @@ impl Taggable for FullRecord { } #[derive(Debug, Hash, Serialize, Display)] -#[display(fmt = "{} {}% on {} (ID: {})", player, progress, demon, id)] +#[display(fmt = "{} {} (ID: {})", player, demon, id)] pub struct MinimalRecordPD { pub id: i32, pub progress: i16, + pub enjoyment: Option, pub video: Option, pub status: RecordStatus, pub demon: MinimalDemon, @@ -163,7 +165,7 @@ pub struct MinimalRecordPD { } #[derive(Debug, Hash, Serialize, Deserialize, Display, PartialEq, Eq)] -#[display(fmt = "{}% on {} (ID: {})", progress, demon, id)] +#[display(fmt = " {} (ID: {})", demon, id)] pub struct MinimalRecordD { pub id: i32, pub progress: i16, @@ -181,6 +183,7 @@ pub struct MinimalRecordP { pub status: RecordStatus, pub player: DatabasePlayer, pub nationality: Option, + pub enjoyment: Option, } impl FullRecord { diff --git a/pointercrate-demonlist/src/record/paginate.rs b/pointercrate-demonlist/src/record/paginate.rs index f58fce1e8..f3eda0c1a 100644 --- a/pointercrate-demonlist/src/record/paginate.rs +++ b/pointercrate-demonlist/src/record/paginate.rs @@ -52,6 +52,9 @@ pub struct RecordPagination { #[serde(default, deserialize_with = "nullable")] video: Option>, + #[serde(default, deserialize_with = "nullable")] + enjoyment: Option>, + #[serde(default, deserialize_with = "non_nullable")] pub submitter: Option, } @@ -93,6 +96,7 @@ impl Paginatable for MinimalRecordPD { .bind(query.video == Some(None)) .bind(query.player) .bind(query.submitter) + .bind(query.enjoyment) .bind(query.params.limit + 1) .fetch(&mut *connection); @@ -105,6 +109,7 @@ impl Paginatable for MinimalRecordPD { id: row.try_get("id")?, progress: row.try_get("progress")?, video: row.try_get("video")?, + enjoyment: row.try_get("enjoyment")?, status: RecordStatus::from_sql(&row.try_get::("status")?), player: DatabasePlayer { id: row.try_get("player_id")?, diff --git a/pointercrate-demonlist/src/record/patch.rs b/pointercrate-demonlist/src/record/patch.rs index 8256a8c07..ba3d073ee 100644 --- a/pointercrate-demonlist/src/record/patch.rs +++ b/pointercrate-demonlist/src/record/patch.rs @@ -20,6 +20,9 @@ pub struct PatchRecord { #[serde(default, deserialize_with = "nullable")] video: Option>, + #[serde(default, deserialize_with = "nullable")] + enjoyment: Option>, + #[serde(default, deserialize_with = "non_nullable")] status: Option, @@ -49,6 +52,13 @@ impl FullRecord { } } + if let Some(enjoyment) = data.enjoyment { + match enjoyment { + None => self.delete_enjoyment(connection).await?, + Some(enjoyment) => self.set_enjoyment(enjoyment, connection).await?, + } + } + if let Some(status) = data.status { self.set_status(status, connection).await? } @@ -198,6 +208,30 @@ impl FullRecord { Ok(()) } + pub async fn delete_enjoyment(&mut self, connection: &mut PgConnection) -> Result<()> { + sqlx::query!("UPDATE records SET enjoyment = NULL WHERE id = $1", self.id) + .execute(connection) + .await?; + + self.enjoyment = None; + + Ok(()) + } + + pub async fn set_enjoyment(&mut self, enjoyment: i32, connection: &mut PgConnection) -> Result<()> { + if Some(&enjoyment) == self.enjoyment.as_ref() { + return Ok(()); + } + + sqlx::query!("UPDATE records SET enjoyment = $1::integer WHERE id = $2", enjoyment, self.id) + .execute(connection) + .await?; + + self.enjoyment = Some(enjoyment); + + Ok(()) + } + pub async fn set_video(&mut self, video: String, connection: &mut PgConnection) -> Result<()> { let video = crate::video::validate(&video)?; @@ -205,13 +239,6 @@ impl FullRecord { 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 }); - } - sqlx::query!("UPDATE records SET video = $1::text WHERE id = $2", video, self.id) .execute(connection) .await?; diff --git a/pointercrate-demonlist/src/record/post.rs b/pointercrate-demonlist/src/record/post.rs index e0a9cdbd7..e44b46fcf 100644 --- a/pointercrate-demonlist/src/record/post.rs +++ b/pointercrate-demonlist/src/record/post.rs @@ -12,7 +12,7 @@ use sqlx::PgConnection; use url::Url; #[derive(Deserialize, Debug, Display)] -#[display(fmt = "{}% on {} by {} [status: {}]", progress, demon, player, status)] +#[display(fmt = "{} by {} [status: {}]", demon, player, status)] pub struct Submission { progress: i16, player: String, @@ -21,6 +21,7 @@ pub struct Submission { video: Option, #[serde(default)] raw_footage: Option, + enjoyment: Option, #[serde(default)] status: RecordStatus, @@ -35,7 +36,7 @@ pub struct NormalizedSubmission { player: DatabasePlayer, demon: MinimalDemon, status: RecordStatus, - + enjoyment: Option, video: Option, raw_footage: Option, note: Option, @@ -46,6 +47,7 @@ pub struct ValidatedSubmission { progress: i16, video: Option, raw_footage: Option, + enjoyment: Option, status: RecordStatus, player: DatabasePlayer, demon: MinimalDemon, @@ -79,6 +81,7 @@ impl Submission { status: self.status, video, raw_footage: self.raw_footage, + enjoyment: self.enjoyment, note: self.note, }) } @@ -108,7 +111,6 @@ impl NormalizedSubmission { 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 }); } @@ -116,10 +118,10 @@ impl NormalizedSubmission { debug!("Submission is valid, checking for duplicates!"); // Search for existing records. If a video exists, we also check if a record with - // exactly that video exists. + // exactly that video exists on the same level. if let Some(ref video) = self.video { - if let Some(row) = sqlx::query!(r#"SELECT id, status_::text as "status_!: String" FROM records WHERE video = $1"#, video.to_string()) + if let Some(row) = sqlx::query!(r#"SELECT id, status_::text as "status_!: String" FROM records WHERE video = $1 AND demon = $2"#, video.to_string(), self.demon.id) .fetch_optional(&mut *connection) // FIXME(sqlx) .await? { @@ -151,10 +153,7 @@ impl NormalizedSubmission { Some(ref raw) => { let _ = Url::parse(raw).map_err(|_| DemonlistError::MalformedRawUrl)?; }, - None if self.status == RecordStatus::Submitted => { - // list mods can submit without raw - return Err(DemonlistError::RawRequired); - }, + _ => (), } @@ -162,6 +161,7 @@ impl NormalizedSubmission { progress: self.progress, video: self.video, raw_footage: self.raw_footage, + enjoyment: self.enjoyment, status: self.status, player: self.player, demon: self.demon, @@ -173,13 +173,14 @@ impl NormalizedSubmission { impl ValidatedSubmission { pub async fn create(self, submitter: Submitter, connection: &mut PgConnection) -> Result { let id = sqlx::query!( - "INSERT INTO records (progress, video, status_, player, submitter, demon, raw_footage) VALUES ($1, $2::TEXT, 'SUBMITTED', $3, $4, $5, $6) RETURNING id", + "INSERT INTO records (progress, video, status_, player, submitter, demon, raw_footage, enjoyment) VALUES ($1, $2::TEXT, 'SUBMITTED', $3, $4, $5, $6, $7) RETURNING id", self.progress, self.video, self.player.id, submitter.id, self.demon.id, - self.raw_footage + self.raw_footage, + self.enjoyment ) .fetch_one(&mut *connection) .await? @@ -194,6 +195,7 @@ impl ValidatedSubmission { player: self.player, demon: self.demon, submitter: Some(submitter), + enjoyment: self.enjoyment, }; // Dealing with different status and upholding their invariant is complicated, we should not @@ -226,10 +228,19 @@ mod tests { player::DatabasePlayer, record::{post::NormalizedSubmission, RecordStatus}, }; - use sqlx::{pool::PoolConnection, Postgres}; + use pointercrate_core::pool::PointercratePool; + use sqlx::{Postgres, Transaction}; + + async fn connection() -> Transaction<'static, Postgres> { + let _ = dotenv::dotenv(); + + PointercratePool::init().await.transaction().await.unwrap() + } + + #[tokio::test] + async fn test_banned_cannot_submit() { + let mut conn = connection().await; - #[sqlx::test(migrations = "../migrations")] - async fn test_banned_cannot_submit(mut conn: PoolConnection) { let result = NormalizedSubmission { progress: 100, player: DatabasePlayer { @@ -245,6 +256,7 @@ mod tests { status: RecordStatus::Submitted, video: None, raw_footage: None, + enjoyment: None, note: None, } .validate(&mut conn) diff --git a/pointercrate-example/.env.sample b/pointercrate-example/.env.sample deleted file mode 100644 index 380abb202..000000000 --- a/pointercrate-example/.env.sample +++ /dev/null @@ -1,14 +0,0 @@ -# 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 index 37196a5a5..5632aeac0 100644 --- a/pointercrate-example/Cargo.toml +++ b/pointercrate-example/Cargo.toml @@ -7,6 +7,7 @@ edition.workspace = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +cloudflare = "0.11.0" dotenv = "0.15.0" maud = "0.26.0" pointercrate-core = { version = "0.1.0", path = "../pointercrate-core" } @@ -19,3 +20,5 @@ pointercrate-user = { version = "0.1.0", path = "../pointercrate-user" } pointercrate-user-api = { version = "0.1.0", path = "../pointercrate-user-api", features = ["legacy_accounts"] } pointercrate-user-pages = { version = "0.1.0", path = "../pointercrate-user-pages", features = ["legacy_accounts"] } rocket = "0.5.1" +shuttle-runtime = "0.48.0" +shuttle-rocket = "0.48.0" \ No newline at end of file diff --git a/pointercrate-example/sample/.env.sample b/pointercrate-example/sample/.env.sample new file mode 100644 index 000000000..4b371d14b --- /dev/null +++ b/pointercrate-example/sample/.env.sample @@ -0,0 +1,2 @@ +# the connection string to your local database. format: "postgresql://rolename:password@localhost/databasename" +DATABASE_URL="postgresql://pointercrate:asdf@localhost/pointercrate" \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/00000000000000_diesel_initial_setup.down.sql b/pointercrate-example/sample/migrations/_new/00000000000000_diesel_initial_setup.down.sql new file mode 100644 index 000000000..a9f526091 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/00000000000000_diesel_initial_setup.down.sql @@ -0,0 +1,6 @@ +-- This file was automatically created by Diesel to setup helper functions +-- and other internal bookkeeping. This file is safe to edit, any future +-- changes will be added to existing projects as new migrations. + +DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass); +DROP FUNCTION IF EXISTS diesel_set_updated_at(); diff --git a/pointercrate-example/sample/migrations/_new/20180918150231_initial.down.sql b/pointercrate-example/sample/migrations/_new/20180918150231_initial.down.sql new file mode 100644 index 000000000..f1bc7918e --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20180918150231_initial.down.sql @@ -0,0 +1,15 @@ +-- This file should undo anything in `up.sql` + +-- Drop everything in reverse order + +DROP TABLE audit_log; +DROP TABLE creators; +DROP TABLE records; +DROP TABLE demons; +DROP TABLE members; +DROP TABLE submitters; +DROP TABLE players; +DROP TYPE AUDIT_OPERATION; + +DROP TYPE RECORD_STATUS; +DROP EXTENSION CITEXT; \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20180918150231_initial.up.sql b/pointercrate-example/sample/migrations/_new/20180918150231_initial.up.sql new file mode 100644 index 000000000..0ee0661b6 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20180918150231_initial.up.sql @@ -0,0 +1,70 @@ +-- Your SQL goes here + +-- This is a workaround +-- To get the new backend to work, we need to run all migrations against the existing database +-- all things from this migration already exist in that database, so theoretically, it should be a NOOP. +-- However, there is no `CREATE ... IF NOT EXISTS` equavalent for types in postgres +-- The following code tries to create the type and ignores the error by explicitly registering a handler for it +DO $$ BEGIN + CREATE TYPE RECORD_STATUS AS ENUM ('APPROVED', 'REJECTED', 'SUBMITTED', 'DELETED'); +EXCEPTION + WHEN duplicate_object THEN null; +END $$; + +CREATE EXTENSION CITEXT; + +CREATE TABLE IF NOT EXISTS players( + id SERIAL PRIMARY KEY, + name CITEXT NOT NULL UNIQUE, + banned BOOLEAN NOT NULL DEFAULT FALSE +); + +CREATE TABLE IF NOT EXISTS submitters ( + submitter_id SERIAL PRIMARY KEY, + ip_address INET NOT NULL, + banned BOOLEAN NOT NULL DEFAULT FALSE +); + +CREATE TABLE IF NOT EXISTS members ( + member_id SERIAL PRIMARY KEY, + name CITEXT UNIQUE NOT NULL, + display_name CITEXT NULL DEFAULT NULL, + youtube_channel VARCHAR(200) NULL DEFAULT NULL, + password_hash BYTEA NOT NULL, + password_salt BYTEA NOT NULL, + permissions BIT(16) NOT NULL DEFAULT B'0000000000000000'::BIT(16) +); + +CREATE TABLE IF NOT EXISTS demons ( + name CITEXT PRIMARY KEY, + position SMALLINT NOT NULL, + requirement SMALLINT NOT NULL, + video VARCHAR(200), + description TEXT NULL, + notes TEXT NULL, + verifier INT NOT NULL REFERENCES players(id) ON DELETE RESTRICT ON UPDATE CASCADE, + publisher INT NOT NULL REFERENCES players(id) ON DELETE RESTRICT ON UPDATE CASCADE, + + CONSTRAINT unique_position UNIQUE (position) DEFERRABLE INITIALLY IMMEDIATE, + CONSTRAINT valid_record_req CHECK (requirement >= 0 AND requirement <= 100) +); + +CREATE TABLE IF NOT EXISTS records ( + id SERIAL PRIMARY KEY, + progress SMALLINT CHECK (progress >= 0 AND progress <= 100) NOT NULL, + video VARCHAR(200) UNIQUE, + status_ RECORD_STATUS NOT NULL, + player INT NOT NULL REFERENCES players(id) ON DELETE RESTRICT ON UPDATE CASCADE, + submitter INT NOT NULL REFERENCES submitters(submitter_id) ON DELETE RESTRICT, + demon CITEXT NOT NULL REFERENCES demons(name) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT records_demon_player_status__key UNIQUE (demon, player, status_) DEFERRABLE INITIALLY IMMEDIATE +); + +CREATE TABLE IF NOT EXISTS creators ( + demon CITEXT NOT NULL REFERENCES demons(name) ON DELETE RESTRICT ON UPDATE CASCADE, + creator INT NOT NULL REFERENCES players(id) ON DELETE RESTRICT ON UPDATE CASCADE, + PRIMARY KEY (demon, creator) +); + +GRANT TRIGGER, REFERENCES, SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO pointercrate; +GRANT USAGE ON ALL SEQUENCES IN SCHEMA public TO pointercrate; \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20181229171817_demon_view.down.sql b/pointercrate-example/sample/migrations/_new/20181229171817_demon_view.down.sql new file mode 100644 index 000000000..60593db51 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20181229171817_demon_view.down.sql @@ -0,0 +1,3 @@ +-- This file should undo anything in `up.sql` + +DROP VIEW demon_publisher_verifier_join; \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20181229171817_demon_view.up.sql b/pointercrate-example/sample/migrations/_new/20181229171817_demon_view.up.sql new file mode 100644 index 000000000..61e69d66a --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20181229171817_demon_view.up.sql @@ -0,0 +1,9 @@ +-- Your SQL goes here + +CREATE OR REPLACE VIEW demon_verifier_publisher_join AS +SELECT p1.name AS vname, p1.id AS vid, p1.banned AS vbanned, p2.name AS pname, p2.id AS pid, p2.banned AS pbanned +FROM demons +INNER JOIN players AS p1 +ON demons.verifier = p1.id +INNER JOIN players AS p2 +ON demons.publisher = p2.id \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20190105154218_new_audit_log.down.sql b/pointercrate-example/sample/migrations/_new/20190105154218_new_audit_log.down.sql new file mode 100644 index 000000000..042954d34 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20190105154218_new_audit_log.down.sql @@ -0,0 +1,47 @@ +-- This file should undo anything in `up.sql` + +DROP TABLE demon_additions; +DROP FUNCTION audit_demon_addition() CASCADE; + +DROP TABLE demon_modifications; +DROP FUNCTION audit_demon_modification() CASCADE; + +DROP TABLE record_additions; +DROP FUNCTION audit_record_addition() CASCADE; + +DROP TABLE record_modifications; +DROP FUNCTION audit_record_modification() CASCADE; + +DROP TABLE record_deletions; +DROP FUNCTION audit_record_deletion() CASCADE; + +DROP TABLE player_additions; +DROP FUNCTION audit_player_addition() CASCADE; + +DROP TABLE player_modifications; +DROP FUNCTION audit_player_modification() CASCADE; + +DROP TABLE player_deletions; +DROP FUNCTION audit_player_deletion() CASCADE; + +DROP TABLE creator_additions; +DROP FUNCTION audit_creator_addition() CASCADE; + +DROP TABLE creator_deletions; +DROP FUNCTION audit_creator_deletion() CASCADE; + +DROP TABLE submitter_modifications; +DROP FUNCTION audit_submitter_modification() CASCADE; + +DROP TABLE user_additions; +DROP FUNCTION audit_user_addition() CASCADE; + +DROP TABLE user_modifications; +DROP FUNCTION audit_user_modification() CASCADE; + +DROP TABLE user_deletions; +DROP FUNCTION audit_user_deletion() CASCADE; + +DROP TABLE audit_log2; + +DROP TABLE active_user; \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20190105154218_new_audit_log.up.sql b/pointercrate-example/sample/migrations/_new/20190105154218_new_audit_log.up.sql new file mode 100644 index 000000000..20ec73d0b --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20190105154218_new_audit_log.up.sql @@ -0,0 +1,369 @@ +-- Alright, audit_log overhault +-- In audit log entries we cannot use foreign keys because that would force us to delete log entries +-- when objects are removed from the db, or disallow the deletion of objects +-- Thus, we need to make sure we references database objects by a non-modifiable primary key, which +-- we do not actually use as foreign key +-- +-- The problem here is, that the "demons" relation doesn't have such a column - +-- position and name (while both unique and even primary in "name"'s case') are modifiable +-- This is the reason we do not allow the deletion of demons as of right now +-- +-- (Once the new backend is running and we no longer need to be backwards compatible with the +-- python codebase, we can ofc modify the demons table) + +-- Global fallback table in case no temporary 'active_user' table was created in the current session +CREATE TABLE active_user (id INTEGER PRIMARY KEY); -- primary key required because diesel migrations break otherwise + +-- TODO: generate a dummy member with ID 0 +INSERT INTO active_user VALUES (0); + +CREATE TABLE audit_log2 ( + time TIMESTAMP WITHOUT TIME ZONE DEFAULT (NOW() AT TIME ZONE 'utc') NOT NULL, + audit_id SERIAL PRIMARY KEY NOT NULL, + userid INTEGER NOT NULL -- REFERENCES members(member_id) +); + +CREATE TABLE demon_additions ( + -- Note that this currently forces us to not delete demons + name CITEXT REFERENCES demons(name) ON DELETE RESTRICT ON UPDATE CASCADE NOT NULL +) INHERITS (audit_log2); + +CREATE FUNCTION audit_demon_addition() RETURNS trigger AS $demon_add_trigger$ + BEGIN + INSERT INTO demon_additions (userid, name) (SELECT id, NEW.name FROM active_user LIMIT 1); + RETURN NEW; + END; +$demon_add_trigger$ LANGUAGE plpgsql; + +CREATE TRIGGER demon_addition_trigger AFTER INSERT ON demons FOR EACH ROW EXECUTE PROCEDURE audit_demon_addition(); + +CREATE TABLE demon_modifications ( + -- Column that keeps track of the demon that was modified + -- Note that this currently forces us to not delete demons + demon CITEXT REFERENCES demons(name) ON DELETE RESTRICT ON UPDATE CASCADE NOT NULL, + + -- Column that keeps track of changes to the demon's name + name CITEXT NULL, + position SMALLINT NULL, + requirement SMALLINT NULL, + video VARCHAR(200) NULL, + verifier INT NULL, + publisher INT NULL +) INHERITS (audit_log2); + +CREATE FUNCTION audit_demon_modification() RETURNS trigger AS $demon_modification_trigger$ + DECLARE + name_change CITEXT; + position_change SMALLINT; + requirement_change SMALLINT; + video_change VARCHAR(200); + verifier_change SMALLINT; + publisher_change SMALLINT; + BEGIN + IF (OLD.name <> NEW.name) THEN + name_change = OLD.name; + END IF; + + IF (OLD.position <> NEW.position) THEN + position_change = OLD.position; + END IF; + + IF (OLD.requirement <> NEW.requirement) THEN + requirement_change = OLD.requirement; + END IF; + + IF (OLD.video <> NEW.video) THEN + video_change = OLD.video; + END IF; + + IF (OLD.verifier <> NEW.verifier) THEN + verifier_change = OLD.verifier; + END IF; + + IF (OLD.publisher <> NEW.publisher) THEN + publisher_change = OLD.publisher; + END IF; + + INSERT INTO demon_modifications (userid, demon, name, position, requirement, video, verifier, publisher) + (SELECT id, NEW.name, name_change, position_change, requirement_change, video_change, verifier_change, publisher_change + FROM active_user LIMIT 1); + + RETURN NEW; + END; +$demon_modification_trigger$ LANGUAGE plpgsql; + +CREATE TRIGGER demon_modification_trigger AFTER UPDATE ON demons FOR EACH ROW EXECUTE PROCEDURE audit_demon_modification(); + +CREATE TABLE record_additions ( + id INTEGER NOT NULL -- REFERENCES records(id) +) INHERITS (audit_log2); + +CREATE FUNCTION audit_record_addition() RETURNS trigger AS $record_add_trigger$ + BEGIN + INSERT INTO record_additions (userid, id) (SELECT id, NEW.id FROM active_user LIMIT 1); + RETURN NEW; + END; +$record_add_trigger$ LANGUAGE plpgsql; + +CREATE TRIGGER record_addition_trigger AFTER INSERT ON records FOR EACH ROW EXECUTE PROCEDURE audit_record_addition(); + +CREATE TABLE record_modifications ( + id INTEGER NOT NULL, -- REFERENCES records(id) + + progress SMALLINT NULL, + video VARCHAR(200) NULL, + status_ RECORD_STATUS NULL, + player INT NULL, -- REFERENCES players(id) + demon CITEXT NULL -- REFERENCES demons(name) +) INHERITS (audit_log2); + +CREATE FUNCTION audit_record_modification() RETURNS trigger AS $record_modification_trigger$ + DECLARE + progress_change SMALLINT; + video_change VARCHAR(200); + status_change RECORD_STATUS; + player_change INT; + demon_change CITEXT; + BEGIN + if (OLD.progress <> NEW.progress) THEN + progress_change = OLD.progress; + END IF; + + IF (OLD.video <> NEW.video) THEN + video_change = OLD.video; + END IF; + + IF (OLD.status_ <> NEW.status_) THEN + status_change = OLD.status_; + END IF; + + IF (OLD.player <> NEW.player) THEN + player_change = OLD.player; + END IF; + + IF (OLD.demon <> NEW.demon) THEN + demon_change = OLD.demon; + END IF; + + INSERT INTO record_modifications (userid, id, progress, video, status_, player, demon) + (SELECT id, NEW.id, progress_change, video_change, status_change, player_change, demon_change + FROM active_user LIMIT 1); + + RETURN NEW; + END; +$record_modification_trigger$ LANGUAGE plpgsql; + +CREATE TRIGGER record_modification_trigger AFTER UPDATE ON records FOR EACH ROW EXECUTE PROCEDURE audit_record_modification(); + +-- Before deletion we add a `record_modifications` entry that's a copy of the record directly before deletion +CREATE TABLE record_deletions ( + id INTEGER NOT NULL -- REFERENCES records(id) +) INHERITS (audit_log2); + +CREATE FUNCTION audit_record_deletion() RETURNS trigger AS $record_deletion_trigger$ + BEGIN + INSERT INTO record_modifications (userid, id, progress, video, status_, player, demon) + (SELECT id, OLD.id, OLD.progress, OLD.video, OLD.status_, OLD.player, OLD.demon + FROM active_user LIMIT 1); + + INSERT INTO record_deletions (userid, id) + (SELECT id, OLD.id FROM active_user LIMIT 1); + + RETURN NULL; + END; +$record_deletion_trigger$ LANGUAGE plpgsql; + +CREATE TRIGGER record_deletion_trigger AFTER DELETE ON records FOR EACH ROW EXECUTE PROCEDURE audit_record_deletion(); + +CREATE TABLE player_additions ( + id INTEGER NOT NULL -- REFERENCES players(id) +) INHERITS (audit_log2); + +CREATE FUNCTION audit_player_addition() RETURNS trigger AS $record_addition_trigger$ + BEGIN + INSERT INTO player_additions(userid, id) + (SELECT id, NEW.id FROM active_user LIMIT 1); + + RETURN NEW; + END; +$record_addition_trigger$ LANGUAGE plpgsql; + +CREATE TRIGGER player_addition_trigger AFTER INSERT ON players FOR EACH ROW EXECUTE PROCEDURE audit_player_addition(); + +CREATE TABLE player_modifications ( + id INTEGER NOT NULL, -- REFERENCES players(id) + + name CITEXT NULL, + banned BOOLEAN NULL +) INHERITS (audit_log2); + +CREATE FUNCTION audit_player_modification() RETURNS trigger as $player_modification_trigger$ + DECLARE + name_change CITEXT; + banned_change BOOLEAN; + BEGIN + IF (OLD.name <> NEW.name) THEN + name_change = OLD.name; + END IF; + + IF (OLD.banned <> NEW.banned) THEN + banned_change = OLD.banned; + END IF; + + INSERT INTO player_modifications (userid, id, name, banned) + (SELECT id, NEW.id, name_change, banned_change FROM active_user LIMIT 1); + + RETURN NEW; + END; +$player_modification_trigger$ LANGUAGE plpgsql; + +CREATE TRIGGER player_modification_trigger AFTER UPDATE ON players FOR EACH ROW EXECUTE PROCEDURE audit_player_modification(); + +-- See handling of record_deletions +CREATE TABLE player_deletions ( + id INTEGER NOT NULL -- REFERENCES players(id) +) INHERITS (audit_log2); + +CREATE FUNCTION audit_player_deletion() RETURNS trigger AS $player_deletion_trigger$ + BEGIN + INSERT INTO player_modifications (userid, id, name, banned) + (SELECT id, OLD.id, OLD.name, OLD.banned + FROM active_user LIMIT 1); + + INSERT INTO player_deletions (userid, id) + (SELECT id, OLD.id FROM active_user LIMIT 1); + + RETURN NULL; + END; +$player_deletion_trigger$ LANGUAGE plpgsql; + +CREATE TRIGGER player_deletion_trigger AFTER DELETE ON players FOR EACH ROW EXECUTE PROCEDURE audit_player_deletion(); + +CREATE TABLE creator_additions ( + creator INTEGER NOT NULL, + demon CITEXT NOT NULL +) INHERITS (audit_log2); + +CREATE FUNCTION audit_creator_addition() RETURNS trigger AS $audit_creator_addition$ + BEGIN + INSERT INTO creator_additions (userid, creator, demon) + (SELECT id, NEW.creator, NEW.demon + FROM active_user LIMIT 1); + + RETURN NEW; + END; +$audit_creator_addition$ LANGUAGE plpgsql; + +CREATE TRIGGER creator_addition_trigger AFTER INSERT ON creators FOR EACH ROW EXECUTE PROCEDURE audit_creator_addition(); + +CREATE TABLE creator_deletions( + creator INTEGER NOT NULL, + demon CITEXT NOT NULL +) INHERITS (audit_log2); + +CREATE FUNCTION audit_creator_deletion() RETURNS trigger AS $creator_deletion_trigger$ + BEGIN + INSERT INTO creator_deletions (userid, creator, demon) + (SELECT id, OLD.creator, OLD.demon + FROM active_user LIMIT 1); + + RETURN NULL; + END; +$creator_deletion_trigger$ LANGUAGE plpgsql; + +CREATE TRIGGER creator_deletion_trigger AFTER DELETE ON creators FOR EACH ROW EXECUTE PROCEDURE audit_creator_deletion(); + +CREATE TABLE submitter_modifications ( + submitter INTEGER NOT NULL, + + banned BOOLEAN NULL +) INHERITS (audit_log2); + +CREATE FUNCTION audit_submitter_modification() RETURNS trigger as $submitter_modifications_trigger$ + DECLARE + banned_change BOOLEAN; + BEGIN + IF (OLD.banned <> NEW.banned) THEN + banned_change = OLD.banned; + END IF; + + INSERT INTO submitter_modifications (userid, submitter, banned) + (SELECT id, NEW.submitter_id, banned_change FROM active_user LIMIT 1); + + RETURN NEW; + END; +$submitter_modifications_trigger$ LANGUAGE plpgsql; + +CREATE TRIGGER submitter_modification_trigger AFTER UPDATE ON submitters FOR EACH ROW EXECUTE PROCEDURE audit_submitter_modification(); + +CREATE TABLE user_additions ( + id INTEGER NOT NULL -- REFERENCES members(member_id) +) INHERITS (audit_log2); + +CREATE FUNCTION audit_user_addition() RETURNS trigger AS $audit_user_addition$ + BEGIN + -- cannot be logged in during registration + INSERT INTO user_additions (userid, id) VALUES (0, NEW.member_id); + + RETURN NEW; + END; +$audit_user_addition$ LANGUAGE plpgsql; + +CREATE TRIGGER user_addition_trigger AFTER INSERT ON members FOR EACH ROW EXECUTE PROCEDURE audit_user_addition(); + +CREATE TABLE user_modifications ( + id INTEGER NOT NULL, -- REFERENCES members(member_id) + + -- fields updatable by user themself + display_name CITEXT NULL, + youtube_channel CITEXT NULL, + + -- fields updatable by staff + permissions BIT(16) NULL +) INHERITS (audit_log2); + +CREATE FUNCTION audit_user_modification() RETURNS trigger as $user_modification_trigger$ + DECLARE + display_name_change CITEXT; + youtube_channel_change BOOLEAN; + permissions_change BIT(16); + BEGIN + IF (OLD.display_name <> NEW.display_name) THEN + display_name_change = OLD.display_name; + END IF; + + IF (OLD.youtube_channel <> NEW.youtube_channel) THEN + youtube_channel_change = OLD.youtube_channel; + END IF; + + IF (OLD.permissions <> NEW.permissions) THEN + permissions_change = OLD.permissions; + END IF; + + INSERT INTO user_modifications (userid, id, display_name, youtube_channel, permissions) + (SELECT id, NEW.member_id, display_name_change, youtube_channel_change, permissions_change FROM active_user LIMIT 1); + + RETURN NEW; + END; +$user_modification_trigger$ LANGUAGE plpgsql; + +CREATE TRIGGER user_modification_trigger AFTER UPDATE ON members FOR EACH ROW EXECUTE PROCEDURE audit_user_modification(); + +CREATE TABLE user_deletions ( + id INTEGER NOT NULL -- REFERENCES members(member_id) +) INHERITS (audit_log2); + +CREATE FUNCTION audit_user_deletion() RETURNS trigger AS $user_deletion_trigger$ + BEGIN + INSERT INTO user_modifications (userid, id, display_name, youtube_channel, permissions) + (SELECT id, OLD.member_id, OLD.display_name, OLD.youtube_channel, OLD.permissions + FROM active_user LIMIT 1); + + INSERT INTO user_deletions (userid, id) + (SELECT id, OLD.member_id FROM active_user LIMIT 1); + + RETURN NULL; + END; +$user_deletion_trigger$ LANGUAGE plpgsql; + +CREATE TRIGGER user_deletion_trigger AFTER DELETE ON members FOR EACH ROW EXECUTE PROCEDURE audit_user_deletion(); diff --git a/pointercrate-example/sample/migrations/_new/20190222212203_list_functions.down.sql b/pointercrate-example/sample/migrations/_new/20190222212203_list_functions.down.sql new file mode 100644 index 000000000..15b7152ce --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20190222212203_list_functions.down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +DROP FUNCTION record_score(progress FLOAT, demon FLOAT, list_size FLOAT); \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20190222212203_list_functions.up.sql b/pointercrate-example/sample/migrations/_new/20190222212203_list_functions.up.sql new file mode 100644 index 000000000..c9e15072b --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20190222212203_list_functions.up.sql @@ -0,0 +1,7 @@ +-- Your SQL goes here + +CREATE FUNCTION record_score(progress FLOAT, demon FLOAT, list_size FLOAT) RETURNS FLOAT AS +$record_score$ + SELECT (progress / 100.0) ^ demon * list_size / (1.0 + (list_size - 1.0) * EXP(-4.0 * (list_size - demon) * LN(list_size - 1.0)/(3.0 * list_size))); +$record_score$ +LANGUAGE SQL IMMUTABLE; \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20190311185303_remove_old_columns.down.sql b/pointercrate-example/sample/migrations/_new/20190311185303_remove_old_columns.down.sql new file mode 100644 index 000000000..1378b9a3e --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20190311185303_remove_old_columns.down.sql @@ -0,0 +1,8 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE demons ADD COLUMN notes TEXT NULL; +ALTER TABLE demons ADD COLUMN description TEXT NULL; + +ALTER TABLE members ADD COLUMN password_salt BYTEA NOT NULL DEFAULT E'\\000'; + +ALTER TABLE members ALTER COLUMN display_name TYPE CITEXT; +ALTER TABLE members ALTER COLUMN name TYPE CITEXT; \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20190311185303_remove_old_columns.up.sql b/pointercrate-example/sample/migrations/_new/20190311185303_remove_old_columns.up.sql new file mode 100644 index 000000000..6b098c890 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20190311185303_remove_old_columns.up.sql @@ -0,0 +1,8 @@ +-- Your SQL goes here +ALTER TABLE demons DROP COLUMN notes; +ALTER TABLE demons DROP COLUMN description; + +ALTER TABLE members DROP COLUMN password_salt; + +ALTER TABLE members ALTER COLUMN display_name TYPE TEXT; +ALTER TABLE members ALTER COLUMN name TYPE TEXT; \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20190311200729_hash_as_string.down.sql b/pointercrate-example/sample/migrations/_new/20190311200729_hash_as_string.down.sql new file mode 100644 index 000000000..443a8e174 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20190311200729_hash_as_string.down.sql @@ -0,0 +1,3 @@ +-- This file should undo anything in `up.sql` + +alter table members alter column password_hash type bytea using decode(password_hash, 'escape'); diff --git a/pointercrate-example/sample/migrations/_new/20190311200729_hash_as_string.up.sql b/pointercrate-example/sample/migrations/_new/20190311200729_hash_as_string.up.sql new file mode 100644 index 000000000..4b30fd3b1 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20190311200729_hash_as_string.up.sql @@ -0,0 +1,3 @@ +-- Your SQL goes here + +alter table members alter column password_hash type text using encode(password_hash, 'escape'); diff --git a/pointercrate-example/sample/migrations/_new/20190312131253_nationality.down.sql b/pointercrate-example/sample/migrations/_new/20190312131253_nationality.down.sql new file mode 100644 index 000000000..928a7b7c9 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20190312131253_nationality.down.sql @@ -0,0 +1,6 @@ +-- This file should undo anything in `up.sql` + +ALTER TABLE players DROP COLUMN nationality; +ALTER TABLE members DROP COLUMN nationality; + +DROP TABLE nationalities; diff --git a/pointercrate-example/sample/migrations/_new/20190312131253_nationality.up.sql b/pointercrate-example/sample/migrations/_new/20190312131253_nationality.up.sql new file mode 100644 index 000000000..16b799f67 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20190312131253_nationality.up.sql @@ -0,0 +1,261 @@ +-- Your SQL goes here + +CREATE TABLE nationalities ( + iso_country_code VARCHAR(2) PRIMARY KEY, + nation CITEXT UNIQUE NOT NULL, + CHECK (LENGTH(iso_country_code) = 2) +); + +INSERT INTO nationalities (nation, iso_country_code) +VALUES ('Afghanistan','AF'), + ('Åland Islands','AX'), + ('Albania','AL'), + ('Algeria','DZ'), + ('American Samoa','AS'), + ('Andorra','AD'), + ('Angola','AO'), + ('Anguilla','AI'), + ('Antarctica','AQ'), + ('Antigua and Barbuda','AG'), + ('Argentina','AR'), + ('Armenia','AM'), + ('Aruba','AW'), + ('Australia','AU'), + ('Austria','AT'), + ('Azerbaijan','AZ'), + ('Bahamas','BS'), + ('Bahrain','BH'), + ('Bangladesh','BD'), + ('Barbados','BB'), + ('Belarus','BY'), + ('Belgium','BE'), + ('Belize','BZ'), + ('Benin','BJ'), + ('Bermuda','BM'), + ('Bhutan','BT'), + ('Bolivia, Plurinational State of','BO'), + ('Bonaire, Sint Eustatius and Saba','BQ'), + ('Bosnia and Herzegovina','BA'), + ('Botswana','BW'), + ('Bouvet Island','BV'), + ('Brazil','BR'), + ('British Indian Ocean Territory','IO'), + ('Brunei Darussalam','BN'), + ('Bulgaria','BG'), + ('Burkina Faso','BF'), + ('Burundi','BI'), + ('Cambodia','KH'), + ('Cameroon','CM'), + ('Canada','CA'), + ('Cape Verde','CV'), + ('Cayman Islands','KY'), + ('Central African Republic','CF'), + ('Chad','TD'), + ('Chile','CL'), + ('China','CN'), + ('Christmas Island','CX'), + ('Cocos (Keeling) Islands','CC'), + ('Colombia','CO'), + ('Comoros','KM'), + ('Congo','CG'), + ('Congo, the Democratic Republic of the','CD'), + ('Cook Islands','CK'), + ('Costa Rica','CR'), + ('Côte d''Ivoire','CI'), + ('Croatia','HR'), + ('Cuba','CU'), + ('Curaçao','CW'), + ('Cyprus','CY'), + ('Czech Republic','CZ'), + ('Denmark','DK'), + ('Djibouti','DJ'), + ('Dominica','DM'), + ('Dominican Republic','DO'), + ('Ecuador','EC'), + ('Egypt','EG'), + ('El Salvador','SV'), + ('Equatorial Guinea','GQ'), + ('Eritrea','ER'), + ('Estonia','EE'), + ('Ethiopia','ET'), + ('Falkland Islands (Malvinas)','FK'), + ('Faroe Islands','FO'), + ('Fiji','FJ'), + ('Finland','FI'), + ('France','FR'), + ('French Guiana','GF'), + ('French Polynesia','PF'), + ('French Southern Territories','TF'), + ('Gabon','GA'), + ('Gambia','GM'), + ('Georgia','GE'), + ('Germany','DE'), + ('Ghana','GH'), + ('Gibraltar','GI'), + ('Greece','GR'), + ('Greenland','GL'), + ('Grenada','GD'), + ('Guadeloupe','GP'), + ('Guam','GU'), + ('Guatemala','GT'), + ('Guernsey','GG'), + ('Guinea','GN'), + ('Guinea-Bissau','GW'), + ('Guyana','GY'), + ('Haiti','HT'), + ('Heard Island and McDonald Islands','HM'), + ('Holy See (Vatican City State)','VA'), + ('Honduras','HN'), + ('Hong Kong','HK'), + ('Hungary','HU'), + ('Iceland','IS'), + ('India','IN'), + ('Indonesia','ID'), + ('Iran, Islamic Republic of','IR'), + ('Iraq','IQ'), + ('Ireland','IE'), + ('Isle of Man','IM'), + ('Israel','IL'), + ('Italy','IT'), + ('Jamaica','JM'), + ('Japan','JP'), + ('Jersey','JE'), + ('Jordan','JO'), + ('Kazakhstan','KZ'), + ('Kenya','KE'), + ('Kiribati','KI'), + ('Korea, Democratic People''s Republic of','KP'), + ('Korea, Republic of','KR'), + ('Kuwait','KW'), + ('Kyrgyzstan','KG'), + ('Lao People''s Democratic Republic','LA'), + ('Latvia','LV'), + ('Lebanon','LB'), + ('Lesotho','LS'), + ('Liberia','LR'), + ('Libya','LY'), + ('Liechtenstein','LI'), + ('Lithuania','LT'), + ('Luxembourg','LU'), + ('Macao','MO'), + ('Macedonia, the Former Yugoslav Republic of','MK'), + ('Madagascar','MG'), + ('Malawi','MW'), + ('Malaysia','MY'), + ('Maldives','MV'), + ('Mali','ML'), + ('Malta','MT'), + ('Marshall Islands','MH'), + ('Martinique','MQ'), + ('Mauritania','MR'), + ('Mauritius','MU'), + ('Mayotte','YT'), + ('Mexico','MX'), + ('Micronesia, Federated States of','FM'), + ('Moldova, Republic of','MD'), + ('Monaco','MC'), + ('Mongolia','MN'), + ('Montenegro','ME'), + ('Montserrat','MS'), + ('Morocco','MA'), + ('Mozambique','MZ'), + ('Myanmar','MM'), + ('Namibia','NA'), + ('Nauru','NR'), + ('Nepal','NP'), + ('Netherlands','NL'), + ('New Caledonia','NC'), + ('New Zealand','NZ'), + ('Nicaragua','NI'), + ('Niger','NE'), + ('Nigeria','NG'), + ('Niue','NU'), + ('Norfolk Island','NF'), + ('Northern Mariana Islands','MP'), + ('Norway','NO'), + ('Oman','OM'), + ('Pakistan','PK'), + ('Palau','PW'), + ('Palestine, State of','PS'), + ('Panama','PA'), + ('Papua New Guinea','PG'), + ('Paraguay','PY'), + ('Peru','PE'), + ('Philippines','PH'), + ('Pitcairn','PN'), + ('Poland','PL'), + ('Portugal','PT'), + ('Puerto Rico','PR'), + ('Qatar','QA'), + ('Réunion','RE'), + ('Romania','RO'), + ('Russian Federation','RU'), + ('Rwanda','RW'), + ('Saint Barthélemy','BL'), + ('Saint Helena, Ascension and Tristan da Cunha','SH'), + ('Saint Kitts and Nevis','KN'), + ('Saint Lucia','LC'), + ('Saint Martin (French part)','MF'), + ('Saint Pierre and Miquelon','PM'), + ('Saint Vincent and the Grenadines','VC'), + ('Samoa','WS'), + ('San Marino','SM'), + ('Sao Tome and Principe','ST'), + ('Saudi Arabia','SA'), + ('Senegal','SN'), + ('Serbia','RS'), + ('Seychelles','SC'), + ('Sierra Leone','SL'), + ('Singapore','SG'), + ('Sint Maarten (Dutch part)','SX'), + ('Slovakia','SK'), + ('Slovenia','SI'), + ('Solomon Islands','SB'), + ('Somalia','SO'), + ('South Africa','ZA'), + ('South Georgia and the South Sandwich Islands','GS'), + ('South Sudan','SS'), + ('Spain','ES'), + ('Sri Lanka','LK'), + ('Sudan','SD'), + ('Suriname','SR'), + ('Svalbard and Jan Mayen','SJ'), + ('Swaziland','SZ'), + ('Sweden','SE'), + ('Switzerland','CH'), + ('Syrian Arab Republic','SY'), + ('Taiwan, Province of China','TW'), + ('Tajikistan','TJ'), + ('Tanzania, United Republic of','TZ'), + ('Thailand','TH'), + ('Timor-Leste','TL'), + ('Togo','TG'), + ('Tokelau','TK'), + ('Tonga','TO'), + ('Trinidad and Tobago','TT'), + ('Tunisia','TN'), + ('Turkey','TR'), + ('Turkmenistan','TM'), + ('Turks and Caicos Islands','TC'), + ('Tuvalu','TV'), + ('Uganda','UG'), + ('Ukraine','UA'), + ('United Arab Emirates','AE'), + ('United Kingdom','GB'), + ('United States','US'), + ('United States Minor Outlying Islands','UM'), + ('Uruguay','UY'), + ('Uzbekistan','UZ'), + ('Vanuatu','VU'), + ('Venezuela, Bolivarian Republic of','VE'), + ('Viet Nam','VN'), + ('Virgin Islands, British','VG'), + ('Virgin Islands, U.S.','VI'), + ('Wallis and Futuna','WF'), + ('Western Sahara','EH'), + ('Yemen','YE'), + ('Zambia','ZM'), + ('Zimbabwe','ZW'); + +ALTER TABLE players ADD COLUMN nationality VARCHAR(2) NULL DEFAULT NULL REFERENCES nationalities(iso_country_code); +ALTER TABLE members ADD COLUMN nationality VARCHAR(2) NULL DEFAULT NULL REFERENCES nationalities(iso_country_code); diff --git a/pointercrate-example/sample/migrations/_new/20190314134153_score_view.down.sql b/pointercrate-example/sample/migrations/_new/20190314134153_score_view.down.sql new file mode 100644 index 000000000..6db267318 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20190314134153_score_view.down.sql @@ -0,0 +1,3 @@ +-- This file should undo anything in `up.sql` + +DROP VIEW players_with_score; \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20190314134153_score_view.up.sql b/pointercrate-example/sample/migrations/_new/20190314134153_score_view.up.sql new file mode 100644 index 000000000..ea67d28b5 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20190314134153_score_view.up.sql @@ -0,0 +1,53 @@ +-- Your SQL goes here + + +-- I call this query Frank +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 + FROM + ( + SELECT pseudo_records.player, + SUM(record_score(pseudo_records.progress::FLOAT, pseudo_records.position::FLOAT, 100::FLOAT)) as total_score + FROM ( + SELECT player, + progress, + demons.position + FROM records + INNER JOIN demons + ON demons.name = demon + WHERE demons.position <= 100 AND status_ = 'APPROVED' + + UNION + + SELECT verifier as player, + CASE WHEN demons.position > 100 THEN 0.0::FLOAT ELSE 100.0::FLOAT END as progress, + position as position + FROM demons + + UNION + + SELECT publisher as player, + 0.0::FLOAT as progress, + position as position + FROM demons + + UNION + + SELECT creator as player, + 0.0::FLOAT as progress, + 1.0::FLOAT as position -- doesn't matter + 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; \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20190708102450_formula_update.down.sql b/pointercrate-example/sample/migrations/_new/20190708102450_formula_update.down.sql new file mode 100644 index 000000000..9ad2a1bc5 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20190708102450_formula_update.down.sql @@ -0,0 +1,63 @@ +-- This file should undo anything in `up.sql` + +-- Your SQL goes here + +DROP VIEW players_with_score; +DROP FUNCTION record_score(FLOAT, FLOAT, FLOAT, FLOAT); + + +CREATE FUNCTION record_score(progress FLOAT, demon FLOAT, list_size FLOAT) RETURNS FLOAT AS +$record_score$ + SELECT (progress / 100.0) ^ demon * list_size / (1.0 + (list_size - 1.0) * EXP(-4.0 * (list_size - demon) * LN(list_size - 1.0)/(3.0 * list_size))); +$record_score$ +LANGUAGE SQL IMMUTABLE; + +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 + FROM + ( + SELECT pseudo_records.player, + SUM(record_score(pseudo_records.progress::FLOAT, pseudo_records.position::FLOAT, 100::FLOAT)) as total_score + FROM ( + SELECT player, + progress, + demons.position + FROM records + INNER JOIN demons + ON demons.name = demon + WHERE demons.position <= 100 AND status_ = 'APPROVED' + + UNION + + SELECT verifier as player, + CASE WHEN demons.position > 100 THEN 0.0::FLOAT ELSE 100.0::FLOAT END as progress, + position as position + FROM demons + + UNION + + SELECT publisher as player, + 0.0::FLOAT as progress, + position as position + FROM demons + + UNION + + SELECT creator as player, + 0.0::FLOAT as progress, + 1.0::FLOAT as position -- doesn't matter + 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; \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20190708102450_formula_update.up.sql b/pointercrate-example/sample/migrations/_new/20190708102450_formula_update.up.sql new file mode 100644 index 000000000..1342b9576 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20190708102450_formula_update.up.sql @@ -0,0 +1,72 @@ +-- Your SQL goes here + +DROP VIEW players_with_score; +DROP FUNCTION record_score(FLOAT, FLOAT, FLOAT); + + +CREATE FUNCTION record_score(progress FLOAT, demon FLOAT, list_size FLOAT, requirement FLOAT) RETURNS FLOAT AS +$record_score$ + SELECT CASE + WHEN progress = 100 THEN + 150.0 * EXP((1.0 - demon) * LN(1.0 / 30.0) / (-149.0)) + WHEN progress < requirement THEN + 0.0 + ELSE + 150.0 * EXP((1.0 - demon) * LN(1.0 / 30.0) / (-149.0)) * (0.25 * (progress - requirement) / (100 - requirement) + 0.25) + END; +$record_score$ +LANGUAGE SQL IMMUTABLE; + +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 + 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, + demons.position, + demons.requirement + FROM records + INNER JOIN demons + ON demons.name = demon + WHERE demons.position <= 150 AND status_ = 'APPROVED' + + UNION + + SELECT verifier as player, + CASE WHEN demons.position > 150 THEN 0.0::FLOAT ELSE 100.0::FLOAT END as progress, + position as position, + 100.0::FLOAT + FROM demons + + UNION + + SELECT publisher as player, + 0.0::FLOAT as progress, + position as 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; \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20191002080414_fix_database_design.down.sql b/pointercrate-example/sample/migrations/_new/20191002080414_fix_database_design.down.sql new file mode 100644 index 000000000..ac0fa0275 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20191002080414_fix_database_design.down.sql @@ -0,0 +1,16 @@ +-- This file should undo anything in `up.sql` +DROP VIEW demons_pv; +DROP VIEW demons_p; +DROP VIEW records_pds; +DROP VIEW records_pd; +DROP VIEW records_p; +DROP VIEW records_d; +DROP VIEW players_n; + +CREATE OR REPLACE VIEW demon_verifier_publisher_join AS +SELECT p1.name AS vname, p1.id AS vid, p1.banned AS vbanned, p2.name AS pname, p2.id AS pid, p2.banned AS pbanned +FROM demons +INNER JOIN players AS p1 +ON demons.verifier = p1.id +INNER JOIN players AS p2 +ON demons.publisher = p2.id \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20191002080414_fix_database_design.up.sql b/pointercrate-example/sample/migrations/_new/20191002080414_fix_database_design.up.sql new file mode 100644 index 000000000..5b67a31d8 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20191002080414_fix_database_design.up.sql @@ -0,0 +1,63 @@ +-- Your SQL goes here + +CREATE VIEW demons_pv AS -- demons with publisher and verifier + SELECT demons.position, demons.name, demons.requirement, demons.video, + publishers.id AS publisher_id, publishers.name AS publisher_name, publishers.banned AS publisher_banned, + verifiers.id AS verifier_id, verifiers.name AS verifier_name, verifiers.banned AS verifier_banned + FROM demons + INNER JOIN players AS verifiers + ON verifiers.id = demons.verifier + INNER JOIN players AS publishers + ON publishers.id = demons.publisher; + +CREATE VIEW demons_p AS -- demons with publisher + SELECT demons.position, demons.name, demons.video, + publishers.id AS publisher_id, publishers.name AS publisher_name, publishers.banned AS publisher_banned + FROM demons + INNER JOIN players AS publishers + ON publishers.id = demons.publisher; + +-- for listed representation +CREATE VIEW records_pd AS -- records with player and demon + SELECT records.id, records.progress, records.video, records.status_, records.submitter AS submitter_id, + players.id AS player_id, players.name AS player_name, players.banned AS player_banned, + demons.name AS demon_name, demons.position + FROM records + INNER JOIN players + ON records.player = players.id + INNER JOIN demons + ON demons.name = records.demon; + +-- for full representation +CREATE VIEW records_pds AS -- records with player, demon and submitter + SELECT records_pd.id, records_pd.progress, records_pd.video, records_pd.status_, + records_pd.player_id, records_pd.player_name, records_pd.player_banned, + records_pd.demon_name, records_pd.position, + submitters.submitter_id, submitters.banned AS submitter_banned + FROM records_pd + INNER JOIN submitters + ON records_pd.submitter_id = submitters.submitter_id; + +-- for minimal representation +CREATE VIEW records_p AS -- records with player + SELECT records.id, records.progress, records.video, records.status_, records.demon, + players.id AS player_id, players.name AS player_name, players.banned AS player_banned + FROM records + INNER JOIN players + ON records.player = players.id; + +CREATE VIEW records_d AS -- records with demon + SELECT records.id, records.progress, records.video, records.status_, records.player, + demons.name AS demon_name, demons.position + FROM records + INNER JOIN demons + ON demons.name = records.demon; + +CREATE VIEW players_n AS -- players with nationality + SELECT players.id, players.name, players.banned, + nationalities.iso_country_code, nationalities.nation + FROM players + LEFT OUTER JOIN nationalities + ON players.nationality = nationalities.iso_country_code; + +DROP VIEW IF EXISTS demon_verifier_publisher_join; \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20191004164504_demon_ids.down.sql b/pointercrate-example/sample/migrations/_new/20191004164504_demon_ids.down.sql new file mode 100644 index 000000000..291a97c5c --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20191004164504_demon_ids.down.sql @@ -0,0 +1 @@ +-- This file should undo anything in `up.sql` \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20191004164504_demon_ids.up.sql b/pointercrate-example/sample/migrations/_new/20191004164504_demon_ids.up.sql new file mode 100644 index 000000000..3e21614ae --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20191004164504_demon_ids.up.sql @@ -0,0 +1,307 @@ +-- Your SQL goes here + +-- Create the ID column +ALTER TABLE demons ADD COLUMN id SERIAL; + +-- Fix up the audit logs +-- demon addition logs +ALTER TABLE demon_additions ADD COLUMN id INTEGER; + +UPDATE demon_additions +SET id = demons.id +FROM demons +WHERE demon_additions.name = demons.name; + +ALTER TABLE demon_additions ALTER COLUMN id SET NOT NULL; +ALTER TABLE demon_additions DROP COLUMN name; + +CREATE OR REPLACE FUNCTION audit_demon_addition() RETURNS trigger AS $demon_add_trigger$ + BEGIN + INSERT INTO demon_additions (userid, id) (SELECT id , NEW.id FROM active_user LIMIT 1); + RETURN NEW; + END; +$demon_add_trigger$ LANGUAGE plpgsql; + +-- demon modification logs +ALTER TABLE demon_modifications ADD COLUMN id INTEGER; + +UPDATE demon_modifications +SET id = demons.id +FROM demons +WHERE demon = demons.name; + +ALTER TABLE demon_modifications ALTER COLUMN id SET NOT NULL; +ALTER TABLE demon_modifications DROP COLUMN demon; + +CREATE OR REPLACE FUNCTION audit_demon_modification() RETURNS trigger AS $demon_modification_trigger$ + DECLARE + name_change CITEXT; + position_change SMALLINT; + requirement_change SMALLINT; + video_change VARCHAR(200); + verifier_change SMALLINT; + publisher_change SMALLINT; + BEGIN + IF (OLD.name <> NEW.name) THEN + name_change = OLD.name; + END IF; + + IF (OLD.position <> NEW.position) THEN + position_change = OLD.position; + END IF; + + IF (OLD.requirement <> NEW.requirement) THEN + requirement_change = OLD.requirement; + END IF; + + IF (OLD.video <> NEW.video) THEN + video_change = OLD.video; + END IF; + + IF (OLD.verifier <> NEW.verifier) THEN + verifier_change = OLD.verifier; + END IF; + + IF (OLD.publisher <> NEW.publisher) THEN + publisher_change = OLD.publisher; + END IF; + + INSERT INTO demon_modifications (userid, name, position, requirement, video, verifier, publisher, id) + (SELECT id, name_change, position_change, requirement_change, video_change, verifier_change, publisher_change, NEW.id + FROM active_user LIMIT 1); + + RETURN NEW; + END; +$demon_modification_trigger$ LANGUAGE plpgsql; + +-- record modifications +ALTER TABLE record_modifications RENAME COLUMN demon to demon_name; +ALTER TABLE record_modifications ADD COLUMN demon INTEGER; + +UPDATE record_modifications +SET demon = demons.id +FROM demons +WHERE demon_name = demons.name; + +ALTER TABLE record_modifications DROP COLUMN demon_name; + +CREATE OR REPLACE FUNCTION audit_record_modification() RETURNS trigger AS $record_modification_trigger$ + DECLARE + progress_change SMALLINT; + video_change VARCHAR(200); + status_change RECORD_STATUS; + player_change INT; + demon_change INTEGER; + BEGIN + if (OLD.progress <> NEW.progress) THEN + progress_change = OLD.progress; + END IF; + + IF (OLD.video <> NEW.video) THEN + video_change = OLD.video; + END IF; + + IF (OLD.status_ <> NEW.status_) THEN + status_change = OLD.status_; + END IF; + + IF (OLD.player <> NEW.player) THEN + player_change = OLD.player; + END IF; + + IF (OLD.demon <> NEW.demon) THEN + demon_change = OLD.demon; + END IF; + + INSERT INTO record_modifications (userid, id, progress, video, status_, player, demon) + (SELECT id, NEW.id, progress_change, video_change, status_change, player_change, demon_change + FROM active_user LIMIT 1); + + RETURN NEW; + END; +$record_modification_trigger$ LANGUAGE plpgsql; + +-- creator additions +ALTER TABLE creator_additions RENAME COLUMN demon TO demon_name; +ALTER TABLE creator_additions ADD COLUMN demon INTEGER; + +UPDATE creator_additions +SET demon = demons.id +FROM demons +WHERE demon_name = demons.name; + +ALTER TABLE creator_additions ALTER COLUMN demon SET NOT NULL; +ALTER TABLE creator_additions DROP COLUMN demon_name; + +-- creator deletions +ALTER TABLE creator_deletions RENAME COLUMN demon TO demon_name; +ALTER TABLE creator_deletions ADD COLUMN demon INTEGER; + +UPDATE creator_deletions +SET demon = demons.id +FROM demons +WHERE demon_name = demons.name; + +ALTER TABLE creator_deletions ALTER COLUMN demon SET NOT NULL; +ALTER TABLE creator_deletions DROP COLUMN demon_name; + +-- Fix up references from the creators table +ALTER TABLE creators RENAME COLUMN demon TO demon_name; +ALTER TABLE creators ADD COLUMN demon INTEGER; + +-- No need to temporarily unregister triggers here, there is no 'creator_modifications' audit log +UPDATE creators +SET demon = demons.id +FROM demons +WHERE demon_name = demons.name; + +ALTER TABLE creators DROP CONSTRAINT creators_pkey; +ALTER TABLE creators DROP COLUMN demon_name; + +-- Fix up references from the records table +ALTER TABLE records RENAME COLUMN demon TO demon_name; +ALTER TABLE records ADD COLUMN demon INTEGER; + +-- We need to temporarily unregister the trigger, otherwise this creates a ton of empty audit log entries +DROP TRIGGER record_modification_trigger ON records; + +UPDATE records +SET demon = demons.id +FROM demons +WHERE demon_name = demons.name; + +-- recreate trigger +CREATE TRIGGER record_modification_trigger AFTER UPDATE ON records FOR EACH ROW EXECUTE PROCEDURE audit_record_modification(); + +ALTER TABLE records ALTER COLUMN demon SET NOT NULL; + +-- Fix up the views over the records table +DROP VIEW records_pds; +DROP VIEW records_pd; +CREATE VIEW records_pd AS -- records with player and demon + SELECT records.id, records.progress, records.video, records.status_, records.submitter AS submitter_id, + players.id AS player_id, players.name AS player_name, players.banned AS player_banned, + demons.id AS demon_id, demons.name AS demon_name, demons.position + FROM records + INNER JOIN players + ON records.player = players.id + INNER JOIN demons + ON demons.id = records.demon; + +CREATE VIEW records_pds AS -- records with player, demon and submitter + SELECT records_pd.id, records_pd.progress, records_pd.video, records_pd.status_, + records_pd.player_id, records_pd.player_name, records_pd.player_banned, + records_pd.demon_id, records_pd.demon_name, records_pd.position, + submitters.submitter_id, submitters.banned AS submitter_banned + FROM records_pd + INNER JOIN submitters + ON records_pd.submitter_id = submitters.submitter_id; + +-- for minimal representation +DROP VIEW records_p; +CREATE VIEW records_p AS -- records with player + SELECT records.id, records.progress, records.video, records.status_, records.demon, + players.id AS player_id, players.name AS player_name, players.banned AS player_banned + FROM records + INNER JOIN players + ON records.player = players.id; + +DROP VIEW records_d; +CREATE VIEW records_d AS -- records with demon + SELECT records.id, records.progress, records.video, records.status_, records.player, + demons.id AS demon_id, demons.name AS demon_name, demons.position + FROM records + INNER JOIN demons + ON demons.id = records.demon; + +-- we need to re-declare the player ranking view since it referenced records.demon_name +CREATE OR REPLACE 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 + 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, + requirement + FROM records + INNER JOIN demons + ON demons.id = demon + WHERE demons.position <= 150 AND status_ = 'APPROVED' + + 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; + +-- replace the views of the demons table +DROP VIEW demons_pv; +CREATE VIEW demons_pv AS -- demons with publisher and verifier + SELECT demons.id, demons.position, demons.name, demons.requirement, demons.video, + publishers.id AS publisher_id, publishers.name AS publisher_name, publishers.banned AS publisher_banned, + verifiers.id AS verifier_id, verifiers.name AS verifier_name, verifiers.banned AS verifier_banned + FROM demons + INNER JOIN players AS verifiers + ON verifiers.id = demons.verifier + INNER JOIN players AS publishers + ON publishers.id = demons.publisher; + +DROP VIEW demons_p; +CREATE VIEW demons_p AS -- demons with publisher + SELECT demons.id, demons.position, demons.name, demons.video, + publishers.id AS publisher_id, publishers.name AS publisher_name, publishers.banned AS publisher_banned + FROM demons + INNER JOIN players AS publishers + ON publishers.id = demons.publisher; + +-- Drop the old audit logs since we migrated the data over to the new ones already. Its a waste of time to update the references into the demons table +-- DROP TABLE audit_log; + +-- Drop the old column +ALTER TABLE records DROP COLUMN demon_name; + +-- change primary key on demons relation +ALTER TABLE demons DROP CONSTRAINT demons_pkey; +ALTER TABLE demons ADD PRIMARY KEY (id); + +--set up new column to be foreign key to new primary key +ALTER TABLE creators ADD CONSTRAINT creators_demon_fkey FOREIGN KEY (demon) REFERENCES demons(id); +ALTER TABLE records ADD CONSTRAINT records_demon_fkey FOREIGN KEY (demon) REFERENCES demons(id); + +-- set up the primary key of the creators table again +ALTER TABLE creators ADD CONSTRAINT creators_pkey PRIMARY KEY (demon, creator); diff --git a/pointercrate-example/sample/migrations/_new/20191009080542_record_notes.down.sql b/pointercrate-example/sample/migrations/_new/20191009080542_record_notes.down.sql new file mode 100644 index 000000000..dc84838f6 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20191009080542_record_notes.down.sql @@ -0,0 +1,14 @@ +-- This file should undo anything in `up.sql` + +-- Fix up the views over the records table +DROP VIEW records_pds; +CREATE VIEW records_pds AS -- records with player, demon and submitter + SELECT records_pd.id, records_pd.progress, records_pd.video, records_pd.status_, + records_pd.player_id, records_pd.player_name, records_pd.player_banned, + records_pd.demon_id, records_pd.demon_name, records_pd.position, + submitters.submitter_id, submitters.banned AS submitter_banned + FROM records_pd + INNER JOIN submitters + ON records_pd.submitter_id = submitters.submitter_id; + +ALTER TABLE records DROP COLUMN notes; \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20191009080542_record_notes.up.sql b/pointercrate-example/sample/migrations/_new/20191009080542_record_notes.up.sql new file mode 100644 index 000000000..8a83454ae --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20191009080542_record_notes.up.sql @@ -0,0 +1,19 @@ +-- Your SQL goes here + +ALTER TABLE records ADD COLUMN notes TEXT; + +-- Fix up the views over the records table +DROP VIEW records_pds; + +CREATE VIEW records_pds AS -- records with player, demon and submitter + SELECT records.id, records.progress, records.video, records.status_, records.notes, + players.id AS player_id, players.name AS player_name, players.banned AS player_banned, + demons.id AS demon_id, demons.name AS demon_name, demons.position, + submitters.submitter_id, submitters.banned AS submitter_banned + FROM records + INNER JOIN submitters + ON records.submitter = submitters.submitter_id + INNER JOIN players + ON records.player = players.id + INNER JOIN demons + ON demons.id = records.demon; diff --git a/pointercrate-example/sample/migrations/_new/20200227174850_view_cleanup.down.sql b/pointercrate-example/sample/migrations/_new/20200227174850_view_cleanup.down.sql new file mode 100644 index 000000000..455914768 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20200227174850_view_cleanup.down.sql @@ -0,0 +1,66 @@ +-- This file should undo anything in `up.sql` + + +CREATE VIEW records_pds AS -- records with player, demon and submitter + SELECT records.id, records.progress, records.video, records.status_, records.notes, + players.id AS player_id, players.name AS player_name, players.banned AS player_banned, + demons.id AS demon_id, demons.name AS demon_name, demons.position, + submitters.submitter_id, submitters.banned AS submitter_banned + FROM records + INNER JOIN submitters + ON records.submitter = submitters.submitter_id + INNER JOIN players + ON records.player = players.id + INNER JOIN demons + ON demons.id = records.demon; + +CREATE VIEW records_pd AS -- records with player and demon + SELECT records.id, records.progress, records.video, records.status_, records.submitter AS submitter_id, + players.id AS player_id, players.name AS player_name, players.banned AS player_banned, + demons.id AS demon_id, demons.name AS demon_name, demons.position + FROM records + INNER JOIN players + ON records.player = players.id + INNER JOIN demons + ON demons.id = records.demon; + +-- for minimal representation +CREATE VIEW records_p AS -- records with player + SELECT records.id, records.progress, records.video, records.status_, records.demon, + players.id AS player_id, players.name AS player_name, players.banned AS player_banned + FROM records + INNER JOIN players + ON records.player = players.id; + +CREATE VIEW records_d AS -- records with demon + SELECT records.id, records.progress, records.video, records.status_, records.player, + demons.id AS demon_id, demons.name AS demon_name, demons.position + FROM records + INNER JOIN demons + ON demons.id = records.demon; + + +CREATE VIEW demons_pv AS -- demons with publisher and verifier + SELECT demons.id, demons.position, demons.name, demons.requirement, demons.video, + publishers.id AS publisher_id, publishers.name AS publisher_name, publishers.banned AS publisher_banned, + verifiers.id AS verifier_id, verifiers.name AS verifier_name, verifiers.banned AS verifier_banned + FROM demons + INNER JOIN players AS verifiers + ON verifiers.id = demons.verifier + INNER JOIN players AS publishers + ON publishers.id = demons.publisher; + +CREATE VIEW demons_p AS -- demons with publisher + SELECT demons.id, demons.position, demons.name, demons.video, + publishers.id AS publisher_id, publishers.name AS publisher_name, publishers.banned AS publisher_banned + FROM demons + INNER JOIN players AS publishers + ON publishers.id = demons.publisher; + + +CREATE VIEW players_n AS -- players with nationality + SELECT players.id, players.name, players.banned, + nationalities.iso_country_code, nationalities.nation + FROM players + LEFT OUTER JOIN nationalities + ON players.nationality = nationalities.iso_country_code; \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20200227174850_view_cleanup.up.sql b/pointercrate-example/sample/migrations/_new/20200227174850_view_cleanup.up.sql new file mode 100644 index 000000000..fc2b4de52 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20200227174850_view_cleanup.up.sql @@ -0,0 +1,9 @@ +-- Your SQL goes here + +DROP VIEW players_n; +DROP VIEW records_d; +DROP VIEW records_p; +DROP VIEW records_pd; +DROP VIEW records_pds; +DROP VIEW demons_p; +DROP VIEW demons_pv; \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20200227185921_record_notes.down.sql b/pointercrate-example/sample/migrations/_new/20200227185921_record_notes.down.sql new file mode 100644 index 000000000..6a980736e --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20200227185921_record_notes.down.sql @@ -0,0 +1,24 @@ +-- This file should undo anything in `up.sql` + +ALTER TABLE records ADD COLUMN notes TEXT; + +WITH transferred_notes AS ( + SELECT record, STRING_AGG(content, '\n') AS content + FROM record_notes + GROUP BY record +) +UPDATE records +SET notes = transferred_notes.content +FROM transferred_notes +WHERE records.id = transferred_notes.record; + +DROP TABLE record_notes; + +DROP TABLE record_notes_additions; +DROP FUNCTION audit_record_notes_addition() CASCADE; + +DROP TABLE record_notes_modifications; +DROP FUNCTION audit_record_notes_modification() CASCADE; + +DROP TABLE record_notes_deletions; +DROP FUNCTION audit_record_notes_deletion() CASCADE; \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20200227185921_record_notes.up.sql b/pointercrate-example/sample/migrations/_new/20200227185921_record_notes.up.sql new file mode 100644 index 000000000..be5f9f577 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20200227185921_record_notes.up.sql @@ -0,0 +1,71 @@ +-- Your SQL goes here +CREATE TABLE record_notes ( + id SERIAL PRIMARY KEY, + record INTEGER REFERENCES records(id) NOT NULL, + content TEXT NOT NULL +); + +INSERT INTO record_notes (record, content) +SELECT id, notes FROM records WHERE notes IS NOT NULL; + +ALTER TABLE records DROP COLUMN notes; + +-- This part of the migration cannot be undone! +ALTER TYPE record_status ADD VALUE 'UNDER_CONSIDERATION'; + +CREATE TABLE record_notes_additions ( + id INTEGER NOT NULL +) INHERITS (audit_log2); + +CREATE TABLE record_notes_modifications ( + id INTEGER NOT NULL, -- which note was changed? + record INTEGER NULL, + content TEXT NULL +) INHERITS (audit_log2); + +CREATE TABLE record_notes_deletions ( + id INTEGER NOT NULL +) INHERITS (audit_log2); + +CREATE FUNCTION audit_record_notes_addition() RETURNS trigger AS $record_notes_add_trigger$ + BEGIN + INSERT INTO record_notes_additions (userid, id) (SELECT id, NEW.id FROM active_user LIMIT 1); + RETURN NEW; + END; +$record_notes_add_trigger$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION audit_record_notes_modification() RETURNS trigger AS $record_notes_modification_trigger$ + DECLARE + record_change INTEGER; + content_change TEXT; + BEGIN + IF (OLD.record <> NEW.record) THEN + record_change = OLD.record; + END IF; + + IF (OLD.content <> NEW.content) THEN + content_change = OLD.content; + END IF; + + INSERT INTO record_notes_modifications (userid, id, record, content) + (SELECT id, OLD.id, record_change, content_change FROM active_user LIMIT 1); + + RETURN NEW; + END; +$record_notes_modification_trigger$ LANGUAGE plpgsql; + +CREATE FUNCTION audit_record_notes_deletion() RETURNS trigger AS $record_notes_deletion_trigger$ + BEGIN + INSERT INTO record_notes_modifications (userid, id, record, content) + (SELECT id, OLD.id, OLD.record, OLD.content FROM active_user LIMIT 1); + + INSERT INTO record_notes_deletion (userid, id) + (SELECT id, OLD.id FROM active_user LIMIT 1); + + RETURN NEW; + END +$record_notes_deletion_trigger$ LANGUAGE plpgsql; + +CREATE TRIGGER record_note_addition_trigger AFTER INSERT ON record_notes FOR EACH ROW EXECUTE PROCEDURE audit_record_notes_addition(); +CREATE TRIGGER record_note_modification_trigger AFTER UPDATE ON record_notes FOR EACH ROW EXECUTE PROCEDURE audit_record_notes_modification(); +CREATE TRIGGER record_note_deletion_trigger AFTER DELETE ON record_notes FOR EACH ROW EXECUTE PROCEDURE audit_record_notes_modification(); \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20200302225154_cascade_note_deletion.down.sql b/pointercrate-example/sample/migrations/_new/20200302225154_cascade_note_deletion.down.sql new file mode 100644 index 000000000..852b6f896 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20200302225154_cascade_note_deletion.down.sql @@ -0,0 +1,7 @@ +-- This file should undo anything in `up.sql` + +ALTER TABLE record_notes + DROP CONSTRAINT record_notes_record_fkey, + ADD CONSTRAINT record_notes_record_fkey + FOREIGN KEY (record) + REFERENCES records(id) \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20200302225154_cascade_note_deletion.up.sql b/pointercrate-example/sample/migrations/_new/20200302225154_cascade_note_deletion.up.sql new file mode 100644 index 000000000..d9f300bd7 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20200302225154_cascade_note_deletion.up.sql @@ -0,0 +1,8 @@ +-- Your SQL goes here + +ALTER TABLE record_notes + DROP CONSTRAINT record_notes_record_fkey, + ADD CONSTRAINT record_notes_record_fkey + FOREIGN KEY (record) + REFERENCES records(id) + ON DELETE CASCADE; \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20200321014223_delete_old_procedures.down.sql b/pointercrate-example/sample/migrations/_new/20200321014223_delete_old_procedures.down.sql new file mode 100644 index 000000000..3943b3f77 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20200321014223_delete_old_procedures.down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +SELECT 1 \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20200321014223_delete_old_procedures.up.sql b/pointercrate-example/sample/migrations/_new/20200321014223_delete_old_procedures.up.sql new file mode 100644 index 000000000..972eadbc6 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20200321014223_delete_old_procedures.up.sql @@ -0,0 +1,7 @@ +-- Your SQL goes here +DROP FUNCTION IF EXISTS ban_player; +DROP FUNCTION IF EXISTS remove_submitted_records_on_user_ban; +DROP FUNCTION IF EXISTS ban_user CASCADE; +DROP FUNCTION IF EXISTS remove_invalid_records CASCADE; +DROP FUNCTION IF EXISTS unban_player; +DROP FUNCTION IF EXISTS player_score; \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20200404074854_shadow_ban.down.sql b/pointercrate-example/sample/migrations/_new/20200404074854_shadow_ban.down.sql new file mode 100644 index 000000000..d7c39eb91 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20200404074854_shadow_ban.down.sql @@ -0,0 +1,4 @@ +-- This file should undo anything in `up.sql` + +ALTER TABLE players +DROP COLUMN link_banned; \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20200404074854_shadow_ban.up.sql b/pointercrate-example/sample/migrations/_new/20200404074854_shadow_ban.up.sql new file mode 100644 index 000000000..01efa2732 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20200404074854_shadow_ban.up.sql @@ -0,0 +1,4 @@ +-- Your SQL goes here + +ALTER TABLE players +ADD COLUMN link_banned BOOL DEFAULT FALSE; \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20200515125343_new_formula.down.sql b/pointercrate-example/sample/migrations/_new/20200515125343_new_formula.down.sql new file mode 100644 index 000000000..ee33a9d29 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20200515125343_new_formula.down.sql @@ -0,0 +1,73 @@ +-- This file should undo anything in `up.sql` +-- Your SQL goes here + +DROP VIEW players_with_score; +DROP FUNCTION record_score(FLOAT, FLOAT, FLOAT, FLOAT); + + +CREATE FUNCTION record_score(progress FLOAT, demon FLOAT, list_size FLOAT, requirement FLOAT) RETURNS FLOAT AS +$record_score$ + SELECT CASE + WHEN progress = 100 THEN + 150.0 * EXP((1.0 - demon) * LN(1.0 / 30.0) / (-149.0)) + WHEN progress < requirement THEN + 0.0 + ELSE + 150.0 * EXP((1.0 - demon) * LN(1.0 / 30.0) / (-149.0)) * (0.25 * (progress - requirement) / (100 - requirement) + 0.25) + END; +$record_score$ +LANGUAGE SQL IMMUTABLE; + +CREATE OR REPLACE 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 + 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, + requirement + FROM records + INNER JOIN demons + ON demons.id = demon + WHERE demons.position <= 150 AND status_ = 'APPROVED' + + 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; \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20200515125343_new_formula.up.sql b/pointercrate-example/sample/migrations/_new/20200515125343_new_formula.up.sql new file mode 100644 index 000000000..26e74d19b --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20200515125343_new_formula.up.sql @@ -0,0 +1,75 @@ +-- Your SQL goes here + +DROP VIEW players_with_score; +DROP FUNCTION record_score(FLOAT, FLOAT, FLOAT, FLOAT); + +CREATE FUNCTION record_score(progress FLOAT, demon FLOAT, list_size FLOAT, requirement FLOAT) RETURNS FLOAT AS +$record_score$ + SELECT CASE + WHEN progress = 100 AND demon <= 10 THEN + 250.0 * EXP((1.0 - demon) * LN(1.0 / 30.0) / (-149.0)) + WHEN progress = 100 THEN + 150.0 * EXP((1.0 - demon) * LN(1.0 / 30.0) / (-149.0)) + WHEN progress < requirement THEN + 0.0 + WHEN demon <= 10 THEN + 250.0 * EXP((1.0 - demon) * LN(1.0 / 30.0) / (-149.0)) * (EXP(LN(46) * (progress - requirement) / (100 - requirement)) + 4) / 100 + ELSE + 150.0 * EXP((1.0 - demon) * LN(1.0 / 30.0) / (-149.0)) * (EXP(LN(46) * (progress - requirement) / (100 - requirement)) + 4) / 100 + END; +$record_score$ +LANGUAGE SQL IMMUTABLE; + +CREATE OR REPLACE 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 + 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, + requirement + FROM records + INNER JOIN demons + ON demons.id = demon + WHERE demons.position <= 150 AND status_ = 'APPROVED' + + 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; \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20200521101927_new_formulaagain.down.sql b/pointercrate-example/sample/migrations/_new/20200521101927_new_formulaagain.down.sql new file mode 100644 index 000000000..64e508bea --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20200521101927_new_formulaagain.down.sql @@ -0,0 +1,75 @@ +-- This file should undo anything in `up.sql` +DROP VIEW players_with_score; +DROP FUNCTION record_score(FLOAT, FLOAT, FLOAT, FLOAT); + +CREATE FUNCTION record_score(progress FLOAT, demon FLOAT, list_size FLOAT, requirement FLOAT) RETURNS FLOAT AS +$record_score$ + SELECT CASE + WHEN progress = 100 AND demon <= 10 THEN + 250.0 * EXP((1.0 - demon) * LN(1.0 / 30.0) / (-149.0)) + WHEN progress = 100 THEN + 150.0 * EXP((1.0 - demon) * LN(1.0 / 30.0) / (-149.0)) + WHEN progress < requirement THEN + 0.0 + WHEN demon <= 10 THEN + 250.0 * EXP((1.0 - demon) * LN(1.0 / 30.0) / (-149.0)) * (EXP(LN(46) * (progress - requirement) / (100 - requirement)) + 4) / 100 + ELSE + 150.0 * EXP((1.0 - demon) * LN(1.0 / 30.0) / (-149.0)) * (EXP(LN(46) * (progress - requirement) / (100 - requirement)) + 4) / 100 + END; +$record_score$ +LANGUAGE SQL IMMUTABLE; + + +CREATE OR REPLACE 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 + 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, + requirement + FROM records + INNER JOIN demons + ON demons.id = demon + WHERE demons.position <= 150 AND status_ = 'APPROVED' + + 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; \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20200521101927_new_formulaagain.up.sql b/pointercrate-example/sample/migrations/_new/20200521101927_new_formulaagain.up.sql new file mode 100644 index 000000000..c6f2ac563 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20200521101927_new_formulaagain.up.sql @@ -0,0 +1,71 @@ +-- Your SQL goes here +DROP VIEW players_with_score; +DROP FUNCTION record_score(FLOAT, FLOAT, FLOAT, FLOAT); + +CREATE FUNCTION record_score(progress FLOAT, demon FLOAT, list_size FLOAT, requirement FLOAT) RETURNS FLOAT AS +$record_score$ + SELECT CASE + WHEN progress = 100 THEN + 150.0 * EXP((1.0 - demon) * LN(1.0 / 30.0) / (-149.0)) + WHEN progress < requirement THEN + 0.0 + ELSE + 150.0 * EXP((1.0 - demon) * LN(1.0 / 30.0) / (-149.0)) * (EXP(LN(5) * (progress - requirement) / (100 - requirement))) / 10 + END; +$record_score$ +LANGUAGE SQL IMMUTABLE; + + +CREATE OR REPLACE 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 + 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, + requirement + FROM records + INNER JOIN demons + ON demons.id = demon + WHERE demons.position <= 150 AND status_ = 'APPROVED' + + 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; diff --git a/pointercrate-example/sample/migrations/_new/20200725072253_fix_smallint.down.sql b/pointercrate-example/sample/migrations/_new/20200725072253_fix_smallint.down.sql new file mode 100644 index 000000000..390b74757 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20200725072253_fix_smallint.down.sql @@ -0,0 +1,43 @@ +-- This file should undo anything in `up.sql` + + +CREATE OR REPLACE FUNCTION audit_demon_modification() RETURNS trigger AS $demon_modification_trigger$ + DECLARE + name_change CITEXT; + position_change SMALLINT; + requirement_change SMALLINT; + video_change VARCHAR(200); + verifier_change SMALLINT; + publisher_change SMALLINT; + BEGIN + IF (OLD.name <> NEW.name) THEN + name_change = OLD.name; + END IF; + + IF (OLD.position <> NEW.position) THEN + position_change = OLD.position; + END IF; + + IF (OLD.requirement <> NEW.requirement) THEN + requirement_change = OLD.requirement; + END IF; + + IF (OLD.video <> NEW.video) THEN + video_change = OLD.video; + END IF; + + IF (OLD.verifier <> NEW.verifier) THEN + verifier_change = OLD.verifier; + END IF; + + IF (OLD.publisher <> NEW.publisher) THEN + publisher_change = OLD.publisher; + END IF; + + INSERT INTO demon_modifications (userid, name, position, requirement, video, verifier, publisher, id) + (SELECT id, name_change, position_change, requirement_change, video_change, verifier_change, publisher_change, NEW.id + FROM active_user LIMIT 1); + + RETURN NEW; + END; +$demon_modification_trigger$ LANGUAGE plpgsql; \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20200725072253_fix_smallint.up.sql b/pointercrate-example/sample/migrations/_new/20200725072253_fix_smallint.up.sql new file mode 100644 index 000000000..7d076cdbf --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20200725072253_fix_smallint.up.sql @@ -0,0 +1,43 @@ +-- Your SQL goes here + + +CREATE OR REPLACE FUNCTION audit_demon_modification() RETURNS trigger AS $demon_modification_trigger$ + DECLARE + name_change CITEXT; + position_change SMALLINT; + requirement_change SMALLINT; + video_change VARCHAR(200); + verifier_change INT; + publisher_change InT; + BEGIN + IF (OLD.name <> NEW.name) THEN + name_change = OLD.name; + END IF; + + IF (OLD.position <> NEW.position) THEN + position_change = OLD.position; + END IF; + + IF (OLD.requirement <> NEW.requirement) THEN + requirement_change = OLD.requirement; + END IF; + + IF (OLD.video <> NEW.video) THEN + video_change = OLD.video; + END IF; + + IF (OLD.verifier <> NEW.verifier) THEN + verifier_change = OLD.verifier; + END IF; + + IF (OLD.publisher <> NEW.publisher) THEN + publisher_change = OLD.publisher; + END IF; + + INSERT INTO demon_modifications (userid, name, position, requirement, video, verifier, publisher, id) + (SELECT id, name_change, position_change, requirement_change, video_change, verifier_change, publisher_change, NEW.id + FROM active_user LIMIT 1); + + RETURN NEW; + END; +$demon_modification_trigger$ LANGUAGE plpgsql; \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20200813055728_dash_rs.down.sql b/pointercrate-example/sample/migrations/_new/20200813055728_dash_rs.down.sql new file mode 100644 index 000000000..7c9fa7b96 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20200813055728_dash_rs.down.sql @@ -0,0 +1,19 @@ +-- This file should undo anything in `up.sql` + +ALTER TABLE demons DROP COLUMN level_id; + +DROP TABLE gj_creator; +DROP TABLE gj_creator_meta; + +DROP TABLE gj_level_data; +DROP TABLE gj_level_data_meta; + +DROP TABLE gj_level; +DROP TABLE gj_level_meta; +DROP TABLE gj_level_request_results; +DROP TABLE gj_level_request_meta; + +DROP TABLE gj_newgrounds_song; +DROP TABLE gj_newgrounds_song_meta; + +DROP TABLE download_lock; \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20200813055728_dash_rs.up.sql b/pointercrate-example/sample/migrations/_new/20200813055728_dash_rs.up.sql new file mode 100644 index 000000000..336d274a9 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20200813055728_dash_rs.up.sql @@ -0,0 +1,97 @@ +-- Your SQL goes here + +CREATE TABLE gj_creator ( + user_id bigint PRIMARY KEY NOT NULL, + name text NOT NULL, + account_id bigint +); + +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 ( + level_id bigint PRIMARY KEY NOT NULL, + level_name text NOT NULL, + description text, + level_version integer NOT NULL, + creator_id bigint NOT NULL, + difficulty smallint NOT NULL, + is_demon boolean not null, + downloads integer NOT NULL, + main_song smallint, + gd_version smallint NOT NULL, + likes integer NOT NULL, + level_length smallint NOT NULL, + stars smallint NOT NULL, + featured integer NOT NULL, + copy_of bigint, + two_player boolean NOT NULL, + custom_song_id bigint, + coin_amount smallint NOT NULL, + coins_verified boolean NOT NULL, + stars_requested smallint, + is_epic boolean NOT NULL, + object_amount integer, + index_46 text, + index_47 text +); + +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 +); + +-- Many-to-many table mapping each level request to the list of levels it returned +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 ( + level_id bigint PRIMARY KEY REFERENCES gj_level(level_id) NOT NULL, + level_data bytea NOT NULL, + level_password integer, + time_since_upload text NOT NULL, + time_since_update text NOT NULL, + index_36 text +); + +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 ( + song_id bigint PRIMARY KEY NOT NULL, + song_name text NOT NULL, + index_3 bigint NOT NULL, + song_artist text NOT NULL, + filesize double precision NOT NULL, + index_6 text, + index_7 text, + index_8 text NOT NULL, + song_link text 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 +); + +ALTER TABLE demons ADD COLUMN level_id INT8 NULL UNIQUE REFERENCES gj_level(level_id); \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20201221091225_no_ext_progress_point.down.sql b/pointercrate-example/sample/migrations/_new/20201221091225_no_ext_progress_point.down.sql new file mode 100644 index 000000000..95dd14915 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20201221091225_no_ext_progress_point.down.sql @@ -0,0 +1,71 @@ +-- Your SQL goes here +DROP VIEW players_with_score; +DROP FUNCTION record_score(FLOAT, FLOAT, FLOAT, FLOAT); + +CREATE FUNCTION record_score(progress FLOAT, demon FLOAT, list_size FLOAT, requirement FLOAT) RETURNS FLOAT AS +$record_score$ +SELECT CASE + WHEN progress = 100 THEN + 150.0 * EXP((1.0 - demon) * LN(1.0 / 30.0) / (-149.0)) + WHEN progress < requirement THEN + 0.0 + ELSE + 150.0 * EXP((1.0 - demon) * LN(1.0 / 30.0) / (-149.0)) * (EXP(LN(5) * (progress - requirement) / (100 - requirement))) / 10 + END; +$record_score$ + LANGUAGE SQL IMMUTABLE; + + +CREATE OR REPLACE 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 +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, + requirement + FROM records + INNER JOIN demons + ON demons.id = demon + WHERE demons.position <= 150 AND status_ = 'APPROVED' + + 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; diff --git a/pointercrate-example/sample/migrations/_new/20201221091225_no_ext_progress_point.up.sql b/pointercrate-example/sample/migrations/_new/20201221091225_no_ext_progress_point.up.sql new file mode 100644 index 000000000..e6252901c --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20201221091225_no_ext_progress_point.up.sql @@ -0,0 +1,71 @@ +-- Your SQL goes here +DROP VIEW players_with_score; +DROP FUNCTION record_score(FLOAT, FLOAT, FLOAT, FLOAT); + +CREATE FUNCTION record_score(progress FLOAT, demon FLOAT, list_size FLOAT, requirement FLOAT) RETURNS FLOAT AS +$record_score$ +SELECT CASE + WHEN progress = 100 THEN + 150.0 * EXP((1.0 - demon) * LN(1.0 / 30.0) / (-149.0)) + WHEN progress < requirement THEN + 0.0 + ELSE + 150.0 * EXP((1.0 - demon) * LN(1.0 / 30.0) / (-149.0)) * (EXP(LN(5) * (progress - requirement) / (100 - requirement))) / 10 + END; +$record_score$ + LANGUAGE SQL IMMUTABLE; + + +CREATE OR REPLACE 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 +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' + + 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; diff --git a/pointercrate-example/sample/migrations/_new/20210224123230_new_formula_once_again.down.sql b/pointercrate-example/sample/migrations/_new/20210224123230_new_formula_once_again.down.sql new file mode 100644 index 000000000..e6252901c --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20210224123230_new_formula_once_again.down.sql @@ -0,0 +1,71 @@ +-- Your SQL goes here +DROP VIEW players_with_score; +DROP FUNCTION record_score(FLOAT, FLOAT, FLOAT, FLOAT); + +CREATE FUNCTION record_score(progress FLOAT, demon FLOAT, list_size FLOAT, requirement FLOAT) RETURNS FLOAT AS +$record_score$ +SELECT CASE + WHEN progress = 100 THEN + 150.0 * EXP((1.0 - demon) * LN(1.0 / 30.0) / (-149.0)) + WHEN progress < requirement THEN + 0.0 + ELSE + 150.0 * EXP((1.0 - demon) * LN(1.0 / 30.0) / (-149.0)) * (EXP(LN(5) * (progress - requirement) / (100 - requirement))) / 10 + END; +$record_score$ + LANGUAGE SQL IMMUTABLE; + + +CREATE OR REPLACE 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 +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' + + 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; diff --git a/pointercrate-example/sample/migrations/_new/20210224123230_new_formula_once_again.up.sql b/pointercrate-example/sample/migrations/_new/20210224123230_new_formula_once_again.up.sql new file mode 100644 index 000000000..84c8e6e48 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20210224123230_new_formula_once_again.up.sql @@ -0,0 +1,94 @@ +-- Your SQL goes here +DROP VIEW players_with_score; +DROP FUNCTION record_score(FLOAT, FLOAT, FLOAT, FLOAT); + +CREATE FUNCTION record_score(progress FLOAT, demon FLOAT, list_size FLOAT, requirement FLOAT) RETURNS FLOAT AS +$record_score$ +SELECT CASE + WHEN progress = 100 THEN + CASE + + WHEN 125 < demon AND demon <= 150 THEN + 150.0 * EXP(((1.0 - demon) * LN(1.0 / 30.0)) / -149.0) + WHEN 50 < demon AND demon <= 125 THEN + 60 * (EXP(LN(2.333) * ((51.0 - demon) * (LN(30.0) / 99.0)))) + 1.884 + WHEN 20 < demon AND demon <= 50 THEN + -100.0 * (EXP(LN(1.01327) * (demon - 26.489))) + 200.0 + WHEN demon <= 20 THEN + (250 - 100.39) * (EXP(LN(1.168) * (1 - demon))) + 100.39 + + END + + WHEN progress < requirement THEN + 0.0 + ELSE + CASE + + WHEN 125 < demon AND demon <= 150 THEN + 150.0 * EXP(((1.0 - demon) * LN(1.0 / 30.0)) / -149.0) * (EXP(LN(5) * (progress - requirement) / (100 - requirement))) / 10 + WHEN 50 < demon AND demon <= 125 THEN + (60 * (EXP(LN(2.333) * ((51.0 - demon) * (LN(30.0) / 99.0)))) + 1.884) * (EXP(LN(5) * (progress - requirement) / (100 - requirement))) / 10 + WHEN 20 < demon AND demon <= 50 THEN + (-100.0 * (EXP(LN(1.01327) * (demon - 26.489))) + 200.0) * (EXP(LN(5) * (progress - requirement) / (100 - requirement))) / 10 + WHEN demon <= 20 THEN + ((250 - 100.39) * (EXP(LN(1.168) * (1 - demon))) + 100.39) * (EXP(LN(5) * (progress - requirement) / (100 - requirement))) / 10 + + END + END; +$record_score$ + LANGUAGE SQL IMMUTABLE; + + +CREATE OR REPLACE 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 +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' + + 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; diff --git a/pointercrate-example/sample/migrations/_new/20210327024242_timemachine.down.sql b/pointercrate-example/sample/migrations/_new/20210327024242_timemachine.down.sql new file mode 100644 index 000000000..643da30d5 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20210327024242_timemachine.down.sql @@ -0,0 +1,3 @@ +-- This file should undo anything in `up.sql` + +DROP FUNCTION list_at(TIMESTAMP WITHOUT TIME ZONE); \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20210327024242_timemachine.up.sql b/pointercrate-example/sample/migrations/_new/20210327024242_timemachine.up.sql new file mode 100644 index 000000000..c0b3e47fc --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20210327024242_timemachine.up.sql @@ -0,0 +1,28 @@ +-- Your SQL goes here + +CREATE FUNCTION list_at(TIMESTAMP WITHOUT TIME ZONE) +RETURNS TABLE ( + name CITEXT, + position_ SMALLINT, + requirement SMALLINT, + video VARCHAR(200), + verifier INTEGER, + publisher INTEGER, + id INTEGER, + level_id BIGINT, + current_position SMALLINT +) +AS $$ + SELECT name, CASE WHEN t.position IS NULL THEN demons.position ELSE t.position END, requirement, video, verifier, publisher, demons.id, level_id, demons.position AS current_position + FROM demons + LEFT OUTER JOIN ( + SELECT DISTINCT ON (id) id, position + FROM demon_modifications + WHERE time >= $1 AND position != -1 + ORDER BY id, time + ) t + ON demons.id = t.id + WHERE NOT EXISTS (SELECT 1 FROM demon_additions WHERE demon_additions.id = demons.id AND time >= $1) +$$ +LANGUAGE SQL +STABLE; \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20210419002933_nationalityupdate.down.sql b/pointercrate-example/sample/migrations/_new/20210419002933_nationalityupdate.down.sql new file mode 100644 index 000000000..39ba1fd5e --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20210419002933_nationalityupdate.down.sql @@ -0,0 +1,102 @@ +-- This file should undo anything in `up.sql` + +-- Undo audit log related changes + +CREATE OR REPLACE FUNCTION audit_player_modification() RETURNS trigger as $player_modification_trigger$ +DECLARE + name_change CITEXT; + banned_change BOOLEAN; +BEGIN + IF (OLD.name <> NEW.name) THEN + name_change = OLD.name; + END IF; + + IF (OLD.banned <> NEW.banned) THEN + banned_change = OLD.banned; + END IF; + + INSERT INTO player_modifications (userid, id, name, banned) + (SELECT id, NEW.id, name_change, banned_change FROM active_user LIMIT 1); + + RETURN NEW; +END; +$player_modification_trigger$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION audit_player_deletion() RETURNS trigger AS $player_deletion_trigger$ +BEGIN + INSERT INTO player_modifications (userid, id, name, banned) + (SELECT id, OLD.id, OLD.name, OLD.banned + FROM active_user LIMIT 1); + + INSERT INTO player_deletions (userid, id) + (SELECT id, OLD.id FROM active_user LIMIT 1); + + RETURN NULL; +END; +$player_deletion_trigger$ LANGUAGE plpgsql; + +ALTER TABLE player_modifications DROP COLUMN nationality, DROP COLUMN subdivision; + +-- cannot drop columns from VIEW via CREATE OR REPLACE + +DROP VIEW players_with_score; +CREATE OR REPLACE 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 +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' + + 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; + +ALTER TABLE nationalities DROP COLUMN continent; + +DROP TABLE subdivisions; +DROP TYPE continent; + +ALTER TABLE players DROP COLUMN subdivision; \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20210419002933_nationalityupdate.up.sql b/pointercrate-example/sample/migrations/_new/20210419002933_nationalityupdate.up.sql new file mode 100644 index 000000000..af03f5af4 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20210419002933_nationalityupdate.up.sql @@ -0,0 +1,428 @@ +CREATE TABLE subdivisions ( + iso_code VARCHAR(3), + name CITEXT UNIQUE NOT NULL, + nation VARCHAR(2) REFERENCES nationalities(iso_country_code), + + PRIMARY KEY (iso_code, nation) +); + +CREATE TYPE continent AS ENUM ('Asia', 'Europe', 'Australia and Oceania', 'Africa', 'North America', 'South America', 'Central America'); + +ALTER TABLE nationalities ADD COLUMN continent continent; + +INSERT INTO nationalities (iso_country_code, nation, continent) +VALUES + ('BQ', 'Bonaire', 'Central America'), + ('JM', 'Jamaica', 'Central America'), + ('PR', 'Puerto Rico', 'Central America'), + ('DO', 'Dominican Republic', 'Central America'), + ('HT', 'Haiti', 'Central America'), + ('SV', 'El Salvador', 'Central America'), + ('GT', 'Guatemala', 'Central America'), + ('HN', 'Honduras', 'Central America'), + ('NI', 'Nicaragua', 'Central America'), + ('PA', 'Panama', 'Central America'), + ('CR', 'Costa Rica', 'Central America'), + ('MX', 'Mexico', 'Central America'), + ('MS', 'Montserrat', 'Central America'), + ('VG', 'British Virgin Islands', 'Central America'), + ('VI', 'US Virgin Islands', 'Central America'), + ('KN', 'Saint Kitts and Nevis', 'Central America'), + ('KY', 'Cayman Islands', 'Central America'), + ('AI', 'Anguilla', 'Central America'), + ('GD', 'Grenada', 'Central America'), + ('LC', 'Saint Lucia', 'Central America'), + ('VC', 'Saint Vincent and the Grenadines', 'Central America'), + ('TC', 'Turks and Caicos Islands', 'Central America'), + ('BB', 'Barbados', 'Central America'), + ('AG', 'Antigua and Barbuda', 'Central America'), + ('SX', 'Sint Maarten (Dutch Part)', 'Central America'), + ('DM', 'Dominica', 'Central America'), + ('TT', 'Trinidad and Tobago', 'Central America'), + ('BS', 'Bahamas', 'Central America'), + ('BZ', 'Belize', 'Central America'), + ('US', 'United States', 'North America'), + ('CA', 'Canada', 'North America'), + ('BR', 'Brazil', 'South America'), + ('CL', 'Chile', 'South America'), + ('AR', 'Argentina', 'South America'), + ('SR', 'Suriname', 'South America'), + ('GY', 'Guyana', 'South America'), + ('VE', 'Venezuela, Bolivarian Republic of', 'South America'), + ('UY', 'Uruguay', 'South America'), + ('PY', 'Paraguay', 'South America'), + ('EC', 'Ecuador', 'South America'), + ('CO', 'Colombia', 'South America'), + ('PE', 'Peru', 'South America'), + ('BO', 'Bolivia, Plurinational State of', 'South America'), + ('AW', 'Aruba', 'South America'), + ('GS', 'South Georgia and the South Sandwich Islands', 'South America'), + ('FK', 'Falkland Islands (Malvinas)', 'South America'), + ('CW', 'Curacao', 'South America'), + ('TD', 'Chad', 'Africa'), + ('DZ', 'Algeria', 'Africa'), + ('EG', 'Egypt', 'Africa'), + ('LY', 'Libya', 'Africa'), + ('MA', 'Morocco', 'Africa'), + ('EH', 'Western Sahara', 'Africa'), + ('SD', 'Sudan', 'Africa'), + ('TN', 'Tunisia', 'Africa'), + ('NE', 'Niger', 'Africa'), + ('MR', 'Mauritania', 'Africa'), + ('ML', 'Mali', 'Africa'), + ('BF', 'Burkina Faso', 'Africa'), + ('ER', 'Eritrea', 'Africa'), + ('SN', 'Senegal', 'Africa'), + ('GM', 'Gambia', 'Africa'), + ('GW', 'Guinea-Bissau', 'Africa'), + ('GN', 'Guinea', 'Africa'), + ('SL', 'Sierra Leone', 'Africa'), + ('LR', 'Liberia', 'Africa'), + ('CI', 'Cote d''Ivoire', 'Africa'), + ('GH', 'Ghana', 'Africa'), + ('TG', 'Togo', 'Africa'), + ('BJ', 'Benin', 'Africa'), + ('CM', 'Cameroon', 'Africa'), + ('CF', 'Central African Republic', 'Africa'), + ('SS', 'South Sudan', 'Africa'), + ('ET', 'Ethiopia', 'Africa'), + ('DJ', 'Djibouti', 'Africa'), + ('SO', 'Somalia', 'Africa'), + ('GQ', 'Equatorial Guinea', 'Africa'), + ('GA', 'Gabon', 'Africa'), + ('KE', 'Kenya', 'Africa'), + ('TZ', 'Tanzania, United Republic of', 'Africa'), + ('UG', 'Uganda', 'Africa'), + ('BI', 'Burundi', 'Africa'), + ('RW', 'Rwanda', 'Africa'), + ('CD', 'Congo, the Democratic Republic of the', 'Africa'), + ('CG', 'Congo', 'Africa'), + ('AO', 'Angola', 'Africa'), + ('ZM', 'Zambia', 'Africa'), + ('MZ', 'Mozambique', 'Africa'), + ('MW', 'Malawi', 'Africa'), + ('ZW', 'Zimbabwe', 'Africa'), + ('NA', 'Namibia', 'Africa'), + ('BW', 'Botswana', 'Africa'), + ('SZ', 'Swaziland', 'Africa'), + ('LS', 'Lesotho', 'Africa'), + ('MG', 'Madagascar', 'Africa'), + ('NG', 'Nigeria', 'Africa'), + ('ZA', 'South Africa', 'Africa'), + ('SH', 'Saint Helena, Ascension and Tristan Da Cunha', 'Africa'), + ('SC', 'Seychelles', 'Africa'), + ('CV', 'Cape Verde', 'Africa'), + ('ST', 'Sao Tome and Principe', 'Africa'), + ('MU', 'Mauritius', 'Africa'), + ('KM', 'Comoros', 'Africa'), + ('FR', 'France', 'Europe'), + ('SI', 'Slovenia', 'Europe'), + ('XK', 'Kosovo', 'Europe'), + ('RS', 'Serbia', 'Europe'), + ('ME', 'Montenegro', 'Europe'), + ('MK', 'Macedonia, the Former Yugoslav Republic of', 'Europe'), + ('GR', 'Greece', 'Europe'), + ('HR', 'Croatia', 'Europe'), + ('BA', 'Bosnia and Herzegovina', 'Europe'), + ('AL', 'Albania', 'Europe'), + ('VA', 'Holy See (Vatican City State)', 'Europe'), + ('SM', 'San Marino', 'Europe'), + ('IT', 'Italy', 'Europe'), + ('SK', 'Slovakia', 'Europe'), + ('RO', 'Romania', 'Europe'), + ('PL', 'Poland', 'Europe'), + ('MD', 'Moldova, Republic of', 'Europe'), + ('HU', 'Hungary', 'Europe'), + ('CZ', 'Czech Republic', 'Europe'), + ('BG', 'Bulgaria', 'Europe'), + ('AT', 'Austria', 'Europe'), + ('CH', 'Switzerland', 'Europe'), + ('DE', 'Germany', 'Europe'), + ('DK', 'Denmark', 'Europe'), + ('NO', 'Norway', 'Europe'), + ('SE', 'Sweden', 'Europe'), + ('FI', 'Finland', 'Europe'), + ('EE', 'Estonia', 'Europe'), + ('LV', 'Latvia', 'Europe'), + ('LT', 'Lithuania', 'Europe'), + ('BY', 'Belarus', 'Europe'), + ('NL', 'Netherlands', 'Europe'), + ('LU', 'Luxembourg', 'Europe'), + ('BE', 'Belgium', 'Europe'), + ('GB', 'United Kingdom', 'Europe'), + ('IE', 'Ireland', 'Europe'), + ('IS', 'Iceland', 'Europe'), + ('AD', 'Andorra', 'Europe'), + ('ES', 'Spain', 'Europe'), + ('PT', 'Portugal', 'Europe'), + ('CY', 'Cyprus', 'Europe'), + ('TR', 'Turkey', 'Europe'), + ('UA', 'Ukraine', 'Europe'), + ('IM', 'Isle of Man', 'Europe'), + ('MC', 'Monaco', 'Europe'), + ('GI', 'Gibraltar', 'Europe'), + ('GG', 'Guernsey', 'Europe'), + ('JE', 'Jersey', 'Europe'), + ('LI', 'Liechtenstein', 'Europe'), + ('MT', 'Malta', 'Europe'), + ('FO', 'Faroe Islands', 'Europe'), + ('LK', 'Sri Lanka', 'Asia'), + ('TW', 'Taiwan', 'Asia'), + ('VN', 'Viet Nam', 'Asia'), + ('MM', 'Myanmar', 'Asia'), + ('KH', 'Cambodia', 'Asia'), + ('LA', 'Lao People''s Democratic Republic', 'Asia'), + ('TH', 'Thailand', 'Asia'), + ('PH', 'Philippines', 'Asia'), + ('MY', 'Malaysia', 'Asia'), + ('OM', 'Oman', 'Asia'), + ('AE', 'United Arab Emirates', 'Asia'), + ('YE', 'Yemen', 'Asia'), + ('QA', 'Qatar', 'Asia'), + ('KW', 'Kuwait', 'Asia'), + ('SA', 'Saudi Arabia', 'Asia'), + ('IL', 'Israel', 'Asia'), + ('LB', 'Lebanon', 'Asia'), + ('SY', 'Syrian Arab Republic', 'Asia'), + ('JO', 'Jordan', 'Asia'), + ('IQ', 'Iraq', 'Asia'), + ('CU', 'Cuba', 'Central America'), + ('RU', 'Russian Federation', 'Asia'), + ('AZ', 'Azerbaijan', 'Asia'), + ('AM', 'Armenia', 'Asia'), + ('GE', 'Georgia', 'Asia'), + ('KP', 'Korea, Democratic People''s Republic of', 'Asia'), + ('KR', 'Korea, Republic of', 'Asia'), + ('JP', 'Japan', 'Asia'), + ('BD', 'Bangladesh', 'Asia'), + ('BT', 'Bhutan', 'Asia'), + ('NP', 'Nepal', 'Asia'), + ('MN', 'Mongolia', 'Asia'), + ('AF', 'Afghanistan', 'Asia'), + ('PK', 'Pakistan', 'Asia'), + ('KG', 'Kyrgyzstan', 'Asia'), + ('IR', 'Iran, Islamic Republic of', 'Asia'), + ('TM', 'Turkmenistan', 'Asia'), + ('TJ', 'Tajikistan', 'Asia'), + ('UZ', 'Uzbekistan', 'Asia'), + ('IN', 'India', 'Asia'), + ('KZ', 'Kazakhstan', 'Asia'), + ('CN', 'China', 'Asia'), + ('HK', 'Hong Kong', 'Asia'), + ('SG', 'Singapore', 'Asia'), + ('MV', 'Maldives', 'Asia'), + ('BH', 'Bahrain', 'Asia'), + ('PS', 'Palestine, State of', 'Asia'), + ('KI', 'Kiribati', 'Australia and Oceania'), + ('NZ', 'New Zealand', 'Australia and Oceania'), + ('BN', 'Brunei Darussalam', 'Australia and Oceania'), + ('TL', 'Timor-Leste', 'Australia and Oceania'), + ('ID', 'Indonesia', 'Australia and Oceania'), + ('PG', 'Papua New Guinea', 'Australia and Oceania'), + ('AU', 'Australia', 'Australia and Oceania'), + ('TK', 'Tokelau', 'Australia and Oceania'), + ('NF', 'Norfolk Island', 'Australia and Oceania'), + ('GU', 'Guam', 'Australia and Oceania'), + ('PN', 'Pitcairn', 'Australia and Oceania'), + ('NR', 'Nauru', 'Australia and Oceania'), + ('TV', 'Tuvalu', 'Australia and Oceania'), + ('MH', 'Marshall Islands', 'Australia and Oceania'), + ('AS', 'American Samoa', 'Australia and Oceania'), + ('CK', 'Cook Islands', 'Australia and Oceania'), + ('NU', 'Niue', 'Australia and Oceania'), + ('TO', 'Tonga', 'Australia and Oceania'), + ('PW', 'Palau', 'Australia and Oceania'), + ('MP', 'Northern Mariana Islands', 'Australia and Oceania'), + ('FM', 'Micronesia, Federated States of', 'Australia and Oceania'), + ('WS', 'Samoa', 'Australia and Oceania'), + ('VU', 'Vanuatu', 'Australia and Oceania'), + ('FJ', 'Fiji', 'Australia and Oceania'), + ('SB', 'Solomon Islands', 'Australia and Oceania') +ON CONFLICT (iso_country_code) DO UPDATE SET nation = EXCLUDED.nation, continent = EXCLUDED.continent; + +DELETE FROM nationalities WHERE continent IS NULL; + +ALTER TABLE nationalities ALTER COLUMN continent SET NOT NULL; + +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'); + +ALTER TABLE players ADD COLUMN subdivision VARCHAR(3) DEFAULT NULL; + +ALTER TABLE player_modifications ADD COLUMN nationality VARCHAR(2) DEFAULT NULL, + ADD COLUMN subdivision VARCHAR(3) DEFAULT NULL; + +CREATE OR REPLACE FUNCTION audit_player_modification() RETURNS trigger as $player_modification_trigger$ +DECLARE + name_change CITEXT; + banned_change BOOLEAN; + nationality_change VARCHAR(2); + subdivision_change VARCHAR(3); +BEGIN + IF (OLD.name <> NEW.name) THEN + name_change = OLD.name; + END IF; + + IF (OLD.banned <> NEW.banned) THEN + banned_change = OLD.banned; + END IF; + + IF (OLD.nationality <> NEW.nationality) THEN + nationality_change = OLD.nationality; + end if; + + IF (OLD.subdivision <> NEW.subdivision) THEN + subdivision_change = OLD.subdivision; + end if; + + INSERT INTO player_modifications (userid, id, name, banned, nationality, subdivision) + (SELECT id, NEW.id, name_change, banned_change, nationality_change, subdivision_change FROM active_user LIMIT 1); + + RETURN NEW; +END; +$player_modification_trigger$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION audit_player_deletion() RETURNS trigger AS $player_deletion_trigger$ +BEGIN + INSERT INTO player_modifications (userid, id, name, banned, nationality, subdivision) + (SELECT id, OLD.id, OLD.name, OLD.banned, OLD.nationality, OLD.subdivision + FROM active_user LIMIT 1); + + INSERT INTO player_deletions (userid, id) + (SELECT id, OLD.id FROM active_user LIMIT 1); + + RETURN NULL; +END; +$player_deletion_trigger$ LANGUAGE plpgsql; + +CREATE OR REPLACE 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; diff --git a/pointercrate-example/sample/migrations/_new/20210725221543_player_claims.down.sql b/pointercrate-example/sample/migrations/_new/20210725221543_player_claims.down.sql new file mode 100644 index 000000000..1f7c18d0c --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20210725221543_player_claims.down.sql @@ -0,0 +1,4 @@ +-- This file should undo anything in `up.sql` + +ALTER TABLE players DROP COLUMN claimed_by; +ALTER TABLE members DROP COLUMN claimed_player; \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20210725221543_player_claims.up.sql b/pointercrate-example/sample/migrations/_new/20210725221543_player_claims.up.sql new file mode 100644 index 000000000..5f8c3166c --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20210725221543_player_claims.up.sql @@ -0,0 +1,4 @@ +-- Your SQL goes here + +ALTER TABLE members ADD COLUMN claimed_player INTEGER REFERENCES players(id) ON DELETE SET NULL; +ALTER TABLE players ADD COLUMN claimed_by INTEGER REFERENCES members(member_id) ON DELETE SET NULL; diff --git a/pointercrate-example/sample/migrations/_new/20210726174613_nation_ranking.down.sql b/pointercrate-example/sample/migrations/_new/20210726174613_nation_ranking.down.sql new file mode 100644 index 000000000..5ae126c97 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20210726174613_nation_ranking.down.sql @@ -0,0 +1,4 @@ +-- This file should undo anything in `up.sql` + +DROP VIEW nations_with_score; +DROP FUNCTION best_records_in(country VARCHAR(2)); \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20210726174613_nation_ranking.up.sql b/pointercrate-example/sample/migrations/_new/20210726174613_nation_ranking.up.sql new file mode 100644 index 000000000..8783b1d45 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20210726174613_nation_ranking.up.sql @@ -0,0 +1,59 @@ +-- Your SQL goes here + +CREATE OR REPLACE FUNCTION best_records_in(country VARCHAR(2)) + 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 + ) + SELECT id, progress, video, status_, player, submitter, demon + FROM grp + WHERE rk = 1; +$body$ +LANGUAGE SQL; + +CREATE OR REPLACE 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; diff --git a/pointercrate-example/sample/migrations/_new/20210825182933_new_claim_system.down.sql b/pointercrate-example/sample/migrations/_new/20210825182933_new_claim_system.down.sql new file mode 100644 index 000000000..f41a075c5 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20210825182933_new_claim_system.down.sql @@ -0,0 +1,8 @@ +-- This file should undo anything in `up.sql` + +ALTER TABLE members ADD COLUMN claimed_player INTEGER REFERENCES players(id) ON DELETE SET NULL; +ALTER TABLE players ADD COLUMN claimed_by INTEGER REFERENCES members(member_id) ON DELETE SET NULL; + +UPDATE members SET claimed_player = (select player_id from player_claims where player_claims.member_id = members.member_id limit 1); + +DROP TABLE player_claims; \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20210825182933_new_claim_system.up.sql b/pointercrate-example/sample/migrations/_new/20210825182933_new_claim_system.up.sql new file mode 100644 index 000000000..1028ea7f8 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20210825182933_new_claim_system.up.sql @@ -0,0 +1,14 @@ +-- Your SQL goes here + +CREATE TABLE player_claims( + id SERIAL PRIMARY KEY, -- only used for pagination + member_id INTEGER NOT NULL REFERENCES members(member_id) ON DELETE CASCADE, + player_id INTEGER NOT NULL REFERENCES players(id) ON DELETE RESTRICT, + verified BOOLEAN NOT NULL DEFAULT FALSE +); + +INSERT INTO player_claims (member_id, player_id) + SELECT member_id, claimed_player FROM members WHERE claimed_player IS NOT NULL; + +ALTER TABLE members DROP COLUMN claimed_player; +ALTER TABLE players DROP COLUMN claimed_by; \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20210903174349_subdivision_ranking.down.sql b/pointercrate-example/sample/migrations/_new/20210903174349_subdivision_ranking.down.sql new file mode 100644 index 000000000..f75a05944 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20210903174349_subdivision_ranking.down.sql @@ -0,0 +1,4 @@ +-- This file should undo anything in `up.sql` + +DROP FUNCTION subdivision_ranking_of(country varchar(2)); +DROP FUNCTION best_records_local(country VARCHAR(2), the_subdivision VARCHAR(3)); \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20210903174349_subdivision_ranking.up.sql b/pointercrate-example/sample/migrations/_new/20210903174349_subdivision_ranking.up.sql new file mode 100644 index 000000000..f6ab6c3db --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20210903174349_subdivision_ranking.up.sql @@ -0,0 +1,68 @@ +-- Your SQL goes here + +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/pointercrate-example/sample/migrations/_new/20220323105850_member_add_email_column.down.sql b/pointercrate-example/sample/migrations/_new/20220323105850_member_add_email_column.down.sql new file mode 100644 index 000000000..4f191b0be --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20220323105850_member_add_email_column.down.sql @@ -0,0 +1,4 @@ +-- This file should undo anything in `up.sql` + +ALTER TABLE members DROP COLUMN email_address; +DROP DOMAIN EMAIL; \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20220323105850_member_add_email_column.up.sql b/pointercrate-example/sample/migrations/_new/20220323105850_member_add_email_column.up.sql new file mode 100644 index 000000000..5cb7b748d --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20220323105850_member_add_email_column.up.sql @@ -0,0 +1,7 @@ +-- Your SQL goes here + +-- https://dba.stackexchange.com/questions/68266/what-is-the-best-way-to-store-an-email-address-in-postgresql +CREATE DOMAIN EMAIL AS CITEXT + CHECK ( value ~ '^[a-zA-Z0-9.!#$%&''*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$' ); + +ALTER TABLE members ADD COLUMN email_address EMAIL; \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20220324104659_lock_submissions.down.sql b/pointercrate-example/sample/migrations/_new/20220324104659_lock_submissions.down.sql new file mode 100644 index 000000000..31410e91f --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20220324104659_lock_submissions.down.sql @@ -0,0 +1,3 @@ +-- This file should undo anything in `up.sql` + +ALTER TABLE player_claims DROP COLUMN lock_submissions; \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20220324104659_lock_submissions.up.sql b/pointercrate-example/sample/migrations/_new/20220324104659_lock_submissions.up.sql new file mode 100644 index 000000000..2390f40e4 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20220324104659_lock_submissions.up.sql @@ -0,0 +1,3 @@ +-- Your SQL goes here + +ALTER TABLE player_claims ADD COLUMN lock_submissions BOOL NOT NULL DEFAULT FALSE; \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20220412171531_public_notes.down.sql b/pointercrate-example/sample/migrations/_new/20220412171531_public_notes.down.sql new file mode 100644 index 000000000..1e86f220e --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20220412171531_public_notes.down.sql @@ -0,0 +1,3 @@ +-- This file should undo anything in `up.sql` + +ALTER TABLE record_notes DROP COLUMN is_public; \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20220412171531_public_notes.up.sql b/pointercrate-example/sample/migrations/_new/20220412171531_public_notes.up.sql new file mode 100644 index 000000000..e7eeec03c --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20220412171531_public_notes.up.sql @@ -0,0 +1,3 @@ +-- Your SQL goes here + +ALTER TABLE record_notes ADD COLUMN is_public BOOLEAN NOT NULL DEFAULT FALSE; \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20220601192100_extended_list_buff.down.sql b/pointercrate-example/sample/migrations/_new/20220601192100_extended_list_buff.down.sql new file mode 100644 index 000000000..4b7697428 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20220601192100_extended_list_buff.down.sql @@ -0,0 +1,38 @@ +-- Your SQL goes here + +CREATE OR REPLACE FUNCTION record_score(progress FLOAT, demon FLOAT, list_size FLOAT, requirement FLOAT) RETURNS FLOAT AS +$record_score$ +SELECT CASE + WHEN progress = 100 THEN + CASE + + WHEN 125 < demon AND demon <= 150 THEN + 150.0 * EXP(((1.0 - demon) * LN(1.0 / 30.0)) / -149.0) + WHEN 50 < demon AND demon <= 125 THEN + 60 * (EXP(LN(2.333) * ((51.0 - demon) * (LN(30.0) / 99.0)))) + 1.884 + WHEN 20 < demon AND demon <= 50 THEN + -100.0 * (EXP(LN(1.01327) * (demon - 26.489))) + 200.0 + WHEN demon <= 20 THEN + (250 - 100.39) * (EXP(LN(1.168) * (1 - demon))) + 100.39 + + END + + WHEN progress < requirement THEN + 0.0 + ELSE + CASE + + WHEN 125 < demon AND demon <= 150 THEN + 150.0 * EXP(((1.0 - demon) * LN(1.0 / 30.0)) / -149.0) * (EXP(LN(5) * (progress - requirement) / (100 - requirement))) / 10 + WHEN 50 < demon AND demon <= 125 THEN + (60 * (EXP(LN(2.333) * ((51.0 - demon) * (LN(30.0) / 99.0)))) + 1.884) * (EXP(LN(5) * (progress - requirement) / (100 - requirement))) / 10 + WHEN 20 < demon AND demon <= 50 THEN + (-100.0 * (EXP(LN(1.01327) * (demon - 26.489))) + 200.0) * (EXP(LN(5) * (progress - requirement) / (100 - requirement))) / 10 + WHEN demon <= 20 THEN + ((250 - 100.39) * (EXP(LN(1.168) * (1 - demon))) + 100.39) * (EXP(LN(5) * (progress - requirement) / (100 - requirement))) / 10 + + END + END; +$record_score$ + LANGUAGE SQL IMMUTABLE; + diff --git a/pointercrate-example/sample/migrations/_new/20220601192100_extended_list_buff.up.sql b/pointercrate-example/sample/migrations/_new/20220601192100_extended_list_buff.up.sql new file mode 100644 index 000000000..c2ff3b8da --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20220601192100_extended_list_buff.up.sql @@ -0,0 +1,37 @@ +-- Your SQL goes here + +CREATE OR REPLACE FUNCTION record_score(progress FLOAT, demon FLOAT, list_size FLOAT, requirement FLOAT) RETURNS FLOAT AS +$record_score$ +SELECT CASE + WHEN progress = 100 THEN + CASE + + WHEN 55 < demon AND demon <= 150 THEN + (56.191 * EXP(LN(2) * ((54.147 - (demon + 3.2)) * LN(50.0)) / 99.0)) + 6.273 + WHEN 35 < demon AND demon <= 55 THEN + 212.61 * (EXP(LN(1.036) * (1 - demon))) + 25.071 + WHEN 20 < demon AND demon <= 35 THEN + (250 - 83.389) * (EXP(LN(1.0099685) * (2 - demon))) - 31.152 + WHEN demon <= 20 THEN + (250 - 100.39) * (EXP(LN(1.168) * (1 - demon))) + 100.39 + + END + + WHEN progress < requirement THEN + 0.0 + ELSE + CASE + + WHEN 55 < demon AND demon <= 150 THEN + ((56.191 * EXP(LN(2) * ((54.147 - (demon + 3.2)) * LN(50.0)) / 99.0)) + 6.273) * (EXP(LN(5) * (progress - requirement) / (100 - requirement))) / 10 + WHEN 35 < demon AND demon <= 55 THEN + (212.61 * (EXP(LN(1.036) * (1 - demon))) + 25.071) * (EXP(LN(5) * (progress - requirement) / (100 - requirement))) / 10 + WHEN 20 < demon AND demon <= 35 THEN + ((250 - 83.389) * (EXP(LN(1.0099685) * (2 - demon))) - 31.152) * (EXP(LN(5) * (progress - requirement) / (100 - requirement))) / 10 + WHEN demon <= 20 THEN + ((250 - 100.39) * (EXP(LN(1.168) * (1 - demon))) + 100.39) * (EXP(LN(5) * (progress - requirement) / (100 - requirement))) / 10 + + END + END; +$record_score$ + LANGUAGE SQL IMMUTABLE; diff --git a/pointercrate-example/sample/migrations/_new/20220805215800_fix_youtube_channel_audit.down.sql b/pointercrate-example/sample/migrations/_new/20220805215800_fix_youtube_channel_audit.down.sql new file mode 100644 index 000000000..147f03494 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20220805215800_fix_youtube_channel_audit.down.sql @@ -0,0 +1,25 @@ +-- This file should undo anything in `up.sql` +CREATE OR REPLACE FUNCTION audit_user_modification() RETURNS trigger as $user_modification_trigger$ +DECLARE + display_name_change CITEXT; + youtube_channel_change BOOLEAN; + permissions_change BIT(16); +BEGIN + IF (OLD.display_name <> NEW.display_name) THEN + display_name_change = OLD.display_name; + END IF; + + IF (OLD.youtube_channel <> NEW.youtube_channel) THEN + youtube_channel_change = OLD.youtube_channel; + END IF; + + IF (OLD.permissions <> NEW.permissions) THEN + permissions_change = OLD.permissions; + END IF; + + INSERT INTO user_modifications (userid, id, display_name, youtube_channel, permissions) + (SELECT id, NEW.member_id, display_name_change, youtube_channel_change, permissions_change FROM active_user LIMIT 1); + + RETURN NEW; +END; +$user_modification_trigger$ LANGUAGE plpgsql; \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20220805215800_fix_youtube_channel_audit.up.sql b/pointercrate-example/sample/migrations/_new/20220805215800_fix_youtube_channel_audit.up.sql new file mode 100644 index 000000000..88344efb2 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20220805215800_fix_youtube_channel_audit.up.sql @@ -0,0 +1,25 @@ +-- Your SQL goes here +CREATE OR REPLACE FUNCTION audit_user_modification() RETURNS trigger as $user_modification_trigger$ +DECLARE + display_name_change CITEXT; + youtube_channel_change VARCHAR(200); + permissions_change BIT(16); +BEGIN + IF (OLD.display_name <> NEW.display_name) THEN + display_name_change = OLD.display_name; + END IF; + + IF (OLD.youtube_channel <> NEW.youtube_channel) THEN + youtube_channel_change = OLD.youtube_channel; + END IF; + + IF (OLD.permissions <> NEW.permissions) THEN + permissions_change = OLD.permissions; + END IF; + + INSERT INTO user_modifications (userid, id, display_name, youtube_channel, permissions) + (SELECT id, NEW.member_id, display_name_change, youtube_channel_change, permissions_change FROM active_user LIMIT 1); + + RETURN NEW; +END; +$user_modification_trigger$ LANGUAGE plpgsql; \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20221009140916_thumbnails.down.sql b/pointercrate-example/sample/migrations/_new/20221009140916_thumbnails.down.sql new file mode 100644 index 000000000..30dd42db3 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20221009140916_thumbnails.down.sql @@ -0,0 +1,77 @@ +-- This file should undo anything in `up.sql` + +DROP TRIGGER demons_insert_set_thumbnail ON demons; +DROP FUNCTION set_initial_thumbnail; + +ALTER TABLE demons DROP COLUMN thumbnail; + +CREATE OR REPLACE FUNCTION audit_demon_modification() RETURNS trigger AS $demon_modification_trigger$ +DECLARE + name_change CITEXT; + position_change SMALLINT; + requirement_change SMALLINT; + video_change VARCHAR(200); + verifier_change INT; + publisher_change InT; +BEGIN + IF (OLD.name <> NEW.name) THEN + name_change = OLD.name; + END IF; + + IF (OLD.position <> NEW.position) THEN + position_change = OLD.position; + END IF; + + IF (OLD.requirement <> NEW.requirement) THEN + requirement_change = OLD.requirement; + END IF; + + IF (OLD.video <> NEW.video) THEN + video_change = OLD.video; + END IF; + + IF (OLD.verifier <> NEW.verifier) THEN + verifier_change = OLD.verifier; + END IF; + + IF (OLD.publisher <> NEW.publisher) THEN + publisher_change = OLD.publisher; + END IF; + + INSERT INTO demon_modifications (userid, name, position, requirement, video, verifier, publisher, id) + (SELECT id, name_change, position_change, requirement_change, video_change, verifier_change, publisher_change, NEW.id + FROM active_user LIMIT 1); + + RETURN NEW; +END; +$demon_modification_trigger$ LANGUAGE plpgsql; + +ALTER TABLE demon_modifications DROP COLUMN thumbnail; + +DROP FUNCTION list_at(TIMESTAMP WITHOUT TIME ZONE); +CREATE OR REPLACE FUNCTION list_at(TIMESTAMP WITHOUT TIME ZONE) + RETURNS TABLE ( + name CITEXT, + position_ SMALLINT, + requirement SMALLINT, + video VARCHAR(200), + verifier INTEGER, + publisher INTEGER, + id INTEGER, + level_id BIGINT, + current_position SMALLINT + ) +AS $$ +SELECT name, CASE WHEN t.position IS NULL THEN demons.position ELSE t.position END, requirement, video, verifier, publisher, demons.id, level_id, demons.position AS current_position +FROM demons + LEFT OUTER JOIN ( + SELECT DISTINCT ON (id) id, position + FROM demon_modifications + WHERE time >= $1 AND position != -1 + ORDER BY id, time +) t + ON demons.id = t.id +WHERE NOT EXISTS (SELECT 1 FROM demon_additions WHERE demon_additions.id = demons.id AND time >= $1) +$$ + LANGUAGE SQL + STABLE; \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20221009140916_thumbnails.up.sql b/pointercrate-example/sample/migrations/_new/20221009140916_thumbnails.up.sql new file mode 100644 index 000000000..123c08003 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20221009140916_thumbnails.up.sql @@ -0,0 +1,100 @@ +-- Your SQL goes here + +ALTER TABLE demons ADD COLUMN thumbnail TEXT NOT NULL DEFAULT 'https://i.ytimg.com/vi/zebrafishes/mqdefault.jpg'; + +UPDATE demons +SET thumbnail = 'https://i.ytimg.com/vi/' || SUBSTRING(video FROM '%v=#"___________#"%' FOR '#') || '/mqdefault.jpg' +WHERE video IS NOT NULL + AND NOT EXISTS (SELECT 1 FROM players WHERE players.id=demons.verifier AND players.link_banned); + +CREATE OR REPLACE FUNCTION set_initial_thumbnail() RETURNS trigger AS ' +BEGIN + IF NEW.video IS NOT NULL AND NOT EXISTS(SELECT 1 FROM players WHERE players.id=NEW.verifier AND players.link_banned) THEN + NEW.thumbnail := ''https://i.ytimg.com/vi/'' || SUBSTRING(NEW.video FROM ''%v=#"___________#"%'' FOR ''#'') || ''/mqdefault.jpg''; + END IF; + RETURN NEW; +END; +' LANGUAGE plpgsql; + +CREATE TRIGGER demons_insert_set_thumbnail BEFORE INSERT ON demons FOR +EACH ROW EXECUTE PROCEDURE set_initial_thumbnail(); + +-- Your SQL goes here + +ALTER TABLE demon_modifications ADD COLUMN thumbnail TEXT NULL DEFAULT NULL; + +CREATE OR REPLACE FUNCTION audit_demon_modification() RETURNS trigger AS $demon_modification_trigger$ +DECLARE + name_change CITEXT; + position_change SMALLINT; + requirement_change SMALLINT; + video_change VARCHAR(200); + thumbnail_change TEXT; + verifier_change INT; + publisher_change INT; +BEGIN + IF (OLD.name <> NEW.name) THEN + name_change = OLD.name; + END IF; + + IF (OLD.position <> NEW.position) THEN + position_change = OLD.position; + END IF; + + IF (OLD.requirement <> NEW.requirement) THEN + requirement_change = OLD.requirement; + END IF; + + IF (OLD.video <> NEW.video) THEN + video_change = OLD.video; + END IF; + + IF (OLD.thumbnail <> NEW.thumbnail) THEN + thumbnail_change = OLD.thumbnail; + END IF; + + IF (OLD.verifier <> NEW.verifier) THEN + verifier_change = OLD.verifier; + END IF; + + IF (OLD.publisher <> NEW.publisher) THEN + publisher_change = OLD.publisher; + END IF; + + INSERT INTO demon_modifications (userid, name, position, requirement, video, verifier, publisher, thumbnail, id) + (SELECT id, name_change, position_change, requirement_change, video_change, verifier_change, publisher_change, thumbnail_change, NEW.id + FROM active_user LIMIT 1); + + RETURN NEW; +END; +$demon_modification_trigger$ LANGUAGE plpgsql; + +DROP FUNCTION list_at(TIMESTAMP WITHOUT TIME ZONE); + +CREATE FUNCTION list_at(TIMESTAMP WITHOUT TIME ZONE) + RETURNS TABLE ( + name CITEXT, + position_ SMALLINT, + requirement SMALLINT, + video VARCHAR(200), + thumbnail TEXT, + verifier INTEGER, + publisher INTEGER, + id INTEGER, + level_id BIGINT, + current_position SMALLINT + ) +AS $$ +SELECT name, CASE WHEN t.position IS NULL THEN demons.position ELSE t.position END, requirement, video, thumbnail, verifier, publisher, demons.id, level_id, demons.position AS current_position +FROM demons + LEFT OUTER JOIN ( + SELECT DISTINCT ON (id) id, position + FROM demon_modifications + WHERE time >= $1 AND position != -1 + ORDER BY id, time +) t + ON demons.id = t.id +WHERE NOT EXISTS (SELECT 1 FROM demon_additions WHERE demon_additions.id = demons.id AND time >= $1) +$$ + LANGUAGE SQL + STABLE; \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20240328150521_simplify_gd_integration.down.sql b/pointercrate-example/sample/migrations/_new/20240328150521_simplify_gd_integration.down.sql new file mode 100644 index 000000000..93479c830 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/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/pointercrate-example/sample/migrations/_new/20240328150521_simplify_gd_integration.up.sql b/pointercrate-example/sample/migrations/_new/20240328150521_simplify_gd_integration.up.sql new file mode 100644 index 000000000..f220f7492 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/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/pointercrate-example/sample/migrations/_new/20240404084539_nationalityupdate_again.down.sql b/pointercrate-example/sample/migrations/_new/20240404084539_nationalityupdate_again.down.sql new file mode 100644 index 000000000..814293862 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/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/pointercrate-example/sample/migrations/_new/20240404084539_nationalityupdate_again.up.sql b/pointercrate-example/sample/migrations/_new/20240404084539_nationalityupdate_again.up.sql new file mode 100644 index 000000000..627e29d8a --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/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/pointercrate-example/sample/migrations/_new/20240418192100_top_10_buff.down.sql b/pointercrate-example/sample/migrations/_new/20240418192100_top_10_buff.down.sql new file mode 100644 index 000000000..c2ff3b8da --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20240418192100_top_10_buff.down.sql @@ -0,0 +1,37 @@ +-- Your SQL goes here + +CREATE OR REPLACE FUNCTION record_score(progress FLOAT, demon FLOAT, list_size FLOAT, requirement FLOAT) RETURNS FLOAT AS +$record_score$ +SELECT CASE + WHEN progress = 100 THEN + CASE + + WHEN 55 < demon AND demon <= 150 THEN + (56.191 * EXP(LN(2) * ((54.147 - (demon + 3.2)) * LN(50.0)) / 99.0)) + 6.273 + WHEN 35 < demon AND demon <= 55 THEN + 212.61 * (EXP(LN(1.036) * (1 - demon))) + 25.071 + WHEN 20 < demon AND demon <= 35 THEN + (250 - 83.389) * (EXP(LN(1.0099685) * (2 - demon))) - 31.152 + WHEN demon <= 20 THEN + (250 - 100.39) * (EXP(LN(1.168) * (1 - demon))) + 100.39 + + END + + WHEN progress < requirement THEN + 0.0 + ELSE + CASE + + WHEN 55 < demon AND demon <= 150 THEN + ((56.191 * EXP(LN(2) * ((54.147 - (demon + 3.2)) * LN(50.0)) / 99.0)) + 6.273) * (EXP(LN(5) * (progress - requirement) / (100 - requirement))) / 10 + WHEN 35 < demon AND demon <= 55 THEN + (212.61 * (EXP(LN(1.036) * (1 - demon))) + 25.071) * (EXP(LN(5) * (progress - requirement) / (100 - requirement))) / 10 + WHEN 20 < demon AND demon <= 35 THEN + ((250 - 83.389) * (EXP(LN(1.0099685) * (2 - demon))) - 31.152) * (EXP(LN(5) * (progress - requirement) / (100 - requirement))) / 10 + WHEN demon <= 20 THEN + ((250 - 100.39) * (EXP(LN(1.168) * (1 - demon))) + 100.39) * (EXP(LN(5) * (progress - requirement) / (100 - requirement))) / 10 + + END + END; +$record_score$ + LANGUAGE SQL IMMUTABLE; diff --git a/pointercrate-example/sample/migrations/_new/20240418192100_top_10_buff.up.sql b/pointercrate-example/sample/migrations/_new/20240418192100_top_10_buff.up.sql new file mode 100644 index 000000000..3bcd66692 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20240418192100_top_10_buff.up.sql @@ -0,0 +1,34 @@ +CREATE OR REPLACE FUNCTION record_score(progress FLOAT, demon FLOAT, list_size FLOAT, requirement FLOAT) RETURNS FLOAT AS +$record_score$ +SELECT CASE + WHEN progress = 100 THEN + CASE + WHEN demon BETWEEN 56 AND 150 THEN + 1.039035131 * ((185.7 * EXP((-0.02715 * demon))) + 14.84) + WHEN demon BETWEEN 36 AND 55 THEN + 1.0371139743 * ((212.61 * POWER(1.036, 1 - demon)) + 25.071) + WHEN demon BETWEEN 21 AND 35 THEN + (((250 - 83.389) * POWER(1.0099685, 2 - demon) - 31.152)) * 1.0371139743 + WHEN demon BETWEEN 4 AND 20 THEN + ((326.1 * EXP((-0.0871 * demon))) + 51.09) * 1.037117142 + WHEN demon BETWEEN 1 AND 3 THEN + (-18.2899079915 * demon) + 368.2899079915 + END + WHEN progress < requirement THEN + 0.0 + ELSE + CASE + WHEN demon BETWEEN 56 AND 150 THEN + 1.039035131 * ((185.7 * EXP((-0.02715 * demon))) + 14.84) * (EXP(LN(5) * (progress - requirement) / (100 - requirement))) / 10 + WHEN demon BETWEEN 36 AND 55 THEN + (1.0371139743 * ((212.61 * POWER(1.036, 1 - demon)) + 25.071)) * (EXP(LN(5) * (progress - requirement) / (100 - requirement))) / 10 + WHEN demon BETWEEN 21 AND 35 THEN + (((250 - 83.389) * POWER(1.0099685, 2 - demon) - 31.152)) * 1.0371139743 * (EXP(LN(5) * (progress - requirement) / (100 - requirement))) / 10 + WHEN demon BETWEEN 4 AND 20 THEN + (((326.1 * EXP((-0.0871 * demon))) + 51.09) * 1.037117142) * (EXP(LN(5) * (progress - requirement) / (100 - requirement))) / 10 + WHEN demon BETWEEN 1 AND 3 THEN + ((-18.2899079915 * demon) + 368.2899079915) * (EXP(LN(5) * (progress - requirement) / (100 - requirement))) / 10 + END + END; +$record_score$ + LANGUAGE SQL IMMUTABLE; diff --git a/pointercrate-example/sample/migrations/_new/20240504194923_cached_points.down.sql b/pointercrate-example/sample/migrations/_new/20240504194923_cached_points.down.sql new file mode 100644 index 000000000..12af63e14 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/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/pointercrate-example/sample/migrations/_new/20240504194923_cached_points.up.sql b/pointercrate-example/sample/migrations/_new/20240504194923_cached_points.up.sql new file mode 100644 index 000000000..306aa9af5 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/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/pointercrate-example/sample/migrations/_new/20240518154803_no_extended_progress_points.down.sql b/pointercrate-example/sample/migrations/_new/20240518154803_no_extended_progress_points.down.sql new file mode 100644 index 000000000..8f33ab529 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/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/pointercrate-example/sample/migrations/_new/20240518154803_no_extended_progress_points.up.sql b/pointercrate-example/sample/migrations/_new/20240518154803_no_extended_progress_points.up.sql new file mode 100644 index 000000000..8efed0766 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/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/pointercrate-example/sample/migrations/_new/20240518161548_properly_zero_scores.down.sql b/pointercrate-example/sample/migrations/_new/20240518161548_properly_zero_scores.down.sql new file mode 100644 index 000000000..f689e4bf7 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/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/pointercrate-example/sample/migrations/_new/20240518161548_properly_zero_scores.up.sql b/pointercrate-example/sample/migrations/_new/20240518161548_properly_zero_scores.up.sql new file mode 100644 index 000000000..dedf68fbf --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/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-example/sample/migrations/_new/20240725201300_better_raw_footage.down.sql b/pointercrate-example/sample/migrations/_new/20240725201300_better_raw_footage.down.sql new file mode 100644 index 000000000..935f603bc --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20240725201300_better_raw_footage.down.sql @@ -0,0 +1,21 @@ +ALTER TABLE records DROP COLUMN raw_footage; + +DROP FUNCTION best_records_in(VARCHAR(2)); + +CREATE OR REPLACE FUNCTION best_records_in(country VARCHAR(2)) + 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 + ) + SELECT id, progress, video, status_, player, submitter, demon + FROM grp + WHERE rk = 1; +$body$ +LANGUAGE SQL; diff --git a/pointercrate-example/sample/migrations/_new/20240725201300_better_raw_footage.up.sql b/pointercrate-example/sample/migrations/_new/20240725201300_better_raw_footage.up.sql new file mode 100644 index 000000000..f6d97bf87 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20240725201300_better_raw_footage.up.sql @@ -0,0 +1 @@ +ALTER TABLE records ADD COLUMN raw_footage TEXT; \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20240804194818_raw_footage_fixup.down.sql b/pointercrate-example/sample/migrations/_new/20240804194818_raw_footage_fixup.down.sql new file mode 100644 index 000000000..e812670c4 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20240804194818_raw_footage_fixup.down.sql @@ -0,0 +1,3 @@ +-- Add down migration script here +-- This migration is undone by the previous one, as it is impossible to restore the partially broken state +-- introduced by only applying 20240725201300_better_raw_footage.up.sql (function with wrong return type). diff --git a/pointercrate-example/sample/migrations/_new/20240804194818_raw_footage_fixup.up.sql b/pointercrate-example/sample/migrations/_new/20240804194818_raw_footage_fixup.up.sql new file mode 100644 index 000000000..4cf52a936 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20240804194818_raw_footage_fixup.up.sql @@ -0,0 +1,27 @@ +DROP FUNCTION best_records_in(VARCHAR(2)); + +CREATE FUNCTION best_records_in(country VARCHAR(2)) + RETURNS TABLE ( + id integer , + progress smallint , + video character varying(200), + status_ public.record_status , + player integer , + submitter integer , + demon integer + ) + 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 + ) + SELECT id, progress, video, status_, player, submitter, demon + FROM grp + WHERE rk = 1; +$body$ +LANGUAGE SQL; diff --git a/pointercrate-example/sample/migrations/_new/20240907133101_resync_database.down.sql b/pointercrate-example/sample/migrations/_new/20240907133101_resync_database.down.sql new file mode 100644 index 000000000..82d58bb46 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20240907133101_resync_database.down.sql @@ -0,0 +1,10 @@ +ALTER TABLE IF EXISTS public.demons + ADD CONSTRAINT demons_level_id_fkey FOREIGN KEY (level_id) + REFERENCES public.gj_level (level_id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE NO ACTION; + + + +ALTER TABLE IF EXISTS public.records + ADD CONSTRAINT records_video_key UNIQUE (video); diff --git a/pointercrate-example/sample/migrations/_new/20240907133101_resync_database.up.sql b/pointercrate-example/sample/migrations/_new/20240907133101_resync_database.up.sql new file mode 100644 index 000000000..5498d510f --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20240907133101_resync_database.up.sql @@ -0,0 +1,8 @@ +ALTER TABLE IF EXISTS public.demons + DROP CONSTRAINT IF EXISTS demons_level_id_fkey; + + + + +ALTER TABLE IF EXISTS public.records + DROP CONSTRAINT IF EXISTS records_video_key; diff --git a/pointercrate-example/sample/migrations/_new/20240913151142_backup_records_on_delete.down.sql b/pointercrate-example/sample/migrations/_new/20240913151142_backup_records_on_delete.down.sql new file mode 100644 index 000000000..fa345c35f --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20240913151142_backup_records_on_delete.down.sql @@ -0,0 +1 @@ +DROP TABLE rec_backup \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/_new/20240913151142_backup_records_on_delete.up.sql b/pointercrate-example/sample/migrations/_new/20240913151142_backup_records_on_delete.up.sql new file mode 100644 index 000000000..a0fb5f4c1 --- /dev/null +++ b/pointercrate-example/sample/migrations/_new/20240913151142_backup_records_on_delete.up.sql @@ -0,0 +1,19 @@ +CREATE TABLE public.rec_backup +( + id integer DEFAULT nextval('records_id_seq'::regclass), + progress smallint, + video character varying(200), + status_ record_status, + player integer, + submitter integer, + demon integer, + raw_footage text, + demon_name citext, + PRIMARY KEY (id) +) + +USING heap +TABLESPACE pg_default; + +ALTER TABLE IF EXISTS public.rec_backup + OWNER to pointercrate; \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/00000000000000_diesel_initial_setup.down.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/00000000000000_diesel_initial_setup.down.sql new file mode 100644 index 000000000..83230d6c3 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/00000000000000_diesel_initial_setup.down.sql @@ -0,0 +1,7 @@ +/* -- This file was automatically created by Diesel to setup helper functions +-- and other internal bookkeeping. This file is safe to edit, any future +-- changes will be added to existing projects as new migrations. + +DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass); +DROP FUNCTION IF EXISTS diesel_set_updated_at(); + */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20180918150231_initial.down.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20180918150231_initial.down.sql new file mode 100644 index 000000000..67cb0c35e --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20180918150231_initial.down.sql @@ -0,0 +1,15 @@ +/* -- This file should undo anything in `up.sql` + +-- Drop everything in reverse order + +DROP TABLE audit_log; +DROP TABLE creators; +DROP TABLE records; +DROP TABLE demons; +DROP TABLE members; +DROP TABLE submitters; +DROP TABLE players; +DROP TYPE AUDIT_OPERATION; + +DROP TYPE RECORD_STATUS; +DROP EXTENSION CITEXT; */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20180918150231_initial.up.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20180918150231_initial.up.sql new file mode 100644 index 000000000..9218c9060 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20180918150231_initial.up.sql @@ -0,0 +1,70 @@ +/* -- Your SQL goes here + +-- This is a workaround +-- To get the new backend to work, we need to run all migrations against the existing database +-- all things from this migration already exist in that database, so theoretically, it should be a NOOP. +-- However, there is no `CREATE ... IF NOT EXISTS` equavalent for types in postgres +-- The following code tries to create the type and ignores the error by explicitly registering a handler for it +DO $$ BEGIN + CREATE TYPE RECORD_STATUS AS ENUM ('APPROVED', 'REJECTED', 'SUBMITTED', 'DELETED'); +EXCEPTION + WHEN duplicate_object THEN null; +END $$; + +CREATE EXTENSION CITEXT; + +CREATE TABLE IF NOT EXISTS players( + id SERIAL PRIMARY KEY, + name CITEXT NOT NULL UNIQUE, + banned BOOLEAN NOT NULL DEFAULT FALSE +); + +CREATE TABLE IF NOT EXISTS submitters ( + submitter_id SERIAL PRIMARY KEY, + ip_address INET NOT NULL, + banned BOOLEAN NOT NULL DEFAULT FALSE +); + +CREATE TABLE IF NOT EXISTS members ( + member_id SERIAL PRIMARY KEY, + name CITEXT UNIQUE NOT NULL, + display_name CITEXT NULL DEFAULT NULL, + youtube_channel VARCHAR(200) NULL DEFAULT NULL, + password_hash BYTEA NOT NULL, + password_salt BYTEA NOT NULL, + permissions BIT(16) NOT NULL DEFAULT B'0000000000000000'::BIT(16) +); + +CREATE TABLE IF NOT EXISTS demons ( + name CITEXT PRIMARY KEY, + position SMALLINT NOT NULL, + requirement SMALLINT NOT NULL, + video VARCHAR(200), + description TEXT NULL, + notes TEXT NULL, + verifier INT NOT NULL REFERENCES players(id) ON DELETE RESTRICT ON UPDATE CASCADE, + publisher INT NOT NULL REFERENCES players(id) ON DELETE RESTRICT ON UPDATE CASCADE, + + CONSTRAINT unique_position UNIQUE (position) DEFERRABLE INITIALLY IMMEDIATE, + CONSTRAINT valid_record_req CHECK (requirement >= 0 AND requirement <= 100) +); + +CREATE TABLE IF NOT EXISTS records ( + id SERIAL PRIMARY KEY, + progress SMALLINT CHECK (progress >= 0 AND progress <= 100) NOT NULL, + video VARCHAR(200) UNIQUE, + status_ RECORD_STATUS NOT NULL, + player INT NOT NULL REFERENCES players(id) ON DELETE RESTRICT ON UPDATE CASCADE, + submitter INT NOT NULL REFERENCES submitters(submitter_id) ON DELETE RESTRICT, + demon CITEXT NOT NULL REFERENCES demons(name) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT records_demon_player_status__key UNIQUE (demon, player, status_) DEFERRABLE INITIALLY IMMEDIATE +); + +CREATE TABLE IF NOT EXISTS creators ( + demon CITEXT NOT NULL REFERENCES demons(name) ON DELETE RESTRICT ON UPDATE CASCADE, + creator INT NOT NULL REFERENCES players(id) ON DELETE RESTRICT ON UPDATE CASCADE, + PRIMARY KEY (demon, creator) +); + +GRANT TRIGGER, REFERENCES, SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO pointercrate; +GRANT USAGE ON ALL SEQUENCES IN SCHEMA public TO pointercrate; */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20181229171817_demon_view.down.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20181229171817_demon_view.down.sql new file mode 100644 index 000000000..fc4b34b5a --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20181229171817_demon_view.down.sql @@ -0,0 +1,3 @@ +/* -- This file should undo anything in `up.sql` + +DROP VIEW demon_publisher_verifier_join; */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20181229171817_demon_view.up.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20181229171817_demon_view.up.sql new file mode 100644 index 000000000..fde470dbd --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20181229171817_demon_view.up.sql @@ -0,0 +1,9 @@ +/* -- Your SQL goes here + +CREATE OR REPLACE VIEW demon_verifier_publisher_join AS +SELECT p1.name AS vname, p1.id AS vid, p1.banned AS vbanned, p2.name AS pname, p2.id AS pid, p2.banned AS pbanned +FROM demons +INNER JOIN players AS p1 +ON demons.verifier = p1.id +INNER JOIN players AS p2 +ON demons.publisher = p2.id */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20190105154218_new_audit_log.down.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20190105154218_new_audit_log.down.sql new file mode 100644 index 000000000..d312a0dfb --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20190105154218_new_audit_log.down.sql @@ -0,0 +1,47 @@ +/* -- This file should undo anything in `up.sql` + +DROP TABLE demon_additions; +DROP FUNCTION audit_demon_addition() CASCADE; + +DROP TABLE demon_modifications; +DROP FUNCTION audit_demon_modification() CASCADE; + +DROP TABLE record_additions; +DROP FUNCTION audit_record_addition() CASCADE; + +DROP TABLE record_modifications; +DROP FUNCTION audit_record_modification() CASCADE; + +DROP TABLE record_deletions; +DROP FUNCTION audit_record_deletion() CASCADE; + +DROP TABLE player_additions; +DROP FUNCTION audit_player_addition() CASCADE; + +DROP TABLE player_modifications; +DROP FUNCTION audit_player_modification() CASCADE; + +DROP TABLE player_deletions; +DROP FUNCTION audit_player_deletion() CASCADE; + +DROP TABLE creator_additions; +DROP FUNCTION audit_creator_addition() CASCADE; + +DROP TABLE creator_deletions; +DROP FUNCTION audit_creator_deletion() CASCADE; + +DROP TABLE submitter_modifications; +DROP FUNCTION audit_submitter_modification() CASCADE; + +DROP TABLE user_additions; +DROP FUNCTION audit_user_addition() CASCADE; + +DROP TABLE user_modifications; +DROP FUNCTION audit_user_modification() CASCADE; + +DROP TABLE user_deletions; +DROP FUNCTION audit_user_deletion() CASCADE; + +DROP TABLE audit_log2; + +DROP TABLE active_user; */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20190105154218_new_audit_log.up.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20190105154218_new_audit_log.up.sql new file mode 100644 index 000000000..50165e662 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20190105154218_new_audit_log.up.sql @@ -0,0 +1,370 @@ +/* -- Alright, audit_log overhault +-- In audit log entries we cannot use foreign keys because that would force us to delete log entries +-- when objects are removed from the db, or disallow the deletion of objects +-- Thus, we need to make sure we references database objects by a non-modifiable primary key, which +-- we do not actually use as foreign key +-- +-- The problem here is, that the "demons" relation doesn't have such a column - +-- position and name (while both unique and even primary in "name"'s case') are modifiable +-- This is the reason we do not allow the deletion of demons as of right now +-- +-- (Once the new backend is running and we no longer need to be backwards compatible with the +-- python codebase, we can ofc modify the demons table) + +-- Global fallback table in case no temporary 'active_user' table was created in the current session +CREATE TABLE active_user (id INTEGER PRIMARY KEY); -- primary key required because diesel migrations break otherwise + +-- TODO: generate a dummy member with ID 0 +INSERT INTO active_user VALUES (0); + +CREATE TABLE audit_log2 ( + time TIMESTAMP WITHOUT TIME ZONE DEFAULT (NOW() AT TIME ZONE 'utc') NOT NULL, + audit_id SERIAL PRIMARY KEY NOT NULL, + userid INTEGER NOT NULL -- REFERENCES members(member_id) +); + +CREATE TABLE demon_additions ( + -- Note that this currently forces us to not delete demons + name CITEXT REFERENCES demons(name) ON DELETE RESTRICT ON UPDATE CASCADE NOT NULL +) INHERITS (audit_log2); + +CREATE FUNCTION audit_demon_addition() RETURNS trigger AS $demon_add_trigger$ + BEGIN + INSERT INTO demon_additions (userid, name) (SELECT id, NEW.name FROM active_user LIMIT 1); + RETURN NEW; + END; +$demon_add_trigger$ LANGUAGE plpgsql; + +CREATE TRIGGER demon_addition_trigger AFTER INSERT ON demons FOR EACH ROW EXECUTE PROCEDURE audit_demon_addition(); + +CREATE TABLE demon_modifications ( + -- Column that keeps track of the demon that was modified + -- Note that this currently forces us to not delete demons + demon CITEXT REFERENCES demons(name) ON DELETE RESTRICT ON UPDATE CASCADE NOT NULL, + + -- Column that keeps track of changes to the demon's name + name CITEXT NULL, + position SMALLINT NULL, + requirement SMALLINT NULL, + video VARCHAR(200) NULL, + verifier INT NULL, + publisher INT NULL +) INHERITS (audit_log2); + +CREATE FUNCTION audit_demon_modification() RETURNS trigger AS $demon_modification_trigger$ + DECLARE + name_change CITEXT; + position_change SMALLINT; + requirement_change SMALLINT; + video_change VARCHAR(200); + verifier_change SMALLINT; + publisher_change SMALLINT; + BEGIN + IF (OLD.name <> NEW.name) THEN + name_change = OLD.name; + END IF; + + IF (OLD.position <> NEW.position) THEN + position_change = OLD.position; + END IF; + + IF (OLD.requirement <> NEW.requirement) THEN + requirement_change = OLD.requirement; + END IF; + + IF (OLD.video <> NEW.video) THEN + video_change = OLD.video; + END IF; + + IF (OLD.verifier <> NEW.verifier) THEN + verifier_change = OLD.verifier; + END IF; + + IF (OLD.publisher <> NEW.publisher) THEN + publisher_change = OLD.publisher; + END IF; + + INSERT INTO demon_modifications (userid, demon, name, position, requirement, video, verifier, publisher) + (SELECT id, NEW.name, name_change, position_change, requirement_change, video_change, verifier_change, publisher_change + FROM active_user LIMIT 1); + + RETURN NEW; + END; +$demon_modification_trigger$ LANGUAGE plpgsql; + +CREATE TRIGGER demon_modification_trigger AFTER UPDATE ON demons FOR EACH ROW EXECUTE PROCEDURE audit_demon_modification(); + +CREATE TABLE record_additions ( + id INTEGER NOT NULL -- REFERENCES records(id) +) INHERITS (audit_log2); + +CREATE FUNCTION audit_record_addition() RETURNS trigger AS $record_add_trigger$ + BEGIN + INSERT INTO record_additions (userid, id) (SELECT id, NEW.id FROM active_user LIMIT 1); + RETURN NEW; + END; +$record_add_trigger$ LANGUAGE plpgsql; + +CREATE TRIGGER record_addition_trigger AFTER INSERT ON records FOR EACH ROW EXECUTE PROCEDURE audit_record_addition(); + +CREATE TABLE record_modifications ( + id INTEGER NOT NULL, -- REFERENCES records(id) + + progress SMALLINT NULL, + video VARCHAR(200) NULL, + status_ RECORD_STATUS NULL, + player INT NULL, -- REFERENCES players(id) + demon CITEXT NULL -- REFERENCES demons(name) +) INHERITS (audit_log2); + +CREATE FUNCTION audit_record_modification() RETURNS trigger AS $record_modification_trigger$ + DECLARE + progress_change SMALLINT; + video_change VARCHAR(200); + status_change RECORD_STATUS; + player_change INT; + demon_change CITEXT; + BEGIN + if (OLD.progress <> NEW.progress) THEN + progress_change = OLD.progress; + END IF; + + IF (OLD.video <> NEW.video) THEN + video_change = OLD.video; + END IF; + + IF (OLD.status_ <> NEW.status_) THEN + status_change = OLD.status_; + END IF; + + IF (OLD.player <> NEW.player) THEN + player_change = OLD.player; + END IF; + + IF (OLD.demon <> NEW.demon) THEN + demon_change = OLD.demon; + END IF; + + INSERT INTO record_modifications (userid, id, progress, video, status_, player, demon) + (SELECT id, NEW.id, progress_change, video_change, status_change, player_change, demon_change + FROM active_user LIMIT 1); + + RETURN NEW; + END; +$record_modification_trigger$ LANGUAGE plpgsql; + +CREATE TRIGGER record_modification_trigger AFTER UPDATE ON records FOR EACH ROW EXECUTE PROCEDURE audit_record_modification(); + +-- Before deletion we add a `record_modifications` entry that's a copy of the record directly before deletion +CREATE TABLE record_deletions ( + id INTEGER NOT NULL -- REFERENCES records(id) +) INHERITS (audit_log2); + +CREATE FUNCTION audit_record_deletion() RETURNS trigger AS $record_deletion_trigger$ + BEGIN + INSERT INTO record_modifications (userid, id, progress, video, status_, player, demon) + (SELECT id, OLD.id, OLD.progress, OLD.video, OLD.status_, OLD.player, OLD.demon + FROM active_user LIMIT 1); + + INSERT INTO record_deletions (userid, id) + (SELECT id, OLD.id FROM active_user LIMIT 1); + + RETURN NULL; + END; +$record_deletion_trigger$ LANGUAGE plpgsql; + +CREATE TRIGGER record_deletion_trigger AFTER DELETE ON records FOR EACH ROW EXECUTE PROCEDURE audit_record_deletion(); + +CREATE TABLE player_additions ( + id INTEGER NOT NULL -- REFERENCES players(id) +) INHERITS (audit_log2); + +CREATE FUNCTION audit_player_addition() RETURNS trigger AS $record_addition_trigger$ + BEGIN + INSERT INTO player_additions(userid, id) + (SELECT id, NEW.id FROM active_user LIMIT 1); + + RETURN NEW; + END; +$record_addition_trigger$ LANGUAGE plpgsql; + +CREATE TRIGGER player_addition_trigger AFTER INSERT ON players FOR EACH ROW EXECUTE PROCEDURE audit_player_addition(); + +CREATE TABLE player_modifications ( + id INTEGER NOT NULL, -- REFERENCES players(id) + + name CITEXT NULL, + banned BOOLEAN NULL +) INHERITS (audit_log2); + +CREATE FUNCTION audit_player_modification() RETURNS trigger as $player_modification_trigger$ + DECLARE + name_change CITEXT; + banned_change BOOLEAN; + BEGIN + IF (OLD.name <> NEW.name) THEN + name_change = OLD.name; + END IF; + + IF (OLD.banned <> NEW.banned) THEN + banned_change = OLD.banned; + END IF; + + INSERT INTO player_modifications (userid, id, name, banned) + (SELECT id, NEW.id, name_change, banned_change FROM active_user LIMIT 1); + + RETURN NEW; + END; +$player_modification_trigger$ LANGUAGE plpgsql; + +CREATE TRIGGER player_modification_trigger AFTER UPDATE ON players FOR EACH ROW EXECUTE PROCEDURE audit_player_modification(); + +-- See handling of record_deletions +CREATE TABLE player_deletions ( + id INTEGER NOT NULL -- REFERENCES players(id) +) INHERITS (audit_log2); + +CREATE FUNCTION audit_player_deletion() RETURNS trigger AS $player_deletion_trigger$ + BEGIN + INSERT INTO player_modifications (userid, id, name, banned) + (SELECT id, OLD.id, OLD.name, OLD.banned + FROM active_user LIMIT 1); + + INSERT INTO player_deletions (userid, id) + (SELECT id, OLD.id FROM active_user LIMIT 1); + + RETURN NULL; + END; +$player_deletion_trigger$ LANGUAGE plpgsql; + +CREATE TRIGGER player_deletion_trigger AFTER DELETE ON players FOR EACH ROW EXECUTE PROCEDURE audit_player_deletion(); + +CREATE TABLE creator_additions ( + creator INTEGER NOT NULL, + demon CITEXT NOT NULL +) INHERITS (audit_log2); + +CREATE FUNCTION audit_creator_addition() RETURNS trigger AS $audit_creator_addition$ + BEGIN + INSERT INTO creator_additions (userid, creator, demon) + (SELECT id, NEW.creator, NEW.demon + FROM active_user LIMIT 1); + + RETURN NEW; + END; +$audit_creator_addition$ LANGUAGE plpgsql; + +CREATE TRIGGER creator_addition_trigger AFTER INSERT ON creators FOR EACH ROW EXECUTE PROCEDURE audit_creator_addition(); + +CREATE TABLE creator_deletions( + creator INTEGER NOT NULL, + demon CITEXT NOT NULL +) INHERITS (audit_log2); + +CREATE FUNCTION audit_creator_deletion() RETURNS trigger AS $creator_deletion_trigger$ + BEGIN + INSERT INTO creator_deletions (userid, creator, demon) + (SELECT id, OLD.creator, OLD.demon + FROM active_user LIMIT 1); + + RETURN NULL; + END; +$creator_deletion_trigger$ LANGUAGE plpgsql; + +CREATE TRIGGER creator_deletion_trigger AFTER DELETE ON creators FOR EACH ROW EXECUTE PROCEDURE audit_creator_deletion(); + +CREATE TABLE submitter_modifications ( + submitter INTEGER NOT NULL, + + banned BOOLEAN NULL +) INHERITS (audit_log2); + +CREATE FUNCTION audit_submitter_modification() RETURNS trigger as $submitter_modifications_trigger$ + DECLARE + banned_change BOOLEAN; + BEGIN + IF (OLD.banned <> NEW.banned) THEN + banned_change = OLD.banned; + END IF; + + INSERT INTO submitter_modifications (userid, submitter, banned) + (SELECT id, NEW.submitter_id, banned_change FROM active_user LIMIT 1); + + RETURN NEW; + END; +$submitter_modifications_trigger$ LANGUAGE plpgsql; + +CREATE TRIGGER submitter_modification_trigger AFTER UPDATE ON submitters FOR EACH ROW EXECUTE PROCEDURE audit_submitter_modification(); + +CREATE TABLE user_additions ( + id INTEGER NOT NULL -- REFERENCES members(member_id) +) INHERITS (audit_log2); + +CREATE FUNCTION audit_user_addition() RETURNS trigger AS $audit_user_addition$ + BEGIN + -- cannot be logged in during registration + INSERT INTO user_additions (userid, id) VALUES (0, NEW.member_id); + + RETURN NEW; + END; +$audit_user_addition$ LANGUAGE plpgsql; + +CREATE TRIGGER user_addition_trigger AFTER INSERT ON members FOR EACH ROW EXECUTE PROCEDURE audit_user_addition(); + +CREATE TABLE user_modifications ( + id INTEGER NOT NULL, -- REFERENCES members(member_id) + + -- fields updatable by user themself + display_name CITEXT NULL, + youtube_channel CITEXT NULL, + + -- fields updatable by staff + permissions BIT(16) NULL +) INHERITS (audit_log2); + +CREATE FUNCTION audit_user_modification() RETURNS trigger as $user_modification_trigger$ + DECLARE + display_name_change CITEXT; + youtube_channel_change BOOLEAN; + permissions_change BIT(16); + BEGIN + IF (OLD.display_name <> NEW.display_name) THEN + display_name_change = OLD.display_name; + END IF; + + IF (OLD.youtube_channel <> NEW.youtube_channel) THEN + youtube_channel_change = OLD.youtube_channel; + END IF; + + IF (OLD.permissions <> NEW.permissions) THEN + permissions_change = OLD.permissions; + END IF; + + INSERT INTO user_modifications (userid, id, display_name, youtube_channel, permissions) + (SELECT id, NEW.member_id, display_name_change, youtube_channel_change, permissions_change FROM active_user LIMIT 1); + + RETURN NEW; + END; +$user_modification_trigger$ LANGUAGE plpgsql; + +CREATE TRIGGER user_modification_trigger AFTER UPDATE ON members FOR EACH ROW EXECUTE PROCEDURE audit_user_modification(); + +CREATE TABLE user_deletions ( + id INTEGER NOT NULL -- REFERENCES members(member_id) +) INHERITS (audit_log2); + +CREATE FUNCTION audit_user_deletion() RETURNS trigger AS $user_deletion_trigger$ + BEGIN + INSERT INTO user_modifications (userid, id, display_name, youtube_channel, permissions) + (SELECT id, OLD.member_id, OLD.display_name, OLD.youtube_channel, OLD.permissions + FROM active_user LIMIT 1); + + INSERT INTO user_deletions (userid, id) + (SELECT id, OLD.member_id FROM active_user LIMIT 1); + + RETURN NULL; + END; +$user_deletion_trigger$ LANGUAGE plpgsql; + +CREATE TRIGGER user_deletion_trigger AFTER DELETE ON members FOR EACH ROW EXECUTE PROCEDURE audit_user_deletion(); + */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20190222212203_list_functions.down.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20190222212203_list_functions.down.sql new file mode 100644 index 000000000..93978e767 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20190222212203_list_functions.down.sql @@ -0,0 +1,2 @@ +/* -- This file should undo anything in `up.sql` +DROP FUNCTION record_score(progress FLOAT, demon FLOAT, list_size FLOAT); */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20190222212203_list_functions.up.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20190222212203_list_functions.up.sql new file mode 100644 index 000000000..d456f83a7 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20190222212203_list_functions.up.sql @@ -0,0 +1,7 @@ +/* -- Your SQL goes here + +CREATE FUNCTION record_score(progress FLOAT, demon FLOAT, list_size FLOAT) RETURNS FLOAT AS +$record_score$ + SELECT (progress / 100.0) ^ demon * list_size / (1.0 + (list_size - 1.0) * EXP(-4.0 * (list_size - demon) * LN(list_size - 1.0)/(3.0 * list_size))); +$record_score$ +LANGUAGE SQL IMMUTABLE; */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20190311185303_remove_old_columns.down.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20190311185303_remove_old_columns.down.sql new file mode 100644 index 000000000..db1af47e4 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20190311185303_remove_old_columns.down.sql @@ -0,0 +1,8 @@ +/* -- This file should undo anything in `up.sql` +ALTER TABLE demons ADD COLUMN notes TEXT NULL; +ALTER TABLE demons ADD COLUMN description TEXT NULL; + +ALTER TABLE members ADD COLUMN password_salt BYTEA NOT NULL DEFAULT E'\\000'; + +ALTER TABLE members ALTER COLUMN display_name TYPE CITEXT; +ALTER TABLE members ALTER COLUMN name TYPE CITEXT; */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20190311185303_remove_old_columns.up.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20190311185303_remove_old_columns.up.sql new file mode 100644 index 000000000..2add9e7b0 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20190311185303_remove_old_columns.up.sql @@ -0,0 +1,8 @@ +/* -- Your SQL goes here +ALTER TABLE demons DROP COLUMN notes; +ALTER TABLE demons DROP COLUMN description; + +ALTER TABLE members DROP COLUMN password_salt; + +ALTER TABLE members ALTER COLUMN display_name TYPE TEXT; +ALTER TABLE members ALTER COLUMN name TYPE TEXT; */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20190311200729_hash_as_string.down.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20190311200729_hash_as_string.down.sql new file mode 100644 index 000000000..44e9a7f4f --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20190311200729_hash_as_string.down.sql @@ -0,0 +1,4 @@ +/* -- This file should undo anything in `up.sql` + +alter table members alter column password_hash type bytea using decode(password_hash, 'escape'); + */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20190311200729_hash_as_string.up.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20190311200729_hash_as_string.up.sql new file mode 100644 index 000000000..3c9ee0323 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20190311200729_hash_as_string.up.sql @@ -0,0 +1,4 @@ +/* -- Your SQL goes here + +alter table members alter column password_hash type text using encode(password_hash, 'escape'); + */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20190312131253_nationality.down.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20190312131253_nationality.down.sql new file mode 100644 index 000000000..bcc191868 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20190312131253_nationality.down.sql @@ -0,0 +1,7 @@ +/* -- This file should undo anything in `up.sql` + +ALTER TABLE players DROP COLUMN nationality; +ALTER TABLE members DROP COLUMN nationality; + +DROP TABLE nationalities; + */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20190312131253_nationality.up.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20190312131253_nationality.up.sql new file mode 100644 index 000000000..2fc8c9919 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20190312131253_nationality.up.sql @@ -0,0 +1,262 @@ +/* -- Your SQL goes here + +CREATE TABLE nationalities ( + iso_country_code VARCHAR(2) PRIMARY KEY, + nation CITEXT UNIQUE NOT NULL, + CHECK (LENGTH(iso_country_code) = 2) +); + +INSERT INTO nationalities (nation, iso_country_code) +VALUES ('Afghanistan','AF'), + ('Åland Islands','AX'), + ('Albania','AL'), + ('Algeria','DZ'), + ('American Samoa','AS'), + ('Andorra','AD'), + ('Angola','AO'), + ('Anguilla','AI'), + ('Antarctica','AQ'), + ('Antigua and Barbuda','AG'), + ('Argentina','AR'), + ('Armenia','AM'), + ('Aruba','AW'), + ('Australia','AU'), + ('Austria','AT'), + ('Azerbaijan','AZ'), + ('Bahamas','BS'), + ('Bahrain','BH'), + ('Bangladesh','BD'), + ('Barbados','BB'), + ('Belarus','BY'), + ('Belgium','BE'), + ('Belize','BZ'), + ('Benin','BJ'), + ('Bermuda','BM'), + ('Bhutan','BT'), + ('Bolivia, Plurinational State of','BO'), + ('Bonaire, Sint Eustatius and Saba','BQ'), + ('Bosnia and Herzegovina','BA'), + ('Botswana','BW'), + ('Bouvet Island','BV'), + ('Brazil','BR'), + ('British Indian Ocean Territory','IO'), + ('Brunei Darussalam','BN'), + ('Bulgaria','BG'), + ('Burkina Faso','BF'), + ('Burundi','BI'), + ('Cambodia','KH'), + ('Cameroon','CM'), + ('Canada','CA'), + ('Cape Verde','CV'), + ('Cayman Islands','KY'), + ('Central African Republic','CF'), + ('Chad','TD'), + ('Chile','CL'), + ('China','CN'), + ('Christmas Island','CX'), + ('Cocos (Keeling) Islands','CC'), + ('Colombia','CO'), + ('Comoros','KM'), + ('Congo','CG'), + ('Congo, the Democratic Republic of the','CD'), + ('Cook Islands','CK'), + ('Costa Rica','CR'), + ('Côte d''Ivoire','CI'), + ('Croatia','HR'), + ('Cuba','CU'), + ('Curaçao','CW'), + ('Cyprus','CY'), + ('Czech Republic','CZ'), + ('Denmark','DK'), + ('Djibouti','DJ'), + ('Dominica','DM'), + ('Dominican Republic','DO'), + ('Ecuador','EC'), + ('Egypt','EG'), + ('El Salvador','SV'), + ('Equatorial Guinea','GQ'), + ('Eritrea','ER'), + ('Estonia','EE'), + ('Ethiopia','ET'), + ('Falkland Islands (Malvinas)','FK'), + ('Faroe Islands','FO'), + ('Fiji','FJ'), + ('Finland','FI'), + ('France','FR'), + ('French Guiana','GF'), + ('French Polynesia','PF'), + ('French Southern Territories','TF'), + ('Gabon','GA'), + ('Gambia','GM'), + ('Georgia','GE'), + ('Germany','DE'), + ('Ghana','GH'), + ('Gibraltar','GI'), + ('Greece','GR'), + ('Greenland','GL'), + ('Grenada','GD'), + ('Guadeloupe','GP'), + ('Guam','GU'), + ('Guatemala','GT'), + ('Guernsey','GG'), + ('Guinea','GN'), + ('Guinea-Bissau','GW'), + ('Guyana','GY'), + ('Haiti','HT'), + ('Heard Island and McDonald Islands','HM'), + ('Holy See (Vatican City State)','VA'), + ('Honduras','HN'), + ('Hong Kong','HK'), + ('Hungary','HU'), + ('Iceland','IS'), + ('India','IN'), + ('Indonesia','ID'), + ('Iran, Islamic Republic of','IR'), + ('Iraq','IQ'), + ('Ireland','IE'), + ('Isle of Man','IM'), + ('Israel','IL'), + ('Italy','IT'), + ('Jamaica','JM'), + ('Japan','JP'), + ('Jersey','JE'), + ('Jordan','JO'), + ('Kazakhstan','KZ'), + ('Kenya','KE'), + ('Kiribati','KI'), + ('Korea, Democratic People''s Republic of','KP'), + ('Korea, Republic of','KR'), + ('Kuwait','KW'), + ('Kyrgyzstan','KG'), + ('Lao People''s Democratic Republic','LA'), + ('Latvia','LV'), + ('Lebanon','LB'), + ('Lesotho','LS'), + ('Liberia','LR'), + ('Libya','LY'), + ('Liechtenstein','LI'), + ('Lithuania','LT'), + ('Luxembourg','LU'), + ('Macao','MO'), + ('Macedonia, the Former Yugoslav Republic of','MK'), + ('Madagascar','MG'), + ('Malawi','MW'), + ('Malaysia','MY'), + ('Maldives','MV'), + ('Mali','ML'), + ('Malta','MT'), + ('Marshall Islands','MH'), + ('Martinique','MQ'), + ('Mauritania','MR'), + ('Mauritius','MU'), + ('Mayotte','YT'), + ('Mexico','MX'), + ('Micronesia, Federated States of','FM'), + ('Moldova, Republic of','MD'), + ('Monaco','MC'), + ('Mongolia','MN'), + ('Montenegro','ME'), + ('Montserrat','MS'), + ('Morocco','MA'), + ('Mozambique','MZ'), + ('Myanmar','MM'), + ('Namibia','NA'), + ('Nauru','NR'), + ('Nepal','NP'), + ('Netherlands','NL'), + ('New Caledonia','NC'), + ('New Zealand','NZ'), + ('Nicaragua','NI'), + ('Niger','NE'), + ('Nigeria','NG'), + ('Niue','NU'), + ('Norfolk Island','NF'), + ('Northern Mariana Islands','MP'), + ('Norway','NO'), + ('Oman','OM'), + ('Pakistan','PK'), + ('Palau','PW'), + ('Palestine, State of','PS'), + ('Panama','PA'), + ('Papua New Guinea','PG'), + ('Paraguay','PY'), + ('Peru','PE'), + ('Philippines','PH'), + ('Pitcairn','PN'), + ('Poland','PL'), + ('Portugal','PT'), + ('Puerto Rico','PR'), + ('Qatar','QA'), + ('Réunion','RE'), + ('Romania','RO'), + ('Russian Federation','RU'), + ('Rwanda','RW'), + ('Saint Barthélemy','BL'), + ('Saint Helena, Ascension and Tristan da Cunha','SH'), + ('Saint Kitts and Nevis','KN'), + ('Saint Lucia','LC'), + ('Saint Martin (French part)','MF'), + ('Saint Pierre and Miquelon','PM'), + ('Saint Vincent and the Grenadines','VC'), + ('Samoa','WS'), + ('San Marino','SM'), + ('Sao Tome and Principe','ST'), + ('Saudi Arabia','SA'), + ('Senegal','SN'), + ('Serbia','RS'), + ('Seychelles','SC'), + ('Sierra Leone','SL'), + ('Singapore','SG'), + ('Sint Maarten (Dutch part)','SX'), + ('Slovakia','SK'), + ('Slovenia','SI'), + ('Solomon Islands','SB'), + ('Somalia','SO'), + ('South Africa','ZA'), + ('South Georgia and the South Sandwich Islands','GS'), + ('South Sudan','SS'), + ('Spain','ES'), + ('Sri Lanka','LK'), + ('Sudan','SD'), + ('Suriname','SR'), + ('Svalbard and Jan Mayen','SJ'), + ('Swaziland','SZ'), + ('Sweden','SE'), + ('Switzerland','CH'), + ('Syrian Arab Republic','SY'), + ('Taiwan, Province of China','TW'), + ('Tajikistan','TJ'), + ('Tanzania, United Republic of','TZ'), + ('Thailand','TH'), + ('Timor-Leste','TL'), + ('Togo','TG'), + ('Tokelau','TK'), + ('Tonga','TO'), + ('Trinidad and Tobago','TT'), + ('Tunisia','TN'), + ('Turkey','TR'), + ('Turkmenistan','TM'), + ('Turks and Caicos Islands','TC'), + ('Tuvalu','TV'), + ('Uganda','UG'), + ('Ukraine','UA'), + ('United Arab Emirates','AE'), + ('United Kingdom','GB'), + ('United States','US'), + ('United States Minor Outlying Islands','UM'), + ('Uruguay','UY'), + ('Uzbekistan','UZ'), + ('Vanuatu','VU'), + ('Venezuela, Bolivarian Republic of','VE'), + ('Viet Nam','VN'), + ('Virgin Islands, British','VG'), + ('Virgin Islands, U.S.','VI'), + ('Wallis and Futuna','WF'), + ('Western Sahara','EH'), + ('Yemen','YE'), + ('Zambia','ZM'), + ('Zimbabwe','ZW'); + +ALTER TABLE players ADD COLUMN nationality VARCHAR(2) NULL DEFAULT NULL REFERENCES nationalities(iso_country_code); +ALTER TABLE members ADD COLUMN nationality VARCHAR(2) NULL DEFAULT NULL REFERENCES nationalities(iso_country_code); + */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20190314134153_score_view.down.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20190314134153_score_view.down.sql new file mode 100644 index 000000000..9583f83c5 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20190314134153_score_view.down.sql @@ -0,0 +1,3 @@ +/* -- This file should undo anything in `up.sql` + +DROP VIEW players_with_score; */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20190314134153_score_view.up.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20190314134153_score_view.up.sql new file mode 100644 index 000000000..6a8375b33 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20190314134153_score_view.up.sql @@ -0,0 +1,53 @@ +/* -- Your SQL goes here + + +-- I call this query Frank +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 + FROM + ( + SELECT pseudo_records.player, + SUM(record_score(pseudo_records.progress::FLOAT, pseudo_records.position::FLOAT, 100::FLOAT)) as total_score + FROM ( + SELECT player, + progress, + demons.position + FROM records + INNER JOIN demons + ON demons.name = demon + WHERE demons.position <= 100 AND status_ = 'APPROVED' + + UNION + + SELECT verifier as player, + CASE WHEN demons.position > 100 THEN 0.0::FLOAT ELSE 100.0::FLOAT END as progress, + position as position + FROM demons + + UNION + + SELECT publisher as player, + 0.0::FLOAT as progress, + position as position + FROM demons + + UNION + + SELECT creator as player, + 0.0::FLOAT as progress, + 1.0::FLOAT as position -- doesn't matter + 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; */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20190708102450_formula_update.down.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20190708102450_formula_update.down.sql new file mode 100644 index 000000000..6873c1455 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20190708102450_formula_update.down.sql @@ -0,0 +1,63 @@ +/* -- This file should undo anything in `up.sql` + +-- Your SQL goes here + +DROP VIEW players_with_score; +DROP FUNCTION record_score(FLOAT, FLOAT, FLOAT, FLOAT); + + +CREATE FUNCTION record_score(progress FLOAT, demon FLOAT, list_size FLOAT) RETURNS FLOAT AS +$record_score$ + SELECT (progress / 100.0) ^ demon * list_size / (1.0 + (list_size - 1.0) * EXP(-4.0 * (list_size - demon) * LN(list_size - 1.0)/(3.0 * list_size))); +$record_score$ +LANGUAGE SQL IMMUTABLE; + +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 + FROM + ( + SELECT pseudo_records.player, + SUM(record_score(pseudo_records.progress::FLOAT, pseudo_records.position::FLOAT, 100::FLOAT)) as total_score + FROM ( + SELECT player, + progress, + demons.position + FROM records + INNER JOIN demons + ON demons.name = demon + WHERE demons.position <= 100 AND status_ = 'APPROVED' + + UNION + + SELECT verifier as player, + CASE WHEN demons.position > 100 THEN 0.0::FLOAT ELSE 100.0::FLOAT END as progress, + position as position + FROM demons + + UNION + + SELECT publisher as player, + 0.0::FLOAT as progress, + position as position + FROM demons + + UNION + + SELECT creator as player, + 0.0::FLOAT as progress, + 1.0::FLOAT as position -- doesn't matter + 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; */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20190708102450_formula_update.up.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20190708102450_formula_update.up.sql new file mode 100644 index 000000000..c1e20a746 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20190708102450_formula_update.up.sql @@ -0,0 +1,72 @@ +/* -- Your SQL goes here + +DROP VIEW players_with_score; +DROP FUNCTION record_score(FLOAT, FLOAT, FLOAT); + + +CREATE FUNCTION record_score(progress FLOAT, demon FLOAT, list_size FLOAT, requirement FLOAT) RETURNS FLOAT AS +$record_score$ + SELECT CASE + WHEN progress = 100 THEN + 150.0 * EXP((1.0 - demon) * LN(1.0 / 30.0) / (-149.0)) + WHEN progress < requirement THEN + 0.0 + ELSE + 150.0 * EXP((1.0 - demon) * LN(1.0 / 30.0) / (-149.0)) * (0.25 * (progress - requirement) / (100 - requirement) + 0.25) + END; +$record_score$ +LANGUAGE SQL IMMUTABLE; + +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 + 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, + demons.position, + demons.requirement + FROM records + INNER JOIN demons + ON demons.name = demon + WHERE demons.position <= 150 AND status_ = 'APPROVED' + + UNION + + SELECT verifier as player, + CASE WHEN demons.position > 150 THEN 0.0::FLOAT ELSE 100.0::FLOAT END as progress, + position as position, + 100.0::FLOAT + FROM demons + + UNION + + SELECT publisher as player, + 0.0::FLOAT as progress, + position as 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; */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20191002080414_fix_database_design.down.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20191002080414_fix_database_design.down.sql new file mode 100644 index 000000000..5e06f53b7 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20191002080414_fix_database_design.down.sql @@ -0,0 +1,16 @@ +/* -- This file should undo anything in `up.sql` +DROP VIEW demons_pv; +DROP VIEW demons_p; +DROP VIEW records_pds; +DROP VIEW records_pd; +DROP VIEW records_p; +DROP VIEW records_d; +DROP VIEW players_n; + +CREATE OR REPLACE VIEW demon_verifier_publisher_join AS +SELECT p1.name AS vname, p1.id AS vid, p1.banned AS vbanned, p2.name AS pname, p2.id AS pid, p2.banned AS pbanned +FROM demons +INNER JOIN players AS p1 +ON demons.verifier = p1.id +INNER JOIN players AS p2 +ON demons.publisher = p2.id */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20191002080414_fix_database_design.up.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20191002080414_fix_database_design.up.sql new file mode 100644 index 000000000..a10e452b3 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20191002080414_fix_database_design.up.sql @@ -0,0 +1,63 @@ +/* -- Your SQL goes here + +CREATE VIEW demons_pv AS -- demons with publisher and verifier + SELECT demons.position, demons.name, demons.requirement, demons.video, + publishers.id AS publisher_id, publishers.name AS publisher_name, publishers.banned AS publisher_banned, + verifiers.id AS verifier_id, verifiers.name AS verifier_name, verifiers.banned AS verifier_banned + FROM demons + INNER JOIN players AS verifiers + ON verifiers.id = demons.verifier + INNER JOIN players AS publishers + ON publishers.id = demons.publisher; + +CREATE VIEW demons_p AS -- demons with publisher + SELECT demons.position, demons.name, demons.video, + publishers.id AS publisher_id, publishers.name AS publisher_name, publishers.banned AS publisher_banned + FROM demons + INNER JOIN players AS publishers + ON publishers.id = demons.publisher; + +-- for listed representation +CREATE VIEW records_pd AS -- records with player and demon + SELECT records.id, records.progress, records.video, records.status_, records.submitter AS submitter_id, + players.id AS player_id, players.name AS player_name, players.banned AS player_banned, + demons.name AS demon_name, demons.position + FROM records + INNER JOIN players + ON records.player = players.id + INNER JOIN demons + ON demons.name = records.demon; + +-- for full representation +CREATE VIEW records_pds AS -- records with player, demon and submitter + SELECT records_pd.id, records_pd.progress, records_pd.video, records_pd.status_, + records_pd.player_id, records_pd.player_name, records_pd.player_banned, + records_pd.demon_name, records_pd.position, + submitters.submitter_id, submitters.banned AS submitter_banned + FROM records_pd + INNER JOIN submitters + ON records_pd.submitter_id = submitters.submitter_id; + +-- for minimal representation +CREATE VIEW records_p AS -- records with player + SELECT records.id, records.progress, records.video, records.status_, records.demon, + players.id AS player_id, players.name AS player_name, players.banned AS player_banned + FROM records + INNER JOIN players + ON records.player = players.id; + +CREATE VIEW records_d AS -- records with demon + SELECT records.id, records.progress, records.video, records.status_, records.player, + demons.name AS demon_name, demons.position + FROM records + INNER JOIN demons + ON demons.name = records.demon; + +CREATE VIEW players_n AS -- players with nationality + SELECT players.id, players.name, players.banned, + nationalities.iso_country_code, nationalities.nation + FROM players + LEFT OUTER JOIN nationalities + ON players.nationality = nationalities.iso_country_code; + +DROP VIEW IF EXISTS demon_verifier_publisher_join; */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20191004164504_demon_ids.down.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20191004164504_demon_ids.down.sql new file mode 100644 index 000000000..42b9a52c6 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20191004164504_demon_ids.down.sql @@ -0,0 +1 @@ +/* -- This file should undo anything in `up.sql` */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20191004164504_demon_ids.up.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20191004164504_demon_ids.up.sql new file mode 100644 index 000000000..f5b3b609e --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20191004164504_demon_ids.up.sql @@ -0,0 +1,308 @@ +/* -- Your SQL goes here + +-- Create the ID column +ALTER TABLE demons ADD COLUMN id SERIAL; + +-- Fix up the audit logs +-- demon addition logs +ALTER TABLE demon_additions ADD COLUMN id INTEGER; + +UPDATE demon_additions +SET id = demons.id +FROM demons +WHERE demon_additions.name = demons.name; + +ALTER TABLE demon_additions ALTER COLUMN id SET NOT NULL; +ALTER TABLE demon_additions DROP COLUMN name; + +CREATE OR REPLACE FUNCTION audit_demon_addition() RETURNS trigger AS $demon_add_trigger$ + BEGIN + INSERT INTO demon_additions (userid, id) (SELECT id , NEW.id FROM active_user LIMIT 1); + RETURN NEW; + END; +$demon_add_trigger$ LANGUAGE plpgsql; + +-- demon modification logs +ALTER TABLE demon_modifications ADD COLUMN id INTEGER; + +UPDATE demon_modifications +SET id = demons.id +FROM demons +WHERE demon = demons.name; + +ALTER TABLE demon_modifications ALTER COLUMN id SET NOT NULL; +ALTER TABLE demon_modifications DROP COLUMN demon; + +CREATE OR REPLACE FUNCTION audit_demon_modification() RETURNS trigger AS $demon_modification_trigger$ + DECLARE + name_change CITEXT; + position_change SMALLINT; + requirement_change SMALLINT; + video_change VARCHAR(200); + verifier_change SMALLINT; + publisher_change SMALLINT; + BEGIN + IF (OLD.name <> NEW.name) THEN + name_change = OLD.name; + END IF; + + IF (OLD.position <> NEW.position) THEN + position_change = OLD.position; + END IF; + + IF (OLD.requirement <> NEW.requirement) THEN + requirement_change = OLD.requirement; + END IF; + + IF (OLD.video <> NEW.video) THEN + video_change = OLD.video; + END IF; + + IF (OLD.verifier <> NEW.verifier) THEN + verifier_change = OLD.verifier; + END IF; + + IF (OLD.publisher <> NEW.publisher) THEN + publisher_change = OLD.publisher; + END IF; + + INSERT INTO demon_modifications (userid, name, position, requirement, video, verifier, publisher, id) + (SELECT id, name_change, position_change, requirement_change, video_change, verifier_change, publisher_change, NEW.id + FROM active_user LIMIT 1); + + RETURN NEW; + END; +$demon_modification_trigger$ LANGUAGE plpgsql; + +-- record modifications +ALTER TABLE record_modifications RENAME COLUMN demon to demon_name; +ALTER TABLE record_modifications ADD COLUMN demon INTEGER; + +UPDATE record_modifications +SET demon = demons.id +FROM demons +WHERE demon_name = demons.name; + +ALTER TABLE record_modifications DROP COLUMN demon_name; + +CREATE OR REPLACE FUNCTION audit_record_modification() RETURNS trigger AS $record_modification_trigger$ + DECLARE + progress_change SMALLINT; + video_change VARCHAR(200); + status_change RECORD_STATUS; + player_change INT; + demon_change INTEGER; + BEGIN + if (OLD.progress <> NEW.progress) THEN + progress_change = OLD.progress; + END IF; + + IF (OLD.video <> NEW.video) THEN + video_change = OLD.video; + END IF; + + IF (OLD.status_ <> NEW.status_) THEN + status_change = OLD.status_; + END IF; + + IF (OLD.player <> NEW.player) THEN + player_change = OLD.player; + END IF; + + IF (OLD.demon <> NEW.demon) THEN + demon_change = OLD.demon; + END IF; + + INSERT INTO record_modifications (userid, id, progress, video, status_, player, demon) + (SELECT id, NEW.id, progress_change, video_change, status_change, player_change, demon_change + FROM active_user LIMIT 1); + + RETURN NEW; + END; +$record_modification_trigger$ LANGUAGE plpgsql; + +-- creator additions +ALTER TABLE creator_additions RENAME COLUMN demon TO demon_name; +ALTER TABLE creator_additions ADD COLUMN demon INTEGER; + +UPDATE creator_additions +SET demon = demons.id +FROM demons +WHERE demon_name = demons.name; + +ALTER TABLE creator_additions ALTER COLUMN demon SET NOT NULL; +ALTER TABLE creator_additions DROP COLUMN demon_name; + +-- creator deletions +ALTER TABLE creator_deletions RENAME COLUMN demon TO demon_name; +ALTER TABLE creator_deletions ADD COLUMN demon INTEGER; + +UPDATE creator_deletions +SET demon = demons.id +FROM demons +WHERE demon_name = demons.name; + +ALTER TABLE creator_deletions ALTER COLUMN demon SET NOT NULL; +ALTER TABLE creator_deletions DROP COLUMN demon_name; + +-- Fix up references from the creators table +ALTER TABLE creators RENAME COLUMN demon TO demon_name; +ALTER TABLE creators ADD COLUMN demon INTEGER; + +-- No need to temporarily unregister triggers here, there is no 'creator_modifications' audit log +UPDATE creators +SET demon = demons.id +FROM demons +WHERE demon_name = demons.name; + +ALTER TABLE creators DROP CONSTRAINT creators_pkey; +ALTER TABLE creators DROP COLUMN demon_name; + +-- Fix up references from the records table +ALTER TABLE records RENAME COLUMN demon TO demon_name; +ALTER TABLE records ADD COLUMN demon INTEGER; + +-- We need to temporarily unregister the trigger, otherwise this creates a ton of empty audit log entries +DROP TRIGGER record_modification_trigger ON records; + +UPDATE records +SET demon = demons.id +FROM demons +WHERE demon_name = demons.name; + +-- recreate trigger +CREATE TRIGGER record_modification_trigger AFTER UPDATE ON records FOR EACH ROW EXECUTE PROCEDURE audit_record_modification(); + +ALTER TABLE records ALTER COLUMN demon SET NOT NULL; + +-- Fix up the views over the records table +DROP VIEW records_pds; +DROP VIEW records_pd; +CREATE VIEW records_pd AS -- records with player and demon + SELECT records.id, records.progress, records.video, records.status_, records.submitter AS submitter_id, + players.id AS player_id, players.name AS player_name, players.banned AS player_banned, + demons.id AS demon_id, demons.name AS demon_name, demons.position + FROM records + INNER JOIN players + ON records.player = players.id + INNER JOIN demons + ON demons.id = records.demon; + +CREATE VIEW records_pds AS -- records with player, demon and submitter + SELECT records_pd.id, records_pd.progress, records_pd.video, records_pd.status_, + records_pd.player_id, records_pd.player_name, records_pd.player_banned, + records_pd.demon_id, records_pd.demon_name, records_pd.position, + submitters.submitter_id, submitters.banned AS submitter_banned + FROM records_pd + INNER JOIN submitters + ON records_pd.submitter_id = submitters.submitter_id; + +-- for minimal representation +DROP VIEW records_p; +CREATE VIEW records_p AS -- records with player + SELECT records.id, records.progress, records.video, records.status_, records.demon, + players.id AS player_id, players.name AS player_name, players.banned AS player_banned + FROM records + INNER JOIN players + ON records.player = players.id; + +DROP VIEW records_d; +CREATE VIEW records_d AS -- records with demon + SELECT records.id, records.progress, records.video, records.status_, records.player, + demons.id AS demon_id, demons.name AS demon_name, demons.position + FROM records + INNER JOIN demons + ON demons.id = records.demon; + +-- we need to re-declare the player ranking view since it referenced records.demon_name +CREATE OR REPLACE 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 + 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, + requirement + FROM records + INNER JOIN demons + ON demons.id = demon + WHERE demons.position <= 150 AND status_ = 'APPROVED' + + 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; + +-- replace the views of the demons table +DROP VIEW demons_pv; +CREATE VIEW demons_pv AS -- demons with publisher and verifier + SELECT demons.id, demons.position, demons.name, demons.requirement, demons.video, + publishers.id AS publisher_id, publishers.name AS publisher_name, publishers.banned AS publisher_banned, + verifiers.id AS verifier_id, verifiers.name AS verifier_name, verifiers.banned AS verifier_banned + FROM demons + INNER JOIN players AS verifiers + ON verifiers.id = demons.verifier + INNER JOIN players AS publishers + ON publishers.id = demons.publisher; + +DROP VIEW demons_p; +CREATE VIEW demons_p AS -- demons with publisher + SELECT demons.id, demons.position, demons.name, demons.video, + publishers.id AS publisher_id, publishers.name AS publisher_name, publishers.banned AS publisher_banned + FROM demons + INNER JOIN players AS publishers + ON publishers.id = demons.publisher; + +-- Drop the old audit logs since we migrated the data over to the new ones already. Its a waste of time to update the references into the demons table +-- DROP TABLE audit_log; + +-- Drop the old column +ALTER TABLE records DROP COLUMN demon_name; + +-- change primary key on demons relation +ALTER TABLE demons DROP CONSTRAINT demons_pkey; +ALTER TABLE demons ADD PRIMARY KEY (id); + +--set up new column to be foreign key to new primary key +ALTER TABLE creators ADD CONSTRAINT creators_demon_fkey FOREIGN KEY (demon) REFERENCES demons(id); +ALTER TABLE records ADD CONSTRAINT records_demon_fkey FOREIGN KEY (demon) REFERENCES demons(id); + +-- set up the primary key of the creators table again +ALTER TABLE creators ADD CONSTRAINT creators_pkey PRIMARY KEY (demon, creator); + */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20191009080542_record_notes.down.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20191009080542_record_notes.down.sql new file mode 100644 index 000000000..18121d942 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20191009080542_record_notes.down.sql @@ -0,0 +1,14 @@ +/* -- This file should undo anything in `up.sql` + +-- Fix up the views over the records table +DROP VIEW records_pds; +CREATE VIEW records_pds AS -- records with player, demon and submitter + SELECT records_pd.id, records_pd.progress, records_pd.video, records_pd.status_, + records_pd.player_id, records_pd.player_name, records_pd.player_banned, + records_pd.demon_id, records_pd.demon_name, records_pd.position, + submitters.submitter_id, submitters.banned AS submitter_banned + FROM records_pd + INNER JOIN submitters + ON records_pd.submitter_id = submitters.submitter_id; + +ALTER TABLE records DROP COLUMN notes; */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20191009080542_record_notes.up.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20191009080542_record_notes.up.sql new file mode 100644 index 000000000..ec2b885a7 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20191009080542_record_notes.up.sql @@ -0,0 +1,20 @@ +/* -- Your SQL goes here + +ALTER TABLE records ADD COLUMN notes TEXT; + +-- Fix up the views over the records table +DROP VIEW records_pds; + +CREATE VIEW records_pds AS -- records with player, demon and submitter + SELECT records.id, records.progress, records.video, records.status_, records.notes, + players.id AS player_id, players.name AS player_name, players.banned AS player_banned, + demons.id AS demon_id, demons.name AS demon_name, demons.position, + submitters.submitter_id, submitters.banned AS submitter_banned + FROM records + INNER JOIN submitters + ON records.submitter = submitters.submitter_id + INNER JOIN players + ON records.player = players.id + INNER JOIN demons + ON demons.id = records.demon; + */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20200227174850_view_cleanup.down.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20200227174850_view_cleanup.down.sql new file mode 100644 index 000000000..ef8d20078 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20200227174850_view_cleanup.down.sql @@ -0,0 +1,66 @@ +/* -- This file should undo anything in `up.sql` + + +CREATE VIEW records_pds AS -- records with player, demon and submitter + SELECT records.id, records.progress, records.video, records.status_, records.notes, + players.id AS player_id, players.name AS player_name, players.banned AS player_banned, + demons.id AS demon_id, demons.name AS demon_name, demons.position, + submitters.submitter_id, submitters.banned AS submitter_banned + FROM records + INNER JOIN submitters + ON records.submitter = submitters.submitter_id + INNER JOIN players + ON records.player = players.id + INNER JOIN demons + ON demons.id = records.demon; + +CREATE VIEW records_pd AS -- records with player and demon + SELECT records.id, records.progress, records.video, records.status_, records.submitter AS submitter_id, + players.id AS player_id, players.name AS player_name, players.banned AS player_banned, + demons.id AS demon_id, demons.name AS demon_name, demons.position + FROM records + INNER JOIN players + ON records.player = players.id + INNER JOIN demons + ON demons.id = records.demon; + +-- for minimal representation +CREATE VIEW records_p AS -- records with player + SELECT records.id, records.progress, records.video, records.status_, records.demon, + players.id AS player_id, players.name AS player_name, players.banned AS player_banned + FROM records + INNER JOIN players + ON records.player = players.id; + +CREATE VIEW records_d AS -- records with demon + SELECT records.id, records.progress, records.video, records.status_, records.player, + demons.id AS demon_id, demons.name AS demon_name, demons.position + FROM records + INNER JOIN demons + ON demons.id = records.demon; + + +CREATE VIEW demons_pv AS -- demons with publisher and verifier + SELECT demons.id, demons.position, demons.name, demons.requirement, demons.video, + publishers.id AS publisher_id, publishers.name AS publisher_name, publishers.banned AS publisher_banned, + verifiers.id AS verifier_id, verifiers.name AS verifier_name, verifiers.banned AS verifier_banned + FROM demons + INNER JOIN players AS verifiers + ON verifiers.id = demons.verifier + INNER JOIN players AS publishers + ON publishers.id = demons.publisher; + +CREATE VIEW demons_p AS -- demons with publisher + SELECT demons.id, demons.position, demons.name, demons.video, + publishers.id AS publisher_id, publishers.name AS publisher_name, publishers.banned AS publisher_banned + FROM demons + INNER JOIN players AS publishers + ON publishers.id = demons.publisher; + + +CREATE VIEW players_n AS -- players with nationality + SELECT players.id, players.name, players.banned, + nationalities.iso_country_code, nationalities.nation + FROM players + LEFT OUTER JOIN nationalities + ON players.nationality = nationalities.iso_country_code; */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20200227174850_view_cleanup.up.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20200227174850_view_cleanup.up.sql new file mode 100644 index 000000000..1f76f9681 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20200227174850_view_cleanup.up.sql @@ -0,0 +1,9 @@ +/* -- Your SQL goes here + +DROP VIEW players_n; +DROP VIEW records_d; +DROP VIEW records_p; +DROP VIEW records_pd; +DROP VIEW records_pds; +DROP VIEW demons_p; +DROP VIEW demons_pv; */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20200227185921_record_notes.down.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20200227185921_record_notes.down.sql new file mode 100644 index 000000000..c8bfd34d2 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20200227185921_record_notes.down.sql @@ -0,0 +1,24 @@ +/* -- This file should undo anything in `up.sql` + +ALTER TABLE records ADD COLUMN notes TEXT; + +WITH transferred_notes AS ( + SELECT record, STRING_AGG(content, '\n') AS content + FROM record_notes + GROUP BY record +) +UPDATE records +SET notes = transferred_notes.content +FROM transferred_notes +WHERE records.id = transferred_notes.record; + +DROP TABLE record_notes; + +DROP TABLE record_notes_additions; +DROP FUNCTION audit_record_notes_addition() CASCADE; + +DROP TABLE record_notes_modifications; +DROP FUNCTION audit_record_notes_modification() CASCADE; + +DROP TABLE record_notes_deletions; +DROP FUNCTION audit_record_notes_deletion() CASCADE; */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20200227185921_record_notes.up.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20200227185921_record_notes.up.sql new file mode 100644 index 000000000..b8bbddd24 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20200227185921_record_notes.up.sql @@ -0,0 +1,71 @@ +/* -- Your SQL goes here +CREATE TABLE record_notes ( + id SERIAL PRIMARY KEY, + record INTEGER REFERENCES records(id) NOT NULL, + content TEXT NOT NULL +); + +INSERT INTO record_notes (record, content) +SELECT id, notes FROM records WHERE notes IS NOT NULL; + +ALTER TABLE records DROP COLUMN notes; + +-- This part of the migration cannot be undone! +ALTER TYPE record_status ADD VALUE 'UNDER_CONSIDERATION'; + +CREATE TABLE record_notes_additions ( + id INTEGER NOT NULL +) INHERITS (audit_log2); + +CREATE TABLE record_notes_modifications ( + id INTEGER NOT NULL, -- which note was changed? + record INTEGER NULL, + content TEXT NULL +) INHERITS (audit_log2); + +CREATE TABLE record_notes_deletions ( + id INTEGER NOT NULL +) INHERITS (audit_log2); + +CREATE FUNCTION audit_record_notes_addition() RETURNS trigger AS $record_notes_add_trigger$ + BEGIN + INSERT INTO record_notes_additions (userid, id) (SELECT id, NEW.id FROM active_user LIMIT 1); + RETURN NEW; + END; +$record_notes_add_trigger$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION audit_record_notes_modification() RETURNS trigger AS $record_notes_modification_trigger$ + DECLARE + record_change INTEGER; + content_change TEXT; + BEGIN + IF (OLD.record <> NEW.record) THEN + record_change = OLD.record; + END IF; + + IF (OLD.content <> NEW.content) THEN + content_change = OLD.content; + END IF; + + INSERT INTO record_notes_modifications (userid, id, record, content) + (SELECT id, OLD.id, record_change, content_change FROM active_user LIMIT 1); + + RETURN NEW; + END; +$record_notes_modification_trigger$ LANGUAGE plpgsql; + +CREATE FUNCTION audit_record_notes_deletion() RETURNS trigger AS $record_notes_deletion_trigger$ + BEGIN + INSERT INTO record_notes_modifications (userid, id, record, content) + (SELECT id, OLD.id, OLD.record, OLD.content FROM active_user LIMIT 1); + + INSERT INTO record_notes_deletion (userid, id) + (SELECT id, OLD.id FROM active_user LIMIT 1); + + RETURN NEW; + END +$record_notes_deletion_trigger$ LANGUAGE plpgsql; + +CREATE TRIGGER record_note_addition_trigger AFTER INSERT ON record_notes FOR EACH ROW EXECUTE PROCEDURE audit_record_notes_addition(); +CREATE TRIGGER record_note_modification_trigger AFTER UPDATE ON record_notes FOR EACH ROW EXECUTE PROCEDURE audit_record_notes_modification(); +CREATE TRIGGER record_note_deletion_trigger AFTER DELETE ON record_notes FOR EACH ROW EXECUTE PROCEDURE audit_record_notes_modification(); */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20200302225154_cascade_note_deletion.down.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20200302225154_cascade_note_deletion.down.sql new file mode 100644 index 000000000..4ff02d54f --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20200302225154_cascade_note_deletion.down.sql @@ -0,0 +1,7 @@ +/* -- This file should undo anything in `up.sql` + +ALTER TABLE record_notes + DROP CONSTRAINT record_notes_record_fkey, + ADD CONSTRAINT record_notes_record_fkey + FOREIGN KEY (record) + REFERENCES records(id) */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20200302225154_cascade_note_deletion.up.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20200302225154_cascade_note_deletion.up.sql new file mode 100644 index 000000000..18216acc6 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20200302225154_cascade_note_deletion.up.sql @@ -0,0 +1,8 @@ +/* -- Your SQL goes here + +ALTER TABLE record_notes + DROP CONSTRAINT record_notes_record_fkey, + ADD CONSTRAINT record_notes_record_fkey + FOREIGN KEY (record) + REFERENCES records(id) + ON DELETE CASCADE; */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20200321014223_delete_old_procedures.down.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20200321014223_delete_old_procedures.down.sql new file mode 100644 index 000000000..7e3c852cb --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20200321014223_delete_old_procedures.down.sql @@ -0,0 +1,2 @@ +/* -- This file should undo anything in `up.sql` +SELECT 1 */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20200321014223_delete_old_procedures.up.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20200321014223_delete_old_procedures.up.sql new file mode 100644 index 000000000..8768aa405 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20200321014223_delete_old_procedures.up.sql @@ -0,0 +1,7 @@ +/* -- Your SQL goes here +DROP FUNCTION IF EXISTS ban_player; +DROP FUNCTION IF EXISTS remove_submitted_records_on_user_ban; +DROP FUNCTION IF EXISTS ban_user CASCADE; +DROP FUNCTION IF EXISTS remove_invalid_records CASCADE; +DROP FUNCTION IF EXISTS unban_player; +DROP FUNCTION IF EXISTS player_score; */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20200404074854_shadow_ban.down.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20200404074854_shadow_ban.down.sql new file mode 100644 index 000000000..1d0fb85aa --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20200404074854_shadow_ban.down.sql @@ -0,0 +1,4 @@ +/* -- This file should undo anything in `up.sql` + +ALTER TABLE players +DROP COLUMN link_banned; */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20200404074854_shadow_ban.up.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20200404074854_shadow_ban.up.sql new file mode 100644 index 000000000..20256f8e1 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20200404074854_shadow_ban.up.sql @@ -0,0 +1,4 @@ +/* -- Your SQL goes here + +ALTER TABLE players +ADD COLUMN link_banned BOOL DEFAULT FALSE; */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20200515125343_new_formula.down.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20200515125343_new_formula.down.sql new file mode 100644 index 000000000..c07348349 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20200515125343_new_formula.down.sql @@ -0,0 +1,73 @@ +/* -- This file should undo anything in `up.sql` +-- Your SQL goes here + +DROP VIEW players_with_score; +DROP FUNCTION record_score(FLOAT, FLOAT, FLOAT, FLOAT); + + +CREATE FUNCTION record_score(progress FLOAT, demon FLOAT, list_size FLOAT, requirement FLOAT) RETURNS FLOAT AS +$record_score$ + SELECT CASE + WHEN progress = 100 THEN + 150.0 * EXP((1.0 - demon) * LN(1.0 / 30.0) / (-149.0)) + WHEN progress < requirement THEN + 0.0 + ELSE + 150.0 * EXP((1.0 - demon) * LN(1.0 / 30.0) / (-149.0)) * (0.25 * (progress - requirement) / (100 - requirement) + 0.25) + END; +$record_score$ +LANGUAGE SQL IMMUTABLE; + +CREATE OR REPLACE 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 + 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, + requirement + FROM records + INNER JOIN demons + ON demons.id = demon + WHERE demons.position <= 150 AND status_ = 'APPROVED' + + 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; */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20200515125343_new_formula.up.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20200515125343_new_formula.up.sql new file mode 100644 index 000000000..cece1e36f --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20200515125343_new_formula.up.sql @@ -0,0 +1,75 @@ +/* -- Your SQL goes here + +DROP VIEW players_with_score; +DROP FUNCTION record_score(FLOAT, FLOAT, FLOAT, FLOAT); + +CREATE FUNCTION record_score(progress FLOAT, demon FLOAT, list_size FLOAT, requirement FLOAT) RETURNS FLOAT AS +$record_score$ + SELECT CASE + WHEN progress = 100 AND demon <= 10 THEN + 250.0 * EXP((1.0 - demon) * LN(1.0 / 30.0) / (-149.0)) + WHEN progress = 100 THEN + 150.0 * EXP((1.0 - demon) * LN(1.0 / 30.0) / (-149.0)) + WHEN progress < requirement THEN + 0.0 + WHEN demon <= 10 THEN + 250.0 * EXP((1.0 - demon) * LN(1.0 / 30.0) / (-149.0)) * (EXP(LN(46) * (progress - requirement) / (100 - requirement)) + 4) / 100 + ELSE + 150.0 * EXP((1.0 - demon) * LN(1.0 / 30.0) / (-149.0)) * (EXP(LN(46) * (progress - requirement) / (100 - requirement)) + 4) / 100 + END; +$record_score$ +LANGUAGE SQL IMMUTABLE; + +CREATE OR REPLACE 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 + 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, + requirement + FROM records + INNER JOIN demons + ON demons.id = demon + WHERE demons.position <= 150 AND status_ = 'APPROVED' + + 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; */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20200521101927_new_formulaagain.down.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20200521101927_new_formulaagain.down.sql new file mode 100644 index 000000000..6c3793e2d --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20200521101927_new_formulaagain.down.sql @@ -0,0 +1,75 @@ +/* -- This file should undo anything in `up.sql` +DROP VIEW players_with_score; +DROP FUNCTION record_score(FLOAT, FLOAT, FLOAT, FLOAT); + +CREATE FUNCTION record_score(progress FLOAT, demon FLOAT, list_size FLOAT, requirement FLOAT) RETURNS FLOAT AS +$record_score$ + SELECT CASE + WHEN progress = 100 AND demon <= 10 THEN + 250.0 * EXP((1.0 - demon) * LN(1.0 / 30.0) / (-149.0)) + WHEN progress = 100 THEN + 150.0 * EXP((1.0 - demon) * LN(1.0 / 30.0) / (-149.0)) + WHEN progress < requirement THEN + 0.0 + WHEN demon <= 10 THEN + 250.0 * EXP((1.0 - demon) * LN(1.0 / 30.0) / (-149.0)) * (EXP(LN(46) * (progress - requirement) / (100 - requirement)) + 4) / 100 + ELSE + 150.0 * EXP((1.0 - demon) * LN(1.0 / 30.0) / (-149.0)) * (EXP(LN(46) * (progress - requirement) / (100 - requirement)) + 4) / 100 + END; +$record_score$ +LANGUAGE SQL IMMUTABLE; + + +CREATE OR REPLACE 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 + 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, + requirement + FROM records + INNER JOIN demons + ON demons.id = demon + WHERE demons.position <= 150 AND status_ = 'APPROVED' + + 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; */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20200521101927_new_formulaagain.up.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20200521101927_new_formulaagain.up.sql new file mode 100644 index 000000000..bdc1645b0 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20200521101927_new_formulaagain.up.sql @@ -0,0 +1,72 @@ +/* -- Your SQL goes here +DROP VIEW players_with_score; +DROP FUNCTION record_score(FLOAT, FLOAT, FLOAT, FLOAT); + +CREATE FUNCTION record_score(progress FLOAT, demon FLOAT, list_size FLOAT, requirement FLOAT) RETURNS FLOAT AS +$record_score$ + SELECT CASE + WHEN progress = 100 THEN + 150.0 * EXP((1.0 - demon) * LN(1.0 / 30.0) / (-149.0)) + WHEN progress < requirement THEN + 0.0 + ELSE + 150.0 * EXP((1.0 - demon) * LN(1.0 / 30.0) / (-149.0)) * (EXP(LN(5) * (progress - requirement) / (100 - requirement))) / 10 + END; +$record_score$ +LANGUAGE SQL IMMUTABLE; + + +CREATE OR REPLACE 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 + 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, + requirement + FROM records + INNER JOIN demons + ON demons.id = demon + WHERE demons.position <= 150 AND status_ = 'APPROVED' + + 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; + */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20200725072253_fix_smallint.down.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20200725072253_fix_smallint.down.sql new file mode 100644 index 000000000..e62d786f1 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20200725072253_fix_smallint.down.sql @@ -0,0 +1,43 @@ +/* -- This file should undo anything in `up.sql` + + +CREATE OR REPLACE FUNCTION audit_demon_modification() RETURNS trigger AS $demon_modification_trigger$ + DECLARE + name_change CITEXT; + position_change SMALLINT; + requirement_change SMALLINT; + video_change VARCHAR(200); + verifier_change SMALLINT; + publisher_change SMALLINT; + BEGIN + IF (OLD.name <> NEW.name) THEN + name_change = OLD.name; + END IF; + + IF (OLD.position <> NEW.position) THEN + position_change = OLD.position; + END IF; + + IF (OLD.requirement <> NEW.requirement) THEN + requirement_change = OLD.requirement; + END IF; + + IF (OLD.video <> NEW.video) THEN + video_change = OLD.video; + END IF; + + IF (OLD.verifier <> NEW.verifier) THEN + verifier_change = OLD.verifier; + END IF; + + IF (OLD.publisher <> NEW.publisher) THEN + publisher_change = OLD.publisher; + END IF; + + INSERT INTO demon_modifications (userid, name, position, requirement, video, verifier, publisher, id) + (SELECT id, name_change, position_change, requirement_change, video_change, verifier_change, publisher_change, NEW.id + FROM active_user LIMIT 1); + + RETURN NEW; + END; +$demon_modification_trigger$ LANGUAGE plpgsql; */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20200725072253_fix_smallint.up.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20200725072253_fix_smallint.up.sql new file mode 100644 index 000000000..4d062129d --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20200725072253_fix_smallint.up.sql @@ -0,0 +1,43 @@ +/* -- Your SQL goes here + + +CREATE OR REPLACE FUNCTION audit_demon_modification() RETURNS trigger AS $demon_modification_trigger$ + DECLARE + name_change CITEXT; + position_change SMALLINT; + requirement_change SMALLINT; + video_change VARCHAR(200); + verifier_change INT; + publisher_change InT; + BEGIN + IF (OLD.name <> NEW.name) THEN + name_change = OLD.name; + END IF; + + IF (OLD.position <> NEW.position) THEN + position_change = OLD.position; + END IF; + + IF (OLD.requirement <> NEW.requirement) THEN + requirement_change = OLD.requirement; + END IF; + + IF (OLD.video <> NEW.video) THEN + video_change = OLD.video; + END IF; + + IF (OLD.verifier <> NEW.verifier) THEN + verifier_change = OLD.verifier; + END IF; + + IF (OLD.publisher <> NEW.publisher) THEN + publisher_change = OLD.publisher; + END IF; + + INSERT INTO demon_modifications (userid, name, position, requirement, video, verifier, publisher, id) + (SELECT id, name_change, position_change, requirement_change, video_change, verifier_change, publisher_change, NEW.id + FROM active_user LIMIT 1); + + RETURN NEW; + END; +$demon_modification_trigger$ LANGUAGE plpgsql; */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20200813055728_dash_rs.down.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20200813055728_dash_rs.down.sql new file mode 100644 index 000000000..8415cc24a --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20200813055728_dash_rs.down.sql @@ -0,0 +1,19 @@ +/* -- This file should undo anything in `up.sql` + +ALTER TABLE demons DROP COLUMN level_id; + +DROP TABLE gj_creator; +DROP TABLE gj_creator_meta; + +DROP TABLE gj_level_data; +DROP TABLE gj_level_data_meta; + +DROP TABLE gj_level; +DROP TABLE gj_level_meta; +DROP TABLE gj_level_request_results; +DROP TABLE gj_level_request_meta; + +DROP TABLE gj_newgrounds_song; +DROP TABLE gj_newgrounds_song_meta; + +DROP TABLE download_lock; */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20200813055728_dash_rs.up.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20200813055728_dash_rs.up.sql new file mode 100644 index 000000000..87846a78e --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20200813055728_dash_rs.up.sql @@ -0,0 +1,97 @@ +/* -- Your SQL goes here + +CREATE TABLE gj_creator ( + user_id bigint PRIMARY KEY NOT NULL, + name text NOT NULL, + account_id bigint +); + +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 ( + level_id bigint PRIMARY KEY NOT NULL, + level_name text NOT NULL, + description text, + level_version integer NOT NULL, + creator_id bigint NOT NULL, + difficulty smallint NOT NULL, + is_demon boolean not null, + downloads integer NOT NULL, + main_song smallint, + gd_version smallint NOT NULL, + likes integer NOT NULL, + level_length smallint NOT NULL, + stars smallint NOT NULL, + featured integer NOT NULL, + copy_of bigint, + two_player boolean NOT NULL, + custom_song_id bigint, + coin_amount smallint NOT NULL, + coins_verified boolean NOT NULL, + stars_requested smallint, + is_epic boolean NOT NULL, + object_amount integer, + index_46 text, + index_47 text +); + +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 +); + +-- Many-to-many table mapping each level request to the list of levels it returned +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 ( + level_id bigint PRIMARY KEY REFERENCES gj_level(level_id) NOT NULL, + level_data bytea NOT NULL, + level_password integer, + time_since_upload text NOT NULL, + time_since_update text NOT NULL, + index_36 text +); + +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 ( + song_id bigint PRIMARY KEY NOT NULL, + song_name text NOT NULL, + index_3 bigint NOT NULL, + song_artist text NOT NULL, + filesize double precision NOT NULL, + index_6 text, + index_7 text, + index_8 text NOT NULL, + song_link text 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 +); + +ALTER TABLE demons ADD COLUMN level_id INT8 NULL UNIQUE REFERENCES gj_level(level_id); */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20201221091225_no_ext_progress_point.down.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20201221091225_no_ext_progress_point.down.sql new file mode 100644 index 000000000..f0b5eb1d7 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20201221091225_no_ext_progress_point.down.sql @@ -0,0 +1,72 @@ +/* -- Your SQL goes here +DROP VIEW players_with_score; +DROP FUNCTION record_score(FLOAT, FLOAT, FLOAT, FLOAT); + +CREATE FUNCTION record_score(progress FLOAT, demon FLOAT, list_size FLOAT, requirement FLOAT) RETURNS FLOAT AS +$record_score$ +SELECT CASE + WHEN progress = 100 THEN + 150.0 * EXP((1.0 - demon) * LN(1.0 / 30.0) / (-149.0)) + WHEN progress < requirement THEN + 0.0 + ELSE + 150.0 * EXP((1.0 - demon) * LN(1.0 / 30.0) / (-149.0)) * (EXP(LN(5) * (progress - requirement) / (100 - requirement))) / 10 + END; +$record_score$ + LANGUAGE SQL IMMUTABLE; + + +CREATE OR REPLACE 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 +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, + requirement + FROM records + INNER JOIN demons + ON demons.id = demon + WHERE demons.position <= 150 AND status_ = 'APPROVED' + + 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; + */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20201221091225_no_ext_progress_point.up.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20201221091225_no_ext_progress_point.up.sql new file mode 100644 index 000000000..ad3e351ae --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20201221091225_no_ext_progress_point.up.sql @@ -0,0 +1,72 @@ +/* -- Your SQL goes here +DROP VIEW players_with_score; +DROP FUNCTION record_score(FLOAT, FLOAT, FLOAT, FLOAT); + +CREATE FUNCTION record_score(progress FLOAT, demon FLOAT, list_size FLOAT, requirement FLOAT) RETURNS FLOAT AS +$record_score$ +SELECT CASE + WHEN progress = 100 THEN + 150.0 * EXP((1.0 - demon) * LN(1.0 / 30.0) / (-149.0)) + WHEN progress < requirement THEN + 0.0 + ELSE + 150.0 * EXP((1.0 - demon) * LN(1.0 / 30.0) / (-149.0)) * (EXP(LN(5) * (progress - requirement) / (100 - requirement))) / 10 + END; +$record_score$ + LANGUAGE SQL IMMUTABLE; + + +CREATE OR REPLACE 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 +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' + + 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; + */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20210224123230_new_formula_once_again.down.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20210224123230_new_formula_once_again.down.sql new file mode 100644 index 000000000..ad3e351ae --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20210224123230_new_formula_once_again.down.sql @@ -0,0 +1,72 @@ +/* -- Your SQL goes here +DROP VIEW players_with_score; +DROP FUNCTION record_score(FLOAT, FLOAT, FLOAT, FLOAT); + +CREATE FUNCTION record_score(progress FLOAT, demon FLOAT, list_size FLOAT, requirement FLOAT) RETURNS FLOAT AS +$record_score$ +SELECT CASE + WHEN progress = 100 THEN + 150.0 * EXP((1.0 - demon) * LN(1.0 / 30.0) / (-149.0)) + WHEN progress < requirement THEN + 0.0 + ELSE + 150.0 * EXP((1.0 - demon) * LN(1.0 / 30.0) / (-149.0)) * (EXP(LN(5) * (progress - requirement) / (100 - requirement))) / 10 + END; +$record_score$ + LANGUAGE SQL IMMUTABLE; + + +CREATE OR REPLACE 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 +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' + + 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; + */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20210224123230_new_formula_once_again.up.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20210224123230_new_formula_once_again.up.sql new file mode 100644 index 000000000..27adf5fcf --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20210224123230_new_formula_once_again.up.sql @@ -0,0 +1,95 @@ +/* -- Your SQL goes here +DROP VIEW players_with_score; +DROP FUNCTION record_score(FLOAT, FLOAT, FLOAT, FLOAT); + +CREATE FUNCTION record_score(progress FLOAT, demon FLOAT, list_size FLOAT, requirement FLOAT) RETURNS FLOAT AS +$record_score$ +SELECT CASE + WHEN progress = 100 THEN + CASE + + WHEN 125 < demon AND demon <= 150 THEN + 150.0 * EXP(((1.0 - demon) * LN(1.0 / 30.0)) / -149.0) + WHEN 50 < demon AND demon <= 125 THEN + 60 * (EXP(LN(2.333) * ((51.0 - demon) * (LN(30.0) / 99.0)))) + 1.884 + WHEN 20 < demon AND demon <= 50 THEN + -100.0 * (EXP(LN(1.01327) * (demon - 26.489))) + 200.0 + WHEN demon <= 20 THEN + (250 - 100.39) * (EXP(LN(1.168) * (1 - demon))) + 100.39 + + END + + WHEN progress < requirement THEN + 0.0 + ELSE + CASE + + WHEN 125 < demon AND demon <= 150 THEN + 150.0 * EXP(((1.0 - demon) * LN(1.0 / 30.0)) / -149.0) * (EXP(LN(5) * (progress - requirement) / (100 - requirement))) / 10 + WHEN 50 < demon AND demon <= 125 THEN + (60 * (EXP(LN(2.333) * ((51.0 - demon) * (LN(30.0) / 99.0)))) + 1.884) * (EXP(LN(5) * (progress - requirement) / (100 - requirement))) / 10 + WHEN 20 < demon AND demon <= 50 THEN + (-100.0 * (EXP(LN(1.01327) * (demon - 26.489))) + 200.0) * (EXP(LN(5) * (progress - requirement) / (100 - requirement))) / 10 + WHEN demon <= 20 THEN + ((250 - 100.39) * (EXP(LN(1.168) * (1 - demon))) + 100.39) * (EXP(LN(5) * (progress - requirement) / (100 - requirement))) / 10 + + END + END; +$record_score$ + LANGUAGE SQL IMMUTABLE; + + +CREATE OR REPLACE 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 +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' + + 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; + */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20210327024242_timemachine.down.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20210327024242_timemachine.down.sql new file mode 100644 index 000000000..0fd0a6df5 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20210327024242_timemachine.down.sql @@ -0,0 +1,3 @@ +/* -- This file should undo anything in `up.sql` + +DROP FUNCTION list_at(TIMESTAMP WITHOUT TIME ZONE); */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20210327024242_timemachine.up.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20210327024242_timemachine.up.sql new file mode 100644 index 000000000..cba689efe --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20210327024242_timemachine.up.sql @@ -0,0 +1,28 @@ +/* -- Your SQL goes here + +CREATE FUNCTION list_at(TIMESTAMP WITHOUT TIME ZONE) +RETURNS TABLE ( + name CITEXT, + position_ SMALLINT, + requirement SMALLINT, + video VARCHAR(200), + verifier INTEGER, + publisher INTEGER, + id INTEGER, + level_id BIGINT, + current_position SMALLINT +) +AS $$ + SELECT name, CASE WHEN t.position IS NULL THEN demons.position ELSE t.position END, requirement, video, verifier, publisher, demons.id, level_id, demons.position AS current_position + FROM demons + LEFT OUTER JOIN ( + SELECT DISTINCT ON (id) id, position + FROM demon_modifications + WHERE time >= $1 AND position != -1 + ORDER BY id, time + ) t + ON demons.id = t.id + WHERE NOT EXISTS (SELECT 1 FROM demon_additions WHERE demon_additions.id = demons.id AND time >= $1) +$$ +LANGUAGE SQL +STABLE; */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20210419002933_nationalityupdate.down.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20210419002933_nationalityupdate.down.sql new file mode 100644 index 000000000..fb4f2999f --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20210419002933_nationalityupdate.down.sql @@ -0,0 +1,102 @@ +/* -- This file should undo anything in `up.sql` + +-- Undo audit log related changes + +CREATE OR REPLACE FUNCTION audit_player_modification() RETURNS trigger as $player_modification_trigger$ +DECLARE + name_change CITEXT; + banned_change BOOLEAN; +BEGIN + IF (OLD.name <> NEW.name) THEN + name_change = OLD.name; + END IF; + + IF (OLD.banned <> NEW.banned) THEN + banned_change = OLD.banned; + END IF; + + INSERT INTO player_modifications (userid, id, name, banned) + (SELECT id, NEW.id, name_change, banned_change FROM active_user LIMIT 1); + + RETURN NEW; +END; +$player_modification_trigger$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION audit_player_deletion() RETURNS trigger AS $player_deletion_trigger$ +BEGIN + INSERT INTO player_modifications (userid, id, name, banned) + (SELECT id, OLD.id, OLD.name, OLD.banned + FROM active_user LIMIT 1); + + INSERT INTO player_deletions (userid, id) + (SELECT id, OLD.id FROM active_user LIMIT 1); + + RETURN NULL; +END; +$player_deletion_trigger$ LANGUAGE plpgsql; + +ALTER TABLE player_modifications DROP COLUMN nationality, DROP COLUMN subdivision; + +-- cannot drop columns from VIEW via CREATE OR REPLACE + +DROP VIEW players_with_score; +CREATE OR REPLACE 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 +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' + + 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; + +ALTER TABLE nationalities DROP COLUMN continent; + +DROP TABLE subdivisions; +DROP TYPE continent; + +ALTER TABLE players DROP COLUMN subdivision; */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20210419002933_nationalityupdate.up.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20210419002933_nationalityupdate.up.sql new file mode 100644 index 000000000..546256191 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20210419002933_nationalityupdate.up.sql @@ -0,0 +1,429 @@ +/* CREATE TABLE subdivisions ( + iso_code VARCHAR(3), + name CITEXT UNIQUE NOT NULL, + nation VARCHAR(2) REFERENCES nationalities(iso_country_code), + + PRIMARY KEY (iso_code, nation) +); + +CREATE TYPE continent AS ENUM ('Asia', 'Europe', 'Australia and Oceania', 'Africa', 'North America', 'South America', 'Central America'); + +ALTER TABLE nationalities ADD COLUMN continent continent; + +INSERT INTO nationalities (iso_country_code, nation, continent) +VALUES + ('BQ', 'Bonaire', 'Central America'), + ('JM', 'Jamaica', 'Central America'), + ('PR', 'Puerto Rico', 'Central America'), + ('DO', 'Dominican Republic', 'Central America'), + ('HT', 'Haiti', 'Central America'), + ('SV', 'El Salvador', 'Central America'), + ('GT', 'Guatemala', 'Central America'), + ('HN', 'Honduras', 'Central America'), + ('NI', 'Nicaragua', 'Central America'), + ('PA', 'Panama', 'Central America'), + ('CR', 'Costa Rica', 'Central America'), + ('MX', 'Mexico', 'Central America'), + ('MS', 'Montserrat', 'Central America'), + ('VG', 'British Virgin Islands', 'Central America'), + ('VI', 'US Virgin Islands', 'Central America'), + ('KN', 'Saint Kitts and Nevis', 'Central America'), + ('KY', 'Cayman Islands', 'Central America'), + ('AI', 'Anguilla', 'Central America'), + ('GD', 'Grenada', 'Central America'), + ('LC', 'Saint Lucia', 'Central America'), + ('VC', 'Saint Vincent and the Grenadines', 'Central America'), + ('TC', 'Turks and Caicos Islands', 'Central America'), + ('BB', 'Barbados', 'Central America'), + ('AG', 'Antigua and Barbuda', 'Central America'), + ('SX', 'Sint Maarten (Dutch Part)', 'Central America'), + ('DM', 'Dominica', 'Central America'), + ('TT', 'Trinidad and Tobago', 'Central America'), + ('BS', 'Bahamas', 'Central America'), + ('BZ', 'Belize', 'Central America'), + ('US', 'United States', 'North America'), + ('CA', 'Canada', 'North America'), + ('BR', 'Brazil', 'South America'), + ('CL', 'Chile', 'South America'), + ('AR', 'Argentina', 'South America'), + ('SR', 'Suriname', 'South America'), + ('GY', 'Guyana', 'South America'), + ('VE', 'Venezuela, Bolivarian Republic of', 'South America'), + ('UY', 'Uruguay', 'South America'), + ('PY', 'Paraguay', 'South America'), + ('EC', 'Ecuador', 'South America'), + ('CO', 'Colombia', 'South America'), + ('PE', 'Peru', 'South America'), + ('BO', 'Bolivia, Plurinational State of', 'South America'), + ('AW', 'Aruba', 'South America'), + ('GS', 'South Georgia and the South Sandwich Islands', 'South America'), + ('FK', 'Falkland Islands (Malvinas)', 'South America'), + ('CW', 'Curacao', 'South America'), + ('TD', 'Chad', 'Africa'), + ('DZ', 'Algeria', 'Africa'), + ('EG', 'Egypt', 'Africa'), + ('LY', 'Libya', 'Africa'), + ('MA', 'Morocco', 'Africa'), + ('EH', 'Western Sahara', 'Africa'), + ('SD', 'Sudan', 'Africa'), + ('TN', 'Tunisia', 'Africa'), + ('NE', 'Niger', 'Africa'), + ('MR', 'Mauritania', 'Africa'), + ('ML', 'Mali', 'Africa'), + ('BF', 'Burkina Faso', 'Africa'), + ('ER', 'Eritrea', 'Africa'), + ('SN', 'Senegal', 'Africa'), + ('GM', 'Gambia', 'Africa'), + ('GW', 'Guinea-Bissau', 'Africa'), + ('GN', 'Guinea', 'Africa'), + ('SL', 'Sierra Leone', 'Africa'), + ('LR', 'Liberia', 'Africa'), + ('CI', 'Cote d''Ivoire', 'Africa'), + ('GH', 'Ghana', 'Africa'), + ('TG', 'Togo', 'Africa'), + ('BJ', 'Benin', 'Africa'), + ('CM', 'Cameroon', 'Africa'), + ('CF', 'Central African Republic', 'Africa'), + ('SS', 'South Sudan', 'Africa'), + ('ET', 'Ethiopia', 'Africa'), + ('DJ', 'Djibouti', 'Africa'), + ('SO', 'Somalia', 'Africa'), + ('GQ', 'Equatorial Guinea', 'Africa'), + ('GA', 'Gabon', 'Africa'), + ('KE', 'Kenya', 'Africa'), + ('TZ', 'Tanzania, United Republic of', 'Africa'), + ('UG', 'Uganda', 'Africa'), + ('BI', 'Burundi', 'Africa'), + ('RW', 'Rwanda', 'Africa'), + ('CD', 'Congo, the Democratic Republic of the', 'Africa'), + ('CG', 'Congo', 'Africa'), + ('AO', 'Angola', 'Africa'), + ('ZM', 'Zambia', 'Africa'), + ('MZ', 'Mozambique', 'Africa'), + ('MW', 'Malawi', 'Africa'), + ('ZW', 'Zimbabwe', 'Africa'), + ('NA', 'Namibia', 'Africa'), + ('BW', 'Botswana', 'Africa'), + ('SZ', 'Swaziland', 'Africa'), + ('LS', 'Lesotho', 'Africa'), + ('MG', 'Madagascar', 'Africa'), + ('NG', 'Nigeria', 'Africa'), + ('ZA', 'South Africa', 'Africa'), + ('SH', 'Saint Helena, Ascension and Tristan Da Cunha', 'Africa'), + ('SC', 'Seychelles', 'Africa'), + ('CV', 'Cape Verde', 'Africa'), + ('ST', 'Sao Tome and Principe', 'Africa'), + ('MU', 'Mauritius', 'Africa'), + ('KM', 'Comoros', 'Africa'), + ('FR', 'France', 'Europe'), + ('SI', 'Slovenia', 'Europe'), + ('XK', 'Kosovo', 'Europe'), + ('RS', 'Serbia', 'Europe'), + ('ME', 'Montenegro', 'Europe'), + ('MK', 'Macedonia, the Former Yugoslav Republic of', 'Europe'), + ('GR', 'Greece', 'Europe'), + ('HR', 'Croatia', 'Europe'), + ('BA', 'Bosnia and Herzegovina', 'Europe'), + ('AL', 'Albania', 'Europe'), + ('VA', 'Holy See (Vatican City State)', 'Europe'), + ('SM', 'San Marino', 'Europe'), + ('IT', 'Italy', 'Europe'), + ('SK', 'Slovakia', 'Europe'), + ('RO', 'Romania', 'Europe'), + ('PL', 'Poland', 'Europe'), + ('MD', 'Moldova, Republic of', 'Europe'), + ('HU', 'Hungary', 'Europe'), + ('CZ', 'Czech Republic', 'Europe'), + ('BG', 'Bulgaria', 'Europe'), + ('AT', 'Austria', 'Europe'), + ('CH', 'Switzerland', 'Europe'), + ('DE', 'Germany', 'Europe'), + ('DK', 'Denmark', 'Europe'), + ('NO', 'Norway', 'Europe'), + ('SE', 'Sweden', 'Europe'), + ('FI', 'Finland', 'Europe'), + ('EE', 'Estonia', 'Europe'), + ('LV', 'Latvia', 'Europe'), + ('LT', 'Lithuania', 'Europe'), + ('BY', 'Belarus', 'Europe'), + ('NL', 'Netherlands', 'Europe'), + ('LU', 'Luxembourg', 'Europe'), + ('BE', 'Belgium', 'Europe'), + ('GB', 'United Kingdom', 'Europe'), + ('IE', 'Ireland', 'Europe'), + ('IS', 'Iceland', 'Europe'), + ('AD', 'Andorra', 'Europe'), + ('ES', 'Spain', 'Europe'), + ('PT', 'Portugal', 'Europe'), + ('CY', 'Cyprus', 'Europe'), + ('TR', 'Turkey', 'Europe'), + ('UA', 'Ukraine', 'Europe'), + ('IM', 'Isle of Man', 'Europe'), + ('MC', 'Monaco', 'Europe'), + ('GI', 'Gibraltar', 'Europe'), + ('GG', 'Guernsey', 'Europe'), + ('JE', 'Jersey', 'Europe'), + ('LI', 'Liechtenstein', 'Europe'), + ('MT', 'Malta', 'Europe'), + ('FO', 'Faroe Islands', 'Europe'), + ('LK', 'Sri Lanka', 'Asia'), + ('TW', 'Taiwan', 'Asia'), + ('VN', 'Viet Nam', 'Asia'), + ('MM', 'Myanmar', 'Asia'), + ('KH', 'Cambodia', 'Asia'), + ('LA', 'Lao People''s Democratic Republic', 'Asia'), + ('TH', 'Thailand', 'Asia'), + ('PH', 'Philippines', 'Asia'), + ('MY', 'Malaysia', 'Asia'), + ('OM', 'Oman', 'Asia'), + ('AE', 'United Arab Emirates', 'Asia'), + ('YE', 'Yemen', 'Asia'), + ('QA', 'Qatar', 'Asia'), + ('KW', 'Kuwait', 'Asia'), + ('SA', 'Saudi Arabia', 'Asia'), + ('IL', 'Israel', 'Asia'), + ('LB', 'Lebanon', 'Asia'), + ('SY', 'Syrian Arab Republic', 'Asia'), + ('JO', 'Jordan', 'Asia'), + ('IQ', 'Iraq', 'Asia'), + ('CU', 'Cuba', 'Central America'), + ('RU', 'Russian Federation', 'Asia'), + ('AZ', 'Azerbaijan', 'Asia'), + ('AM', 'Armenia', 'Asia'), + ('GE', 'Georgia', 'Asia'), + ('KP', 'Korea, Democratic People''s Republic of', 'Asia'), + ('KR', 'Korea, Republic of', 'Asia'), + ('JP', 'Japan', 'Asia'), + ('BD', 'Bangladesh', 'Asia'), + ('BT', 'Bhutan', 'Asia'), + ('NP', 'Nepal', 'Asia'), + ('MN', 'Mongolia', 'Asia'), + ('AF', 'Afghanistan', 'Asia'), + ('PK', 'Pakistan', 'Asia'), + ('KG', 'Kyrgyzstan', 'Asia'), + ('IR', 'Iran, Islamic Republic of', 'Asia'), + ('TM', 'Turkmenistan', 'Asia'), + ('TJ', 'Tajikistan', 'Asia'), + ('UZ', 'Uzbekistan', 'Asia'), + ('IN', 'India', 'Asia'), + ('KZ', 'Kazakhstan', 'Asia'), + ('CN', 'China', 'Asia'), + ('HK', 'Hong Kong', 'Asia'), + ('SG', 'Singapore', 'Asia'), + ('MV', 'Maldives', 'Asia'), + ('BH', 'Bahrain', 'Asia'), + ('PS', 'Palestine, State of', 'Asia'), + ('KI', 'Kiribati', 'Australia and Oceania'), + ('NZ', 'New Zealand', 'Australia and Oceania'), + ('BN', 'Brunei Darussalam', 'Australia and Oceania'), + ('TL', 'Timor-Leste', 'Australia and Oceania'), + ('ID', 'Indonesia', 'Australia and Oceania'), + ('PG', 'Papua New Guinea', 'Australia and Oceania'), + ('AU', 'Australia', 'Australia and Oceania'), + ('TK', 'Tokelau', 'Australia and Oceania'), + ('NF', 'Norfolk Island', 'Australia and Oceania'), + ('GU', 'Guam', 'Australia and Oceania'), + ('PN', 'Pitcairn', 'Australia and Oceania'), + ('NR', 'Nauru', 'Australia and Oceania'), + ('TV', 'Tuvalu', 'Australia and Oceania'), + ('MH', 'Marshall Islands', 'Australia and Oceania'), + ('AS', 'American Samoa', 'Australia and Oceania'), + ('CK', 'Cook Islands', 'Australia and Oceania'), + ('NU', 'Niue', 'Australia and Oceania'), + ('TO', 'Tonga', 'Australia and Oceania'), + ('PW', 'Palau', 'Australia and Oceania'), + ('MP', 'Northern Mariana Islands', 'Australia and Oceania'), + ('FM', 'Micronesia, Federated States of', 'Australia and Oceania'), + ('WS', 'Samoa', 'Australia and Oceania'), + ('VU', 'Vanuatu', 'Australia and Oceania'), + ('FJ', 'Fiji', 'Australia and Oceania'), + ('SB', 'Solomon Islands', 'Australia and Oceania') +ON CONFLICT (iso_country_code) DO UPDATE SET nation = EXCLUDED.nation, continent = EXCLUDED.continent; + +DELETE FROM nationalities WHERE continent IS NULL; + +ALTER TABLE nationalities ALTER COLUMN continent SET NOT NULL; + +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'); + +ALTER TABLE players ADD COLUMN subdivision VARCHAR(3) DEFAULT NULL; + +ALTER TABLE player_modifications ADD COLUMN nationality VARCHAR(2) DEFAULT NULL, + ADD COLUMN subdivision VARCHAR(3) DEFAULT NULL; + +CREATE OR REPLACE FUNCTION audit_player_modification() RETURNS trigger as $player_modification_trigger$ +DECLARE + name_change CITEXT; + banned_change BOOLEAN; + nationality_change VARCHAR(2); + subdivision_change VARCHAR(3); +BEGIN + IF (OLD.name <> NEW.name) THEN + name_change = OLD.name; + END IF; + + IF (OLD.banned <> NEW.banned) THEN + banned_change = OLD.banned; + END IF; + + IF (OLD.nationality <> NEW.nationality) THEN + nationality_change = OLD.nationality; + end if; + + IF (OLD.subdivision <> NEW.subdivision) THEN + subdivision_change = OLD.subdivision; + end if; + + INSERT INTO player_modifications (userid, id, name, banned, nationality, subdivision) + (SELECT id, NEW.id, name_change, banned_change, nationality_change, subdivision_change FROM active_user LIMIT 1); + + RETURN NEW; +END; +$player_modification_trigger$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION audit_player_deletion() RETURNS trigger AS $player_deletion_trigger$ +BEGIN + INSERT INTO player_modifications (userid, id, name, banned, nationality, subdivision) + (SELECT id, OLD.id, OLD.name, OLD.banned, OLD.nationality, OLD.subdivision + FROM active_user LIMIT 1); + + INSERT INTO player_deletions (userid, id) + (SELECT id, OLD.id FROM active_user LIMIT 1); + + RETURN NULL; +END; +$player_deletion_trigger$ LANGUAGE plpgsql; + +CREATE OR REPLACE 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; + */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20210725221543_player_claims.down.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20210725221543_player_claims.down.sql new file mode 100644 index 000000000..46badf93d --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20210725221543_player_claims.down.sql @@ -0,0 +1,4 @@ +/* -- This file should undo anything in `up.sql` + +ALTER TABLE players DROP COLUMN claimed_by; +ALTER TABLE members DROP COLUMN claimed_player; */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20210725221543_player_claims.up.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20210725221543_player_claims.up.sql new file mode 100644 index 000000000..ebb126ccf --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20210725221543_player_claims.up.sql @@ -0,0 +1,5 @@ +/* -- Your SQL goes here + +ALTER TABLE members ADD COLUMN claimed_player INTEGER REFERENCES players(id) ON DELETE SET NULL; +ALTER TABLE players ADD COLUMN claimed_by INTEGER REFERENCES members(member_id) ON DELETE SET NULL; + */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20210726174613_nation_ranking.down.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20210726174613_nation_ranking.down.sql new file mode 100644 index 000000000..ae8cd452a --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20210726174613_nation_ranking.down.sql @@ -0,0 +1,4 @@ +/* -- This file should undo anything in `up.sql` + +DROP VIEW nations_with_score; +DROP FUNCTION best_records_in(country VARCHAR(2)); */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20210726174613_nation_ranking.up.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20210726174613_nation_ranking.up.sql new file mode 100644 index 000000000..6df1fda97 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20210726174613_nation_ranking.up.sql @@ -0,0 +1,60 @@ +/* -- Your SQL goes here + +CREATE OR REPLACE FUNCTION best_records_in(country VARCHAR(2)) + 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 + ) + SELECT id, progress, video, status_, player, submitter, demon + FROM grp + WHERE rk = 1; +$body$ +LANGUAGE SQL; + +CREATE OR REPLACE 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; + */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20210825182933_new_claim_system.down.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20210825182933_new_claim_system.down.sql new file mode 100644 index 000000000..8ca40e2ee --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20210825182933_new_claim_system.down.sql @@ -0,0 +1,8 @@ +/* -- This file should undo anything in `up.sql` + +ALTER TABLE members ADD COLUMN claimed_player INTEGER REFERENCES players(id) ON DELETE SET NULL; +ALTER TABLE players ADD COLUMN claimed_by INTEGER REFERENCES members(member_id) ON DELETE SET NULL; + +UPDATE members SET claimed_player = (select player_id from player_claims where player_claims.member_id = members.member_id limit 1); + +DROP TABLE player_claims; */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20210825182933_new_claim_system.up.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20210825182933_new_claim_system.up.sql new file mode 100644 index 000000000..e6d6d47d1 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20210825182933_new_claim_system.up.sql @@ -0,0 +1,14 @@ +/* -- Your SQL goes here + +CREATE TABLE player_claims( + id SERIAL PRIMARY KEY, -- only used for pagination + member_id INTEGER NOT NULL REFERENCES members(member_id) ON DELETE CASCADE, + player_id INTEGER NOT NULL REFERENCES players(id) ON DELETE RESTRICT, + verified BOOLEAN NOT NULL DEFAULT FALSE +); + +INSERT INTO player_claims (member_id, player_id) + SELECT member_id, claimed_player FROM members WHERE claimed_player IS NOT NULL; + +ALTER TABLE members DROP COLUMN claimed_player; +ALTER TABLE players DROP COLUMN claimed_by; */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20210903174349_subdivision_ranking.down.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20210903174349_subdivision_ranking.down.sql new file mode 100644 index 000000000..640024681 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20210903174349_subdivision_ranking.down.sql @@ -0,0 +1,4 @@ +/* -- This file should undo anything in `up.sql` + +DROP FUNCTION subdivision_ranking_of(country varchar(2)); +DROP FUNCTION best_records_local(country VARCHAR(2), the_subdivision VARCHAR(3)); */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20210903174349_subdivision_ranking.up.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20210903174349_subdivision_ranking.up.sql new file mode 100644 index 000000000..d8481a232 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20210903174349_subdivision_ranking.up.sql @@ -0,0 +1,69 @@ +/* -- Your SQL goes here + +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; + + */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20220323105850_member_add_email_column.down.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20220323105850_member_add_email_column.down.sql new file mode 100644 index 000000000..90118748e --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20220323105850_member_add_email_column.down.sql @@ -0,0 +1,4 @@ +/* -- This file should undo anything in `up.sql` + +ALTER TABLE members DROP COLUMN email_address; +DROP DOMAIN EMAIL; */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20220323105850_member_add_email_column.up.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20220323105850_member_add_email_column.up.sql new file mode 100644 index 000000000..b346bbb08 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20220323105850_member_add_email_column.up.sql @@ -0,0 +1,7 @@ +/* -- Your SQL goes here + +-- https://dba.stackexchange.com/questions/68266/what-is-the-best-way-to-store-an-email-address-in-postgresql +CREATE DOMAIN EMAIL AS CITEXT + CHECK ( value ~ '^[a-zA-Z0-9.!#$%&''*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$' ); + +ALTER TABLE members ADD COLUMN email_address EMAIL; */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20220324104659_lock_submissions.down.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20220324104659_lock_submissions.down.sql new file mode 100644 index 000000000..5249b28bd --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20220324104659_lock_submissions.down.sql @@ -0,0 +1,3 @@ +/* -- This file should undo anything in `up.sql` + +ALTER TABLE player_claims DROP COLUMN lock_submissions; */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20220324104659_lock_submissions.up.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20220324104659_lock_submissions.up.sql new file mode 100644 index 000000000..ece808aae --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20220324104659_lock_submissions.up.sql @@ -0,0 +1,3 @@ +/* -- Your SQL goes here + +ALTER TABLE player_claims ADD COLUMN lock_submissions BOOL NOT NULL DEFAULT FALSE; */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20220412171531_public_notes.down.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20220412171531_public_notes.down.sql new file mode 100644 index 000000000..600250452 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20220412171531_public_notes.down.sql @@ -0,0 +1,3 @@ +/* -- This file should undo anything in `up.sql` + +ALTER TABLE record_notes DROP COLUMN is_public; */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20220412171531_public_notes.up.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20220412171531_public_notes.up.sql new file mode 100644 index 000000000..055b0d147 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20220412171531_public_notes.up.sql @@ -0,0 +1,3 @@ +/* -- Your SQL goes here + +ALTER TABLE record_notes ADD COLUMN is_public BOOLEAN NOT NULL DEFAULT FALSE; */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20220601192100_extended_list_buff.down.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20220601192100_extended_list_buff.down.sql new file mode 100644 index 000000000..eb87e63cf --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20220601192100_extended_list_buff.down.sql @@ -0,0 +1,39 @@ +/* -- Your SQL goes here + +CREATE OR REPLACE FUNCTION record_score(progress FLOAT, demon FLOAT, list_size FLOAT, requirement FLOAT) RETURNS FLOAT AS +$record_score$ +SELECT CASE + WHEN progress = 100 THEN + CASE + + WHEN 125 < demon AND demon <= 150 THEN + 150.0 * EXP(((1.0 - demon) * LN(1.0 / 30.0)) / -149.0) + WHEN 50 < demon AND demon <= 125 THEN + 60 * (EXP(LN(2.333) * ((51.0 - demon) * (LN(30.0) / 99.0)))) + 1.884 + WHEN 20 < demon AND demon <= 50 THEN + -100.0 * (EXP(LN(1.01327) * (demon - 26.489))) + 200.0 + WHEN demon <= 20 THEN + (250 - 100.39) * (EXP(LN(1.168) * (1 - demon))) + 100.39 + + END + + WHEN progress < requirement THEN + 0.0 + ELSE + CASE + + WHEN 125 < demon AND demon <= 150 THEN + 150.0 * EXP(((1.0 - demon) * LN(1.0 / 30.0)) / -149.0) * (EXP(LN(5) * (progress - requirement) / (100 - requirement))) / 10 + WHEN 50 < demon AND demon <= 125 THEN + (60 * (EXP(LN(2.333) * ((51.0 - demon) * (LN(30.0) / 99.0)))) + 1.884) * (EXP(LN(5) * (progress - requirement) / (100 - requirement))) / 10 + WHEN 20 < demon AND demon <= 50 THEN + (-100.0 * (EXP(LN(1.01327) * (demon - 26.489))) + 200.0) * (EXP(LN(5) * (progress - requirement) / (100 - requirement))) / 10 + WHEN demon <= 20 THEN + ((250 - 100.39) * (EXP(LN(1.168) * (1 - demon))) + 100.39) * (EXP(LN(5) * (progress - requirement) / (100 - requirement))) / 10 + + END + END; +$record_score$ + LANGUAGE SQL IMMUTABLE; + + */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20220601192100_extended_list_buff.up.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20220601192100_extended_list_buff.up.sql new file mode 100644 index 000000000..6df3d382e --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20220601192100_extended_list_buff.up.sql @@ -0,0 +1,38 @@ +/* -- Your SQL goes here + +CREATE OR REPLACE FUNCTION record_score(progress FLOAT, demon FLOAT, list_size FLOAT, requirement FLOAT) RETURNS FLOAT AS +$record_score$ +SELECT CASE + WHEN progress = 100 THEN + CASE + + WHEN 55 < demon AND demon <= 150 THEN + (56.191 * EXP(LN(2) * ((54.147 - (demon + 3.2)) * LN(50.0)) / 99.0)) + 6.273 + WHEN 35 < demon AND demon <= 55 THEN + 212.61 * (EXP(LN(1.036) * (1 - demon))) + 25.071 + WHEN 20 < demon AND demon <= 35 THEN + (250 - 83.389) * (EXP(LN(1.0099685) * (2 - demon))) - 31.152 + WHEN demon <= 20 THEN + (250 - 100.39) * (EXP(LN(1.168) * (1 - demon))) + 100.39 + + END + + WHEN progress < requirement THEN + 0.0 + ELSE + CASE + + WHEN 55 < demon AND demon <= 150 THEN + ((56.191 * EXP(LN(2) * ((54.147 - (demon + 3.2)) * LN(50.0)) / 99.0)) + 6.273) * (EXP(LN(5) * (progress - requirement) / (100 - requirement))) / 10 + WHEN 35 < demon AND demon <= 55 THEN + (212.61 * (EXP(LN(1.036) * (1 - demon))) + 25.071) * (EXP(LN(5) * (progress - requirement) / (100 - requirement))) / 10 + WHEN 20 < demon AND demon <= 35 THEN + ((250 - 83.389) * (EXP(LN(1.0099685) * (2 - demon))) - 31.152) * (EXP(LN(5) * (progress - requirement) / (100 - requirement))) / 10 + WHEN demon <= 20 THEN + ((250 - 100.39) * (EXP(LN(1.168) * (1 - demon))) + 100.39) * (EXP(LN(5) * (progress - requirement) / (100 - requirement))) / 10 + + END + END; +$record_score$ + LANGUAGE SQL IMMUTABLE; + */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20220805215800_fix_youtube_channel_audit.down.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20220805215800_fix_youtube_channel_audit.down.sql new file mode 100644 index 000000000..1dbba5561 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20220805215800_fix_youtube_channel_audit.down.sql @@ -0,0 +1,25 @@ +/* -- This file should undo anything in `up.sql` +CREATE OR REPLACE FUNCTION audit_user_modification() RETURNS trigger as $user_modification_trigger$ +DECLARE + display_name_change CITEXT; + youtube_channel_change BOOLEAN; + permissions_change BIT(16); +BEGIN + IF (OLD.display_name <> NEW.display_name) THEN + display_name_change = OLD.display_name; + END IF; + + IF (OLD.youtube_channel <> NEW.youtube_channel) THEN + youtube_channel_change = OLD.youtube_channel; + END IF; + + IF (OLD.permissions <> NEW.permissions) THEN + permissions_change = OLD.permissions; + END IF; + + INSERT INTO user_modifications (userid, id, display_name, youtube_channel, permissions) + (SELECT id, NEW.member_id, display_name_change, youtube_channel_change, permissions_change FROM active_user LIMIT 1); + + RETURN NEW; +END; +$user_modification_trigger$ LANGUAGE plpgsql; */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20220805215800_fix_youtube_channel_audit.up.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20220805215800_fix_youtube_channel_audit.up.sql new file mode 100644 index 000000000..054c62c57 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20220805215800_fix_youtube_channel_audit.up.sql @@ -0,0 +1,25 @@ +/* -- Your SQL goes here +CREATE OR REPLACE FUNCTION audit_user_modification() RETURNS trigger as $user_modification_trigger$ +DECLARE + display_name_change CITEXT; + youtube_channel_change VARCHAR(200); + permissions_change BIT(16); +BEGIN + IF (OLD.display_name <> NEW.display_name) THEN + display_name_change = OLD.display_name; + END IF; + + IF (OLD.youtube_channel <> NEW.youtube_channel) THEN + youtube_channel_change = OLD.youtube_channel; + END IF; + + IF (OLD.permissions <> NEW.permissions) THEN + permissions_change = OLD.permissions; + END IF; + + INSERT INTO user_modifications (userid, id, display_name, youtube_channel, permissions) + (SELECT id, NEW.member_id, display_name_change, youtube_channel_change, permissions_change FROM active_user LIMIT 1); + + RETURN NEW; +END; +$user_modification_trigger$ LANGUAGE plpgsql; */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20221009140916_thumbnails.down.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20221009140916_thumbnails.down.sql new file mode 100644 index 000000000..9f44571db --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20221009140916_thumbnails.down.sql @@ -0,0 +1,77 @@ +/* -- This file should undo anything in `up.sql` + +DROP TRIGGER demons_insert_set_thumbnail ON demons; +DROP FUNCTION set_initial_thumbnail; + +ALTER TABLE demons DROP COLUMN thumbnail; + +CREATE OR REPLACE FUNCTION audit_demon_modification() RETURNS trigger AS $demon_modification_trigger$ +DECLARE + name_change CITEXT; + position_change SMALLINT; + requirement_change SMALLINT; + video_change VARCHAR(200); + verifier_change INT; + publisher_change InT; +BEGIN + IF (OLD.name <> NEW.name) THEN + name_change = OLD.name; + END IF; + + IF (OLD.position <> NEW.position) THEN + position_change = OLD.position; + END IF; + + IF (OLD.requirement <> NEW.requirement) THEN + requirement_change = OLD.requirement; + END IF; + + IF (OLD.video <> NEW.video) THEN + video_change = OLD.video; + END IF; + + IF (OLD.verifier <> NEW.verifier) THEN + verifier_change = OLD.verifier; + END IF; + + IF (OLD.publisher <> NEW.publisher) THEN + publisher_change = OLD.publisher; + END IF; + + INSERT INTO demon_modifications (userid, name, position, requirement, video, verifier, publisher, id) + (SELECT id, name_change, position_change, requirement_change, video_change, verifier_change, publisher_change, NEW.id + FROM active_user LIMIT 1); + + RETURN NEW; +END; +$demon_modification_trigger$ LANGUAGE plpgsql; + +ALTER TABLE demon_modifications DROP COLUMN thumbnail; + +DROP FUNCTION list_at(TIMESTAMP WITHOUT TIME ZONE); +CREATE OR REPLACE FUNCTION list_at(TIMESTAMP WITHOUT TIME ZONE) + RETURNS TABLE ( + name CITEXT, + position_ SMALLINT, + requirement SMALLINT, + video VARCHAR(200), + verifier INTEGER, + publisher INTEGER, + id INTEGER, + level_id BIGINT, + current_position SMALLINT + ) +AS $$ +SELECT name, CASE WHEN t.position IS NULL THEN demons.position ELSE t.position END, requirement, video, verifier, publisher, demons.id, level_id, demons.position AS current_position +FROM demons + LEFT OUTER JOIN ( + SELECT DISTINCT ON (id) id, position + FROM demon_modifications + WHERE time >= $1 AND position != -1 + ORDER BY id, time +) t + ON demons.id = t.id +WHERE NOT EXISTS (SELECT 1 FROM demon_additions WHERE demon_additions.id = demons.id AND time >= $1) +$$ + LANGUAGE SQL + STABLE; */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20221009140916_thumbnails.up.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20221009140916_thumbnails.up.sql new file mode 100644 index 000000000..b519b6693 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20221009140916_thumbnails.up.sql @@ -0,0 +1,100 @@ +/* -- Your SQL goes here + +ALTER TABLE demons ADD COLUMN thumbnail TEXT NOT NULL DEFAULT 'https://i.ytimg.com/vi/zebrafishes/mqdefault.jpg'; + +UPDATE demons +SET thumbnail = 'https://i.ytimg.com/vi/' || SUBSTRING(video FROM '%v=#"___________#"%' FOR '#') || '/mqdefault.jpg' +WHERE video IS NOT NULL + AND NOT EXISTS (SELECT 1 FROM players WHERE players.id=demons.verifier AND players.link_banned); + +CREATE OR REPLACE FUNCTION set_initial_thumbnail() RETURNS trigger AS ' +BEGIN + IF NEW.video IS NOT NULL AND NOT EXISTS(SELECT 1 FROM players WHERE players.id=NEW.verifier AND players.link_banned) THEN + NEW.thumbnail := ''https://i.ytimg.com/vi/'' || SUBSTRING(NEW.video FROM ''%v=#"___________#"%'' FOR ''#'') || ''/mqdefault.jpg''; + END IF; + RETURN NEW; +END; +' LANGUAGE plpgsql; + +CREATE TRIGGER demons_insert_set_thumbnail BEFORE INSERT ON demons FOR +EACH ROW EXECUTE PROCEDURE set_initial_thumbnail(); + +-- Your SQL goes here + +ALTER TABLE demon_modifications ADD COLUMN thumbnail TEXT NULL DEFAULT NULL; + +CREATE OR REPLACE FUNCTION audit_demon_modification() RETURNS trigger AS $demon_modification_trigger$ +DECLARE + name_change CITEXT; + position_change SMALLINT; + requirement_change SMALLINT; + video_change VARCHAR(200); + thumbnail_change TEXT; + verifier_change INT; + publisher_change INT; +BEGIN + IF (OLD.name <> NEW.name) THEN + name_change = OLD.name; + END IF; + + IF (OLD.position <> NEW.position) THEN + position_change = OLD.position; + END IF; + + IF (OLD.requirement <> NEW.requirement) THEN + requirement_change = OLD.requirement; + END IF; + + IF (OLD.video <> NEW.video) THEN + video_change = OLD.video; + END IF; + + IF (OLD.thumbnail <> NEW.thumbnail) THEN + thumbnail_change = OLD.thumbnail; + END IF; + + IF (OLD.verifier <> NEW.verifier) THEN + verifier_change = OLD.verifier; + END IF; + + IF (OLD.publisher <> NEW.publisher) THEN + publisher_change = OLD.publisher; + END IF; + + INSERT INTO demon_modifications (userid, name, position, requirement, video, verifier, publisher, thumbnail, id) + (SELECT id, name_change, position_change, requirement_change, video_change, verifier_change, publisher_change, thumbnail_change, NEW.id + FROM active_user LIMIT 1); + + RETURN NEW; +END; +$demon_modification_trigger$ LANGUAGE plpgsql; + +DROP FUNCTION list_at(TIMESTAMP WITHOUT TIME ZONE); + +CREATE FUNCTION list_at(TIMESTAMP WITHOUT TIME ZONE) + RETURNS TABLE ( + name CITEXT, + position_ SMALLINT, + requirement SMALLINT, + video VARCHAR(200), + thumbnail TEXT, + verifier INTEGER, + publisher INTEGER, + id INTEGER, + level_id BIGINT, + current_position SMALLINT + ) +AS $$ +SELECT name, CASE WHEN t.position IS NULL THEN demons.position ELSE t.position END, requirement, video, thumbnail, verifier, publisher, demons.id, level_id, demons.position AS current_position +FROM demons + LEFT OUTER JOIN ( + SELECT DISTINCT ON (id) id, position + FROM demon_modifications + WHERE time >= $1 AND position != -1 + ORDER BY id, time +) t + ON demons.id = t.id +WHERE NOT EXISTS (SELECT 1 FROM demon_additions WHERE demon_additions.id = demons.id AND time >= $1) +$$ + LANGUAGE SQL + STABLE; */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20240328150521_simplify_gd_integration.down.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20240328150521_simplify_gd_integration.down.sql new file mode 100644 index 000000000..a2b70365f --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/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/pointercrate-example/sample/migrations/pointercrate_temp_com/20240328150521_simplify_gd_integration.up.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20240328150521_simplify_gd_integration.up.sql new file mode 100644 index 000000000..f14a8904d --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/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/pointercrate-example/sample/migrations/pointercrate_temp_com/20240404084539_nationalityupdate_again.down.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20240404084539_nationalityupdate_again.down.sql new file mode 100644 index 000000000..667830167 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/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/pointercrate-example/sample/migrations/pointercrate_temp_com/20240404084539_nationalityupdate_again.up.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20240404084539_nationalityupdate_again.up.sql new file mode 100644 index 000000000..ecc0a9561 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20240404084539_nationalityupdate_again.up.sql @@ -0,0 +1,429 @@ +/* -- 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'); + */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20240418192100_top_10_buff.down.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20240418192100_top_10_buff.down.sql new file mode 100644 index 000000000..6df3d382e --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20240418192100_top_10_buff.down.sql @@ -0,0 +1,38 @@ +/* -- Your SQL goes here + +CREATE OR REPLACE FUNCTION record_score(progress FLOAT, demon FLOAT, list_size FLOAT, requirement FLOAT) RETURNS FLOAT AS +$record_score$ +SELECT CASE + WHEN progress = 100 THEN + CASE + + WHEN 55 < demon AND demon <= 150 THEN + (56.191 * EXP(LN(2) * ((54.147 - (demon + 3.2)) * LN(50.0)) / 99.0)) + 6.273 + WHEN 35 < demon AND demon <= 55 THEN + 212.61 * (EXP(LN(1.036) * (1 - demon))) + 25.071 + WHEN 20 < demon AND demon <= 35 THEN + (250 - 83.389) * (EXP(LN(1.0099685) * (2 - demon))) - 31.152 + WHEN demon <= 20 THEN + (250 - 100.39) * (EXP(LN(1.168) * (1 - demon))) + 100.39 + + END + + WHEN progress < requirement THEN + 0.0 + ELSE + CASE + + WHEN 55 < demon AND demon <= 150 THEN + ((56.191 * EXP(LN(2) * ((54.147 - (demon + 3.2)) * LN(50.0)) / 99.0)) + 6.273) * (EXP(LN(5) * (progress - requirement) / (100 - requirement))) / 10 + WHEN 35 < demon AND demon <= 55 THEN + (212.61 * (EXP(LN(1.036) * (1 - demon))) + 25.071) * (EXP(LN(5) * (progress - requirement) / (100 - requirement))) / 10 + WHEN 20 < demon AND demon <= 35 THEN + ((250 - 83.389) * (EXP(LN(1.0099685) * (2 - demon))) - 31.152) * (EXP(LN(5) * (progress - requirement) / (100 - requirement))) / 10 + WHEN demon <= 20 THEN + ((250 - 100.39) * (EXP(LN(1.168) * (1 - demon))) + 100.39) * (EXP(LN(5) * (progress - requirement) / (100 - requirement))) / 10 + + END + END; +$record_score$ + LANGUAGE SQL IMMUTABLE; + */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20240418192100_top_10_buff.up.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20240418192100_top_10_buff.up.sql new file mode 100644 index 000000000..c25cbd5f9 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20240418192100_top_10_buff.up.sql @@ -0,0 +1,35 @@ +/* CREATE OR REPLACE FUNCTION record_score(progress FLOAT, demon FLOAT, list_size FLOAT, requirement FLOAT) RETURNS FLOAT AS +$record_score$ +SELECT CASE + WHEN progress = 100 THEN + CASE + WHEN demon BETWEEN 56 AND 150 THEN + 1.039035131 * ((185.7 * EXP((-0.02715 * demon))) + 14.84) + WHEN demon BETWEEN 36 AND 55 THEN + 1.0371139743 * ((212.61 * POWER(1.036, 1 - demon)) + 25.071) + WHEN demon BETWEEN 21 AND 35 THEN + (((250 - 83.389) * POWER(1.0099685, 2 - demon) - 31.152)) * 1.0371139743 + WHEN demon BETWEEN 4 AND 20 THEN + ((326.1 * EXP((-0.0871 * demon))) + 51.09) * 1.037117142 + WHEN demon BETWEEN 1 AND 3 THEN + (-18.2899079915 * demon) + 368.2899079915 + END + WHEN progress < requirement THEN + 0.0 + ELSE + CASE + WHEN demon BETWEEN 56 AND 150 THEN + 1.039035131 * ((185.7 * EXP((-0.02715 * demon))) + 14.84) * (EXP(LN(5) * (progress - requirement) / (100 - requirement))) / 10 + WHEN demon BETWEEN 36 AND 55 THEN + (1.0371139743 * ((212.61 * POWER(1.036, 1 - demon)) + 25.071)) * (EXP(LN(5) * (progress - requirement) / (100 - requirement))) / 10 + WHEN demon BETWEEN 21 AND 35 THEN + (((250 - 83.389) * POWER(1.0099685, 2 - demon) - 31.152)) * 1.0371139743 * (EXP(LN(5) * (progress - requirement) / (100 - requirement))) / 10 + WHEN demon BETWEEN 4 AND 20 THEN + (((326.1 * EXP((-0.0871 * demon))) + 51.09) * 1.037117142) * (EXP(LN(5) * (progress - requirement) / (100 - requirement))) / 10 + WHEN demon BETWEEN 1 AND 3 THEN + ((-18.2899079915 * demon) + 368.2899079915) * (EXP(LN(5) * (progress - requirement) / (100 - requirement))) / 10 + END + END; +$record_score$ + LANGUAGE SQL IMMUTABLE; + */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20240504194923_cached_points.down.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20240504194923_cached_points.down.sql new file mode 100644 index 000000000..9699955ba --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20240504194923_cached_points.down.sql @@ -0,0 +1,184 @@ +/* -- 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; + + */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20240504194923_cached_points.up.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20240504194923_cached_points.up.sql new file mode 100644 index 000000000..68e3b4edc --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/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/pointercrate-example/sample/migrations/pointercrate_temp_com/20240518154803_no_extended_progress_points.down.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20240518154803_no_extended_progress_points.down.sql new file mode 100644 index 000000000..67614627d --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/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/pointercrate-example/sample/migrations/pointercrate_temp_com/20240518154803_no_extended_progress_points.up.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20240518154803_no_extended_progress_points.up.sql new file mode 100644 index 000000000..d0d6aab45 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/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/pointercrate-example/sample/migrations/pointercrate_temp_com/20240518161548_properly_zero_scores.down.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20240518161548_properly_zero_scores.down.sql new file mode 100644 index 000000000..a875894f2 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/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/pointercrate-example/sample/migrations/pointercrate_temp_com/20240518161548_properly_zero_scores.up.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20240518161548_properly_zero_scores.up.sql new file mode 100644 index 000000000..e055cd4b1 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/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-example/sample/migrations/pointercrate_temp_com/20240725201300_better_raw_footage.down.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20240725201300_better_raw_footage.down.sql new file mode 100644 index 000000000..bc8d8fc43 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20240725201300_better_raw_footage.down.sql @@ -0,0 +1,22 @@ +/* ALTER TABLE records DROP COLUMN raw_footage; + +DROP FUNCTION best_records_in(VARCHAR(2)); + +CREATE OR REPLACE FUNCTION best_records_in(country VARCHAR(2)) + 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 + ) + SELECT id, progress, video, status_, player, submitter, demon + FROM grp + WHERE rk = 1; +$body$ +LANGUAGE SQL; + */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20240725201300_better_raw_footage.up.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20240725201300_better_raw_footage.up.sql new file mode 100644 index 000000000..c36cba836 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20240725201300_better_raw_footage.up.sql @@ -0,0 +1 @@ +/* ALTER TABLE records ADD COLUMN raw_footage TEXT; */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20240804194818_raw_footage_fixup.down.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20240804194818_raw_footage_fixup.down.sql new file mode 100644 index 000000000..e438348fb --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20240804194818_raw_footage_fixup.down.sql @@ -0,0 +1,4 @@ +/* -- Add down migration script here +-- This migration is undone by the previous one, as it is impossible to restore the partially broken state +-- introduced by only applying 20240725201300_better_raw_footage.up.sql (function with wrong return type). + */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20240804194818_raw_footage_fixup.up.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20240804194818_raw_footage_fixup.up.sql new file mode 100644 index 000000000..c518a9604 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20240804194818_raw_footage_fixup.up.sql @@ -0,0 +1,28 @@ +/* DROP FUNCTION best_records_in(VARCHAR(2)); + +CREATE FUNCTION best_records_in(country VARCHAR(2)) + RETURNS TABLE ( + id integer , + progress smallint , + video character varying(200), + status_ public.record_status , + player integer , + submitter integer , + demon integer + ) + 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 + ) + SELECT id, progress, video, status_, player, submitter, demon + FROM grp + WHERE rk = 1; +$body$ +LANGUAGE SQL; + */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20240907133101_resync_database.down.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20240907133101_resync_database.down.sql new file mode 100644 index 000000000..e006f0800 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20240907133101_resync_database.down.sql @@ -0,0 +1,11 @@ +/* ALTER TABLE IF EXISTS public.demons + ADD CONSTRAINT demons_level_id_fkey FOREIGN KEY (level_id) + REFERENCES public.gj_level (level_id) MATCH SIMPLE + ON UPDATE NO ACTION + ON DELETE NO ACTION; + + + +ALTER TABLE IF EXISTS public.records + ADD CONSTRAINT records_video_key UNIQUE (video); + */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20240907133101_resync_database.up.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20240907133101_resync_database.up.sql new file mode 100644 index 000000000..226514ea9 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20240907133101_resync_database.up.sql @@ -0,0 +1,9 @@ +/* ALTER TABLE IF EXISTS public.demons + DROP CONSTRAINT IF EXISTS demons_level_id_fkey; + + + + +ALTER TABLE IF EXISTS public.records + DROP CONSTRAINT IF EXISTS records_video_key; + */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20240913151142_backup_records_on_delete.down.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20240913151142_backup_records_on_delete.down.sql new file mode 100644 index 000000000..c910d1624 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20240913151142_backup_records_on_delete.down.sql @@ -0,0 +1 @@ +/* DROP TABLE rec_backup */ \ No newline at end of file diff --git a/pointercrate-example/sample/migrations/pointercrate_temp_com/20240913151142_backup_records_on_delete.up.sql b/pointercrate-example/sample/migrations/pointercrate_temp_com/20240913151142_backup_records_on_delete.up.sql new file mode 100644 index 000000000..5e36ac216 --- /dev/null +++ b/pointercrate-example/sample/migrations/pointercrate_temp_com/20240913151142_backup_records_on_delete.up.sql @@ -0,0 +1,19 @@ +/* CREATE TABLE public.rec_backup +( + id integer DEFAULT nextval('records_id_seq'::regclass), + progress smallint, + video character varying(200), + status_ record_status, + player integer, + submitter integer, + demon integer, + raw_footage text, + demon_name citext, + PRIMARY KEY (id) +) + +USING heap +TABLESPACE pg_default; + +ALTER TABLE IF EXISTS public.rec_backup + OWNER to pointercrate; */ \ No newline at end of file diff --git a/pointercrate-example/src/main.rs b/pointercrate-example/src/main.rs index f6fb8e1cd..ff4768669 100644 --- a/pointercrate-example/src/main.rs +++ b/pointercrate-example/src/main.rs @@ -1,3 +1,4 @@ +use dotenv::dotenv; use maud::html; use pointercrate_core::error::CoreError; use pointercrate_core::pool::PointercratePool; @@ -13,24 +14,13 @@ use pointercrate_demonlist_pages::account::{ }; 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)] +use rocket::{build, catch, fs::FileServer, get, response::Redirect, uri, 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() } -/// Failures in json deserialization of request bodies will just return -/// an immediate 422 response. This catcher is needed to translate them into a pointercrate -/// error response. #[rocket::catch(422)] fn catch_422() -> ErrorResponder { CoreError::UnprocessableEntity.into() @@ -42,157 +32,101 @@ fn catch_401() -> ErrorResponder { CoreError::Unauthorized.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/")) + Redirect::to(uri!("/list/")) } -#[rocket::launch] -async fn rocket() -> _ { - // Load the configuration from your .env file +async fn configure_rocket() -> Result, Box> { 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`) + let rocket = build() .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_401, catch_404, catch_422]) // 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(ListIntegrationTab("https://discord.com/invite/W7Eqqj8NG2")) .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 + Ok(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 + .mount("/static/user", FileServer::from("pointercrate-user-pages/static"))) } -/// 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") + let nav_bar = NavigationBar::new("/static/core/thecscl.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. + "/list/", + html! { + span { + "Challenge List" + } + }, + ) + .with_sub_item("/list/statsviewer/", html! {"Stats Viewer"}) + .with_sub_item("/list/?submitter=true", html! {"Record Submitter"}) + .with_sub_item("/list/?timemachine=true", html! {"Time Machine"}), + ) + .with_item(TopLevelNavigationBarItem::new( + "/login/", html! { span { - "Demonlist" + "User Area" } }, - ) - // 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/", + "https://discord.com/invite/W7Eqqj8NG2", html! { span { - "User Area" + "Discord Server" } }, )); - // 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" + "The Clicksync Challenge list and Pointercrate are in no way affiliated with RobTopGamesAB ® or eachother." }) - // Add a column with links for various list-related highlights .with_column(FooterColumn::LinkList { - heading: "Demonlist", + heading: "The Clicksync Challenge list v1.6.3", 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", - ), + Link::new("/list/1/", "Top 1 Challenge"), + Link::new("/list/statsviewer/", "Stats Viewer"), + Link::new("/account/", "User Area"), ], }) - // 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") + .with_link("https://twitter.com/stadust1971", "Site Dev"); + + PageConfiguration::new("Clicksync Challenge List", nav_bar, footer).author("sphericle") +} + +#[shuttle_runtime::main] +async fn main() -> shuttle_rocket::ShuttleRocket { + dotenv().ok(); + let rocket = configure_rocket().await.expect("Failed to configure Rocket"); + + rocket::build(); + Ok(rocket.into()) } diff --git a/pointercrate-test/tests/demonlist/demon.rs b/pointercrate-test/tests/demonlist/demon.rs index 10d68a776..904892b1c 100644 --- a/pointercrate-test/tests/demonlist/demon.rs +++ b/pointercrate-test/tests/demonlist/demon.rs @@ -8,6 +8,8 @@ use pointercrate_demonlist::{ use rocket::http::Status; use sqlx::{Pool, Postgres}; +const DEFAULT_THUMBNAIL: &str = "https://i.ytimg.com/vi/zebrafishes/mqdefault.jpg"; + #[sqlx::test(migrations = "../migrations")] async fn test_add_demon_ratelimits(pool: Pool) { let (clnt, mut connection) = pointercrate_test::demonlist::setup_rocket(pool).await; @@ -34,6 +36,77 @@ async fn test_add_demon_ratelimits(pool: Pool) { assert_eq!(result["code"].as_i64(), Some(42900)) } +#[sqlx::test(migrations = "../migrations")] +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 demon = serde_json::json! {{"name": "Bloodbath", "requirement": 90, "position": 1, "verifier": "Riot", "publisher": "Riot", "creators": []}}; + + // first one should succeed + let result: serde_json::Value = clnt + .post("/api/v2/demons/", &demon) + .authorize_as(&user) + .expect_status(Status::Created) + .get_result() + .await; + + dbg!(&result); + + assert_eq!(result["data"]["thumbnail"].as_str(), Some(DEFAULT_THUMBNAIL)) +} + +#[sqlx::test(migrations = "../migrations")] +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 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) + .await + .unwrap(); + + // first one should succeed + let result: serde_json::Value = clnt + .post("/api/v2/demons/", &demon) + .authorize_as(&user) + .expect_status(Status::Created) + .get_result() + .await; + + dbg!(&result); + + assert_eq!(result["data"]["thumbnail"].as_str(), Some(DEFAULT_THUMBNAIL)) +} + +#[sqlx::test(migrations = "../migrations")] +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 demon = serde_json::json! {{"name": "Bloodbath", "requirement": 90, "position": 1, "verifier": "Riot", "publisher": "Riot", "creators": [], "video": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"}}; + + // first one should succeed + let result: serde_json::Value = clnt + .post("/api/v2/demons/", &demon) + .authorize_as(&user) + .expect_status(Status::Created) + .get_result() + .await; + + dbg!(&result); + + assert_eq!( + result["data"]["thumbnail"].as_str(), + 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 diff --git a/pointercrate-user-api/src/lib.rs b/pointercrate-user-api/src/lib.rs index 80ce0173b..163d444ea 100644 --- a/pointercrate-user-api/src/lib.rs +++ b/pointercrate-user-api/src/lib.rs @@ -7,7 +7,6 @@ mod endpoints; mod pages; mod ratelimits; -#[allow(unused_mut)] pub fn setup(rocket: Rocket) -> Rocket { let ratelimits = UserRatelimits::new(); diff --git a/pointercrate-user-api/src/ratelimits.rs b/pointercrate-user-api/src/ratelimits.rs index 9ee362782..1d57c57f0 100644 --- a/pointercrate-user-api/src/ratelimits.rs +++ b/pointercrate-user-api/src/ratelimits.rs @@ -4,8 +4,8 @@ use pointercrate_core::ratelimits; ratelimits! { UserRatelimits { - registrations[1u32 per 86400 per IpAddr] => "Too many registrations!", - soft_registrations[5u32 per 21600 per IpAddr] => "Too many failed registration attempts!", + registrations[1u32 per 86400 per IpAddr] => "Too many registrations! Complain to sphericle in the discord server if you see this!", + soft_registrations[5u32 per 21600 per IpAddr] => "Too many failed registration attempts! Complain to sphericle in the discord if you see this!", login_attempts[3u32 per 1800 per IpAddr] => "Too many login attempts!", } } diff --git a/pointercrate-user-pages/src/account/profile.rs b/pointercrate-user-pages/src/account/profile.rs index af8334f66..2b9a96068 100644 --- a/pointercrate-user-pages/src/account/profile.rs +++ b/pointercrate-user-pages/src/account/profile.rs @@ -76,7 +76,7 @@ impl AccountPageTab for ProfileTab { } i #profile-youtube-channel { @match user.youtube_channel { - Some(ref yc) => a.link href = (yc) {}, + Some(ref yc) => a.link href = (yc) {(yc)}, None => "-" } } @@ -84,13 +84,14 @@ impl AccountPageTab for ProfileTab { "A link to your YouTube channel, if you have one. If set, all mentions of your name will turn into links to it." } } + span { b { "Permissions: " } (permission_string) p { - "The permissions you have on pointercrate. 'List ...' means you're a member of the demonlist team. 'Moderator' and 'Administrator' mean you're part of pointercrate's staff team." + "The permissions you have on pointercrate. 'List ...' means you're a member of the list team. 'Moderator' and 'Administrator' mean you're part of pointercrate's staff team." } } } diff --git a/pointercrate-user-pages/src/login.rs b/pointercrate-user-pages/src/login.rs index 807dde737..c265117e1 100644 --- a/pointercrate-user-pages/src/login.rs +++ b/pointercrate-user-pages/src/login.rs @@ -57,7 +57,7 @@ fn login_page_body() -> Markup { div.flex.col { h2 {"Login"} p { - "Log in to an existing pointercrate account. You have 3 login attempts by 30 minutes. If you do not have an account yet, register on the right or below. " + "Log in to an existing pointercrate account. You have 3 login attempts every 30 minutes. If you do not have an account yet, register on the right or below. " } form.flex.col.grow #login-form novalidate = "" { p.info-red.output {} diff --git a/pointercrate-user-pages/static/css/account.css b/pointercrate-user-pages/static/css/account.css index d1069aa94..dc1af1e92 100644 --- a/pointercrate-user-pages/static/css/account.css +++ b/pointercrate-user-pages/static/css/account.css @@ -45,3 +45,16 @@ form { padding: 15px; border-bottom: 1px lightgray dashed; } + +/* dark mode */ +@media (prefers-color-scheme: dark) { + .tab-active { + background-color: #616161; + color: #fff; + } + + #record-notes-container > div:first-child { + padding: 15px; + border-bottom: 1px #6d6d6d dashed; + } +} \ No newline at end of file diff --git a/pointercrate-user-pages/static/js/account/profile.js b/pointercrate-user-pages/static/js/account/profile.js index f2fec96b7..2efec7fbc 100644 --- a/pointercrate-user-pages/static/js/account/profile.js +++ b/pointercrate-user-pages/static/js/account/profile.js @@ -22,7 +22,9 @@ function setupGetAccessToken() { getTokenForm.onSubmit(function () { post("/api/v1/auth/", {}) .then((response) => { - accessToken.innerText = response.data.token; + loginPassword.value = ""; + accessToken.innerHTML = response.data.token; + htmlLoginForm.style.display = "none"; accessTokenArea.style.display = "block"; }) .catch(displayError(getTokenForm));