From 6b7d5421356c84acf67235b64fe1e85b7987886a Mon Sep 17 00:00:00 2001 From: Buster Neece Date: Tue, 23 Jul 2024 02:37:47 -0500 Subject: [PATCH 01/10] Fix bug with full URL parsing of VRC world IDs. --- backend/src/Service/VrcApi.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/src/Service/VrcApi.php b/backend/src/Service/VrcApi.php index a6be4eb..3cf7812 100644 --- a/backend/src/Service/VrcApi.php +++ b/backend/src/Service/VrcApi.php @@ -104,8 +104,9 @@ public static function parseWorldId(string $worldId): string // URLs in the form of: // https://vrchat.com/home/launch?worldId=wrld_4cf554b4-430c-4f8f-b53e-1f294eed230b&... $queryParams = Query::parse($uri->getQuery()); - if (!empty($queryParams['worldid'])) { - return trim($queryParams['worldid']); + + if (!empty($queryParams['worldId'])) { + return trim($queryParams['worldId']); } } From 7b7d5f65eec20261af790214fa78f6a902c4d80d Mon Sep 17 00:00:00 2001 From: Buster Neece Date: Tue, 23 Jul 2024 02:38:49 -0500 Subject: [PATCH 02/10] Move various utility functions to their own JS libraries. --- backend/templates/account/recover.twig | 2 ++ backend/templates/account/register.twig | 2 ++ backend/templates/dashboard/password.twig | 2 ++ backend/templates/events/defective.twig | 2 ++ frontend/dateTime.js | 12 ++++++++++++ frontend/layout.js | 19 ------------------- frontend/passwordStrength.js | 3 +++ vite.config.js | 14 ++++++++++---- 8 files changed, 33 insertions(+), 23 deletions(-) create mode 100644 frontend/dateTime.js create mode 100644 frontend/passwordStrength.js diff --git a/backend/templates/account/recover.twig b/backend/templates/account/recover.twig index ddebb6c..3922623 100644 --- a/backend/templates/account/recover.twig +++ b/backend/templates/account/recover.twig @@ -3,6 +3,8 @@ {% block head %} {{ parent() }} + {{ renderAssets('frontend/passwordStrength.js') }} + {% endblock %} @@ -53,10 +26,6 @@
- - -
- @@ -67,7 +36,7 @@ - + diff --git a/backend/templates/dashboard/admin/worlds/list.twig b/backend/templates/dashboard/admin/worlds/list.twig index f658e0a..8a6d9bc 100644 --- a/backend/templates/dashboard/admin/worlds/list.twig +++ b/backend/templates/dashboard/admin/worlds/list.twig @@ -5,38 +5,11 @@ {% block head %} {{ parent() }} - {% endblock %} @@ -53,30 +26,26 @@
Team? DJ? Banned?ActionsActions
+
- + - + {% for row in worlds %} {% endif %} - -
ImageImage World NameActionsActions
- + World Image {{ row.title }} diff --git a/frontend/dataTable.js b/frontend/dataTable.js new file mode 100644 index 0000000..34d1c96 --- /dev/null +++ b/frontend/dataTable.js @@ -0,0 +1,4 @@ +import DataTable from 'datatables.net-bs5'; +import "datatables.net-bs5/css/dataTables.bootstrap5.css"; + +window.DataTable = DataTable; diff --git a/package-lock.json b/package-lock.json index db40c9d..c88ecbf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,11 +7,13 @@ "dependencies": { "bootstrap": "^5.3.2", "bootstrap-icons": "^1.11.3", + "datatables.net-bs5": "^2.1.0", "jquery": "^3.7.1", "luxon": "^3.4.4", "sweetalert2": "11.4.8" }, "devDependencies": { + "glob": "^11.0.0", "sass": "^1.70.0", "vite": "^5.0.12" } @@ -407,6 +409,35 @@ "node": ">=12" } }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@popperjs/core": { "version": "2.11.8", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", @@ -649,6 +680,32 @@ "dev": true, "license": "MIT" }, + "node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -663,6 +720,13 @@ "node": ">= 8" } }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -711,6 +775,16 @@ ], "license": "MIT" }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, "node_modules/braces": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", @@ -749,6 +823,74 @@ "fsevents": "~2.3.2" } }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/datatables.net": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/datatables.net/-/datatables.net-2.1.0.tgz", + "integrity": "sha512-pFfCCwbMjAY3NY947y6oNMxPwABcsK+YY18/tZdZxp5FLeluWbS93efWVBxeXoHV9mm+hq5KfhYOe6LXtVIe0g==", + "license": "MIT", + "dependencies": { + "jquery": ">=1.7" + } + }, + "node_modules/datatables.net-bs5": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/datatables.net-bs5/-/datatables.net-bs5-2.1.0.tgz", + "integrity": "sha512-8eIkLVvcnTAk6rebLUD334yC96tYkARkVjeGn4ovGtmtAsgHYljiJl7ReWbCsSIoDfoBh512R7xk2x5MjvUi8g==", + "license": "MIT", + "dependencies": { + "datatables.net": "2.1.0", + "jquery": ">=1.7" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, "node_modules/esbuild": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", @@ -801,6 +943,23 @@ "node": ">=8" } }, + "node_modules/foreground-child": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", + "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -816,6 +975,30 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/glob": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz", + "integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^4.0.1", + "minimatch": "^10.0.0", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", @@ -859,6 +1042,16 @@ "node": ">=0.10.0" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -882,12 +1075,48 @@ "node": ">=0.12.0" } }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.1.tgz", + "integrity": "sha512-cub8rahkh0Q/bw1+GxP7aeSe29hHHn2V4m29nnDlvCdlgU+3UGxkZp7Z53jLUdpX3jdTO0nJZUDl3xvbWc2Xog==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jquery": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==", "license": "MIT" }, + "node_modules/lru-cache": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.0.tgz", + "integrity": "sha512-Qv32eSV1RSCfhY3fpPE2GNZ8jgM9X7rdAfemLWqTUxwiyIC4jJ6Sy0fZ8H+oLWevO6i4/bizg7c8d8i6bxrzbA==", + "dev": true, + "license": "ISC", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/luxon": { "version": "3.4.4", "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.4.4.tgz", @@ -897,6 +1126,32 @@ "node": ">=12" } }, + "node_modules/minimatch": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -926,6 +1181,40 @@ "node": ">=0.10.0" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-scurry": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/picocolors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", @@ -1042,6 +1331,42 @@ "node": ">=14.0.0" } }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/source-map-js": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", @@ -1052,6 +1377,110 @@ "node": ">=0.10.0" } }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/sweetalert2": { "version": "11.4.8", "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.4.8.tgz", @@ -1130,6 +1559,120 @@ "optional": true } } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } } } } diff --git a/package.json b/package.json index 9450f9c..240faa1 100644 --- a/package.json +++ b/package.json @@ -3,11 +3,13 @@ "dependencies": { "bootstrap": "^5.3.2", "bootstrap-icons": "^1.11.3", + "datatables.net-bs5": "^2.1.0", "jquery": "^3.7.1", "luxon": "^3.4.4", "sweetalert2": "11.4.8" }, "devDependencies": { + "glob": "^11.0.0", "sass": "^1.70.0", "vite": "^5.0.12" }, From 74291542890d0e63157e5ae927cc26fb0649ab78 Mon Sep 17 00:00:00 2001 From: Buster Neece Date: Tue, 23 Jul 2024 02:45:14 -0500 Subject: [PATCH 04/10] DataTable-ify Short URLs management UI. --- .../templates/dashboard/admin/worlds/list.twig | 2 +- backend/templates/dashboard/short_urls/list.twig | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/backend/templates/dashboard/admin/worlds/list.twig b/backend/templates/dashboard/admin/worlds/list.twig index 8a6d9bc..c457971 100644 --- a/backend/templates/dashboard/admin/worlds/list.twig +++ b/backend/templates/dashboard/admin/worlds/list.twig @@ -27,7 +27,7 @@
diff --git a/backend/templates/dashboard/short_urls/list.twig b/backend/templates/dashboard/short_urls/list.twig index e7a49a4..dc13382 100644 --- a/backend/templates/dashboard/short_urls/list.twig +++ b/backend/templates/dashboard/short_urls/list.twig @@ -2,6 +2,18 @@ {% import "macros/breadcrumbs.twig" as breadcrumbs %} +{% block head %} + {{ parent() }} + + {{ renderAssets('frontend/dataTable.js') }} + + +{% endblock %} + {% block content %} {{ breadcrumbs.body( { @@ -22,13 +34,13 @@
- +
- + From 11a7847fe49a66218e87ec1468773863e038fad2 Mon Sep 17 00:00:00 2001 From: Buster Neece Date: Tue, 23 Jul 2024 03:19:47 -0500 Subject: [PATCH 05/10] Update default permissions on console binary. --- backend/bin/console | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 backend/bin/console diff --git a/backend/bin/console b/backend/bin/console old mode 100644 new mode 100755 From d4a389be57bcb36d4eb6c0e3f099d838631bdbae Mon Sep 17 00:00:00 2001 From: Buster Neece Date: Tue, 23 Jul 2024 03:21:27 -0500 Subject: [PATCH 06/10] Fixes #34 -- Expand long_url length for short URLs. --- .../templates/dashboard/short_urls/edit.twig | 2 +- .../20240723081524_expand_short_url.php | 23 +++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 db/migrations/20240723081524_expand_short_url.php diff --git a/backend/templates/dashboard/short_urls/edit.twig b/backend/templates/dashboard/short_urls/edit.twig index bbcfaab..aa41a2a 100644 --- a/backend/templates/dashboard/short_urls/edit.twig +++ b/backend/templates/dashboard/short_urls/edit.twig @@ -37,7 +37,7 @@ + class="form-control form-control-lg" required>

You can use a relative (i.e. /dashboard) or an absolute (i.e. https://example.com) URL.

diff --git a/db/migrations/20240723081524_expand_short_url.php b/db/migrations/20240723081524_expand_short_url.php new file mode 100644 index 0000000..bd1b92b --- /dev/null +++ b/db/migrations/20240723081524_expand_short_url.php @@ -0,0 +1,23 @@ +table('web_short_urls') + ->changeColumn( + 'long_url', + (new Column()) + ->setType(Column::TEXT) + ->setNull(true) + )->update(); + } +} From 9a81322351bfacf21a30f320e0ae69f0dbb3a94b Mon Sep 17 00:00:00 2001 From: Buster Neece Date: Tue, 23 Jul 2024 03:26:40 -0500 Subject: [PATCH 07/10] Fixes #37 -- Sort talents by number of people who have them. --- backend/src/Controller/TalentAction.php | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/Controller/TalentAction.php b/backend/src/Controller/TalentAction.php index 1f34902..9de5792 100644 --- a/backend/src/Controller/TalentAction.php +++ b/backend/src/Controller/TalentAction.php @@ -26,6 +26,7 @@ public function __invoke( JOIN web_users u ON s.creator = u.id WHERE u.banned != 1 GROUP BY skill + ORDER BY occurrences DESC SQL ); From 9dd3385defb38f8dc525ddb6b98c9da92c6faab4 Mon Sep 17 00:00:00 2001 From: Buster Neece Date: Tue, 23 Jul 2024 06:01:58 -0500 Subject: [PATCH 08/10] Implement a new common CRUD controller. --- .../Dashboard/AbstractCrudController.php | 177 ++++++++++++++++++ .../Dashboard/Admin/UsersAction.php | 2 +- .../Dashboard/Admin/WorldsController.php | 154 +++++---------- .../Dashboard/ShortUrlsController.php | 176 ++--------------- backend/templates/dashboard/admin/users.twig | 80 ++++---- .../dashboard/admin/worlds/list.twig | 7 +- .../templates/dashboard/short_urls/list.twig | 32 ++-- 7 files changed, 306 insertions(+), 322 deletions(-) create mode 100644 backend/src/Controller/Dashboard/AbstractCrudController.php diff --git a/backend/src/Controller/Dashboard/AbstractCrudController.php b/backend/src/Controller/Dashboard/AbstractCrudController.php new file mode 100644 index 0000000..b258c2e --- /dev/null +++ b/backend/src/Controller/Dashboard/AbstractCrudController.php @@ -0,0 +1,177 @@ +db->createQueryBuilder() + ->select('*') + ->from($this->tableName) + ->fetchAllAssociative(); + + return $request->getView()->renderToResponse( + $response, + $this->listTemplateName, + [ + 'records' => $records, + ] + ); + } + + public function createAction( + ServerRequest $request, + Response $response, + array $params + ): ResponseInterface { + $row = []; + $error = null; + + if ($request->isPost()) { + try { + $row = $this->modifyFromRequest($request); + + unset($row[$this->idField]); + + $this->db->insert($this->tableName, $row); + + $request->getFlash()->success(sprintf('%s created.', $this->itemName)); + + return $response->withRedirect( + $request->getRouter()->urlFor($this->listRouteName) + ); + } catch (\Throwable $e) { + $error = $e->getMessage(); + } + } + + return $request->getView()->renderToResponse( + $response, + $this->editTemplateName, + [ + 'isEditMode' => false, + 'row' => $row, + 'error' => $error, + ] + ); + } + + public function editAction( + ServerRequest $request, + Response $response, + array $params + ): ResponseInterface { + $id = $this->requireId($request, $params); + + $row = $this->getRecord($id); + unset($row[$this->idField]); + + $error = null; + + if ($request->isPost()) { + try { + $row = $this->modifyFromRequest( + $request, + $row, + true + ); + + $this->db->update($this->tableName, $row, [$this->idField => $id]); + + $request->getFlash()->success(sprintf('%s updated.', $this->itemName)); + + return $response->withRedirect( + $request->getRouter()->urlFor($this->listRouteName) + ); + } catch (\Throwable $e) { + $error = $e->getMessage(); + } + } + + return $request->getView()->renderToResponse( + $response, + $this->editTemplateName, + [ + 'isEditMode' => true, + 'row' => $row, + 'error' => $error, + ] + ); + } + + protected function requireId( + ServerRequest $request, + array $params + ): int|string { + $id = $params[$this->idField] ?? $request->getParam($this->idField); + if (empty($id)) { + throw new \InvalidArgumentException('ID is required.'); + } + + return $id; + } + + protected function getRecord( + int|string $id + ): array { + $row = $this->db->createQueryBuilder() + ->select('*') + ->from($this->tableName) + ->where($this->db->createExpressionBuilder()->eq($this->idField, ':id')) + ->setParameter('id', $id) + ->fetchAssociative(); + + if ($row === false) { + throw new \InvalidArgumentException(sprintf('%s not found!', $this->itemName)); + } + + return $row; + } + + abstract protected function modifyFromRequest( + ServerRequest $request, + array $row = [], + bool $isEditMode = false + ): array; + + public function deleteAction( + ServerRequest $request, + Response $response, + array $params + ): ResponseInterface { + $id = $this->requireId($request, $params); + + $this->db->delete( + $this->tableName, + [ + $this->idField => $id, + ] + ); + + $request->getFlash()->success(sprintf('%s deleted.', $this->itemName)); + + return $response->withRedirect( + $request->getRouter()->urlFor($this->listRouteName) + ); + } +} diff --git a/backend/src/Controller/Dashboard/Admin/UsersAction.php b/backend/src/Controller/Dashboard/Admin/UsersAction.php index 98717e1..7ca0d57 100644 --- a/backend/src/Controller/Dashboard/Admin/UsersAction.php +++ b/backend/src/Controller/Dashboard/Admin/UsersAction.php @@ -31,7 +31,7 @@ public function __invoke( $response, 'dashboard/admin/users', [ - 'users' => $users, + 'records' => $users, ] ); } diff --git a/backend/src/Controller/Dashboard/Admin/WorldsController.php b/backend/src/Controller/Dashboard/Admin/WorldsController.php index f2af8db..77c17be 100644 --- a/backend/src/Controller/Dashboard/Admin/WorldsController.php +++ b/backend/src/Controller/Dashboard/Admin/WorldsController.php @@ -2,118 +2,79 @@ namespace App\Controller\Dashboard\Admin; -use App\Exception\NotFoundException; +use App\Controller\Dashboard\AbstractCrudController; use App\Http\Response; use App\Http\ServerRequest; use App\Media; use App\Service\VrcApi; -use Doctrine\DBAL\ArrayParameterType; use Doctrine\DBAL\Connection; use GuzzleHttp\Client; use Intervention\Image\ImageManager; -use League\Flysystem\UnableToDeleteFile; use Psr\Http\Message\ResponseInterface; -use Psr\Http\Message\UploadedFileInterface; -final readonly class WorldsController +final class WorldsController extends AbstractCrudController { private Client $vrcApiClient; public function __construct( - private Connection $db, private ImageManager $imageManager, private Client $httpClient, + Connection $db, VrcApi $vrcApi ) { + parent::__construct( + $db, + 'Spotlighted World', + 'dashboard:admin:worlds', + 'dashboard/admin/worlds/list', + 'dashboard/admin/worlds/create', + 'web_worlds' + ); + $this->vrcApiClient = $vrcApi->getHttpClient(); } - public function listAction( - ServerRequest $request, - Response $response, - array $params - ): ResponseInterface { - $worlds = $this->db->fetchAllAssociative( - <<<'SQL' - SELECT w.* - FROM web_worlds AS w - ORDER BY id DESC - SQL - ); - - return $request->getView()->renderToResponse( - $response, - 'dashboard/admin/worlds/list', - [ - 'worlds' => $worlds, - ] - ); + public function editAction(ServerRequest $request, Response $response, array $params): ResponseInterface + { + throw new \RuntimeException('Not available!'); } - public function createAction( - ServerRequest $request, - Response $response, - array $params - ): ResponseInterface { + protected function modifyFromRequest(ServerRequest $request, array $row = [], bool $isEditMode = false): array + { $currentUser = $request->getCurrentUser(); assert($currentUser !== null); - $error = null; - - if ($request->isPost()) { - try { - $worldId = $request->getParam('id'); - if (empty($worldId)) { - throw new \InvalidArgumentException('World ID not specified.'); - } - - $worldId = VrcApi::parseWorldId($worldId); - - // Fetch world info from the VRC API. - $worldInfo = VrcApi::processResponse( - $this->vrcApiClient->get(sprintf('worlds/%s', $worldId)) - ); - - // Pull the world image - $imageUrl = $worldInfo['imageUrl']; - $imageData = $this->httpClient->get($imageUrl)->getBody()->getContents(); - - $imageRelativePath = Media::worldPath($worldId . '.png'); - - $image = $this->imageManager->read($imageData); - - $fs = Media::getFilesystem(); - $fs->write($imageRelativePath, $image->encodeByPath($imageRelativePath)->toString()); - - // Add the DB record - $this->db->insert( - 'web_worlds', - [ - 'title' => $worldInfo['name'], - 'creator' => $currentUser['id'], - 'image' => $imageRelativePath, - 'description' => $worldInfo['description'], - 'world_id' => $worldId, - 'world_creator' => $worldInfo['authorName'], - ] - ); - - $request->getFlash()->success('World successfully imported!'); - return $response->withRedirect( - $request->getRouter()->urlFor('dashboard:admin:worlds') - ); - } catch (\Throwable $e) { - $error = $e->getMessage(); - } + $worldId = $request->getParam('id'); + if (empty($worldId)) { + throw new \InvalidArgumentException('World ID not specified.'); } - return $request->getView()->renderToResponse( - $response, - 'dashboard/admin/worlds/create', - [ - 'error' => $error, - ] + $worldId = VrcApi::parseWorldId($worldId); + + // Fetch world info from the VRC API. + $worldInfo = VrcApi::processResponse( + $this->vrcApiClient->get(sprintf('worlds/%s', $worldId)) ); + + // Pull the world image + $imageUrl = $worldInfo['imageUrl']; + $imageData = $this->httpClient->get($imageUrl)->getBody()->getContents(); + + $imageRelativePath = Media::worldPath($worldId . '.png'); + + $image = $this->imageManager->read($imageData); + + $fs = Media::getFilesystem(); + $fs->write($imageRelativePath, $image->encodeByPath($imageRelativePath)->toString()); + + return [ + 'title' => $worldInfo['name'], + 'creator' => $currentUser['id'], + 'image' => $imageRelativePath, + 'description' => $worldInfo['description'], + 'world_id' => $worldId, + 'world_creator' => $worldInfo['authorName'], + ]; } public function deleteAction( @@ -121,36 +82,23 @@ public function deleteAction( Response $response, array $params ): ResponseInterface { - $id = $params['id']; - - $world = $this->db->fetchAssociative( - <<<'SQL' - SELECT id, image - FROM web_worlds - WHERE id = :id - SQL, - [ - 'id' => $id, - ] - ); + $id = $this->requireId($request, $params); - if ($world === false) { - throw NotFoundException::world($request); - } + $world = $this->getRecord($id); $fs = Media::getFilesystem(); $fs->delete($world['image']); $this->db->delete( - 'web_worlds', + $this->tableName, [ - 'id' => $world['id'], + $this->idField => $world[$this->idField], ] ); - $request->getFlash()->success('World removed.'); + $request->getFlash()->success(sprintf('%s removed.', $this->itemName)); return $response->withRedirect( - $request->getRouter()->urlFor('dashboard:admin:worlds') + $request->getRouter()->urlFor($this->listRouteName) ); } } diff --git a/backend/src/Controller/Dashboard/ShortUrlsController.php b/backend/src/Controller/Dashboard/ShortUrlsController.php index fb9a9a0..478afb4 100644 --- a/backend/src/Controller/Dashboard/ShortUrlsController.php +++ b/backend/src/Controller/Dashboard/ShortUrlsController.php @@ -2,181 +2,39 @@ namespace App\Controller\Dashboard; -use App\Http\Response; use App\Http\ServerRequest; use Doctrine\DBAL\Connection; -use Psr\Http\Message\ResponseInterface; -final readonly class ShortUrlsController +final class ShortUrlsController extends AbstractCrudController { public function __construct( - private Connection $db + Connection $db ) { - } - - public function listAction( - ServerRequest $request, - Response $response, - array $params - ): ResponseInterface { - $shortUrls = $this->db->fetchAllAssociative( - <<<'SQL' - SELECT * - FROM web_short_urls - ORDER BY short_url ASC - SQL - ); - - return $request->getView()->renderToResponse( - $response, + parent::__construct( + $db, + 'Short URL', + 'dashboard:short_urls', 'dashboard/short_urls/list', - [ - 'shortUrls' => $shortUrls, - ] + 'dashboard/short_urls/edit', + 'web_short_urls' ); } - public function createAction( - ServerRequest $request, - Response $response, - array $params - ): ResponseInterface { + protected function modifyFromRequest(ServerRequest $request, array $row = [], bool $isEditMode = false): array + { $currentUser = $request->getCurrentUser(); assert($currentUser !== null); - $row = []; - $error = null; - - if ($request->isPost()) { - try { - $postData = $request->getParsedBody(); - $row['short_url'] = trim($postData['short_url'] ?? '', '/'); - $row['long_url'] = $postData['long_url'] ?? null; - - if (empty($row['short_url']) || empty($row['long_url'])) { - throw new \InvalidArgumentException('Short and Long URL are required.'); - } - - $this->db->insert( - 'web_short_urls', - [ - 'short_url' => $row['short_url'], - 'long_url' => $row['long_url'], - 'creator' => $currentUser['id'], - ] - ); - - $request->getFlash()->success('Short URL created.'); + $row['creator'] = $currentUser['id']; - return $response->withRedirect( - $request->getRouter()->urlFor('dashboard:short_urls') - ); - } catch (\Throwable $e) { - $error = $e->getMessage(); - } - } - - return $request->getView()->renderToResponse( - $response, - 'dashboard/short_urls/edit', - [ - 'isEditMode' => false, - 'row' => $row, - 'error' => $error, - ] - ); - } - - public function editAction( - ServerRequest $request, - Response $response, - array $params - ): ResponseInterface { - $id = $params['id'] ?? $request->getParam('id'); - if (empty($id)) { - throw new \InvalidArgumentException('ID is required.'); - } - - $row = $this->db->fetchAssociative( - <<<'SQL' - SELECT * - FROM web_short_urls - WHERE id = :id - SQL, - [ - 'id' => $id, - ] - ); + $postData = $request->getParsedBody(); + $row['short_url'] = trim($postData['short_url'] ?? '', '/'); + $row['long_url'] = $postData['long_url'] ?? null; - if ($row === false) { - throw new \InvalidArgumentException('Record not found!'); + if (empty($row['short_url']) || empty($row['long_url'])) { + throw new \InvalidArgumentException('Short and Long URL are required.'); } - $error = null; - - if ($request->isPost()) { - try { - $postData = $request->getParsedBody(); - $row['short_url'] = trim($postData['short_url'] ?? '', '/'); - $row['long_url'] = $postData['long_url'] ?? null; - - if (empty($row['short_url']) || empty($row['long_url'])) { - throw new \InvalidArgumentException('Short and Long URL are required.'); - } - - $this->db->update( - 'web_short_urls', - [ - 'short_url' => $row['short_url'], - 'long_url' => $row['long_url'], - ], - [ - 'id' => $row['id'], - ] - ); - - $request->getFlash()->success('Short URL updated.'); - - return $response->withRedirect( - $request->getRouter()->urlFor('dashboard:short_urls') - ); - } catch (\Throwable $e) { - $error = $e->getMessage(); - } - } - - return $request->getView()->renderToResponse( - $response, - 'dashboard/short_urls/edit', - [ - 'isEditMode' => true, - 'row' => $row, - 'error' => $error, - ] - ); - } - - public function deleteAction( - ServerRequest $request, - Response $response, - array $params - ): ResponseInterface { - $id = $params['id'] ?? $request->getParam('id'); - if (empty($id)) { - throw new \InvalidArgumentException('ID is required.'); - } - - $this->db->delete( - 'web_short_urls', - [ - 'id' => $id, - ] - ); - - $request->getFlash()->success('Short URL deleted.'); - - return $response->withRedirect( - $request->getRouter()->urlFor('dashboard:short_urls') - ); + return $row; } } diff --git a/backend/templates/dashboard/admin/users.twig b/backend/templates/dashboard/admin/users.twig index 149a2ab..a8383f5 100644 --- a/backend/templates/dashboard/admin/users.twig +++ b/backend/templates/dashboard/admin/users.twig @@ -40,46 +40,46 @@
- {% for row in users %} - - - - {% if row.is_admin is same as 1 %} - - {% else %} - - {% endif %} - {% if row.is_mod is same as 1 %} - - {% else %} - - {% endif %} - {% if row.is_team is same as 1 %} - - {% else %} - - {% endif %} - {% if row.is_dj is same as 1 %} - - {% else %} - - {% endif %} - {% if row.banned is same as 1 %} - - {% else %} - - {% endif %} - - + {% for row in records %} + + + + {% if row.is_admin is same as 1 %} + + {% else %} + + {% endif %} + {% if row.is_mod is same as 1 %} + + {% else %} + + {% endif %} + {% if row.is_team is same as 1 %} + + {% else %} + + {% endif %} + {% if row.is_dj is same as 1 %} + + {% else %} + + {% endif %} + {% if row.banned is same as 1 %} + + {% else %} + + {% endif %} + + {% endfor %}
Short URL Long URL ViewsActionsActions
{{ row.id }}YesNoYesNoYesNoYesNoYesNo - - Edit - - - View - -
{{ row.id }}YesNoYesNoYesNoYesNoYesNo + + Edit + + + View + +
diff --git a/backend/templates/dashboard/admin/worlds/list.twig b/backend/templates/dashboard/admin/worlds/list.twig index c457971..cf41ed0 100644 --- a/backend/templates/dashboard/admin/worlds/list.twig +++ b/backend/templates/dashboard/admin/worlds/list.twig @@ -33,7 +33,7 @@
+ data-order="[[1, "asc"]]"> @@ -42,10 +42,11 @@ - {% for row in worlds %} + {% for row in records %} - {% for row in shortUrls %} - - - - - - + {% for row in records %} + + + + + + {% endfor %}
Image
- World Image + World Image {{ row.title }} diff --git a/backend/templates/dashboard/short_urls/list.twig b/backend/templates/dashboard/short_urls/list.twig index dc13382..0544491 100644 --- a/backend/templates/dashboard/short_urls/list.twig +++ b/backend/templates/dashboard/short_urls/list.twig @@ -44,22 +44,22 @@
{{ row.short_url }}{{ row.long_url }}{{ row.views }} - - Edit - - - Delete - -
{{ row.short_url }}{{ row.long_url }}{{ row.views }} + + Edit + + + Delete + +
From 4c81ee80c50aa91e0cb7e8106d64b55210dceaef Mon Sep 17 00:00:00 2001 From: Buster Neece Date: Tue, 23 Jul 2024 07:15:58 -0500 Subject: [PATCH 09/10] Fixes #29 -- Add CRUD controllers for Groups and Poster Types. --- backend/config/routes.php | 58 ++++++- .../Dashboard/AbstractCrudController.php | 2 - .../Dashboard/Admin/GroupsController.php | 43 +++++ .../Dashboard/Admin/PosterTypesController.php | 43 +++++ .../dashboard/admin/groups/edit.twig | 63 ++++++++ .../dashboard/admin/groups/list.twig | 68 ++++++++ .../dashboard/admin/poster_types/edit.twig | 63 ++++++++ .../dashboard/admin/poster_types/list.twig | 68 ++++++++ backend/templates/dashboard/admin/users.twig | 2 +- .../dashboard/admin/worlds/list.twig | 2 +- backend/templates/dashboard/index.twig | 150 +++++++++--------- .../templates/dashboard/short_urls/edit.twig | 6 + .../layouts/sections/dashboard_sidebar.twig | 19 ++- 13 files changed, 504 insertions(+), 83 deletions(-) create mode 100644 backend/src/Controller/Dashboard/Admin/GroupsController.php create mode 100644 backend/src/Controller/Dashboard/Admin/PosterTypesController.php create mode 100644 backend/templates/dashboard/admin/groups/edit.twig create mode 100644 backend/templates/dashboard/admin/groups/list.twig create mode 100644 backend/templates/dashboard/admin/poster_types/edit.twig create mode 100644 backend/templates/dashboard/admin/poster_types/list.twig diff --git a/backend/config/routes.php b/backend/config/routes.php index b6348ab..1276299 100644 --- a/backend/config/routes.php +++ b/backend/config/routes.php @@ -37,6 +37,59 @@ ->setName('dashboard'); $group->group('/admin', function (RouteCollectorProxy $group) { + $group->group('/groups', function (RouteCollectorProxy $group) { + $group->get( + '', + App\Controller\Dashboard\Admin\GroupsController::class . ':listAction' + )->setName('dashboard:admin:groups'); + + $group->map( + ['GET', 'POST'], + '/create', + App\Controller\Dashboard\Admin\GroupsController::class . ':createAction' + )->setName('dashboard:admin:groups:create'); + + $group->map( + ['GET', 'POST'], + '/edit[/{id}]', + App\Controller\Dashboard\Admin\GroupsController::class . ':editAction' + )->setName('dashboard:admin:groups:edit'); + + $group->get( + '/delete[/{id}]', + App\Controller\Dashboard\Admin\GroupsController::class . ':deleteAction' + )->setName('dashboard:admin:groups:delete'); + }); + + $group->group('/poster_types', function (RouteCollectorProxy $group) { + $group->get( + '', + App\Controller\Dashboard\Admin\PosterTypesController::class . ':listAction' + )->setName('dashboard:admin:poster_types'); + + $group->map( + ['GET', 'POST'], + '/create', + App\Controller\Dashboard\Admin\PosterTypesController::class . ':createAction' + )->setName('dashboard:admin:poster_types:create'); + + $group->map( + ['GET', 'POST'], + '/edit[/{id}]', + App\Controller\Dashboard\Admin\PosterTypesController::class . ':editAction' + )->setName('dashboard:admin:poster_types:edit'); + + $group->get( + '/delete[/{id}]', + App\Controller\Dashboard\Admin\PosterTypesController::class . ':deleteAction' + )->setName('dashboard:admin:poster_types:delete'); + }); + + $group->get( + '/users', + App\Controller\Dashboard\Admin\UsersAction::class + )->setName('dashboard:admin:users'); + $group->group('/worlds', function (RouteCollectorProxy $group) { $group->get( '', @@ -54,11 +107,6 @@ App\Controller\Dashboard\Admin\WorldsController::class . ':deleteAction' )->setName('dashboard:admin:worlds:delete'); }); - - $group->get( - '/users', - App\Controller\Dashboard\Admin\UsersAction::class - )->setName('dashboard:admin:users'); })->add(new App\Middleware\Auth\RequireAdmin()); $group->map( diff --git a/backend/src/Controller/Dashboard/AbstractCrudController.php b/backend/src/Controller/Dashboard/AbstractCrudController.php index b258c2e..2f2e195 100644 --- a/backend/src/Controller/Dashboard/AbstractCrudController.php +++ b/backend/src/Controller/Dashboard/AbstractCrudController.php @@ -51,8 +51,6 @@ public function createAction( try { $row = $this->modifyFromRequest($request); - unset($row[$this->idField]); - $this->db->insert($this->tableName, $row); $request->getFlash()->success(sprintf('%s created.', $this->itemName)); diff --git a/backend/src/Controller/Dashboard/Admin/GroupsController.php b/backend/src/Controller/Dashboard/Admin/GroupsController.php new file mode 100644 index 0000000..0c1f9dd --- /dev/null +++ b/backend/src/Controller/Dashboard/Admin/GroupsController.php @@ -0,0 +1,43 @@ +getParsedBody(); + + if (!$isEditMode) { + $row['id'] = $postData['id'] ?? null; + if (empty($row['id'])) { + throw new \InvalidArgumentException('ID is required.'); + } + } + + $row['name'] = $postData['name'] ?? null; + + if (empty($row['name'])) { + throw new \InvalidArgumentException('Name is required.'); + } + + return $row; + } +} diff --git a/backend/src/Controller/Dashboard/Admin/PosterTypesController.php b/backend/src/Controller/Dashboard/Admin/PosterTypesController.php new file mode 100644 index 0000000..afbaeb4 --- /dev/null +++ b/backend/src/Controller/Dashboard/Admin/PosterTypesController.php @@ -0,0 +1,43 @@ +getParsedBody(); + + if (!$isEditMode) { + $row['id'] = $postData['id'] ?? null; + if (empty($row['id'])) { + throw new \InvalidArgumentException('ID is required.'); + } + } + + $row['name'] = $postData['name'] ?? null; + + if (empty($row['name'])) { + throw new \InvalidArgumentException('Name is required.'); + } + + return $row; + } +} diff --git a/backend/templates/dashboard/admin/groups/edit.twig b/backend/templates/dashboard/admin/groups/edit.twig new file mode 100644 index 0000000..6154ab1 --- /dev/null +++ b/backend/templates/dashboard/admin/groups/edit.twig @@ -0,0 +1,63 @@ +{% extends "layouts/dashboard.twig" %} + +{% import "macros/breadcrumbs.twig" as breadcrumbs %} + +{% block content %} + {{ breadcrumbs.body( + { + 'dashboard': 'My Dashboard', + 'dashboard:admin:groups': 'Manage Groups' + }, + (isEditMode) ? 'Edit Group' : 'Create Group' + ) }} + +

Manage Groups

+ +
+

+ {% if isEditMode %} + Edit Group + {% else %} + Create Group + {% endif %} +

+
+ {% if error %} +
+ Error: {{ error }} +
+ {% endif %} + +
+
+
+ {% if not isEditMode %} +
+ + + +

+ The programmatic ID of the group. Should be only letters, numbers and underscores + (i.e. my_group_1). +

+
+ {% endif %} + +
+ + +
+
+
+ +
+ +
+
+
+
+{% endblock %} diff --git a/backend/templates/dashboard/admin/groups/list.twig b/backend/templates/dashboard/admin/groups/list.twig new file mode 100644 index 0000000..3258d8c --- /dev/null +++ b/backend/templates/dashboard/admin/groups/list.twig @@ -0,0 +1,68 @@ +{% extends "layouts/dashboard.twig" %} + +{% import "macros/breadcrumbs.twig" as breadcrumbs %} + +{% block head %} + {{ parent() }} + + {{ renderAssets('frontend/dataTable.js') }} + + +{% endblock %} + +{% block content %} + {{ breadcrumbs.body( + { + 'dashboard': 'My Dashboard' + }, + 'Manage Groups' + ) }} + +

Manage Groups

+ +
+

View Groups

+
+ + + + + + + + + + + + {% for row in records %} + + + + + + {% endfor %} + +
Group IDGroup NameActions
{{ row.id }}{{ row.name }} + + Edit + + + Delete + +
+
+
+{% endblock %} diff --git a/backend/templates/dashboard/admin/poster_types/edit.twig b/backend/templates/dashboard/admin/poster_types/edit.twig new file mode 100644 index 0000000..1bd1ea0 --- /dev/null +++ b/backend/templates/dashboard/admin/poster_types/edit.twig @@ -0,0 +1,63 @@ +{% extends "layouts/dashboard.twig" %} + +{% import "macros/breadcrumbs.twig" as breadcrumbs %} + +{% block content %} + {{ breadcrumbs.body( + { + 'dashboard': 'My Dashboard', + 'dashboard:admin:poster_types': 'Manage Poster Types' + }, + (isEditMode) ? 'Edit Poster Type' : 'Create Poster Type' + ) }} + +

Manage Poster Types

+ +
+

+ {% if isEditMode %} + Edit Poster Type + {% else %} + Create Poster Type + {% endif %} +

+
+ {% if error %} +
+ Error: {{ error }} +
+ {% endif %} + +
+
+
+ {% if not isEditMode %} +
+ + + +

+ The programmatic ID of the poster type. Should be only letters, numbers and + underscores (i.e. my_poster_type_1). +

+
+ {% endif %} + +
+ + +
+
+
+ +
+ +
+
+
+
+{% endblock %} diff --git a/backend/templates/dashboard/admin/poster_types/list.twig b/backend/templates/dashboard/admin/poster_types/list.twig new file mode 100644 index 0000000..0578b79 --- /dev/null +++ b/backend/templates/dashboard/admin/poster_types/list.twig @@ -0,0 +1,68 @@ +{% extends "layouts/dashboard.twig" %} + +{% import "macros/breadcrumbs.twig" as breadcrumbs %} + +{% block head %} + {{ parent() }} + + {{ renderAssets('frontend/dataTable.js') }} + + +{% endblock %} + +{% block content %} + {{ breadcrumbs.body( + { + 'dashboard': 'My Dashboard' + }, + 'Manage Poster Types' + ) }} + +

Manage Poster Types

+ +
+

View Poster Types

+
+ + + + + + + + + + + + {% for row in records %} + + + + + + {% endfor %} + +
Group IDGroup NameActions
{{ row.id }}{{ row.name }} + + Edit + + + Delete + +
+
+
+{% endblock %} diff --git a/backend/templates/dashboard/admin/users.twig b/backend/templates/dashboard/admin/users.twig index a8383f5..8f8da6b 100644 --- a/backend/templates/dashboard/admin/users.twig +++ b/backend/templates/dashboard/admin/users.twig @@ -69,7 +69,7 @@ {% else %}
No + Edit diff --git a/backend/templates/dashboard/admin/worlds/list.twig b/backend/templates/dashboard/admin/worlds/list.twig index cf41ed0..e3f6579 100644 --- a/backend/templates/dashboard/admin/worlds/list.twig +++ b/backend/templates/dashboard/admin/worlds/list.twig @@ -49,7 +49,7 @@ style="max-width: 128px;"> {{ row.title }} + View diff --git a/backend/templates/dashboard/index.twig b/backend/templates/dashboard/index.twig index 2fef771..51546ea 100644 --- a/backend/templates/dashboard/index.twig +++ b/backend/templates/dashboard/index.twig @@ -1,96 +1,104 @@ {% extends "layouts/site.twig" %} {% block content %} -
-

My Dashboard

+
+

My Dashboard

-
-
-
-
-
-
-
-

{{ user.username }}

-
- {% set pronouns = user.pronouns|trim %} - {% if pronouns is not empty %} - +
+
+
+
+
+
+
+

{{ user.username }}

+
+ {% set pronouns = user.pronouns|trim %} + {% if pronouns is not empty %} + {{ pronouns }} - {% endif %} + {% endif %} - {% set title = user.title|trim|default('Team Member') %} - {% if user.isTeam() %} - + {% set title = user.title|trim|default('Team Member') %} + {% if user.isTeam() %} + {{ title }} - {% endif %} + {% endif %} +
-
-
-
-
-
-
-
-

Site Features

-
- - Poster Network - - - My Skills - - - {% if user.isMod() %} +
+
+
+
+

Site Features

+
-
- {% if user.isAdmin() %} -
-
-

Administration

+ {% if user.isAdmin() %} + + {% endif %}
- {% endif %}
-
{% endblock %} diff --git a/backend/templates/dashboard/short_urls/edit.twig b/backend/templates/dashboard/short_urls/edit.twig index aa41a2a..024c212 100644 --- a/backend/templates/dashboard/short_urls/edit.twig +++ b/backend/templates/dashboard/short_urls/edit.twig @@ -22,6 +22,12 @@ {% endif %}
+ {% if error %} +
+ Error: {{ error }} +
+ {% endif %} +
diff --git a/backend/templates/layouts/sections/dashboard_sidebar.twig b/backend/templates/layouts/sections/dashboard_sidebar.twig index 527f1dd..8883bb6 100644 --- a/backend/templates/layouts/sections/dashboard_sidebar.twig +++ b/backend/templates/layouts/sections/dashboard_sidebar.twig @@ -47,11 +47,24 @@ From 0422d25c57f22adb5c7643c73e615277bc1b342e Mon Sep 17 00:00:00 2001 From: Buster Neece Date: Tue, 23 Jul 2024 07:20:22 -0500 Subject: [PATCH 10/10] Fix confirm data attribute on delete buttons. --- backend/templates/dashboard/admin/groups/list.twig | 2 +- backend/templates/dashboard/admin/poster_types/list.twig | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/templates/dashboard/admin/groups/list.twig b/backend/templates/dashboard/admin/groups/list.twig index 3258d8c..e8d2afa 100644 --- a/backend/templates/dashboard/admin/groups/list.twig +++ b/backend/templates/dashboard/admin/groups/list.twig @@ -54,7 +54,7 @@ Delete diff --git a/backend/templates/dashboard/admin/poster_types/list.twig b/backend/templates/dashboard/admin/poster_types/list.twig index 0578b79..630256d 100644 --- a/backend/templates/dashboard/admin/poster_types/list.twig +++ b/backend/templates/dashboard/admin/poster_types/list.twig @@ -54,7 +54,7 @@ Delete