From 8b1bd3bc13d1790ffefdeade5d1c48aac57ab4d2 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sat, 13 Nov 2021 20:47:42 -0700 Subject: [PATCH 001/169] install "pg" module --- package-lock.json | 273 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 1 + 2 files changed, 268 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 006fc12..6499394 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "http-errors": "~1.6.2", "morgan": "~1.9.0", "nodemailer": "^6.6.5", + "pg": "^8.7.1", "pug": "2.0.0-beta11" }, "devDependencies": { @@ -195,6 +196,14 @@ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, + "node_modules/buffer-writer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", + "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==", + "engines": { + "node": ">=4" + } + }, "node_modules/bytes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", @@ -965,6 +974,11 @@ "wrappy": "1" } }, + "node_modules/packet-reader": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", + "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" + }, "node_modules/parseurl": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", @@ -992,6 +1006,115 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, + "node_modules/pg": { + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.7.1.tgz", + "integrity": "sha512-7bdYcv7V6U3KAtWjpQJJBww0UEsWuh4yQ/EjNf2HeO/NnvKjpvhEIe/A/TleP6wtmSKnUnghs5A9jUoK6iDdkA==", + "dependencies": { + "buffer-writer": "2.0.0", + "packet-reader": "1.0.0", + "pg-connection-string": "^2.5.0", + "pg-pool": "^3.4.1", + "pg-protocol": "^1.5.0", + "pg-types": "^2.1.0", + "pgpass": "1.x" + }, + "engines": { + "node": ">= 8.0.0" + }, + "peerDependencies": { + "pg-native": ">=2.0.0" + }, + "peerDependenciesMeta": { + "pg-native": { + "optional": true + } + } + }, + "node_modules/pg-connection-string": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz", + "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==" + }, + "node_modules/pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pg-pool": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.4.1.tgz", + "integrity": "sha512-TVHxR/gf3MeJRvchgNHxsYsTCHQ+4wm3VIHSS19z8NC0+gioEhq1okDY1sm/TYbfoP6JLFx01s0ShvZ3puP/iQ==", + "peerDependencies": { + "pg": ">=8.0" + } + }, + "node_modules/pg-protocol": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz", + "integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ==" + }, + "node_modules/pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "dependencies": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pgpass": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.4.tgz", + "integrity": "sha512-YmuA56alyBq7M59vxVBfPJrGSozru8QAdoNlWuW3cz8l+UX3cWge0vTvjKhsSHSJpo3Bom8/Mm6hf0TR5GY0+w==", + "dependencies": { + "split2": "^3.1.1" + } + }, + "node_modules/postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha1-AntTPAqokOJtFy1Hz5zOzFIazTU=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "dependencies": { + "xtend": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/process-nextick-args": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", @@ -1294,6 +1417,27 @@ "node": ">=0.8.0" } }, + "node_modules/split2": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", + "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", + "dependencies": { + "readable-stream": "^3.0.0" + } + }, + "node_modules/split2/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/statuses": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", @@ -1306,7 +1450,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, "dependencies": { "safe-buffer": "~5.1.0" } @@ -1434,8 +1577,7 @@ "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "node_modules/utils-merge": { "version": "1.0.1", @@ -1492,6 +1634,14 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, "node_modules/yargs": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", @@ -1650,6 +1800,11 @@ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", "dev": true }, + "buffer-writer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz", + "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==" + }, "bytes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", @@ -2258,6 +2413,11 @@ "wrappy": "1" } }, + "packet-reader": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz", + "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" + }, "parseurl": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", @@ -2279,6 +2439,84 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, + "pg": { + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.7.1.tgz", + "integrity": "sha512-7bdYcv7V6U3KAtWjpQJJBww0UEsWuh4yQ/EjNf2HeO/NnvKjpvhEIe/A/TleP6wtmSKnUnghs5A9jUoK6iDdkA==", + "requires": { + "buffer-writer": "2.0.0", + "packet-reader": "1.0.0", + "pg-connection-string": "^2.5.0", + "pg-pool": "^3.4.1", + "pg-protocol": "^1.5.0", + "pg-types": "^2.1.0", + "pgpass": "1.x" + } + }, + "pg-connection-string": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz", + "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ==" + }, + "pg-int8": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz", + "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==" + }, + "pg-pool": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.4.1.tgz", + "integrity": "sha512-TVHxR/gf3MeJRvchgNHxsYsTCHQ+4wm3VIHSS19z8NC0+gioEhq1okDY1sm/TYbfoP6JLFx01s0ShvZ3puP/iQ==", + "requires": {} + }, + "pg-protocol": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz", + "integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ==" + }, + "pg-types": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz", + "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==", + "requires": { + "pg-int8": "1.0.1", + "postgres-array": "~2.0.0", + "postgres-bytea": "~1.0.0", + "postgres-date": "~1.0.4", + "postgres-interval": "^1.1.0" + } + }, + "pgpass": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.4.tgz", + "integrity": "sha512-YmuA56alyBq7M59vxVBfPJrGSozru8QAdoNlWuW3cz8l+UX3cWge0vTvjKhsSHSJpo3Bom8/Mm6hf0TR5GY0+w==", + "requires": { + "split2": "^3.1.1" + } + }, + "postgres-array": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz", + "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==" + }, + "postgres-bytea": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", + "integrity": "sha1-AntTPAqokOJtFy1Hz5zOzFIazTU=" + }, + "postgres-date": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz", + "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==" + }, + "postgres-interval": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz", + "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==", + "requires": { + "xtend": "^4.0.0" + } + }, "process-nextick-args": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", @@ -2550,6 +2788,26 @@ "amdefine": ">=0.0.4" } }, + "split2": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz", + "integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==", + "requires": { + "readable-stream": "^3.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, "statuses": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", @@ -2559,7 +2817,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, "requires": { "safe-buffer": "~5.1.0" } @@ -2662,8 +2919,7 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "utils-merge": { "version": "1.0.1", @@ -2705,6 +2961,11 @@ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + }, "yargs": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", diff --git a/package.json b/package.json index 0a035a8..8fbe5ba 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "http-errors": "~1.6.2", "morgan": "~1.9.0", "nodemailer": "^6.6.5", + "pg": "^8.7.1", "pug": "2.0.0-beta11" }, "devDependencies": { From 95d0979f53e82219f168573d9bfb9bd1fd5f453b Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Mon, 15 Nov 2021 10:39:03 -0700 Subject: [PATCH 002/169] Begin adding support for PostgreSQL database storage --- database/database.js | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 database/database.js diff --git a/database/database.js b/database/database.js new file mode 100644 index 0000000..e653b9a --- /dev/null +++ b/database/database.js @@ -0,0 +1,9 @@ +const app = require('../app'); +const { Client } = require('pg'); + +if (process.env.NODE_ENV !== 'production' || process.env.NODE_ENV !== 'testing') { + require('dotenv').config(); +} + +const client = new Client(); +client.connect(); \ No newline at end of file From 80632bf8b37b359f2a5e344b932796f91a8df0a8 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Mon, 15 Nov 2021 17:34:05 -0700 Subject: [PATCH 003/169] Add PG variables to .env.example --- .env.example | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.env.example b/.env.example index 9ef5217..11d1e69 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,11 @@ NODE_ENV=development +PGUSER=dbuser +PGHOST=database.server.com +PGPASSWORD=dbuserpassword +PGDATABASE=mydatabase +PGPORT=5432 + MAIL_FROM=fromaddress@example.com MAIL_HOST=smtp.smtphost.net MAIL_PORT=465 From 2557a83e3763f98b39db08267b8365f7d7c3318c Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Thu, 18 Nov 2021 14:12:46 -0700 Subject: [PATCH 004/169] Add packages "async" and "passport" --- package-lock.json | 60 +++++++++++++++++++++++++++++++++++++++++++++++ package.json | 2 ++ 2 files changed, 62 insertions(+) diff --git a/package-lock.json b/package-lock.json index 6499394..225db7b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "demo", "version": "0.0.0", "dependencies": { + "async": "^3.2.2", "cookie-parser": "~1.4.3", "debug": "~2.6.9", "dotenv": "^10.0.0", @@ -15,6 +16,7 @@ "http-errors": "~1.6.2", "morgan": "~1.9.0", "nodemailer": "^6.6.5", + "passport": "^0.5.0", "pg": "^8.7.1", "pug": "2.0.0-beta11" }, @@ -109,6 +111,11 @@ "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" }, + "node_modules/async": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.2.tgz", + "integrity": "sha512-H0E+qZaDEfx/FY4t7iLRv1W2fFI6+pyCeTw1uN20AQPiwqwM6ojPxHxdLv4z8hi2DtnW9BOckSspLucW7pIE5g==" + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -987,6 +994,30 @@ "node": ">= 0.8" } }, + "node_modules/passport": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.5.0.tgz", + "integrity": "sha512-ln+ue5YaNDS+fes6O5PCzXKSseY5u8MYhX9H5Co4s+HfYI5oqvnHKoOORLYDUPh+8tHvrxugF2GFcUA1Q1Gqfg==", + "dependencies": { + "passport-strategy": "1.x.x", + "pause": "0.0.1" + }, + "engines": { + "node": ">= 0.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/jaredhanson" + } + }, + "node_modules/passport-strategy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ=", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -1006,6 +1037,11 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, + "node_modules/pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=" + }, "node_modules/pg": { "version": "8.7.1", "resolved": "https://registry.npmjs.org/pg/-/pg-8.7.1.tgz", @@ -1722,6 +1758,11 @@ "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" }, + "async": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.2.tgz", + "integrity": "sha512-H0E+qZaDEfx/FY4t7iLRv1W2fFI6+pyCeTw1uN20AQPiwqwM6ojPxHxdLv4z8hi2DtnW9BOckSspLucW7pIE5g==" + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -2423,6 +2464,20 @@ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" }, + "passport": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/passport/-/passport-0.5.0.tgz", + "integrity": "sha512-ln+ue5YaNDS+fes6O5PCzXKSseY5u8MYhX9H5Co4s+HfYI5oqvnHKoOORLYDUPh+8tHvrxugF2GFcUA1Q1Gqfg==", + "requires": { + "passport-strategy": "1.x.x", + "pause": "0.0.1" + } + }, + "passport-strategy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", + "integrity": "sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ=" + }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -2439,6 +2494,11 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, + "pause": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", + "integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=" + }, "pg": { "version": "8.7.1", "resolved": "https://registry.npmjs.org/pg/-/pg-8.7.1.tgz", diff --git a/package.json b/package.json index 8fbe5ba..c94c979 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "test": "mocha" }, "dependencies": { + "async": "^3.2.2", "cookie-parser": "~1.4.3", "debug": "~2.6.9", "dotenv": "^10.0.0", @@ -14,6 +15,7 @@ "http-errors": "~1.6.2", "morgan": "~1.9.0", "nodemailer": "^6.6.5", + "passport": "^0.5.0", "pg": "^8.7.1", "pug": "2.0.0-beta11" }, From 9554d54496ace8ea808c540e9e4a4b89313dcbde Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Thu, 18 Nov 2021 14:13:03 -0700 Subject: [PATCH 005/169] Add database initialization --- database/database.js | 34 +++++++++++- database/init_database.sql | 105 +++++++++++++++++++++++++++++++++++++ routes/index.js | 1 + 3 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 database/init_database.sql diff --git a/database/database.js b/database/database.js index e653b9a..766168d 100644 --- a/database/database.js +++ b/database/database.js @@ -1,9 +1,41 @@ const app = require('../app'); const { Client } = require('pg'); +const fs = require('fs'); if (process.env.NODE_ENV !== 'production' || process.env.NODE_ENV !== 'testing') { require('dotenv').config(); } const client = new Client(); -client.connect(); \ No newline at end of file +client.connect(); + +async function executeQuery(query, values = []) { + const result = await client.query({ + rowMode: 'array', + text: query, + values: values + }); + return result.rows; +} + +async function InitializeDatabase() { + console.log("Initializing database...") + const sql = fs.readFileSync('database/init_database.sql').toString(); + await executeQuery(sql); + console.log("Database initialized.") +} + + + + +async function checkForDatabaseInitialization() { + const query = `SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'scores'`; + let result = await executeQuery(query); + + const scoresSchemaExists = result.length !== 0; + + if(!scoresSchemaExists) { + InitializeDatabase(); + } +} +checkForDatabaseInitialization(); \ No newline at end of file diff --git a/database/init_database.sql b/database/init_database.sql new file mode 100644 index 0000000..5df20f8 --- /dev/null +++ b/database/init_database.sql @@ -0,0 +1,105 @@ +/* SCORE TRACKER DATABASE LAYOUT + +scores: + + sports: + *sport_id* | name | currently_active + + divisions: + *division_id* | name | gender | *sport_id* | currently_active + + teams: + *team_id* | name | ~sport_id~ | currently_active + + seasons: + *season_id* | school_year + + games: + *game_id* | ~division_id~ | ~season_id~ | date | ~team1_id~ | ~team2_id~ | team1_score | team2_score | ~submitter_id~ | updated_timestamp + + + +accounts: + + users: + *user_id* | email | salt | password_hash | role | approved + + sessions: + *session_id* | ~user_id~ | expiration_date + +*/ + + +BEGIN; + + +CREATE SCHEMA IF NOT EXISTS scores; + + +CREATE TABLE IF NOT EXISTS scores.sports( + sport_id BIGINT GENERATED ALWAYS AS IDENTITY, + sport_name TEXT UNIQUE NOT NULL, + currently_active BOOLEAN DEFAULT TRUE, + PRIMARY KEY(sport_id) + ); + +CREATE TABLE IF NOT EXISTS scores.divisions( + division_id BIGINT GENERATED ALWAYS AS IDENTITY, + division_name TEXT NOT NULL, + gender VARCHAR(1) CHECK (gender IN ( 'F', 'M' ) ), + sport_id BIGINT, + currently_active BOOLEAN DEFAULT TRUE, + PRIMARY KEY(division_id), + CONSTRAINT fk_sport + FOREIGN KEY(sport_id) + REFERENCES scores.sports(sport_id) + ); + +CREATE TABLE IF NOT EXISTS scores.teams( + team_id BIGINT GENERATED ALWAYS AS IDENTITY, + team_name TEXT NOT NULL, + sport_id BIGINT, + currently_active BOOLEAN DEFAULT TRUE, + PRIMARY KEY(team_id), + CONSTRAINT fk_sport + FOREIGN KEY(sport_id) + REFERENCES scores.sports(sport_id) + ); + +CREATE TABLE IF NOT EXISTS scores.seasons( + season_id BIGINT GENERATED ALWAYS AS IDENTITY, + school_year INTEGER NOT NULL, + PRIMARY KEY(season_id) + ); + +CREATE TABLE IF NOT EXISTS scores.games( + game_id BIGINT GENERATED ALWAYS AS IDENTITY, + division_id BIGINT, + season_id BIGINT, + game_date DATE, + team1_id BIGINT, + team2_id BIGINT, + team1_score INTEGER, + team2_score INTEGER, +/* submitter_id BIGINT,*/ + updated_timestamp TIMESTAMP WITH TIME ZONE DEFAULT now(), + PRIMARY KEY(game_id), + CONSTRAINT fk_division + FOREIGN KEY(division_id) + REFERENCES scores.divisions(division_id), + CONSTRAINT fk_season + FOREIGN KEY(season_id) + REFERENCES scores.seasons(season_id), + CONSTRAINT fk_team1 + FOREIGN KEY(team1_id) + REFERENCES scores.teams(team_id), + CONSTRAINT fk_team2 + FOREIGN KEY(team2_id) + REFERENCES scores.teams(team_id) +/* CONSTRAINT fk_submitter + FOREIGN KEY(submitter_id) + REFERENCES accounts.users(user_id)*/ + ); + + +COMMIT; \ No newline at end of file diff --git a/routes/index.js b/routes/index.js index 9399195..c806b1c 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,5 +1,6 @@ var express = require('express'); var router = express.Router(); +var database = require('../database/database'); /* GET home page. */ router.get('/', function(req, res, next) { From 7ceccd2928e7af8860f5dbefe8716caf88427ae0 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Fri, 19 Nov 2021 11:42:08 -0700 Subject: [PATCH 006/169] Fix database layout comment --- database/init_database.sql | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/database/init_database.sql b/database/init_database.sql index 5df20f8..ad4f0e7 100644 --- a/database/init_database.sql +++ b/database/init_database.sql @@ -3,19 +3,19 @@ scores: sports: - *sport_id* | name | currently_active + *sport_id* | sport_name | currently_active divisions: - *division_id* | name | gender | *sport_id* | currently_active + *division_id* | division_name | gender | *sport_id* | currently_active teams: - *team_id* | name | ~sport_id~ | currently_active + *team_id* | team_name | ~sport_id~ | currently_active seasons: *season_id* | school_year games: - *game_id* | ~division_id~ | ~season_id~ | date | ~team1_id~ | ~team2_id~ | team1_score | team2_score | ~submitter_id~ | updated_timestamp + *game_id* | ~division_id~ | ~season_id~ | game_date | ~team1_id~ | ~team2_id~ | team1_score | team2_score | ~submitter_id~ | updated_timestamp From cc0b5ee087dc69aac6d6943f31fe8ac15d81eee6 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sat, 20 Nov 2021 16:48:48 -0700 Subject: [PATCH 007/169] Edit submit page to better match database values --- views/index.pug | 40 ---------------------------------------- views/submit.pug | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 40 deletions(-) diff --git a/views/index.pug b/views/index.pug index 7f6baf1..e69de29 100644 --- a/views/index.pug +++ b/views/index.pug @@ -1,40 +0,0 @@ -extends layout - -block stylesheets - link(rel='stylesheet', href='/stylesheets/submit.css') - -block content - h1 Submit Score - form(action='/submit', method='POST') - span(class='form-section') - label Sport - span(class='form-section-input') - select#sport-dropdown(name="sport") - option(value="Football" selected) Football - select#gender-dropdown(name="gender") - option(value="Male" selected) Male - option(value="Female") Female - select#division-dropdown(name="division") - option(value="Varsity") Varsity - option(value="JV-A") JV-A - option(value="JV-B") JV-B - span(class='form-section') - label Home Team - span(class='form-section-input') - input(type="text", name="home-team") - input(class="score-input", type="number", name="home-team-score", value="0") - span(class='form-section') - label Visiting Team - span(class='form-section-input') - input(type="text", name="visiting-team") - input(class="score-input", type="number", name="visiting-team-score", value="0") - span(class='form-section') - label Submitter - span(class='form-section-input') - input(type="text", name="submitter") - span(class='form-section') - label Send info to - span(class='form-section-input') - input(type="email", name="email", placeholder="email@example.com") - span(class='form-section') - button(type="submit") Submit diff --git a/views/submit.pug b/views/submit.pug index e69de29..385ad51 100644 --- a/views/submit.pug +++ b/views/submit.pug @@ -0,0 +1,41 @@ +extends layout + +block stylesheets + link(rel='stylesheet', href='/stylesheets/submit.css') + +block content + h1 Submit Score + form(action='/submit', method='POST') + span(class='form-section') + label Year + span(class='form-section-input') + select#year-dropdown(name="year" class="main-dropdown") + option(value="2022" selected) 2021-2022 + span(class='form-section') + label Sport + span(class='form-section-input') + select#sport-dropdown(name="sport" class="main-dropdown") + option(value="Basketball" selected) Basketball + select#gender-dropdown(name="gender") + option(value="Male" selected) Male + option(value="Female") Female + select#division-dropdown(name="division") + option(value="Varsity") Varsity + option(value="JV-A") JV-A + option(value="JV-B") JV-B + span(class='form-section') + label Date of match + span(class='form-section-input') + input(type="date", name="date", value=date) + span(class='form-section') + label Your team + span(class='form-section-input') + select#team1-dropdown(name="team1" class="main-dropdown") + input(class="score-input", type="number", name="team1-score", value="0") + span(class='form-section') + label Opponent + span(class='form-section-input') + select#team2-dropdown(name="team2" class="main-dropdown") + input(class="score-input", type="number", name="team2-score", value="0") + span(class='form-section') + button(type="submit") Submit From 93521dc431e155ee1c7f220fa607f9103324cbc5 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sat, 20 Nov 2021 16:50:23 -0700 Subject: [PATCH 008/169] Edit CSS for submit page --- public/stylesheets/submit.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/stylesheets/submit.css b/public/stylesheets/submit.css index 89dd41c..80a8146 100644 --- a/public/stylesheets/submit.css +++ b/public/stylesheets/submit.css @@ -16,7 +16,7 @@ span { } -#sport-dropdown { +.main-dropdown { width: 100%; } From 82137290098bc3da97c9d8db8c384c4c4e8955c3 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sat, 20 Nov 2021 16:51:08 -0700 Subject: [PATCH 009/169] Remove mail functions for submit page --- routes/submit.js | 41 +++++++++++++++++------------------------ 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/routes/submit.js b/routes/submit.js index 30901be..e79ff92 100644 --- a/routes/submit.js +++ b/routes/submit.js @@ -1,38 +1,31 @@ var express = require('express'); -var mail = require('../mail/mail'); var router = express.Router(); +var database = require('../database/database'); /* GET submit page. */ router.get('/', function(req, res, next) { - res.send('Nothing to send'); + const date_ob = new Date(); + const date = ("0" + date_ob.getDate()).slice(-2); + const month = ("0" + (date_ob.getMonth() + 1)).slice(-2); + const year = date_ob.getFullYear(); + + const currentDate = year + '-' + month + '-' + date; + res.render('submit', { title: 'Submit Score', date: currentDate }); }); /* POST submit page. */ router.post('/', function(req, res, next) { - let sport = req.body.sport; - let gender = req.body.gender; - let division = req.body.division; - let home = req.body['home-team']; - let homeScore = req.body['home-team-score']; - let visiting = req.body['visiting-team']; - let visitingScore = req.body['visiting-team-score']; - let submitter = req.body['submitter']; - let recipient = req.body['email']; + const year = req.body['year']; + const sport = req.body['sport']; + const gender = req.body['gender']; + const division = req.body['division']; + const date = req.body['date']; + const team1 = req.body['team1']; + const team1Score = req.body['team1-score']; + const team2 = req.body['team2']; + const team2Score = req.body['team2-score']; - let message = prepMailBody(sport, gender, division, home, homeScore, visiting, visitingScore, submitter); - mail.send(recipient, "Score Report", message); - - res.send('Score sent'); }); -var prepMailBody = function(sport, gender, division, home, homeScore, visiting, visitingScore, submitter) { - return( - "Score report from " + submitter + "

" + - "Sport:
" + sport + " - " + gender + " - " + division + "

" + - "Home team:
" + home + " - " + homeScore + "

" + - "Visiting team:
" + visiting + " - " + visitingScore + "

"); -}; - - module.exports = router; From 6f4a41fbd7e19b6ae7428b39406840e37fa41c5a Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sat, 20 Nov 2021 16:51:49 -0700 Subject: [PATCH 010/169] Edit database.js --- database/database.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/database/database.js b/database/database.js index 766168d..f166980 100644 --- a/database/database.js +++ b/database/database.js @@ -18,7 +18,7 @@ async function executeQuery(query, values = []) { return result.rows; } -async function InitializeDatabase() { +async function Initialize() { console.log("Initializing database...") const sql = fs.readFileSync('database/init_database.sql').toString(); await executeQuery(sql); @@ -35,7 +35,13 @@ async function checkForDatabaseInitialization() { const scoresSchemaExists = result.length !== 0; if(!scoresSchemaExists) { - InitializeDatabase(); + Initialize(); } } -checkForDatabaseInitialization(); \ No newline at end of file +checkForDatabaseInitialization(); + + + + + +exports.executeQuery = executeQuery; \ No newline at end of file From 64f4f9fb32dcee891a822e6df9b2d5744016b5d7 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sat, 20 Nov 2021 16:53:13 -0700 Subject: [PATCH 011/169] Add createSport function --- database/scores/sports.js | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 database/scores/sports.js diff --git a/database/scores/sports.js b/database/scores/sports.js new file mode 100644 index 0000000..abdea6b --- /dev/null +++ b/database/scores/sports.js @@ -0,0 +1,26 @@ +const database = require('./database'); + + + +class Sport { + constructor(id) { + this.id = id; + } +} + +async function createSport(name) { + query = `INSERT INTO scores.sports(sport_name) + VALUES($1);`; + await database.executeQuery(query, [name]); + + query = `SELECT sport_id FROM scores.sports + WHERE sport_name = $1` + const sportId = await database.executeQuery(query, [name]); + console.log(sportId); + + return new Sport(sportId[0][0]); +} + + + +exports.createSport = createSport; \ No newline at end of file From 2191218fe1809c7cfb049d5ac47bb7fd30729658 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sat, 20 Nov 2021 17:52:09 -0700 Subject: [PATCH 012/169] Add rename sport rename function --- database/scores/sports.js | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/database/scores/sports.js b/database/scores/sports.js index abdea6b..b1be445 100644 --- a/database/scores/sports.js +++ b/database/scores/sports.js @@ -1,26 +1,35 @@ -const database = require('./database'); +const database = require('./../database'); class Sport { - constructor(id) { + constructor(id, name) { this.id = id; + this.name = name; } } -async function createSport(name) { + + +async function create(name) { query = `INSERT INTO scores.sports(sport_name) - VALUES($1);`; - await database.executeQuery(query, [name]); - - query = `SELECT sport_id FROM scores.sports - WHERE sport_name = $1` + VALUES($1) + RETURNING sport_id;`; const sportId = await database.executeQuery(query, [name]); - console.log(sportId); + return new Sport(sportId[0][0], name); +} - return new Sport(sportId[0][0]); +async function rename(id, name) { + query = `UPDATE scores.sports + SET sport_name = $2 + WHERE sport_id = $1;` + await database.executeQuery(query, [id, name]); + return new Sport(id, name); } -exports.createSport = createSport; \ No newline at end of file + + +exports.create = create; +exports.rename = rename; \ No newline at end of file From c176e279877db26d5f4066656ad7eb06b4a67fed Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sat, 20 Nov 2021 17:58:29 -0700 Subject: [PATCH 013/169] Add function to remove sport --- database/scores/sports.js | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/database/scores/sports.js b/database/scores/sports.js index b1be445..bd719fc 100644 --- a/database/scores/sports.js +++ b/database/scores/sports.js @@ -15,21 +15,30 @@ async function create(name) { query = `INSERT INTO scores.sports(sport_name) VALUES($1) RETURNING sport_id;`; - const sportId = await database.executeQuery(query, [name]); - return new Sport(sportId[0][0], name); + const id = (await database.executeQuery(query, [name]))[0][0]; + return new Sport(id, name); } async function rename(id, name) { query = `UPDATE scores.sports SET sport_name = $2 - WHERE sport_id = $1;` + WHERE sport_id = $1;`; await database.executeQuery(query, [id, name]); return new Sport(id, name); } +async function remove(id) { + query = `DELETE FROM scores.sports + WHERE sport_id = $1 + RETURNING sport_name;`; + name = (await database.executeQuery(query, [id]))[0][0]; + return new Sport(id, name); +} + exports.create = create; -exports.rename = rename; \ No newline at end of file +exports.rename = rename; +exports.remove = remove; \ No newline at end of file From 2d14395b61c930862c072dd66561796027fe8383 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sat, 20 Nov 2021 18:07:37 -0700 Subject: [PATCH 014/169] Add function to retrieve all sports from database --- database/scores/sports.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/database/scores/sports.js b/database/scores/sports.js index bd719fc..38f35cf 100644 --- a/database/scores/sports.js +++ b/database/scores/sports.js @@ -35,10 +35,24 @@ async function remove(id) { return new Sport(id, name); } +async function retrieveAll() { + query = `SELECT * + FROM scores.sports + ORDER BY sport_name;`; + const table = await database.executeQuery(query); + + const sportsList = []; + table.forEach((row) => { + sportsList.push(new Sport(row[0], row[1])); + }); + return sportsList; +} + exports.create = create; exports.rename = rename; -exports.remove = remove; \ No newline at end of file +exports.remove = remove; +exports.retrieveAll = retrieveAll; \ No newline at end of file From 65238e374561e7f95dcde76c0bf2144996498c6a Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sat, 20 Nov 2021 18:53:22 -0700 Subject: [PATCH 015/169] Add module for defining genders --- database/scores/genders.js | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 database/scores/genders.js diff --git a/database/scores/genders.js b/database/scores/genders.js new file mode 100644 index 0000000..402d58f --- /dev/null +++ b/database/scores/genders.js @@ -0,0 +1,8 @@ +class Gender { + constructor(name) { + this.name = name; + } +} + +exports.male = new Gender("male"); +exports.female = new Gender("female"); \ No newline at end of file From f86717d99f0fddbbd2c9ecb6b8bb48c61e82489b Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sat, 20 Nov 2021 19:00:06 -0700 Subject: [PATCH 016/169] Move genders definition to dedicated constants folder --- {database/scores => constants}/genders.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {database/scores => constants}/genders.js (100%) diff --git a/database/scores/genders.js b/constants/genders.js similarity index 100% rename from database/scores/genders.js rename to constants/genders.js From 2db5bc4480e69d00e2ccfc470e0b819abe6f4993 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sat, 20 Nov 2021 19:14:43 -0700 Subject: [PATCH 017/169] Create functions for database management of divisions --- database/scores/divisions.js | 40 ++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 database/scores/divisions.js diff --git a/database/scores/divisions.js b/database/scores/divisions.js new file mode 100644 index 0000000..0a0e8be --- /dev/null +++ b/database/scores/divisions.js @@ -0,0 +1,40 @@ +const database = require('./../database'); +const genders = require('../../constants/genders'); + + + + + +class Division { + constructor(id, name) { + this.id = id; + this.name = name; + } +} + + + +async function create(name, gender, sportID) { + query = `INSERT INTO scores.divisions(division_name,gender,sport_id) + VALUES($1,$2,$3) + RETURNING division_id;`; + const genderID = (gender == genders.male) ? "M" : "F"; + const id = (await database.executeQuery(query, [name, genderID, sportID]))[0][0]; + return new Division(id, name); +} + +async function rename(id, division) { + query = `UPDATE scores.divisions + SET division_name = $2 + WHERE division_id = $1;`; + await database.executeQuery(query, [id, name]); + return new Division(id, name); +} + +async function remove(id) { + query = `DELETE FROM scores.divisions + WHERE division_id = $1 + RETURNING division_name;`; + name = (await database.executeQuery(query, [id]))[0][0]; + return new Division(id, name); +} \ No newline at end of file From 6e108ef975a7415a023964baab65f6c63b998253 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sat, 20 Nov 2021 21:25:29 -0700 Subject: [PATCH 018/169] Create function to retrieve divisions from database --- database/scores/divisions.js | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/database/scores/divisions.js b/database/scores/divisions.js index 0a0e8be..8c5510d 100644 --- a/database/scores/divisions.js +++ b/database/scores/divisions.js @@ -14,11 +14,17 @@ class Division { +function getGenderID(gender) { + return (gender == genders.male) ? "M" : "F"; +} + + + async function create(name, gender, sportID) { query = `INSERT INTO scores.divisions(division_name,gender,sport_id) VALUES($1,$2,$3) RETURNING division_id;`; - const genderID = (gender == genders.male) ? "M" : "F"; + const genderID = getGenderID(gender); const id = (await database.executeQuery(query, [name, genderID, sportID]))[0][0]; return new Division(id, name); } @@ -37,4 +43,19 @@ async function remove(id) { RETURNING division_name;`; name = (await database.executeQuery(query, [id]))[0][0]; return new Division(id, name); +} + +async function retrieveBySportAndGender(sportID, gender) { + query = `SELECT * + FROM scores.divisions + WHERE sport_id = $1 AND gender = $2 + ORDER BY division_name;`; + const genderID = getGenderID(gender); + const table = await database.executeQuery(query, [sportID, genderID]); + + const divisionsList = []; + table.forEach((row) => { + divisionsList.push(new Division(row[0], row[1])); + }); + return divisionsList; } \ No newline at end of file From 9c55999ad88deea85a50cc6e0c38015a92efc43f Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sat, 20 Nov 2021 21:35:56 -0700 Subject: [PATCH 019/169] Add function to create teams --- database/scores/teams.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 database/scores/teams.js diff --git a/database/scores/teams.js b/database/scores/teams.js new file mode 100644 index 0000000..8d9858b --- /dev/null +++ b/database/scores/teams.js @@ -0,0 +1,22 @@ +const database = require('./../database'); + + + + + +class Team { + constructor(id, name) { + this.id = id; + this.name = name; + } +} + + + +async function create(name, sportID) { + query = `INSERT INTO scores.teams(team_name, sport_id) + VALUES($1, $2) + RETURNING team_id;`; + const id = (await database.executeQuery(query, [name, sportID]))[0][0]; + return new Team(id, name); +} \ No newline at end of file From 7e07236779421710d06b8559e7a5ba4c5c1a7d1e Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sat, 20 Nov 2021 21:38:08 -0700 Subject: [PATCH 020/169] Add function to rename teams --- database/scores/teams.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/database/scores/teams.js b/database/scores/teams.js index 8d9858b..53450fd 100644 --- a/database/scores/teams.js +++ b/database/scores/teams.js @@ -19,4 +19,12 @@ async function create(name, sportID) { RETURNING team_id;`; const id = (await database.executeQuery(query, [name, sportID]))[0][0]; return new Team(id, name); +} + +async function rename(id, name) { + query = `UPDATE scores.teams + SET team_name = $2 + WHERE team_id = $1;`; + await database.executeQuery(query, [id, name]); + return new Team(id, name); } \ No newline at end of file From 16c90cf8839d697f11b6c2a0c62cfd9e81fbe1e7 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sat, 20 Nov 2021 21:39:30 -0700 Subject: [PATCH 021/169] Add function to remove teams --- database/scores/teams.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/database/scores/teams.js b/database/scores/teams.js index 53450fd..3e09496 100644 --- a/database/scores/teams.js +++ b/database/scores/teams.js @@ -27,4 +27,12 @@ async function rename(id, name) { WHERE team_id = $1;`; await database.executeQuery(query, [id, name]); return new Team(id, name); +} + +async function remove(id) { + query = `DELETE FROM scores.teams + WHERE team_id = $1 + RETURNING team_name;`; + name = (await database.executeQuery(query, [id]))[0][0]; + return new Team(id, name); } \ No newline at end of file From e37b10596e7f45668bf5a7235eb512947353414e Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sat, 20 Nov 2021 21:43:19 -0700 Subject: [PATCH 022/169] Add function to retrieve teams by sport --- database/scores/teams.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/database/scores/teams.js b/database/scores/teams.js index 3e09496..fe27d6b 100644 --- a/database/scores/teams.js +++ b/database/scores/teams.js @@ -35,4 +35,18 @@ async function remove(id) { RETURNING team_name;`; name = (await database.executeQuery(query, [id]))[0][0]; return new Team(id, name); +} + +async function retrieveBySport(sportID) { + query = `SELECT * + FROM scores.teams + WHERE sport_id = $1 + ORDER BY team_name;`; + const table = await database.executeQuery(query); + + const teamsList = []; + table.forEach((row) => { + teamsList.push(new Team(row[0], row[1])); + }); + return teamsList; } \ No newline at end of file From e056743022059243f6545985748eb1e79f51f4ca Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sat, 20 Nov 2021 21:45:28 -0700 Subject: [PATCH 023/169] Add "const" to accidental global variables --- database/scores/divisions.js | 10 +++++----- database/scores/sports.js | 10 +++++----- database/scores/teams.js | 10 +++++----- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/database/scores/divisions.js b/database/scores/divisions.js index 8c5510d..9003da9 100644 --- a/database/scores/divisions.js +++ b/database/scores/divisions.js @@ -21,7 +21,7 @@ function getGenderID(gender) { async function create(name, gender, sportID) { - query = `INSERT INTO scores.divisions(division_name,gender,sport_id) + const query = `INSERT INTO scores.divisions(division_name,gender,sport_id) VALUES($1,$2,$3) RETURNING division_id;`; const genderID = getGenderID(gender); @@ -30,7 +30,7 @@ async function create(name, gender, sportID) { } async function rename(id, division) { - query = `UPDATE scores.divisions + const query = `UPDATE scores.divisions SET division_name = $2 WHERE division_id = $1;`; await database.executeQuery(query, [id, name]); @@ -38,15 +38,15 @@ async function rename(id, division) { } async function remove(id) { - query = `DELETE FROM scores.divisions + const query = `DELETE FROM scores.divisions WHERE division_id = $1 RETURNING division_name;`; - name = (await database.executeQuery(query, [id]))[0][0]; + const name = (await database.executeQuery(query, [id]))[0][0]; return new Division(id, name); } async function retrieveBySportAndGender(sportID, gender) { - query = `SELECT * + const query = `SELECT * FROM scores.divisions WHERE sport_id = $1 AND gender = $2 ORDER BY division_name;`; diff --git a/database/scores/sports.js b/database/scores/sports.js index 38f35cf..c801612 100644 --- a/database/scores/sports.js +++ b/database/scores/sports.js @@ -12,7 +12,7 @@ class Sport { async function create(name) { - query = `INSERT INTO scores.sports(sport_name) + const query = `INSERT INTO scores.sports(sport_name) VALUES($1) RETURNING sport_id;`; const id = (await database.executeQuery(query, [name]))[0][0]; @@ -20,7 +20,7 @@ async function create(name) { } async function rename(id, name) { - query = `UPDATE scores.sports + const query = `UPDATE scores.sports SET sport_name = $2 WHERE sport_id = $1;`; await database.executeQuery(query, [id, name]); @@ -28,15 +28,15 @@ async function rename(id, name) { } async function remove(id) { - query = `DELETE FROM scores.sports + const query = `DELETE FROM scores.sports WHERE sport_id = $1 RETURNING sport_name;`; - name = (await database.executeQuery(query, [id]))[0][0]; + const name = (await database.executeQuery(query, [id]))[0][0]; return new Sport(id, name); } async function retrieveAll() { - query = `SELECT * + const query = `SELECT * FROM scores.sports ORDER BY sport_name;`; const table = await database.executeQuery(query); diff --git a/database/scores/teams.js b/database/scores/teams.js index fe27d6b..0bdb33d 100644 --- a/database/scores/teams.js +++ b/database/scores/teams.js @@ -14,7 +14,7 @@ class Team { async function create(name, sportID) { - query = `INSERT INTO scores.teams(team_name, sport_id) + const query = `INSERT INTO scores.teams(team_name, sport_id) VALUES($1, $2) RETURNING team_id;`; const id = (await database.executeQuery(query, [name, sportID]))[0][0]; @@ -22,7 +22,7 @@ async function create(name, sportID) { } async function rename(id, name) { - query = `UPDATE scores.teams + const query = `UPDATE scores.teams SET team_name = $2 WHERE team_id = $1;`; await database.executeQuery(query, [id, name]); @@ -30,15 +30,15 @@ async function rename(id, name) { } async function remove(id) { - query = `DELETE FROM scores.teams + const query = `DELETE FROM scores.teams WHERE team_id = $1 RETURNING team_name;`; - name = (await database.executeQuery(query, [id]))[0][0]; + const name = (await database.executeQuery(query, [id]))[0][0]; return new Team(id, name); } async function retrieveBySport(sportID) { - query = `SELECT * + const query = `SELECT * FROM scores.teams WHERE sport_id = $1 ORDER BY team_name;`; From c2e3c95cfdaa65764f67fdee49c3ec79fd51df14 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sat, 20 Nov 2021 21:46:28 -0700 Subject: [PATCH 024/169] Add module exports to divisions.js --- database/scores/divisions.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/database/scores/divisions.js b/database/scores/divisions.js index 9003da9..6f0a443 100644 --- a/database/scores/divisions.js +++ b/database/scores/divisions.js @@ -58,4 +58,13 @@ async function retrieveBySportAndGender(sportID, gender) { divisionsList.push(new Division(row[0], row[1])); }); return divisionsList; -} \ No newline at end of file +} + + + + + +exports.create = create; +exports.rename = rename; +exports.remove = remove; +exports.retrieveBySportAndGender = retrieveBySportAndGender; \ No newline at end of file From 90e302c3ae535ad70545f3e0edebae521eeb1f02 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sat, 20 Nov 2021 21:47:15 -0700 Subject: [PATCH 025/169] Add module exports to teams.js --- database/scores/teams.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/database/scores/teams.js b/database/scores/teams.js index 0bdb33d..a6dac68 100644 --- a/database/scores/teams.js +++ b/database/scores/teams.js @@ -49,4 +49,13 @@ async function retrieveBySport(sportID) { teamsList.push(new Team(row[0], row[1])); }); return teamsList; -} \ No newline at end of file +} + + + + + +exports.create = create; +exports.rename = rename; +exports.remove = remove; +exports.retrieveBySport = retrieveBySport; \ No newline at end of file From b533668445755edec9069951b212dcbe8870578b Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sat, 20 Nov 2021 21:52:32 -0700 Subject: [PATCH 026/169] Add function to create season --- database/scores/seasons.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 database/scores/seasons.js diff --git a/database/scores/seasons.js b/database/scores/seasons.js new file mode 100644 index 0000000..de32b2b --- /dev/null +++ b/database/scores/seasons.js @@ -0,0 +1,22 @@ +const database = require('./../database'); + + + + + +class Season { + constructor(id, year) { + this.id = id; + this.year = year; + } +} + + + +async function create(year) { + const query = `INSERT INTO scores.seasons(school_year) + VALUES($1) + RETURNING season_id;`; + const id = (await database.executeQuery(query, [year]))[0][0]; + return new Season(id, year); +} \ No newline at end of file From e9bda3c807bcf18c7f5cec6c402ddab12a0ede51 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sat, 20 Nov 2021 21:55:31 -0700 Subject: [PATCH 027/169] Add function to remove season --- database/scores/seasons.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/database/scores/seasons.js b/database/scores/seasons.js index de32b2b..55e9941 100644 --- a/database/scores/seasons.js +++ b/database/scores/seasons.js @@ -19,4 +19,12 @@ async function create(year) { RETURNING season_id;`; const id = (await database.executeQuery(query, [year]))[0][0]; return new Season(id, year); +} + +async function remove(id) { + const query = `DELETE FROM scores.seasons + WHERE season_id = $1 + RETURNING school_year;`; + const year = (await database.executeQuery(query, [id]))[0][0]; + return new Season(id, year); } \ No newline at end of file From c046eeb6acd42f2e17fd8698ea3e3a9cabc73f96 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sat, 20 Nov 2021 21:57:27 -0700 Subject: [PATCH 028/169] Create function to retrive seasons --- database/scores/seasons.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/database/scores/seasons.js b/database/scores/seasons.js index 55e9941..b8ff917 100644 --- a/database/scores/seasons.js +++ b/database/scores/seasons.js @@ -27,4 +27,17 @@ async function remove(id) { RETURNING school_year;`; const year = (await database.executeQuery(query, [id]))[0][0]; return new Season(id, year); +} + +async function retrieveAll() { + const query = `SELECT * + FROM scores.seasons + ORDER BY school_year;`; + const table = await database.executeQuery(query); + + const seasonsList = []; + table.forEach((row) => { + seasonsList.push(new Season(row[0], row[1])); + }); + return seasonsList; } \ No newline at end of file From 4eadf64fac3692869bcf38fb6846e867c63683d9 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sat, 20 Nov 2021 22:01:55 -0700 Subject: [PATCH 029/169] Rename "create" function to "add" in "scores" modules --- database/scores/divisions.js | 4 ++-- database/scores/seasons.js | 2 +- database/scores/sports.js | 4 ++-- database/scores/teams.js | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/database/scores/divisions.js b/database/scores/divisions.js index 6f0a443..b04d6a5 100644 --- a/database/scores/divisions.js +++ b/database/scores/divisions.js @@ -20,7 +20,7 @@ function getGenderID(gender) { -async function create(name, gender, sportID) { +async function add(name, gender, sportID) { const query = `INSERT INTO scores.divisions(division_name,gender,sport_id) VALUES($1,$2,$3) RETURNING division_id;`; @@ -64,7 +64,7 @@ async function retrieveBySportAndGender(sportID, gender) { -exports.create = create; +exports.add = add; exports.rename = rename; exports.remove = remove; exports.retrieveBySportAndGender = retrieveBySportAndGender; \ No newline at end of file diff --git a/database/scores/seasons.js b/database/scores/seasons.js index b8ff917..bd9704d 100644 --- a/database/scores/seasons.js +++ b/database/scores/seasons.js @@ -13,7 +13,7 @@ class Season { -async function create(year) { +async function add(year) { const query = `INSERT INTO scores.seasons(school_year) VALUES($1) RETURNING season_id;`; diff --git a/database/scores/sports.js b/database/scores/sports.js index c801612..14a6e45 100644 --- a/database/scores/sports.js +++ b/database/scores/sports.js @@ -11,7 +11,7 @@ class Sport { -async function create(name) { +async function add(name) { const query = `INSERT INTO scores.sports(sport_name) VALUES($1) RETURNING sport_id;`; @@ -52,7 +52,7 @@ async function retrieveAll() { -exports.create = create; +exports.add = add; exports.rename = rename; exports.remove = remove; exports.retrieveAll = retrieveAll; \ No newline at end of file diff --git a/database/scores/teams.js b/database/scores/teams.js index a6dac68..1bad3fa 100644 --- a/database/scores/teams.js +++ b/database/scores/teams.js @@ -13,7 +13,7 @@ class Team { -async function create(name, sportID) { +async function add(name, sportID) { const query = `INSERT INTO scores.teams(team_name, sport_id) VALUES($1, $2) RETURNING team_id;`; @@ -55,7 +55,7 @@ async function retrieveBySport(sportID) { -exports.create = create; +exports.add = add; exports.rename = rename; exports.remove = remove; exports.retrieveBySport = retrieveBySport; \ No newline at end of file From 5f68b24d8badf4fb03f0c402d32d14ad9bf8a436 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sat, 20 Nov 2021 22:02:20 -0700 Subject: [PATCH 030/169] Add exports to seasons.js --- database/scores/seasons.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/database/scores/seasons.js b/database/scores/seasons.js index bd9704d..381d064 100644 --- a/database/scores/seasons.js +++ b/database/scores/seasons.js @@ -40,4 +40,12 @@ async function retrieveAll() { seasonsList.push(new Season(row[0], row[1])); }); return seasonsList; -} \ No newline at end of file +} + + + + + +exports.add = add; +exports.remove = remove; +exports.retrieveAll = retrieveAll; \ No newline at end of file From acb987ffd40854aeeab1541f596eb2ebc1db28c2 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sat, 20 Nov 2021 22:14:12 -0700 Subject: [PATCH 031/169] Add function to create game --- database/scores/games.js | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 database/scores/games.js diff --git a/database/scores/games.js b/database/scores/games.js new file mode 100644 index 0000000..256919b --- /dev/null +++ b/database/scores/games.js @@ -0,0 +1,26 @@ +const database = require('./../database'); + + + + + +class Game { + constructor(id, date, team1ID, team2ID, team1Score, team2Score) { + this.id = id; + this.date = date; + this.team1ID = team1ID; + this.team2ID = team2ID; + this.team1Score = team1Score; + this.team2Score = team2Score; + } +} + + + +async function add(divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score) { + const query = `INSERT INTO scores.games(division_id, season_id, game_date, team1_id, team2_id, team1_score, team2_score) + VALUES($1, $2, $3, $4, $5, $6, $7) + RETURNING game_id;`; + const id = (await database.executeQuery(query, [divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score]))[0][0]; + return new Game(id, date, team1ID, team2ID, team1Score, team2Score); +} \ No newline at end of file From 9ca8641f207a45b2f808454719da9f30f13dafcb Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sat, 20 Nov 2021 22:20:38 -0700 Subject: [PATCH 032/169] Create function to remove games from database --- database/scores/games.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/database/scores/games.js b/database/scores/games.js index 256919b..c0ad895 100644 --- a/database/scores/games.js +++ b/database/scores/games.js @@ -23,4 +23,12 @@ async function add(divisionID, seasonID, date, team1ID, team2ID, team1Score, tea RETURNING game_id;`; const id = (await database.executeQuery(query, [divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score]))[0][0]; return new Game(id, date, team1ID, team2ID, team1Score, team2Score); +} + +async function remove(id) { + const query = `DELETE FROM scores.games + WHERE game_id = $1 + RETURNING * ;`; + const row = (await database.executeQuery(query, [id]))[0]; + return new Game(id, row[3], row[4], row[5], row[6], row[7]); } \ No newline at end of file From 42ccda2aba492aa788982fc879e139d4d59e5941 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sat, 20 Nov 2021 23:01:13 -0700 Subject: [PATCH 033/169] Add proper date handling functionality to "add" function of "games.js" --- database/scores/games.js | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/database/scores/games.js b/database/scores/games.js index c0ad895..94e017c 100644 --- a/database/scores/games.js +++ b/database/scores/games.js @@ -21,7 +21,14 @@ async function add(divisionID, seasonID, date, team1ID, team2ID, team1Score, tea const query = `INSERT INTO scores.games(division_id, season_id, game_date, team1_id, team2_id, team1_score, team2_score) VALUES($1, $2, $3, $4, $5, $6, $7) RETURNING game_id;`; - const id = (await database.executeQuery(query, [divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score]))[0][0]; + + const day = ("0" + date.getDate()).slice(-2); + const month = ("0" + (date.getMonth() + 1)).slice(-2); + const year = date.getFullYear(); + + const dateISO = year + '-' + month + '-' + day; + + const id = (await database.executeQuery(query, [divisionID, seasonID, dateISO, team1ID, team2ID, team1Score, team2Score]))[0][0]; return new Game(id, date, team1ID, team2ID, team1Score, team2Score); } @@ -31,4 +38,23 @@ async function remove(id) { RETURNING * ;`; const row = (await database.executeQuery(query, [id]))[0]; return new Game(id, row[3], row[4], row[5], row[6], row[7]); +} + +async function retrieveByTeamDivisionAndSeason(teamID, divisionID, seasonID) { + const query = `SELECT * + FROM scores.games + WHERE (team1_id = $1 OR team2_id = $1) AND division_id = $2 AND season_id = $3 + ORDER BY game_date DESC;`; + const table = (await database.executeQuery(query, [teamID,divisionID,seasonID])); + + const gamesList = []; + table.forEach((row) => { + opponentIsTeam2 = teamID != row[5]; + opponentID = opponentIsTeam2 ? row[5] : row[4]; + teamScore = opponentIsTeam2 ? row[6] : row[7]; + opponentScore = opponentIsTeam2 ? row[7] : row[6]; + + gamesList.push(new Game(row[0], row[3], teamID, opponentID, teamScore, opponentScore)); + }); + return gamesList; } \ No newline at end of file From 78136df5fd809fe526cd3815c562fd4e2a9da467 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sat, 20 Nov 2021 23:02:22 -0700 Subject: [PATCH 034/169] Add module exports for games.js --- database/scores/games.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/database/scores/games.js b/database/scores/games.js index 94e017c..b990a31 100644 --- a/database/scores/games.js +++ b/database/scores/games.js @@ -57,4 +57,12 @@ async function retrieveByTeamDivisionAndSeason(teamID, divisionID, seasonID) { gamesList.push(new Game(row[0], row[3], teamID, opponentID, teamScore, opponentScore)); }); return gamesList; -} \ No newline at end of file +} + + + + + +exports.add = add; +exports.remove = remove; +exports.retrieveByTeamDivisionAndSeason = retrieveByTeamDivisionAndSeason; \ No newline at end of file From 8c3ffcc5fada71f9bf9a957d99a0a47a0c07ea37 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sun, 21 Nov 2021 14:37:35 -0700 Subject: [PATCH 035/169] Start script to fetch data from server --- app.js | 4 +++- public/scripts/submit.js | 0 routes/data.js | 10 ++++++++++ views/layout.pug | 1 + views/submit.pug | 3 +++ 5 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 public/scripts/submit.js create mode 100644 routes/data.js diff --git a/app.js b/app.js index 3cff1ca..29f7b4f 100644 --- a/app.js +++ b/app.js @@ -6,7 +6,8 @@ var logger = require('morgan'); var indexRouter = require('./routes/index'); var usersRouter = require('./routes/users'); -var submitRouter = require('./routes/submit') +var submitRouter = require('./routes/submit'); +var dataRouter = require('./routes/data'); var app = express(); @@ -23,6 +24,7 @@ app.use(express.static(path.join(__dirname, 'public'))); app.use('/', indexRouter); app.use('/users', usersRouter); app.use('/submit', submitRouter); +app.use('/data', dataRouter); // catch 404 and forward to error handler app.use(function(req, res, next) { diff --git a/public/scripts/submit.js b/public/scripts/submit.js new file mode 100644 index 0000000..e69de29 diff --git a/routes/data.js b/routes/data.js new file mode 100644 index 0000000..05f5291 --- /dev/null +++ b/routes/data.js @@ -0,0 +1,10 @@ +var express = require('express'); +var router = express.Router(); +var database = require('../database/database'); + +/* GET submit page. */ +router.get('/', function(req, res, next) { + res.json( { message : "test" }); +}); + +module.exports = router; \ No newline at end of file diff --git a/views/layout.pug b/views/layout.pug index 69f5a59..e4f85eb 100644 --- a/views/layout.pug +++ b/views/layout.pug @@ -7,3 +7,4 @@ html block stylesheets body block content + block scripts diff --git a/views/submit.pug b/views/submit.pug index 385ad51..149fde7 100644 --- a/views/submit.pug +++ b/views/submit.pug @@ -39,3 +39,6 @@ block content input(class="score-input", type="number", name="team2-score", value="0") span(class='form-section') button(type="submit") Submit + +block scripts + script(src='/scripts/submit.js') \ No newline at end of file From 30ddb13ec6815007a80c4db7de6a3b2b64108fa1 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sun, 21 Nov 2021 15:30:54 -0700 Subject: [PATCH 036/169] Add function to list sports in submit form dropdown --- public/scripts/data.js | 5 +++++ public/scripts/submit.js | 20 ++++++++++++++++++++ routes/data.js | 7 ++++--- views/submit.pug | 3 +-- 4 files changed, 30 insertions(+), 5 deletions(-) create mode 100644 public/scripts/data.js diff --git a/public/scripts/data.js b/public/scripts/data.js new file mode 100644 index 0000000..01b681b --- /dev/null +++ b/public/scripts/data.js @@ -0,0 +1,5 @@ +export async function getSports() { + const response = await fetch('/data/sports'); + const sportsList = await response.json(); + return sportsList; +} \ No newline at end of file diff --git a/public/scripts/submit.js b/public/scripts/submit.js index e69de29..dc3b2b7 100644 --- a/public/scripts/submit.js +++ b/public/scripts/submit.js @@ -0,0 +1,20 @@ +import * as Data from "./data.js"; + + +const sportDropdown = document.getElementById('sport-dropdown'); + + + + + +async function listSports() { + let sportsList = await Data.getSports(); + + sportsList.forEach(sport => { + const option = document.createElement('option'); + option.text = sport.name; + option.value = sport.id; + sportDropdown.appendChild(option); + }); +} +listSports(); \ No newline at end of file diff --git a/routes/data.js b/routes/data.js index 05f5291..072a63e 100644 --- a/routes/data.js +++ b/routes/data.js @@ -1,10 +1,11 @@ var express = require('express'); var router = express.Router(); -var database = require('../database/database'); +var sports = require('../database/scores/sports'); /* GET submit page. */ -router.get('/', function(req, res, next) { - res.json( { message : "test" }); +router.get('/sports', function(req, res, next) { + sports.retrieveAll() + .then(data => res.json(data)); }); module.exports = router; \ No newline at end of file diff --git a/views/submit.pug b/views/submit.pug index 149fde7..25ebe71 100644 --- a/views/submit.pug +++ b/views/submit.pug @@ -15,7 +15,6 @@ block content label Sport span(class='form-section-input') select#sport-dropdown(name="sport" class="main-dropdown") - option(value="Basketball" selected) Basketball select#gender-dropdown(name="gender") option(value="Male" selected) Male option(value="Female") Female @@ -41,4 +40,4 @@ block content button(type="submit") Submit block scripts - script(src='/scripts/submit.js') \ No newline at end of file + script(src='/scripts/submit.js' type="module") \ No newline at end of file From 0d9e70896a0d9fd853a48f26cfe49f1cd7ca7467 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sun, 21 Nov 2021 15:40:48 -0700 Subject: [PATCH 037/169] Add function to list seasons in submit form --- public/scripts/data.js | 6 ++++++ public/scripts/submit.js | 17 +++++++++++++++-- routes/data.js | 8 +++++++- views/submit.pug | 1 - 4 files changed, 28 insertions(+), 4 deletions(-) diff --git a/public/scripts/data.js b/public/scripts/data.js index 01b681b..44d50a1 100644 --- a/public/scripts/data.js +++ b/public/scripts/data.js @@ -2,4 +2,10 @@ export async function getSports() { const response = await fetch('/data/sports'); const sportsList = await response.json(); return sportsList; +} + +export async function getSeasons() { + const response = await fetch('/data/seasons'); + const seasonsList = await response.json(); + return seasonsList; } \ No newline at end of file diff --git a/public/scripts/submit.js b/public/scripts/submit.js index dc3b2b7..64b62ac 100644 --- a/public/scripts/submit.js +++ b/public/scripts/submit.js @@ -2,13 +2,14 @@ import * as Data from "./data.js"; const sportDropdown = document.getElementById('sport-dropdown'); +const seasonDropdown = document.getElementById('year-dropdown'); async function listSports() { - let sportsList = await Data.getSports(); + const sportsList = await Data.getSports(); sportsList.forEach(sport => { const option = document.createElement('option'); @@ -17,4 +18,16 @@ async function listSports() { sportDropdown.appendChild(option); }); } -listSports(); \ No newline at end of file +listSports(); + +async function listSeasons() { + const seasonsList = await Data.getSeasons(); + + seasonsList.forEach(season => { + const option = document.createElement('option'); + option.text = season.year - 1 + "-" + season.year; + option.value = season.id; + seasonDropdown.appendChild(option); + }); +} +listSeasons(); \ No newline at end of file diff --git a/routes/data.js b/routes/data.js index 072a63e..aa61618 100644 --- a/routes/data.js +++ b/routes/data.js @@ -1,11 +1,17 @@ var express = require('express'); var router = express.Router(); var sports = require('../database/scores/sports'); +var seasons = require('../database/scores/seasons'); + -/* GET submit page. */ router.get('/sports', function(req, res, next) { sports.retrieveAll() .then(data => res.json(data)); }); +router.get('/seasons', function(req, res, next) { + seasons.retrieveAll() + .then(data => res.json(data)); +}) + module.exports = router; \ No newline at end of file diff --git a/views/submit.pug b/views/submit.pug index 25ebe71..5ac6ccf 100644 --- a/views/submit.pug +++ b/views/submit.pug @@ -10,7 +10,6 @@ block content label Year span(class='form-section-input') select#year-dropdown(name="year" class="main-dropdown") - option(value="2022" selected) 2021-2022 span(class='form-section') label Sport span(class='form-section-input') From c0ab9d45816ff0051a4ebdcaaa64e03d6ea19f77 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sun, 21 Nov 2021 15:52:54 -0700 Subject: [PATCH 038/169] Create module to retrieve a list of genders by sport --- database/scores/genders.js | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 database/scores/genders.js diff --git a/database/scores/genders.js b/database/scores/genders.js new file mode 100644 index 0000000..857190f --- /dev/null +++ b/database/scores/genders.js @@ -0,0 +1,34 @@ +const database = require('./../database'); +const genders = require('../../constants/genders'); + + + + + +function getGendersBySport(sportID) { + const query = `SELECT gender + from scores.divisions + WHERE sport_id = $1;`; + const table = await database.executeQuery(query, [sportID]); + + const gendersList = []; + + if(table.length() == 2) { + gendersList.push(genders.female); + gendersList.push(genders.male); + } + else if(table[0][0] = "F") { + gendersList.push(genders.female); + } + else if(table[0][0] = "M") { + gendersList.push(genders.male); + } + + return gendersList; +} + + + + + +exports.getGendersBySport = getGendersBySport; \ No newline at end of file From 07c4d9b6221987da465806de8f4be2cfebf52ba7 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sun, 21 Nov 2021 15:53:58 -0700 Subject: [PATCH 039/169] Retrieve seasons in descending order --- database/scores/seasons.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/scores/seasons.js b/database/scores/seasons.js index 381d064..f787ade 100644 --- a/database/scores/seasons.js +++ b/database/scores/seasons.js @@ -32,7 +32,7 @@ async function remove(id) { async function retrieveAll() { const query = `SELECT * FROM scores.seasons - ORDER BY school_year;`; + ORDER BY school_year DESC;`; const table = await database.executeQuery(query); const seasonsList = []; From a8eb7dae0a9e65fcb88f73b065e16c1d38c59bfe Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sun, 21 Nov 2021 15:57:09 -0700 Subject: [PATCH 040/169] Rename function in genders.js from "getGenderBySport" to "retrieveBySport" --- database/scores/genders.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/database/scores/genders.js b/database/scores/genders.js index 857190f..9e6ba44 100644 --- a/database/scores/genders.js +++ b/database/scores/genders.js @@ -5,7 +5,7 @@ const genders = require('../../constants/genders'); -function getGendersBySport(sportID) { +function retrieveBySport(sportID) { const query = `SELECT gender from scores.divisions WHERE sport_id = $1;`; @@ -31,4 +31,4 @@ function getGendersBySport(sportID) { -exports.getGendersBySport = getGendersBySport; \ No newline at end of file +exports.retrieveBySport = retrieveBySport; \ No newline at end of file From e5b17912598205daa24502fecd4468fd14f0f6b8 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sun, 21 Nov 2021 15:59:07 -0700 Subject: [PATCH 041/169] Make "retrieveBySport" function in genders.js async --- database/scores/genders.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/scores/genders.js b/database/scores/genders.js index 9e6ba44..c86221a 100644 --- a/database/scores/genders.js +++ b/database/scores/genders.js @@ -5,7 +5,7 @@ const genders = require('../../constants/genders'); -function retrieveBySport(sportID) { +async function retrieveBySport(sportID) { const query = `SELECT gender from scores.divisions WHERE sport_id = $1;`; From ff070acdf7ea9889c7e3b604f28d300acb732577 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sun, 21 Nov 2021 16:24:43 -0700 Subject: [PATCH 042/169] Add function to get genders per sport in submit form --- database/scores/genders.js | 10 +++++++--- public/scripts/data.js | 6 ++++++ public/scripts/submit.js | 23 ++++++++++++++++++++++- routes/data.js | 6 ++++++ views/submit.pug | 2 -- 5 files changed, 41 insertions(+), 6 deletions(-) diff --git a/database/scores/genders.js b/database/scores/genders.js index c86221a..27e811e 100644 --- a/database/scores/genders.js +++ b/database/scores/genders.js @@ -13,18 +13,22 @@ async function retrieveBySport(sportID) { const gendersList = []; - if(table.length() == 2) { + if(table.length == 0) { + return gendersList; + } + if(table.length == 2) { gendersList.push(genders.female); gendersList.push(genders.male); + return gendersList; } else if(table[0][0] = "F") { gendersList.push(genders.female); + return gendersList; } else if(table[0][0] = "M") { gendersList.push(genders.male); + return gendersList; } - - return gendersList; } diff --git a/public/scripts/data.js b/public/scripts/data.js index 44d50a1..cccd7cf 100644 --- a/public/scripts/data.js +++ b/public/scripts/data.js @@ -8,4 +8,10 @@ export async function getSeasons() { const response = await fetch('/data/seasons'); const seasonsList = await response.json(); return seasonsList; +} + +export async function getGenders(sportID) { + const response = await fetch(`/data/genders?sport=${+sportID}`); + const gendersList = await response.json(); + return gendersList; } \ No newline at end of file diff --git a/public/scripts/submit.js b/public/scripts/submit.js index 64b62ac..e51fda6 100644 --- a/public/scripts/submit.js +++ b/public/scripts/submit.js @@ -3,6 +3,7 @@ import * as Data from "./data.js"; const sportDropdown = document.getElementById('sport-dropdown'); const seasonDropdown = document.getElementById('year-dropdown'); +const genderDropdown = document.getElementById('gender-dropdown'); @@ -30,4 +31,24 @@ async function listSeasons() { seasonDropdown.appendChild(option); }); } -listSeasons(); \ No newline at end of file +listSeasons(); + +async function listGenders() { + genderDropdown.innerHTML = ""; + + const selectedSportID = sportDropdown.value; + const gendersList = await Data.getGenders(selectedSportID); + + gendersList.forEach(gender => { + const option = document.createElement('option'); + option.text = (gender.name == "female") ? "Female" : (gender.name == "male") ? "Male" : ""; + option.value = gender.name; + genderDropdown.appendChild(option); + }); +} + + + + + +sportDropdown.onchange = listGenders; \ No newline at end of file diff --git a/routes/data.js b/routes/data.js index aa61618..8b4ea87 100644 --- a/routes/data.js +++ b/routes/data.js @@ -2,6 +2,7 @@ var express = require('express'); var router = express.Router(); var sports = require('../database/scores/sports'); var seasons = require('../database/scores/seasons'); +var genders = require('../database/scores/genders'); router.get('/sports', function(req, res, next) { @@ -14,4 +15,9 @@ router.get('/seasons', function(req, res, next) { .then(data => res.json(data)); }) +router.get('/genders', function(req, res, next) { + genders.retrieveBySport(req.query.sport) + .then(data => res.json(data)); +}) + module.exports = router; \ No newline at end of file diff --git a/views/submit.pug b/views/submit.pug index 5ac6ccf..93fdec1 100644 --- a/views/submit.pug +++ b/views/submit.pug @@ -15,8 +15,6 @@ block content span(class='form-section-input') select#sport-dropdown(name="sport" class="main-dropdown") select#gender-dropdown(name="gender") - option(value="Male" selected) Male - option(value="Female") Female select#division-dropdown(name="division") option(value="Varsity") Varsity option(value="JV-A") JV-A From bdd943d422fefbec529b5309cb587e63b41d7f46 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sun, 21 Nov 2021 16:30:36 -0700 Subject: [PATCH 043/169] Move gender constants into genders.js file in database directory --- constants/genders.js | 8 -------- database/scores/divisions.js | 4 ++-- database/scores/genders.js | 22 ++++++++++++++++------ 3 files changed, 18 insertions(+), 16 deletions(-) delete mode 100644 constants/genders.js diff --git a/constants/genders.js b/constants/genders.js deleted file mode 100644 index 402d58f..0000000 --- a/constants/genders.js +++ /dev/null @@ -1,8 +0,0 @@ -class Gender { - constructor(name) { - this.name = name; - } -} - -exports.male = new Gender("male"); -exports.female = new Gender("female"); \ No newline at end of file diff --git a/database/scores/divisions.js b/database/scores/divisions.js index b04d6a5..38fa8be 100644 --- a/database/scores/divisions.js +++ b/database/scores/divisions.js @@ -1,5 +1,5 @@ const database = require('./../database'); -const genders = require('../../constants/genders'); +const genders = require('./genders'); @@ -15,7 +15,7 @@ class Division { function getGenderID(gender) { - return (gender == genders.male) ? "M" : "F"; + return (gender == genders.MALE) ? "M" : "F"; } diff --git a/database/scores/genders.js b/database/scores/genders.js index 27e811e..126e57b 100644 --- a/database/scores/genders.js +++ b/database/scores/genders.js @@ -1,10 +1,19 @@ const database = require('./../database'); -const genders = require('../../constants/genders'); +class Gender { + constructor(name) { + this.name = name; + } +} +const MALE = new Gender("male"); +const FEMALE = new Gender("female"); + + + async function retrieveBySport(sportID) { const query = `SELECT gender from scores.divisions @@ -17,16 +26,16 @@ async function retrieveBySport(sportID) { return gendersList; } if(table.length == 2) { - gendersList.push(genders.female); - gendersList.push(genders.male); + gendersList.push(genders.FEMALE); + gendersList.push(genders.MALE); return gendersList; } else if(table[0][0] = "F") { - gendersList.push(genders.female); + gendersList.push(genders.FEMALE); return gendersList; } else if(table[0][0] = "M") { - gendersList.push(genders.male); + gendersList.push(genders.MALE); return gendersList; } } @@ -34,5 +43,6 @@ async function retrieveBySport(sportID) { - +exports.MALE = MALE; +exports.FEMALE = FEMALE; exports.retrieveBySport = retrieveBySport; \ No newline at end of file From d561a34055fc0086cd8edf2322f1bd8d52461afc Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sun, 21 Nov 2021 16:39:51 -0700 Subject: [PATCH 044/169] Fix bug in genders.js regarding constants --- database/scores/genders.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/database/scores/genders.js b/database/scores/genders.js index 126e57b..b59bdb0 100644 --- a/database/scores/genders.js +++ b/database/scores/genders.js @@ -26,16 +26,16 @@ async function retrieveBySport(sportID) { return gendersList; } if(table.length == 2) { - gendersList.push(genders.FEMALE); - gendersList.push(genders.MALE); + gendersList.push(FEMALE); + gendersList.push(MALE); return gendersList; } else if(table[0][0] = "F") { - gendersList.push(genders.FEMALE); + gendersList.push(FEMALE); return gendersList; } else if(table[0][0] = "M") { - gendersList.push(genders.MALE); + gendersList.push(MALE); return gendersList; } } From 9b7ec5f3d679fa0e13f028900dc4c1ba8e88e0e8 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sun, 21 Nov 2021 16:48:03 -0700 Subject: [PATCH 045/169] Add function to list divisions in submit form --- public/scripts/data.js | 6 ++++++ public/scripts/submit.js | 26 +++++++++++++++++++++++++- routes/data.js | 8 ++++++++ views/submit.pug | 3 --- 4 files changed, 39 insertions(+), 4 deletions(-) diff --git a/public/scripts/data.js b/public/scripts/data.js index cccd7cf..27ba099 100644 --- a/public/scripts/data.js +++ b/public/scripts/data.js @@ -14,4 +14,10 @@ export async function getGenders(sportID) { const response = await fetch(`/data/genders?sport=${+sportID}`); const gendersList = await response.json(); return gendersList; +} + +export async function getDivisions(sportID, gender) { + const response = await fetch(`/data/divisions?sport=${+sportID}&gender=${gender}`); + const divisionsList = await response.json(); + return divisionsList; } \ No newline at end of file diff --git a/public/scripts/submit.js b/public/scripts/submit.js index e51fda6..85fc410 100644 --- a/public/scripts/submit.js +++ b/public/scripts/submit.js @@ -4,6 +4,7 @@ import * as Data from "./data.js"; const sportDropdown = document.getElementById('sport-dropdown'); const seasonDropdown = document.getElementById('year-dropdown'); const genderDropdown = document.getElementById('gender-dropdown'); +const divisionDropdown = document.getElementById('division-dropdown'); @@ -18,6 +19,8 @@ async function listSports() { option.value = sport.id; sportDropdown.appendChild(option); }); + + listSeasons(); } listSports(); @@ -45,10 +48,31 @@ async function listGenders() { option.value = gender.name; genderDropdown.appendChild(option); }); + + listDivisions(); +} + +async function listDivisions() { + divisionDropdown.innerHTML = ""; + + const selectedSportID = sportDropdown.value; + const selectedGender = genderDropdown.value; + + if(!selectedGender) return; + + const divisionsList = await Data.getDivisions(selectedSportID, selectedGender); + + divisionsList.forEach(division => { + const option = document.createElement('option'); + option.text = division.name; + option.value = division.id; + divisionDropdown.appendChild(option); + }) } -sportDropdown.onchange = listGenders; \ No newline at end of file +sportDropdown.onchange = listGenders; +genderDropdown.onchange = listDivisions; \ No newline at end of file diff --git a/routes/data.js b/routes/data.js index 8b4ea87..68a7e6d 100644 --- a/routes/data.js +++ b/routes/data.js @@ -3,6 +3,7 @@ var router = express.Router(); var sports = require('../database/scores/sports'); var seasons = require('../database/scores/seasons'); var genders = require('../database/scores/genders'); +var divisions = require('../database/scores/divisions'); router.get('/sports', function(req, res, next) { @@ -20,4 +21,11 @@ router.get('/genders', function(req, res, next) { .then(data => res.json(data)); }) +router.get('/divisions', function(req, res, next) { + const gender = req.body.gender == 'female' ? genders.FEMALE : genders.MALE; + + divisions.retrieveBySportAndGender(req.query.sport, gender) + .then(data => res.json(data)); +}) + module.exports = router; \ No newline at end of file diff --git a/views/submit.pug b/views/submit.pug index 93fdec1..97d7b37 100644 --- a/views/submit.pug +++ b/views/submit.pug @@ -16,9 +16,6 @@ block content select#sport-dropdown(name="sport" class="main-dropdown") select#gender-dropdown(name="gender") select#division-dropdown(name="division") - option(value="Varsity") Varsity - option(value="JV-A") JV-A - option(value="JV-B") JV-B span(class='form-section') label Date of match span(class='form-section-input') From e1acf1951ccaf49a61318af046d1507149d1ebc9 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sun, 21 Nov 2021 16:51:35 -0700 Subject: [PATCH 046/169] Fix bugs in submit form dropdowns --- public/scripts/submit.js | 64 ++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 29 deletions(-) diff --git a/public/scripts/submit.js b/public/scripts/submit.js index 85fc410..678fa16 100644 --- a/public/scripts/submit.js +++ b/public/scripts/submit.js @@ -10,21 +10,9 @@ const divisionDropdown = document.getElementById('division-dropdown'); -async function listSports() { - const sportsList = await Data.getSports(); - - sportsList.forEach(sport => { - const option = document.createElement('option'); - option.text = sport.name; - option.value = sport.id; - sportDropdown.appendChild(option); - }); - - listSeasons(); -} -listSports(); - async function listSeasons() { + seasonDropdown.innerHTML = ""; + const seasonsList = await Data.getSeasons(); seasonsList.forEach(season => { @@ -36,18 +24,36 @@ async function listSeasons() { } listSeasons(); +async function listSports() { + sportDropdown.innerHTML = ""; + + const sportsList = await Data.getSports(); + + sportsList.forEach(sport => { + const option = document.createElement('option'); + option.text = sport.name; + option.value = sport.id; + sportDropdown.appendChild(option); + }); + + listGenders(); +} +listSports(); + async function listGenders() { genderDropdown.innerHTML = ""; const selectedSportID = sportDropdown.value; const gendersList = await Data.getGenders(selectedSportID); - gendersList.forEach(gender => { - const option = document.createElement('option'); - option.text = (gender.name == "female") ? "Female" : (gender.name == "male") ? "Male" : ""; - option.value = gender.name; - genderDropdown.appendChild(option); - }); + if(selectedSportID) { + gendersList.forEach(gender => { + const option = document.createElement('option'); + option.text = (gender.name == "female") ? "Female" : (gender.name == "male") ? "Male" : ""; + option.value = gender.name; + genderDropdown.appendChild(option); + }); + } listDivisions(); } @@ -58,16 +64,16 @@ async function listDivisions() { const selectedSportID = sportDropdown.value; const selectedGender = genderDropdown.value; - if(!selectedGender) return; + if(selectedGender) { + const divisionsList = await Data.getDivisions(selectedSportID, selectedGender); - const divisionsList = await Data.getDivisions(selectedSportID, selectedGender); - - divisionsList.forEach(division => { - const option = document.createElement('option'); - option.text = division.name; - option.value = division.id; - divisionDropdown.appendChild(option); - }) + divisionsList.forEach(division => { + const option = document.createElement('option'); + option.text = division.name; + option.value = division.id; + divisionDropdown.appendChild(option); + }); + } } From edfd42a6232676053b0e5808e2b3591208d8c6dc Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sun, 21 Nov 2021 17:08:16 -0700 Subject: [PATCH 047/169] Fix bug in "retrieveBySport" function in teams.js --- database/scores/teams.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/scores/teams.js b/database/scores/teams.js index 1bad3fa..89f2c4c 100644 --- a/database/scores/teams.js +++ b/database/scores/teams.js @@ -42,7 +42,7 @@ async function retrieveBySport(sportID) { FROM scores.teams WHERE sport_id = $1 ORDER BY team_name;`; - const table = await database.executeQuery(query); + const table = await database.executeQuery(query, [sportID]); const teamsList = []; table.forEach((row) => { From 27d05752b59d773855dde52bc5f749be87102145 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sun, 21 Nov 2021 17:15:02 -0700 Subject: [PATCH 048/169] Add function to list teams on submit form --- public/scripts/data.js | 6 ++++++ public/scripts/submit.js | 31 ++++++++++++++++++++++++++++++- routes/data.js | 6 ++++++ 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/public/scripts/data.js b/public/scripts/data.js index 27ba099..7e21436 100644 --- a/public/scripts/data.js +++ b/public/scripts/data.js @@ -20,4 +20,10 @@ export async function getDivisions(sportID, gender) { const response = await fetch(`/data/divisions?sport=${+sportID}&gender=${gender}`); const divisionsList = await response.json(); return divisionsList; +} + +export async function getTeams(sportID) { + const response = await fetch(`/data/teams?sport=${+sportID}`); + const teamsList = await response.json(); + return teamsList; } \ No newline at end of file diff --git a/public/scripts/submit.js b/public/scripts/submit.js index 678fa16..bd4d08a 100644 --- a/public/scripts/submit.js +++ b/public/scripts/submit.js @@ -5,6 +5,8 @@ const sportDropdown = document.getElementById('sport-dropdown'); const seasonDropdown = document.getElementById('year-dropdown'); const genderDropdown = document.getElementById('gender-dropdown'); const divisionDropdown = document.getElementById('division-dropdown'); +const team1Dropdown = document.getElementById('team1-dropdown'); +const team2Dropdown = document.getElementById('team2-dropdown'); @@ -37,6 +39,7 @@ async function listSports() { }); listGenders(); + listTeams(); } listSports(); @@ -76,9 +79,35 @@ async function listDivisions() { } } +async function listTeams() { + team1Dropdown.innerHTML = ""; + team2Dropdown.innerHTML = ""; + + const selectedSportID = sportDropdown.value; + + if(selectedSportID) { + const teamsList = await Data.getTeams(selectedSportID); + + teamsList.forEach(team => { + const optionT1 = document.createElement('option'); + optionT1.text = team.name; + optionT1.value = team.id; + team1Dropdown.appendChild(optionT1); + + const optionT2 = document.createElement('option'); + optionT2.text = team.name; + optionT2.value = team.id; + team2Dropdown.appendChild(optionT2); + }); + } +} -sportDropdown.onchange = listGenders; + +sportDropdown.onchange = (() => { + listGenders(); + listTeams(); +}); genderDropdown.onchange = listDivisions; \ No newline at end of file diff --git a/routes/data.js b/routes/data.js index 68a7e6d..fc6c947 100644 --- a/routes/data.js +++ b/routes/data.js @@ -4,6 +4,7 @@ var sports = require('../database/scores/sports'); var seasons = require('../database/scores/seasons'); var genders = require('../database/scores/genders'); var divisions = require('../database/scores/divisions'); +var teams = require('../database/scores/teams'); router.get('/sports', function(req, res, next) { @@ -28,4 +29,9 @@ router.get('/divisions', function(req, res, next) { .then(data => res.json(data)); }) +router.get('/teams', function(req, res, next) { + teams.retrieveBySport(req.query.sport) + .then(data => res.json(data)); +}) + module.exports = router; \ No newline at end of file From 7d4dcc4d82826c000b5263a0a2d6a247161ba765 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sun, 21 Nov 2021 17:20:14 -0700 Subject: [PATCH 049/169] Add moment.js --- package-lock.json | 14 ++++++++++++++ package.json | 1 + 2 files changed, 15 insertions(+) diff --git a/package-lock.json b/package-lock.json index 225db7b..821a54c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "dotenv": "^10.0.0", "express": "~4.16.0", "http-errors": "~1.6.2", + "moment": "^2.29.1", "morgan": "~1.9.0", "nodemailer": "^6.6.5", "passport": "^0.5.0", @@ -909,6 +910,14 @@ "ms": "2.0.0" } }, + "node_modules/moment": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", + "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==", + "engines": { + "node": "*" + } + }, "node_modules/morgan": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.0.tgz", @@ -2400,6 +2409,11 @@ } } }, + "moment": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", + "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==" + }, "morgan": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.0.tgz", diff --git a/package.json b/package.json index c94c979..2e3323a 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "dotenv": "^10.0.0", "express": "~4.16.0", "http-errors": "~1.6.2", + "moment": "^2.29.1", "morgan": "~1.9.0", "nodemailer": "^6.6.5", "passport": "^0.5.0", From 4576b7261287dc59d5396162635cb935fab597c6 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sun, 21 Nov 2021 17:37:45 -0700 Subject: [PATCH 050/169] Switch date from native object to moment.js --- database/scores/games.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/database/scores/games.js b/database/scores/games.js index b990a31..8d49aeb 100644 --- a/database/scores/games.js +++ b/database/scores/games.js @@ -1,4 +1,5 @@ const database = require('./../database'); +const moment = require('moment'); @@ -22,11 +23,7 @@ async function add(divisionID, seasonID, date, team1ID, team2ID, team1Score, tea VALUES($1, $2, $3, $4, $5, $6, $7) RETURNING game_id;`; - const day = ("0" + date.getDate()).slice(-2); - const month = ("0" + (date.getMonth() + 1)).slice(-2); - const year = date.getFullYear(); - - const dateISO = year + '-' + month + '-' + day; + const dateISO = date.format(YYYY-MM-DD); const id = (await database.executeQuery(query, [divisionID, seasonID, dateISO, team1ID, team2ID, team1Score, team2Score]))[0][0]; return new Game(id, date, team1ID, team2ID, team1Score, team2Score); From 344e8b66d394515176924ac632d1a77bbb2537e0 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sun, 21 Nov 2021 17:39:47 -0700 Subject: [PATCH 051/169] Report date as moment object in list generated by "retrieveByTeamDivisionAndSeason" function in games.js --- database/scores/games.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/scores/games.js b/database/scores/games.js index 8d49aeb..1560768 100644 --- a/database/scores/games.js +++ b/database/scores/games.js @@ -51,7 +51,7 @@ async function retrieveByTeamDivisionAndSeason(teamID, divisionID, seasonID) { teamScore = opponentIsTeam2 ? row[6] : row[7]; opponentScore = opponentIsTeam2 ? row[7] : row[6]; - gamesList.push(new Game(row[0], row[3], teamID, opponentID, teamScore, opponentScore)); + gamesList.push(new Game(row[0], moment(row[3]), teamID, opponentID, teamScore, opponentScore)); }); return gamesList; } From 49aff3f741c95c52d480cbc35d4c0bd5be5695bf Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sun, 21 Nov 2021 17:41:45 -0700 Subject: [PATCH 052/169] Fix bug regarding date formatting in games.js --- database/scores/games.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/scores/games.js b/database/scores/games.js index 1560768..96463ec 100644 --- a/database/scores/games.js +++ b/database/scores/games.js @@ -23,7 +23,7 @@ async function add(divisionID, seasonID, date, team1ID, team2ID, team1Score, tea VALUES($1, $2, $3, $4, $5, $6, $7) RETURNING game_id;`; - const dateISO = date.format(YYYY-MM-DD); + const dateISO = date.format('YYYY-MM-DD'); const id = (await database.executeQuery(query, [divisionID, seasonID, dateISO, team1ID, team2ID, team1Score, team2Score]))[0][0]; return new Game(id, date, team1ID, team2ID, team1Score, team2Score); From 12c39e271e63845819118fa2962e22c3878a06a9 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sun, 21 Nov 2021 17:43:34 -0700 Subject: [PATCH 053/169] Complete submit game feature --- routes/submit.js | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/routes/submit.js b/routes/submit.js index e79ff92..8510303 100644 --- a/routes/submit.js +++ b/routes/submit.js @@ -1,31 +1,28 @@ var express = require('express'); var router = express.Router(); -var database = require('../database/database'); +var genders = require('../database/scores/genders'); +var games = require('../database/scores/games'); +var moment = require('moment'); /* GET submit page. */ router.get('/', function(req, res, next) { - const date_ob = new Date(); - const date = ("0" + date_ob.getDate()).slice(-2); - const month = ("0" + (date_ob.getMonth() + 1)).slice(-2); - const year = date_ob.getFullYear(); - - const currentDate = year + '-' + month + '-' + date; - res.render('submit', { title: 'Submit Score', date: currentDate }); + res.render('submit', { title: 'Submit Score', date: moment().format('YYYY-MM-DD') }); }); /* POST submit page. */ router.post('/', function(req, res, next) { - const year = req.body['year']; - const sport = req.body['sport']; - const gender = req.body['gender']; - const division = req.body['division']; - const date = req.body['date']; - const team1 = req.body['team1']; + const seasonID = req.body['year']; + const sportID = req.body['sport']; + const gender = (req.body['gender'] == "female") ? genders.FEMALE : genders.MALE; + const divisionID = req.body['division']; + const date = moment(req.body['date']); + const team1ID = req.body['team1']; const team1Score = req.body['team1-score']; - const team2 = req.body['team2']; + const team2ID = req.body['team2']; const team2Score = req.body['team2-score']; - + games.add(divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score) + .then(res.send("SUCCESS")); }); module.exports = router; From 9b4575c666d15044823e251a61026a3d53f66d47 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sun, 21 Nov 2021 19:07:44 -0700 Subject: [PATCH 054/169] Begin index page --- public/scripts/index.js | 106 ++++++++++++++++++++++++++++++++++ public/stylesheets/form.css | 34 +++++++++++ public/stylesheets/index.css | 3 + public/stylesheets/style.css | 8 +++ public/stylesheets/submit.css | 38 ------------ routes/index.js | 2 +- views/index.pug | 36 ++++++++++++ views/submit.pug | 58 ++++++++++--------- 8 files changed, 218 insertions(+), 67 deletions(-) create mode 100644 public/scripts/index.js create mode 100644 public/stylesheets/form.css create mode 100644 public/stylesheets/index.css diff --git a/public/scripts/index.js b/public/scripts/index.js new file mode 100644 index 0000000..38627c0 --- /dev/null +++ b/public/scripts/index.js @@ -0,0 +1,106 @@ +import * as Data from "./data.js"; + + +const sportDropdown = document.getElementById('sport-dropdown'); +const seasonDropdown = document.getElementById('year-dropdown'); +const genderDropdown = document.getElementById('gender-dropdown'); +const divisionDropdown = document.getElementById('division-dropdown'); +const teamDropdown = document.getElementById('team-dropdown'); + + + + + +async function listSeasons() { + seasonDropdown.innerHTML = ""; + + const seasonsList = await Data.getSeasons(); + + seasonsList.forEach(season => { + const option = document.createElement('option'); + option.text = season.year - 1 + "-" + season.year; + option.value = season.id; + seasonDropdown.appendChild(option); + }); +} +listSeasons(); + +async function listSports() { + sportDropdown.innerHTML = ""; + + const sportsList = await Data.getSports(); + + sportsList.forEach(sport => { + const option = document.createElement('option'); + option.text = sport.name; + option.value = sport.id; + sportDropdown.appendChild(option); + }); + + listGenders(); + listTeams(); +} +listSports(); + +async function listGenders() { + genderDropdown.innerHTML = ""; + + const selectedSportID = sportDropdown.value; + const gendersList = await Data.getGenders(selectedSportID); + + if(selectedSportID) { + gendersList.forEach(gender => { + const option = document.createElement('option'); + option.text = (gender.name == "female") ? "Female" : (gender.name == "male") ? "Male" : ""; + option.value = gender.name; + genderDropdown.appendChild(option); + }); + } + + listDivisions(); +} + +async function listDivisions() { + divisionDropdown.innerHTML = ""; + + const selectedSportID = sportDropdown.value; + const selectedGender = genderDropdown.value; + + if(selectedGender) { + const divisionsList = await Data.getDivisions(selectedSportID, selectedGender); + + divisionsList.forEach(division => { + const option = document.createElement('option'); + option.text = division.name; + option.value = division.id; + divisionDropdown.appendChild(option); + }); + } +} + +async function listTeams() { + teamDropdown.innerHTML = ""; + + const selectedSportID = sportDropdown.value; + + if(selectedSportID) { + const teamsList = await Data.getTeams(selectedSportID); + + teamsList.forEach(team => { + const option = document.createElement('option'); + option.text = team.name; + option.value = team.id; + teamDropdown.appendChild(option); + }); + } +} + + + + + +sportDropdown.onchange = (() => { + listGenders(); + listTeams(); +}); +genderDropdown.onchange = listDivisions; \ No newline at end of file diff --git a/public/stylesheets/form.css b/public/stylesheets/form.css new file mode 100644 index 0000000..85b34bc --- /dev/null +++ b/public/stylesheets/form.css @@ -0,0 +1,34 @@ +form { + display: flex; + flex-direction: column; + } + + span { + display: flex; + flex-direction: column; + } + + + .form-main-dropdown { + width: 100%; + } + + .form-section { + margin-bottom: 0.75em; + } + + .form-section-input { + flex-direction: row; + } + + input { + width: 100%; + } + + .form-score-input{ + width: 35%; + } + + #submit-button { + margin-top: 1.5em; + } \ No newline at end of file diff --git a/public/stylesheets/index.css b/public/stylesheets/index.css new file mode 100644 index 0000000..e4d4d83 --- /dev/null +++ b/public/stylesheets/index.css @@ -0,0 +1,3 @@ +h1 { + text-align: left; +} \ No newline at end of file diff --git a/public/stylesheets/style.css b/public/stylesheets/style.css index 6a727df..b6a5c49 100644 --- a/public/stylesheets/style.css +++ b/public/stylesheets/style.css @@ -7,3 +7,11 @@ a { color: #00B7FF; } +#mobile-view { + max-width: 20em; + margin-left: auto; + margin-right: auto; + display: flex; + flex-direction: column; +} + diff --git a/public/stylesheets/submit.css b/public/stylesheets/submit.css index 80a8146..af4e239 100644 --- a/public/stylesheets/submit.css +++ b/public/stylesheets/submit.css @@ -1,41 +1,3 @@ h1 { text-align: center; -} - -form { - display: flex; - flex-direction: column; - max-width: 20em; - margin-left: auto; - margin-right: auto; -} - -span { - display: flex; - flex-direction: column; -} - - -.main-dropdown { - width: 100%; -} - -.form-section { - margin-bottom: 0.75em; -} - -.form-section-input { - flex-direction: row; -} - -input { - width: 100%; -} - -.score-input{ - width: 35%; -} - -button { - margin-top: 1.5em; } \ No newline at end of file diff --git a/routes/index.js b/routes/index.js index c806b1c..4cb7f20 100644 --- a/routes/index.js +++ b/routes/index.js @@ -4,7 +4,7 @@ var database = require('../database/database'); /* GET home page. */ router.get('/', function(req, res, next) { - res.render('index', { title: 'Submit Score' }); + res.render('index', { title: 'View Scores' }); }); module.exports = router; diff --git a/views/index.pug b/views/index.pug index e69de29..8a93bd0 100644 --- a/views/index.pug +++ b/views/index.pug @@ -0,0 +1,36 @@ +extends layout + +block stylesheets + link(rel='stylesheet', href='/stylesheets/index.css') + link(rel='stylesheet', href='/stylesheets/form.css') + +block content + div#mobile-view + h1 Score Tracker + div + span(class='form-section') + label Year + span(class='form-section-input') + select#year-dropdown(name="year" class="form-main-dropdown") + span(class='form-section') + label Sport + span(class='form-section-input') + select#sport-dropdown(name="sport" class="form-main-dropdown") + select#gender-dropdown(name="gender") + select#division-dropdown(name="division") + span(class='form-section') + label Team + span(class='form-section-input') + select#team-dropdown(name="team" class="form-main-dropdown") + span(class='form-section') + div + table + tr + th Win? + th Opponent + th Score + th Date + + +block scripts + script(src='/scripts/index.js' type="module") \ No newline at end of file diff --git a/views/submit.pug b/views/submit.pug index 97d7b37..85f352f 100644 --- a/views/submit.pug +++ b/views/submit.pug @@ -2,36 +2,38 @@ extends layout block stylesheets link(rel='stylesheet', href='/stylesheets/submit.css') + link(rel='stylesheet', href='/stylesheets/form.css') block content - h1 Submit Score - form(action='/submit', method='POST') - span(class='form-section') - label Year - span(class='form-section-input') - select#year-dropdown(name="year" class="main-dropdown") - span(class='form-section') - label Sport - span(class='form-section-input') - select#sport-dropdown(name="sport" class="main-dropdown") - select#gender-dropdown(name="gender") - select#division-dropdown(name="division") - span(class='form-section') - label Date of match - span(class='form-section-input') - input(type="date", name="date", value=date) - span(class='form-section') - label Your team - span(class='form-section-input') - select#team1-dropdown(name="team1" class="main-dropdown") - input(class="score-input", type="number", name="team1-score", value="0") - span(class='form-section') - label Opponent - span(class='form-section-input') - select#team2-dropdown(name="team2" class="main-dropdown") - input(class="score-input", type="number", name="team2-score", value="0") - span(class='form-section') - button(type="submit") Submit + div#mobile-view + h1 Submit Score + form(action='/submit', method='POST') + span(class='form-section') + label Year + span(class='form-section-input') + select#year-dropdown(name="year" class="form-main-dropdown") + span(class='form-section') + label Sport + span(class='form-section-input') + select#sport-dropdown(name="sport" class="form-main-dropdown") + select#gender-dropdown(name="gender") + select#division-dropdown(name="division") + span(class='form-section') + label Date of match + span(class='form-section-input') + input(type="date", name="date", value=date) + span(class='form-section') + label Your team + span(class='form-section-input') + select#team1-dropdown(name="team1" class="form-main-dropdown") + input(class="form-score-input", type="number", name="team1-score", value="0") + span(class='form-section') + label Opponent + span(class='form-section-input') + select#team2-dropdown(name="team2" class="form-main-dropdown") + input(class="form-score-input", type="number", name="team2-score", value="0") + span(class='form-section') + button#submit-button(type="submit") Submit block scripts script(src='/scripts/submit.js' type="module") \ No newline at end of file From 1abffb43bde5c2a19a48c37e037adb91b4ebdb19 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sun, 21 Nov 2021 19:33:10 -0700 Subject: [PATCH 055/169] Add function to get team name from an ID --- database/scores/teams.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/database/scores/teams.js b/database/scores/teams.js index 89f2c4c..7aa3d32 100644 --- a/database/scores/teams.js +++ b/database/scores/teams.js @@ -51,6 +51,15 @@ async function retrieveBySport(sportID) { return teamsList; } +async function getFromID(id) { + const query = `SELECT team_name + FROM scores.teams + WHERE team_id = $1;`; + const name = await database.executeQuery(query, [teamID])[0][0]; + + return new Team(id, name); +} + @@ -58,4 +67,5 @@ async function retrieveBySport(sportID) { exports.add = add; exports.rename = rename; exports.remove = remove; -exports.retrieveBySport = retrieveBySport; \ No newline at end of file +exports.retrieveBySport = retrieveBySport; +exports.getFromID = getFromID; \ No newline at end of file From 6de2f13b327c951344b74639149c7036dfc850b2 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sun, 21 Nov 2021 20:53:16 -0700 Subject: [PATCH 056/169] Add moment.js to client scripts --- public/scripts/moment.js | 5670 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 5670 insertions(+) create mode 100644 public/scripts/moment.js diff --git a/public/scripts/moment.js b/public/scripts/moment.js new file mode 100644 index 0000000..1484d6c --- /dev/null +++ b/public/scripts/moment.js @@ -0,0 +1,5670 @@ +//! moment.js +//! version : 2.29.1 +//! authors : Tim Wood, Iskren Chernev, Moment.js contributors +//! license : MIT +//! momentjs.com + +;(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + global.moment = factory() +}(this, (function () { 'use strict'; + + var hookCallback; + + function hooks() { + return hookCallback.apply(null, arguments); + } + + // This is done to register the method called with moment() + // without creating circular dependencies. + function setHookCallback(callback) { + hookCallback = callback; + } + + function isArray(input) { + return ( + input instanceof Array || + Object.prototype.toString.call(input) === '[object Array]' + ); + } + + function isObject(input) { + // IE8 will treat undefined and null as object if it wasn't for + // input != null + return ( + input != null && + Object.prototype.toString.call(input) === '[object Object]' + ); + } + + function hasOwnProp(a, b) { + return Object.prototype.hasOwnProperty.call(a, b); + } + + function isObjectEmpty(obj) { + if (Object.getOwnPropertyNames) { + return Object.getOwnPropertyNames(obj).length === 0; + } else { + var k; + for (k in obj) { + if (hasOwnProp(obj, k)) { + return false; + } + } + return true; + } + } + + function isUndefined(input) { + return input === void 0; + } + + function isNumber(input) { + return ( + typeof input === 'number' || + Object.prototype.toString.call(input) === '[object Number]' + ); + } + + function isDate(input) { + return ( + input instanceof Date || + Object.prototype.toString.call(input) === '[object Date]' + ); + } + + function map(arr, fn) { + var res = [], + i; + for (i = 0; i < arr.length; ++i) { + res.push(fn(arr[i], i)); + } + return res; + } + + function extend(a, b) { + for (var i in b) { + if (hasOwnProp(b, i)) { + a[i] = b[i]; + } + } + + if (hasOwnProp(b, 'toString')) { + a.toString = b.toString; + } + + if (hasOwnProp(b, 'valueOf')) { + a.valueOf = b.valueOf; + } + + return a; + } + + function createUTC(input, format, locale, strict) { + return createLocalOrUTC(input, format, locale, strict, true).utc(); + } + + function defaultParsingFlags() { + // We need to deep clone this object. + return { + empty: false, + unusedTokens: [], + unusedInput: [], + overflow: -2, + charsLeftOver: 0, + nullInput: false, + invalidEra: null, + invalidMonth: null, + invalidFormat: false, + userInvalidated: false, + iso: false, + parsedDateParts: [], + era: null, + meridiem: null, + rfc2822: false, + weekdayMismatch: false, + }; + } + + function getParsingFlags(m) { + if (m._pf == null) { + m._pf = defaultParsingFlags(); + } + return m._pf; + } + + var some; + if (Array.prototype.some) { + some = Array.prototype.some; + } else { + some = function (fun) { + var t = Object(this), + len = t.length >>> 0, + i; + + for (i = 0; i < len; i++) { + if (i in t && fun.call(this, t[i], i, t)) { + return true; + } + } + + return false; + }; + } + + function isValid(m) { + if (m._isValid == null) { + var flags = getParsingFlags(m), + parsedParts = some.call(flags.parsedDateParts, function (i) { + return i != null; + }), + isNowValid = + !isNaN(m._d.getTime()) && + flags.overflow < 0 && + !flags.empty && + !flags.invalidEra && + !flags.invalidMonth && + !flags.invalidWeekday && + !flags.weekdayMismatch && + !flags.nullInput && + !flags.invalidFormat && + !flags.userInvalidated && + (!flags.meridiem || (flags.meridiem && parsedParts)); + + if (m._strict) { + isNowValid = + isNowValid && + flags.charsLeftOver === 0 && + flags.unusedTokens.length === 0 && + flags.bigHour === undefined; + } + + if (Object.isFrozen == null || !Object.isFrozen(m)) { + m._isValid = isNowValid; + } else { + return isNowValid; + } + } + return m._isValid; + } + + function createInvalid(flags) { + var m = createUTC(NaN); + if (flags != null) { + extend(getParsingFlags(m), flags); + } else { + getParsingFlags(m).userInvalidated = true; + } + + return m; + } + + // Plugins that add properties should also add the key here (null value), + // so we can properly clone ourselves. + var momentProperties = (hooks.momentProperties = []), + updateInProgress = false; + + function copyConfig(to, from) { + var i, prop, val; + + if (!isUndefined(from._isAMomentObject)) { + to._isAMomentObject = from._isAMomentObject; + } + if (!isUndefined(from._i)) { + to._i = from._i; + } + if (!isUndefined(from._f)) { + to._f = from._f; + } + if (!isUndefined(from._l)) { + to._l = from._l; + } + if (!isUndefined(from._strict)) { + to._strict = from._strict; + } + if (!isUndefined(from._tzm)) { + to._tzm = from._tzm; + } + if (!isUndefined(from._isUTC)) { + to._isUTC = from._isUTC; + } + if (!isUndefined(from._offset)) { + to._offset = from._offset; + } + if (!isUndefined(from._pf)) { + to._pf = getParsingFlags(from); + } + if (!isUndefined(from._locale)) { + to._locale = from._locale; + } + + if (momentProperties.length > 0) { + for (i = 0; i < momentProperties.length; i++) { + prop = momentProperties[i]; + val = from[prop]; + if (!isUndefined(val)) { + to[prop] = val; + } + } + } + + return to; + } + + // Moment prototype object + function Moment(config) { + copyConfig(this, config); + this._d = new Date(config._d != null ? config._d.getTime() : NaN); + if (!this.isValid()) { + this._d = new Date(NaN); + } + // Prevent infinite loop in case updateOffset creates new moment + // objects. + if (updateInProgress === false) { + updateInProgress = true; + hooks.updateOffset(this); + updateInProgress = false; + } + } + + function isMoment(obj) { + return ( + obj instanceof Moment || (obj != null && obj._isAMomentObject != null) + ); + } + + function warn(msg) { + if ( + hooks.suppressDeprecationWarnings === false && + typeof console !== 'undefined' && + console.warn + ) { + console.warn('Deprecation warning: ' + msg); + } + } + + function deprecate(msg, fn) { + var firstTime = true; + + return extend(function () { + if (hooks.deprecationHandler != null) { + hooks.deprecationHandler(null, msg); + } + if (firstTime) { + var args = [], + arg, + i, + key; + for (i = 0; i < arguments.length; i++) { + arg = ''; + if (typeof arguments[i] === 'object') { + arg += '\n[' + i + '] '; + for (key in arguments[0]) { + if (hasOwnProp(arguments[0], key)) { + arg += key + ': ' + arguments[0][key] + ', '; + } + } + arg = arg.slice(0, -2); // Remove trailing comma and space + } else { + arg = arguments[i]; + } + args.push(arg); + } + warn( + msg + + '\nArguments: ' + + Array.prototype.slice.call(args).join('') + + '\n' + + new Error().stack + ); + firstTime = false; + } + return fn.apply(this, arguments); + }, fn); + } + + var deprecations = {}; + + function deprecateSimple(name, msg) { + if (hooks.deprecationHandler != null) { + hooks.deprecationHandler(name, msg); + } + if (!deprecations[name]) { + warn(msg); + deprecations[name] = true; + } + } + + hooks.suppressDeprecationWarnings = false; + hooks.deprecationHandler = null; + + function isFunction(input) { + return ( + (typeof Function !== 'undefined' && input instanceof Function) || + Object.prototype.toString.call(input) === '[object Function]' + ); + } + + function set(config) { + var prop, i; + for (i in config) { + if (hasOwnProp(config, i)) { + prop = config[i]; + if (isFunction(prop)) { + this[i] = prop; + } else { + this['_' + i] = prop; + } + } + } + this._config = config; + // Lenient ordinal parsing accepts just a number in addition to + // number + (possibly) stuff coming from _dayOfMonthOrdinalParse. + // TODO: Remove "ordinalParse" fallback in next major release. + this._dayOfMonthOrdinalParseLenient = new RegExp( + (this._dayOfMonthOrdinalParse.source || this._ordinalParse.source) + + '|' + + /\d{1,2}/.source + ); + } + + function mergeConfigs(parentConfig, childConfig) { + var res = extend({}, parentConfig), + prop; + for (prop in childConfig) { + if (hasOwnProp(childConfig, prop)) { + if (isObject(parentConfig[prop]) && isObject(childConfig[prop])) { + res[prop] = {}; + extend(res[prop], parentConfig[prop]); + extend(res[prop], childConfig[prop]); + } else if (childConfig[prop] != null) { + res[prop] = childConfig[prop]; + } else { + delete res[prop]; + } + } + } + for (prop in parentConfig) { + if ( + hasOwnProp(parentConfig, prop) && + !hasOwnProp(childConfig, prop) && + isObject(parentConfig[prop]) + ) { + // make sure changes to properties don't modify parent config + res[prop] = extend({}, res[prop]); + } + } + return res; + } + + function Locale(config) { + if (config != null) { + this.set(config); + } + } + + var keys; + + if (Object.keys) { + keys = Object.keys; + } else { + keys = function (obj) { + var i, + res = []; + for (i in obj) { + if (hasOwnProp(obj, i)) { + res.push(i); + } + } + return res; + }; + } + + var defaultCalendar = { + sameDay: '[Today at] LT', + nextDay: '[Tomorrow at] LT', + nextWeek: 'dddd [at] LT', + lastDay: '[Yesterday at] LT', + lastWeek: '[Last] dddd [at] LT', + sameElse: 'L', + }; + + function calendar(key, mom, now) { + var output = this._calendar[key] || this._calendar['sameElse']; + return isFunction(output) ? output.call(mom, now) : output; + } + + function zeroFill(number, targetLength, forceSign) { + var absNumber = '' + Math.abs(number), + zerosToFill = targetLength - absNumber.length, + sign = number >= 0; + return ( + (sign ? (forceSign ? '+' : '') : '-') + + Math.pow(10, Math.max(0, zerosToFill)).toString().substr(1) + + absNumber + ); + } + + var formattingTokens = /(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|N{1,5}|YYYYYY|YYYYY|YYYY|YY|y{2,4}|yo?|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g, + localFormattingTokens = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g, + formatFunctions = {}, + formatTokenFunctions = {}; + + // token: 'M' + // padded: ['MM', 2] + // ordinal: 'Mo' + // callback: function () { this.month() + 1 } + function addFormatToken(token, padded, ordinal, callback) { + var func = callback; + if (typeof callback === 'string') { + func = function () { + return this[callback](); + }; + } + if (token) { + formatTokenFunctions[token] = func; + } + if (padded) { + formatTokenFunctions[padded[0]] = function () { + return zeroFill(func.apply(this, arguments), padded[1], padded[2]); + }; + } + if (ordinal) { + formatTokenFunctions[ordinal] = function () { + return this.localeData().ordinal( + func.apply(this, arguments), + token + ); + }; + } + } + + function removeFormattingTokens(input) { + if (input.match(/\[[\s\S]/)) { + return input.replace(/^\[|\]$/g, ''); + } + return input.replace(/\\/g, ''); + } + + function makeFormatFunction(format) { + var array = format.match(formattingTokens), + i, + length; + + for (i = 0, length = array.length; i < length; i++) { + if (formatTokenFunctions[array[i]]) { + array[i] = formatTokenFunctions[array[i]]; + } else { + array[i] = removeFormattingTokens(array[i]); + } + } + + return function (mom) { + var output = '', + i; + for (i = 0; i < length; i++) { + output += isFunction(array[i]) + ? array[i].call(mom, format) + : array[i]; + } + return output; + }; + } + + // format date using native date object + function formatMoment(m, format) { + if (!m.isValid()) { + return m.localeData().invalidDate(); + } + + format = expandFormat(format, m.localeData()); + formatFunctions[format] = + formatFunctions[format] || makeFormatFunction(format); + + return formatFunctions[format](m); + } + + function expandFormat(format, locale) { + var i = 5; + + function replaceLongDateFormatTokens(input) { + return locale.longDateFormat(input) || input; + } + + localFormattingTokens.lastIndex = 0; + while (i >= 0 && localFormattingTokens.test(format)) { + format = format.replace( + localFormattingTokens, + replaceLongDateFormatTokens + ); + localFormattingTokens.lastIndex = 0; + i -= 1; + } + + return format; + } + + var defaultLongDateFormat = { + LTS: 'h:mm:ss A', + LT: 'h:mm A', + L: 'MM/DD/YYYY', + LL: 'MMMM D, YYYY', + LLL: 'MMMM D, YYYY h:mm A', + LLLL: 'dddd, MMMM D, YYYY h:mm A', + }; + + function longDateFormat(key) { + var format = this._longDateFormat[key], + formatUpper = this._longDateFormat[key.toUpperCase()]; + + if (format || !formatUpper) { + return format; + } + + this._longDateFormat[key] = formatUpper + .match(formattingTokens) + .map(function (tok) { + if ( + tok === 'MMMM' || + tok === 'MM' || + tok === 'DD' || + tok === 'dddd' + ) { + return tok.slice(1); + } + return tok; + }) + .join(''); + + return this._longDateFormat[key]; + } + + var defaultInvalidDate = 'Invalid date'; + + function invalidDate() { + return this._invalidDate; + } + + var defaultOrdinal = '%d', + defaultDayOfMonthOrdinalParse = /\d{1,2}/; + + function ordinal(number) { + return this._ordinal.replace('%d', number); + } + + var defaultRelativeTime = { + future: 'in %s', + past: '%s ago', + s: 'a few seconds', + ss: '%d seconds', + m: 'a minute', + mm: '%d minutes', + h: 'an hour', + hh: '%d hours', + d: 'a day', + dd: '%d days', + w: 'a week', + ww: '%d weeks', + M: 'a month', + MM: '%d months', + y: 'a year', + yy: '%d years', + }; + + function relativeTime(number, withoutSuffix, string, isFuture) { + var output = this._relativeTime[string]; + return isFunction(output) + ? output(number, withoutSuffix, string, isFuture) + : output.replace(/%d/i, number); + } + + function pastFuture(diff, output) { + var format = this._relativeTime[diff > 0 ? 'future' : 'past']; + return isFunction(format) ? format(output) : format.replace(/%s/i, output); + } + + var aliases = {}; + + function addUnitAlias(unit, shorthand) { + var lowerCase = unit.toLowerCase(); + aliases[lowerCase] = aliases[lowerCase + 's'] = aliases[shorthand] = unit; + } + + function normalizeUnits(units) { + return typeof units === 'string' + ? aliases[units] || aliases[units.toLowerCase()] + : undefined; + } + + function normalizeObjectUnits(inputObject) { + var normalizedInput = {}, + normalizedProp, + prop; + + for (prop in inputObject) { + if (hasOwnProp(inputObject, prop)) { + normalizedProp = normalizeUnits(prop); + if (normalizedProp) { + normalizedInput[normalizedProp] = inputObject[prop]; + } + } + } + + return normalizedInput; + } + + var priorities = {}; + + function addUnitPriority(unit, priority) { + priorities[unit] = priority; + } + + function getPrioritizedUnits(unitsObj) { + var units = [], + u; + for (u in unitsObj) { + if (hasOwnProp(unitsObj, u)) { + units.push({ unit: u, priority: priorities[u] }); + } + } + units.sort(function (a, b) { + return a.priority - b.priority; + }); + return units; + } + + function isLeapYear(year) { + return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; + } + + function absFloor(number) { + if (number < 0) { + // -0 -> 0 + return Math.ceil(number) || 0; + } else { + return Math.floor(number); + } + } + + function toInt(argumentForCoercion) { + var coercedNumber = +argumentForCoercion, + value = 0; + + if (coercedNumber !== 0 && isFinite(coercedNumber)) { + value = absFloor(coercedNumber); + } + + return value; + } + + function makeGetSet(unit, keepTime) { + return function (value) { + if (value != null) { + set$1(this, unit, value); + hooks.updateOffset(this, keepTime); + return this; + } else { + return get(this, unit); + } + }; + } + + function get(mom, unit) { + return mom.isValid() + ? mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]() + : NaN; + } + + function set$1(mom, unit, value) { + if (mom.isValid() && !isNaN(value)) { + if ( + unit === 'FullYear' && + isLeapYear(mom.year()) && + mom.month() === 1 && + mom.date() === 29 + ) { + value = toInt(value); + mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit]( + value, + mom.month(), + daysInMonth(value, mom.month()) + ); + } else { + mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value); + } + } + } + + // MOMENTS + + function stringGet(units) { + units = normalizeUnits(units); + if (isFunction(this[units])) { + return this[units](); + } + return this; + } + + function stringSet(units, value) { + if (typeof units === 'object') { + units = normalizeObjectUnits(units); + var prioritized = getPrioritizedUnits(units), + i; + for (i = 0; i < prioritized.length; i++) { + this[prioritized[i].unit](units[prioritized[i].unit]); + } + } else { + units = normalizeUnits(units); + if (isFunction(this[units])) { + return this[units](value); + } + } + return this; + } + + var match1 = /\d/, // 0 - 9 + match2 = /\d\d/, // 00 - 99 + match3 = /\d{3}/, // 000 - 999 + match4 = /\d{4}/, // 0000 - 9999 + match6 = /[+-]?\d{6}/, // -999999 - 999999 + match1to2 = /\d\d?/, // 0 - 99 + match3to4 = /\d\d\d\d?/, // 999 - 9999 + match5to6 = /\d\d\d\d\d\d?/, // 99999 - 999999 + match1to3 = /\d{1,3}/, // 0 - 999 + match1to4 = /\d{1,4}/, // 0 - 9999 + match1to6 = /[+-]?\d{1,6}/, // -999999 - 999999 + matchUnsigned = /\d+/, // 0 - inf + matchSigned = /[+-]?\d+/, // -inf - inf + matchOffset = /Z|[+-]\d\d:?\d\d/gi, // +00:00 -00:00 +0000 -0000 or Z + matchShortOffset = /Z|[+-]\d\d(?::?\d\d)?/gi, // +00 -00 +00:00 -00:00 +0000 -0000 or Z + matchTimestamp = /[+-]?\d+(\.\d{1,3})?/, // 123456789 123456789.123 + // any word (or two) characters or numbers including two/three word month in arabic. + // includes scottish gaelic two word and hyphenated months + matchWord = /[0-9]{0,256}['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFF07\uFF10-\uFFEF]{1,256}|[\u0600-\u06FF\/]{1,256}(\s*?[\u0600-\u06FF]{1,256}){1,2}/i, + regexes; + + regexes = {}; + + function addRegexToken(token, regex, strictRegex) { + regexes[token] = isFunction(regex) + ? regex + : function (isStrict, localeData) { + return isStrict && strictRegex ? strictRegex : regex; + }; + } + + function getParseRegexForToken(token, config) { + if (!hasOwnProp(regexes, token)) { + return new RegExp(unescapeFormat(token)); + } + + return regexes[token](config._strict, config._locale); + } + + // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript + function unescapeFormat(s) { + return regexEscape( + s + .replace('\\', '') + .replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function ( + matched, + p1, + p2, + p3, + p4 + ) { + return p1 || p2 || p3 || p4; + }) + ); + } + + function regexEscape(s) { + return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); + } + + var tokens = {}; + + function addParseToken(token, callback) { + var i, + func = callback; + if (typeof token === 'string') { + token = [token]; + } + if (isNumber(callback)) { + func = function (input, array) { + array[callback] = toInt(input); + }; + } + for (i = 0; i < token.length; i++) { + tokens[token[i]] = func; + } + } + + function addWeekParseToken(token, callback) { + addParseToken(token, function (input, array, config, token) { + config._w = config._w || {}; + callback(input, config._w, config, token); + }); + } + + function addTimeToArrayFromToken(token, input, config) { + if (input != null && hasOwnProp(tokens, token)) { + tokens[token](input, config._a, config, token); + } + } + + var YEAR = 0, + MONTH = 1, + DATE = 2, + HOUR = 3, + MINUTE = 4, + SECOND = 5, + MILLISECOND = 6, + WEEK = 7, + WEEKDAY = 8; + + function mod(n, x) { + return ((n % x) + x) % x; + } + + var indexOf; + + if (Array.prototype.indexOf) { + indexOf = Array.prototype.indexOf; + } else { + indexOf = function (o) { + // I know + var i; + for (i = 0; i < this.length; ++i) { + if (this[i] === o) { + return i; + } + } + return -1; + }; + } + + function daysInMonth(year, month) { + if (isNaN(year) || isNaN(month)) { + return NaN; + } + var modMonth = mod(month, 12); + year += (month - modMonth) / 12; + return modMonth === 1 + ? isLeapYear(year) + ? 29 + : 28 + : 31 - ((modMonth % 7) % 2); + } + + // FORMATTING + + addFormatToken('M', ['MM', 2], 'Mo', function () { + return this.month() + 1; + }); + + addFormatToken('MMM', 0, 0, function (format) { + return this.localeData().monthsShort(this, format); + }); + + addFormatToken('MMMM', 0, 0, function (format) { + return this.localeData().months(this, format); + }); + + // ALIASES + + addUnitAlias('month', 'M'); + + // PRIORITY + + addUnitPriority('month', 8); + + // PARSING + + addRegexToken('M', match1to2); + addRegexToken('MM', match1to2, match2); + addRegexToken('MMM', function (isStrict, locale) { + return locale.monthsShortRegex(isStrict); + }); + addRegexToken('MMMM', function (isStrict, locale) { + return locale.monthsRegex(isStrict); + }); + + addParseToken(['M', 'MM'], function (input, array) { + array[MONTH] = toInt(input) - 1; + }); + + addParseToken(['MMM', 'MMMM'], function (input, array, config, token) { + var month = config._locale.monthsParse(input, token, config._strict); + // if we didn't find a month name, mark the date as invalid. + if (month != null) { + array[MONTH] = month; + } else { + getParsingFlags(config).invalidMonth = input; + } + }); + + // LOCALES + + var defaultLocaleMonths = 'January_February_March_April_May_June_July_August_September_October_November_December'.split( + '_' + ), + defaultLocaleMonthsShort = 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split( + '_' + ), + MONTHS_IN_FORMAT = /D[oD]?(\[[^\[\]]*\]|\s)+MMMM?/, + defaultMonthsShortRegex = matchWord, + defaultMonthsRegex = matchWord; + + function localeMonths(m, format) { + if (!m) { + return isArray(this._months) + ? this._months + : this._months['standalone']; + } + return isArray(this._months) + ? this._months[m.month()] + : this._months[ + (this._months.isFormat || MONTHS_IN_FORMAT).test(format) + ? 'format' + : 'standalone' + ][m.month()]; + } + + function localeMonthsShort(m, format) { + if (!m) { + return isArray(this._monthsShort) + ? this._monthsShort + : this._monthsShort['standalone']; + } + return isArray(this._monthsShort) + ? this._monthsShort[m.month()] + : this._monthsShort[ + MONTHS_IN_FORMAT.test(format) ? 'format' : 'standalone' + ][m.month()]; + } + + function handleStrictParse(monthName, format, strict) { + var i, + ii, + mom, + llc = monthName.toLocaleLowerCase(); + if (!this._monthsParse) { + // this is not used + this._monthsParse = []; + this._longMonthsParse = []; + this._shortMonthsParse = []; + for (i = 0; i < 12; ++i) { + mom = createUTC([2000, i]); + this._shortMonthsParse[i] = this.monthsShort( + mom, + '' + ).toLocaleLowerCase(); + this._longMonthsParse[i] = this.months(mom, '').toLocaleLowerCase(); + } + } + + if (strict) { + if (format === 'MMM') { + ii = indexOf.call(this._shortMonthsParse, llc); + return ii !== -1 ? ii : null; + } else { + ii = indexOf.call(this._longMonthsParse, llc); + return ii !== -1 ? ii : null; + } + } else { + if (format === 'MMM') { + ii = indexOf.call(this._shortMonthsParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._longMonthsParse, llc); + return ii !== -1 ? ii : null; + } else { + ii = indexOf.call(this._longMonthsParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._shortMonthsParse, llc); + return ii !== -1 ? ii : null; + } + } + } + + function localeMonthsParse(monthName, format, strict) { + var i, mom, regex; + + if (this._monthsParseExact) { + return handleStrictParse.call(this, monthName, format, strict); + } + + if (!this._monthsParse) { + this._monthsParse = []; + this._longMonthsParse = []; + this._shortMonthsParse = []; + } + + // TODO: add sorting + // Sorting makes sure if one month (or abbr) is a prefix of another + // see sorting in computeMonthsParse + for (i = 0; i < 12; i++) { + // make the regex if we don't have it already + mom = createUTC([2000, i]); + if (strict && !this._longMonthsParse[i]) { + this._longMonthsParse[i] = new RegExp( + '^' + this.months(mom, '').replace('.', '') + '$', + 'i' + ); + this._shortMonthsParse[i] = new RegExp( + '^' + this.monthsShort(mom, '').replace('.', '') + '$', + 'i' + ); + } + if (!strict && !this._monthsParse[i]) { + regex = + '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, ''); + this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i'); + } + // test the regex + if ( + strict && + format === 'MMMM' && + this._longMonthsParse[i].test(monthName) + ) { + return i; + } else if ( + strict && + format === 'MMM' && + this._shortMonthsParse[i].test(monthName) + ) { + return i; + } else if (!strict && this._monthsParse[i].test(monthName)) { + return i; + } + } + } + + // MOMENTS + + function setMonth(mom, value) { + var dayOfMonth; + + if (!mom.isValid()) { + // No op + return mom; + } + + if (typeof value === 'string') { + if (/^\d+$/.test(value)) { + value = toInt(value); + } else { + value = mom.localeData().monthsParse(value); + // TODO: Another silent failure? + if (!isNumber(value)) { + return mom; + } + } + } + + dayOfMonth = Math.min(mom.date(), daysInMonth(mom.year(), value)); + mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth); + return mom; + } + + function getSetMonth(value) { + if (value != null) { + setMonth(this, value); + hooks.updateOffset(this, true); + return this; + } else { + return get(this, 'Month'); + } + } + + function getDaysInMonth() { + return daysInMonth(this.year(), this.month()); + } + + function monthsShortRegex(isStrict) { + if (this._monthsParseExact) { + if (!hasOwnProp(this, '_monthsRegex')) { + computeMonthsParse.call(this); + } + if (isStrict) { + return this._monthsShortStrictRegex; + } else { + return this._monthsShortRegex; + } + } else { + if (!hasOwnProp(this, '_monthsShortRegex')) { + this._monthsShortRegex = defaultMonthsShortRegex; + } + return this._monthsShortStrictRegex && isStrict + ? this._monthsShortStrictRegex + : this._monthsShortRegex; + } + } + + function monthsRegex(isStrict) { + if (this._monthsParseExact) { + if (!hasOwnProp(this, '_monthsRegex')) { + computeMonthsParse.call(this); + } + if (isStrict) { + return this._monthsStrictRegex; + } else { + return this._monthsRegex; + } + } else { + if (!hasOwnProp(this, '_monthsRegex')) { + this._monthsRegex = defaultMonthsRegex; + } + return this._monthsStrictRegex && isStrict + ? this._monthsStrictRegex + : this._monthsRegex; + } + } + + function computeMonthsParse() { + function cmpLenRev(a, b) { + return b.length - a.length; + } + + var shortPieces = [], + longPieces = [], + mixedPieces = [], + i, + mom; + for (i = 0; i < 12; i++) { + // make the regex if we don't have it already + mom = createUTC([2000, i]); + shortPieces.push(this.monthsShort(mom, '')); + longPieces.push(this.months(mom, '')); + mixedPieces.push(this.months(mom, '')); + mixedPieces.push(this.monthsShort(mom, '')); + } + // Sorting makes sure if one month (or abbr) is a prefix of another it + // will match the longer piece. + shortPieces.sort(cmpLenRev); + longPieces.sort(cmpLenRev); + mixedPieces.sort(cmpLenRev); + for (i = 0; i < 12; i++) { + shortPieces[i] = regexEscape(shortPieces[i]); + longPieces[i] = regexEscape(longPieces[i]); + } + for (i = 0; i < 24; i++) { + mixedPieces[i] = regexEscape(mixedPieces[i]); + } + + this._monthsRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i'); + this._monthsShortRegex = this._monthsRegex; + this._monthsStrictRegex = new RegExp( + '^(' + longPieces.join('|') + ')', + 'i' + ); + this._monthsShortStrictRegex = new RegExp( + '^(' + shortPieces.join('|') + ')', + 'i' + ); + } + + // FORMATTING + + addFormatToken('Y', 0, 0, function () { + var y = this.year(); + return y <= 9999 ? zeroFill(y, 4) : '+' + y; + }); + + addFormatToken(0, ['YY', 2], 0, function () { + return this.year() % 100; + }); + + addFormatToken(0, ['YYYY', 4], 0, 'year'); + addFormatToken(0, ['YYYYY', 5], 0, 'year'); + addFormatToken(0, ['YYYYYY', 6, true], 0, 'year'); + + // ALIASES + + addUnitAlias('year', 'y'); + + // PRIORITIES + + addUnitPriority('year', 1); + + // PARSING + + addRegexToken('Y', matchSigned); + addRegexToken('YY', match1to2, match2); + addRegexToken('YYYY', match1to4, match4); + addRegexToken('YYYYY', match1to6, match6); + addRegexToken('YYYYYY', match1to6, match6); + + addParseToken(['YYYYY', 'YYYYYY'], YEAR); + addParseToken('YYYY', function (input, array) { + array[YEAR] = + input.length === 2 ? hooks.parseTwoDigitYear(input) : toInt(input); + }); + addParseToken('YY', function (input, array) { + array[YEAR] = hooks.parseTwoDigitYear(input); + }); + addParseToken('Y', function (input, array) { + array[YEAR] = parseInt(input, 10); + }); + + // HELPERS + + function daysInYear(year) { + return isLeapYear(year) ? 366 : 365; + } + + // HOOKS + + hooks.parseTwoDigitYear = function (input) { + return toInt(input) + (toInt(input) > 68 ? 1900 : 2000); + }; + + // MOMENTS + + var getSetYear = makeGetSet('FullYear', true); + + function getIsLeapYear() { + return isLeapYear(this.year()); + } + + function createDate(y, m, d, h, M, s, ms) { + // can't just apply() to create a date: + // https://stackoverflow.com/q/181348 + var date; + // the date constructor remaps years 0-99 to 1900-1999 + if (y < 100 && y >= 0) { + // preserve leap years using a full 400 year cycle, then reset + date = new Date(y + 400, m, d, h, M, s, ms); + if (isFinite(date.getFullYear())) { + date.setFullYear(y); + } + } else { + date = new Date(y, m, d, h, M, s, ms); + } + + return date; + } + + function createUTCDate(y) { + var date, args; + // the Date.UTC function remaps years 0-99 to 1900-1999 + if (y < 100 && y >= 0) { + args = Array.prototype.slice.call(arguments); + // preserve leap years using a full 400 year cycle, then reset + args[0] = y + 400; + date = new Date(Date.UTC.apply(null, args)); + if (isFinite(date.getUTCFullYear())) { + date.setUTCFullYear(y); + } + } else { + date = new Date(Date.UTC.apply(null, arguments)); + } + + return date; + } + + // start-of-first-week - start-of-year + function firstWeekOffset(year, dow, doy) { + var // first-week day -- which january is always in the first week (4 for iso, 1 for other) + fwd = 7 + dow - doy, + // first-week day local weekday -- which local weekday is fwd + fwdlw = (7 + createUTCDate(year, 0, fwd).getUTCDay() - dow) % 7; + + return -fwdlw + fwd - 1; + } + + // https://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday + function dayOfYearFromWeeks(year, week, weekday, dow, doy) { + var localWeekday = (7 + weekday - dow) % 7, + weekOffset = firstWeekOffset(year, dow, doy), + dayOfYear = 1 + 7 * (week - 1) + localWeekday + weekOffset, + resYear, + resDayOfYear; + + if (dayOfYear <= 0) { + resYear = year - 1; + resDayOfYear = daysInYear(resYear) + dayOfYear; + } else if (dayOfYear > daysInYear(year)) { + resYear = year + 1; + resDayOfYear = dayOfYear - daysInYear(year); + } else { + resYear = year; + resDayOfYear = dayOfYear; + } + + return { + year: resYear, + dayOfYear: resDayOfYear, + }; + } + + function weekOfYear(mom, dow, doy) { + var weekOffset = firstWeekOffset(mom.year(), dow, doy), + week = Math.floor((mom.dayOfYear() - weekOffset - 1) / 7) + 1, + resWeek, + resYear; + + if (week < 1) { + resYear = mom.year() - 1; + resWeek = week + weeksInYear(resYear, dow, doy); + } else if (week > weeksInYear(mom.year(), dow, doy)) { + resWeek = week - weeksInYear(mom.year(), dow, doy); + resYear = mom.year() + 1; + } else { + resYear = mom.year(); + resWeek = week; + } + + return { + week: resWeek, + year: resYear, + }; + } + + function weeksInYear(year, dow, doy) { + var weekOffset = firstWeekOffset(year, dow, doy), + weekOffsetNext = firstWeekOffset(year + 1, dow, doy); + return (daysInYear(year) - weekOffset + weekOffsetNext) / 7; + } + + // FORMATTING + + addFormatToken('w', ['ww', 2], 'wo', 'week'); + addFormatToken('W', ['WW', 2], 'Wo', 'isoWeek'); + + // ALIASES + + addUnitAlias('week', 'w'); + addUnitAlias('isoWeek', 'W'); + + // PRIORITIES + + addUnitPriority('week', 5); + addUnitPriority('isoWeek', 5); + + // PARSING + + addRegexToken('w', match1to2); + addRegexToken('ww', match1to2, match2); + addRegexToken('W', match1to2); + addRegexToken('WW', match1to2, match2); + + addWeekParseToken(['w', 'ww', 'W', 'WW'], function ( + input, + week, + config, + token + ) { + week[token.substr(0, 1)] = toInt(input); + }); + + // HELPERS + + // LOCALES + + function localeWeek(mom) { + return weekOfYear(mom, this._week.dow, this._week.doy).week; + } + + var defaultLocaleWeek = { + dow: 0, // Sunday is the first day of the week. + doy: 6, // The week that contains Jan 6th is the first week of the year. + }; + + function localeFirstDayOfWeek() { + return this._week.dow; + } + + function localeFirstDayOfYear() { + return this._week.doy; + } + + // MOMENTS + + function getSetWeek(input) { + var week = this.localeData().week(this); + return input == null ? week : this.add((input - week) * 7, 'd'); + } + + function getSetISOWeek(input) { + var week = weekOfYear(this, 1, 4).week; + return input == null ? week : this.add((input - week) * 7, 'd'); + } + + // FORMATTING + + addFormatToken('d', 0, 'do', 'day'); + + addFormatToken('dd', 0, 0, function (format) { + return this.localeData().weekdaysMin(this, format); + }); + + addFormatToken('ddd', 0, 0, function (format) { + return this.localeData().weekdaysShort(this, format); + }); + + addFormatToken('dddd', 0, 0, function (format) { + return this.localeData().weekdays(this, format); + }); + + addFormatToken('e', 0, 0, 'weekday'); + addFormatToken('E', 0, 0, 'isoWeekday'); + + // ALIASES + + addUnitAlias('day', 'd'); + addUnitAlias('weekday', 'e'); + addUnitAlias('isoWeekday', 'E'); + + // PRIORITY + addUnitPriority('day', 11); + addUnitPriority('weekday', 11); + addUnitPriority('isoWeekday', 11); + + // PARSING + + addRegexToken('d', match1to2); + addRegexToken('e', match1to2); + addRegexToken('E', match1to2); + addRegexToken('dd', function (isStrict, locale) { + return locale.weekdaysMinRegex(isStrict); + }); + addRegexToken('ddd', function (isStrict, locale) { + return locale.weekdaysShortRegex(isStrict); + }); + addRegexToken('dddd', function (isStrict, locale) { + return locale.weekdaysRegex(isStrict); + }); + + addWeekParseToken(['dd', 'ddd', 'dddd'], function (input, week, config, token) { + var weekday = config._locale.weekdaysParse(input, token, config._strict); + // if we didn't get a weekday name, mark the date as invalid + if (weekday != null) { + week.d = weekday; + } else { + getParsingFlags(config).invalidWeekday = input; + } + }); + + addWeekParseToken(['d', 'e', 'E'], function (input, week, config, token) { + week[token] = toInt(input); + }); + + // HELPERS + + function parseWeekday(input, locale) { + if (typeof input !== 'string') { + return input; + } + + if (!isNaN(input)) { + return parseInt(input, 10); + } + + input = locale.weekdaysParse(input); + if (typeof input === 'number') { + return input; + } + + return null; + } + + function parseIsoWeekday(input, locale) { + if (typeof input === 'string') { + return locale.weekdaysParse(input) % 7 || 7; + } + return isNaN(input) ? null : input; + } + + // LOCALES + function shiftWeekdays(ws, n) { + return ws.slice(n, 7).concat(ws.slice(0, n)); + } + + var defaultLocaleWeekdays = 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split( + '_' + ), + defaultLocaleWeekdaysShort = 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'), + defaultLocaleWeekdaysMin = 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'), + defaultWeekdaysRegex = matchWord, + defaultWeekdaysShortRegex = matchWord, + defaultWeekdaysMinRegex = matchWord; + + function localeWeekdays(m, format) { + var weekdays = isArray(this._weekdays) + ? this._weekdays + : this._weekdays[ + m && m !== true && this._weekdays.isFormat.test(format) + ? 'format' + : 'standalone' + ]; + return m === true + ? shiftWeekdays(weekdays, this._week.dow) + : m + ? weekdays[m.day()] + : weekdays; + } + + function localeWeekdaysShort(m) { + return m === true + ? shiftWeekdays(this._weekdaysShort, this._week.dow) + : m + ? this._weekdaysShort[m.day()] + : this._weekdaysShort; + } + + function localeWeekdaysMin(m) { + return m === true + ? shiftWeekdays(this._weekdaysMin, this._week.dow) + : m + ? this._weekdaysMin[m.day()] + : this._weekdaysMin; + } + + function handleStrictParse$1(weekdayName, format, strict) { + var i, + ii, + mom, + llc = weekdayName.toLocaleLowerCase(); + if (!this._weekdaysParse) { + this._weekdaysParse = []; + this._shortWeekdaysParse = []; + this._minWeekdaysParse = []; + + for (i = 0; i < 7; ++i) { + mom = createUTC([2000, 1]).day(i); + this._minWeekdaysParse[i] = this.weekdaysMin( + mom, + '' + ).toLocaleLowerCase(); + this._shortWeekdaysParse[i] = this.weekdaysShort( + mom, + '' + ).toLocaleLowerCase(); + this._weekdaysParse[i] = this.weekdays(mom, '').toLocaleLowerCase(); + } + } + + if (strict) { + if (format === 'dddd') { + ii = indexOf.call(this._weekdaysParse, llc); + return ii !== -1 ? ii : null; + } else if (format === 'ddd') { + ii = indexOf.call(this._shortWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } else { + ii = indexOf.call(this._minWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } + } else { + if (format === 'dddd') { + ii = indexOf.call(this._weekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._shortWeekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._minWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } else if (format === 'ddd') { + ii = indexOf.call(this._shortWeekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._weekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._minWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } else { + ii = indexOf.call(this._minWeekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._weekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._shortWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } + } + } + + function localeWeekdaysParse(weekdayName, format, strict) { + var i, mom, regex; + + if (this._weekdaysParseExact) { + return handleStrictParse$1.call(this, weekdayName, format, strict); + } + + if (!this._weekdaysParse) { + this._weekdaysParse = []; + this._minWeekdaysParse = []; + this._shortWeekdaysParse = []; + this._fullWeekdaysParse = []; + } + + for (i = 0; i < 7; i++) { + // make the regex if we don't have it already + + mom = createUTC([2000, 1]).day(i); + if (strict && !this._fullWeekdaysParse[i]) { + this._fullWeekdaysParse[i] = new RegExp( + '^' + this.weekdays(mom, '').replace('.', '\\.?') + '$', + 'i' + ); + this._shortWeekdaysParse[i] = new RegExp( + '^' + this.weekdaysShort(mom, '').replace('.', '\\.?') + '$', + 'i' + ); + this._minWeekdaysParse[i] = new RegExp( + '^' + this.weekdaysMin(mom, '').replace('.', '\\.?') + '$', + 'i' + ); + } + if (!this._weekdaysParse[i]) { + regex = + '^' + + this.weekdays(mom, '') + + '|^' + + this.weekdaysShort(mom, '') + + '|^' + + this.weekdaysMin(mom, ''); + this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i'); + } + // test the regex + if ( + strict && + format === 'dddd' && + this._fullWeekdaysParse[i].test(weekdayName) + ) { + return i; + } else if ( + strict && + format === 'ddd' && + this._shortWeekdaysParse[i].test(weekdayName) + ) { + return i; + } else if ( + strict && + format === 'dd' && + this._minWeekdaysParse[i].test(weekdayName) + ) { + return i; + } else if (!strict && this._weekdaysParse[i].test(weekdayName)) { + return i; + } + } + } + + // MOMENTS + + function getSetDayOfWeek(input) { + if (!this.isValid()) { + return input != null ? this : NaN; + } + var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay(); + if (input != null) { + input = parseWeekday(input, this.localeData()); + return this.add(input - day, 'd'); + } else { + return day; + } + } + + function getSetLocaleDayOfWeek(input) { + if (!this.isValid()) { + return input != null ? this : NaN; + } + var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7; + return input == null ? weekday : this.add(input - weekday, 'd'); + } + + function getSetISODayOfWeek(input) { + if (!this.isValid()) { + return input != null ? this : NaN; + } + + // behaves the same as moment#day except + // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6) + // as a setter, sunday should belong to the previous week. + + if (input != null) { + var weekday = parseIsoWeekday(input, this.localeData()); + return this.day(this.day() % 7 ? weekday : weekday - 7); + } else { + return this.day() || 7; + } + } + + function weekdaysRegex(isStrict) { + if (this._weekdaysParseExact) { + if (!hasOwnProp(this, '_weekdaysRegex')) { + computeWeekdaysParse.call(this); + } + if (isStrict) { + return this._weekdaysStrictRegex; + } else { + return this._weekdaysRegex; + } + } else { + if (!hasOwnProp(this, '_weekdaysRegex')) { + this._weekdaysRegex = defaultWeekdaysRegex; + } + return this._weekdaysStrictRegex && isStrict + ? this._weekdaysStrictRegex + : this._weekdaysRegex; + } + } + + function weekdaysShortRegex(isStrict) { + if (this._weekdaysParseExact) { + if (!hasOwnProp(this, '_weekdaysRegex')) { + computeWeekdaysParse.call(this); + } + if (isStrict) { + return this._weekdaysShortStrictRegex; + } else { + return this._weekdaysShortRegex; + } + } else { + if (!hasOwnProp(this, '_weekdaysShortRegex')) { + this._weekdaysShortRegex = defaultWeekdaysShortRegex; + } + return this._weekdaysShortStrictRegex && isStrict + ? this._weekdaysShortStrictRegex + : this._weekdaysShortRegex; + } + } + + function weekdaysMinRegex(isStrict) { + if (this._weekdaysParseExact) { + if (!hasOwnProp(this, '_weekdaysRegex')) { + computeWeekdaysParse.call(this); + } + if (isStrict) { + return this._weekdaysMinStrictRegex; + } else { + return this._weekdaysMinRegex; + } + } else { + if (!hasOwnProp(this, '_weekdaysMinRegex')) { + this._weekdaysMinRegex = defaultWeekdaysMinRegex; + } + return this._weekdaysMinStrictRegex && isStrict + ? this._weekdaysMinStrictRegex + : this._weekdaysMinRegex; + } + } + + function computeWeekdaysParse() { + function cmpLenRev(a, b) { + return b.length - a.length; + } + + var minPieces = [], + shortPieces = [], + longPieces = [], + mixedPieces = [], + i, + mom, + minp, + shortp, + longp; + for (i = 0; i < 7; i++) { + // make the regex if we don't have it already + mom = createUTC([2000, 1]).day(i); + minp = regexEscape(this.weekdaysMin(mom, '')); + shortp = regexEscape(this.weekdaysShort(mom, '')); + longp = regexEscape(this.weekdays(mom, '')); + minPieces.push(minp); + shortPieces.push(shortp); + longPieces.push(longp); + mixedPieces.push(minp); + mixedPieces.push(shortp); + mixedPieces.push(longp); + } + // Sorting makes sure if one weekday (or abbr) is a prefix of another it + // will match the longer piece. + minPieces.sort(cmpLenRev); + shortPieces.sort(cmpLenRev); + longPieces.sort(cmpLenRev); + mixedPieces.sort(cmpLenRev); + + this._weekdaysRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i'); + this._weekdaysShortRegex = this._weekdaysRegex; + this._weekdaysMinRegex = this._weekdaysRegex; + + this._weekdaysStrictRegex = new RegExp( + '^(' + longPieces.join('|') + ')', + 'i' + ); + this._weekdaysShortStrictRegex = new RegExp( + '^(' + shortPieces.join('|') + ')', + 'i' + ); + this._weekdaysMinStrictRegex = new RegExp( + '^(' + minPieces.join('|') + ')', + 'i' + ); + } + + // FORMATTING + + function hFormat() { + return this.hours() % 12 || 12; + } + + function kFormat() { + return this.hours() || 24; + } + + addFormatToken('H', ['HH', 2], 0, 'hour'); + addFormatToken('h', ['hh', 2], 0, hFormat); + addFormatToken('k', ['kk', 2], 0, kFormat); + + addFormatToken('hmm', 0, 0, function () { + return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2); + }); + + addFormatToken('hmmss', 0, 0, function () { + return ( + '' + + hFormat.apply(this) + + zeroFill(this.minutes(), 2) + + zeroFill(this.seconds(), 2) + ); + }); + + addFormatToken('Hmm', 0, 0, function () { + return '' + this.hours() + zeroFill(this.minutes(), 2); + }); + + addFormatToken('Hmmss', 0, 0, function () { + return ( + '' + + this.hours() + + zeroFill(this.minutes(), 2) + + zeroFill(this.seconds(), 2) + ); + }); + + function meridiem(token, lowercase) { + addFormatToken(token, 0, 0, function () { + return this.localeData().meridiem( + this.hours(), + this.minutes(), + lowercase + ); + }); + } + + meridiem('a', true); + meridiem('A', false); + + // ALIASES + + addUnitAlias('hour', 'h'); + + // PRIORITY + addUnitPriority('hour', 13); + + // PARSING + + function matchMeridiem(isStrict, locale) { + return locale._meridiemParse; + } + + addRegexToken('a', matchMeridiem); + addRegexToken('A', matchMeridiem); + addRegexToken('H', match1to2); + addRegexToken('h', match1to2); + addRegexToken('k', match1to2); + addRegexToken('HH', match1to2, match2); + addRegexToken('hh', match1to2, match2); + addRegexToken('kk', match1to2, match2); + + addRegexToken('hmm', match3to4); + addRegexToken('hmmss', match5to6); + addRegexToken('Hmm', match3to4); + addRegexToken('Hmmss', match5to6); + + addParseToken(['H', 'HH'], HOUR); + addParseToken(['k', 'kk'], function (input, array, config) { + var kInput = toInt(input); + array[HOUR] = kInput === 24 ? 0 : kInput; + }); + addParseToken(['a', 'A'], function (input, array, config) { + config._isPm = config._locale.isPM(input); + config._meridiem = input; + }); + addParseToken(['h', 'hh'], function (input, array, config) { + array[HOUR] = toInt(input); + getParsingFlags(config).bigHour = true; + }); + addParseToken('hmm', function (input, array, config) { + var pos = input.length - 2; + array[HOUR] = toInt(input.substr(0, pos)); + array[MINUTE] = toInt(input.substr(pos)); + getParsingFlags(config).bigHour = true; + }); + addParseToken('hmmss', function (input, array, config) { + var pos1 = input.length - 4, + pos2 = input.length - 2; + array[HOUR] = toInt(input.substr(0, pos1)); + array[MINUTE] = toInt(input.substr(pos1, 2)); + array[SECOND] = toInt(input.substr(pos2)); + getParsingFlags(config).bigHour = true; + }); + addParseToken('Hmm', function (input, array, config) { + var pos = input.length - 2; + array[HOUR] = toInt(input.substr(0, pos)); + array[MINUTE] = toInt(input.substr(pos)); + }); + addParseToken('Hmmss', function (input, array, config) { + var pos1 = input.length - 4, + pos2 = input.length - 2; + array[HOUR] = toInt(input.substr(0, pos1)); + array[MINUTE] = toInt(input.substr(pos1, 2)); + array[SECOND] = toInt(input.substr(pos2)); + }); + + // LOCALES + + function localeIsPM(input) { + // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays + // Using charAt should be more compatible. + return (input + '').toLowerCase().charAt(0) === 'p'; + } + + var defaultLocaleMeridiemParse = /[ap]\.?m?\.?/i, + // Setting the hour should keep the time, because the user explicitly + // specified which hour they want. So trying to maintain the same hour (in + // a new timezone) makes sense. Adding/subtracting hours does not follow + // this rule. + getSetHour = makeGetSet('Hours', true); + + function localeMeridiem(hours, minutes, isLower) { + if (hours > 11) { + return isLower ? 'pm' : 'PM'; + } else { + return isLower ? 'am' : 'AM'; + } + } + + var baseConfig = { + calendar: defaultCalendar, + longDateFormat: defaultLongDateFormat, + invalidDate: defaultInvalidDate, + ordinal: defaultOrdinal, + dayOfMonthOrdinalParse: defaultDayOfMonthOrdinalParse, + relativeTime: defaultRelativeTime, + + months: defaultLocaleMonths, + monthsShort: defaultLocaleMonthsShort, + + week: defaultLocaleWeek, + + weekdays: defaultLocaleWeekdays, + weekdaysMin: defaultLocaleWeekdaysMin, + weekdaysShort: defaultLocaleWeekdaysShort, + + meridiemParse: defaultLocaleMeridiemParse, + }; + + // internal storage for locale config files + var locales = {}, + localeFamilies = {}, + globalLocale; + + function commonPrefix(arr1, arr2) { + var i, + minl = Math.min(arr1.length, arr2.length); + for (i = 0; i < minl; i += 1) { + if (arr1[i] !== arr2[i]) { + return i; + } + } + return minl; + } + + function normalizeLocale(key) { + return key ? key.toLowerCase().replace('_', '-') : key; + } + + // pick the locale from the array + // try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each + // substring from most specific to least, but move to the next array item if it's a more specific variant than the current root + function chooseLocale(names) { + var i = 0, + j, + next, + locale, + split; + + while (i < names.length) { + split = normalizeLocale(names[i]).split('-'); + j = split.length; + next = normalizeLocale(names[i + 1]); + next = next ? next.split('-') : null; + while (j > 0) { + locale = loadLocale(split.slice(0, j).join('-')); + if (locale) { + return locale; + } + if ( + next && + next.length >= j && + commonPrefix(split, next) >= j - 1 + ) { + //the next array item is better than a shallower substring of this one + break; + } + j--; + } + i++; + } + return globalLocale; + } + + function loadLocale(name) { + var oldLocale = null, + aliasedRequire; + // TODO: Find a better way to register and load all the locales in Node + if ( + locales[name] === undefined && + typeof module !== 'undefined' && + module && + module.exports + ) { + try { + oldLocale = globalLocale._abbr; + aliasedRequire = require; + aliasedRequire('./locale/' + name); + getSetGlobalLocale(oldLocale); + } catch (e) { + // mark as not found to avoid repeating expensive file require call causing high CPU + // when trying to find en-US, en_US, en-us for every format call + locales[name] = null; // null means not found + } + } + return locales[name]; + } + + // This function will load locale and then set the global locale. If + // no arguments are passed in, it will simply return the current global + // locale key. + function getSetGlobalLocale(key, values) { + var data; + if (key) { + if (isUndefined(values)) { + data = getLocale(key); + } else { + data = defineLocale(key, values); + } + + if (data) { + // moment.duration._locale = moment._locale = data; + globalLocale = data; + } else { + if (typeof console !== 'undefined' && console.warn) { + //warn user if arguments are passed but the locale could not be set + console.warn( + 'Locale ' + key + ' not found. Did you forget to load it?' + ); + } + } + } + + return globalLocale._abbr; + } + + function defineLocale(name, config) { + if (config !== null) { + var locale, + parentConfig = baseConfig; + config.abbr = name; + if (locales[name] != null) { + deprecateSimple( + 'defineLocaleOverride', + 'use moment.updateLocale(localeName, config) to change ' + + 'an existing locale. moment.defineLocale(localeName, ' + + 'config) should only be used for creating a new locale ' + + 'See http://momentjs.com/guides/#/warnings/define-locale/ for more info.' + ); + parentConfig = locales[name]._config; + } else if (config.parentLocale != null) { + if (locales[config.parentLocale] != null) { + parentConfig = locales[config.parentLocale]._config; + } else { + locale = loadLocale(config.parentLocale); + if (locale != null) { + parentConfig = locale._config; + } else { + if (!localeFamilies[config.parentLocale]) { + localeFamilies[config.parentLocale] = []; + } + localeFamilies[config.parentLocale].push({ + name: name, + config: config, + }); + return null; + } + } + } + locales[name] = new Locale(mergeConfigs(parentConfig, config)); + + if (localeFamilies[name]) { + localeFamilies[name].forEach(function (x) { + defineLocale(x.name, x.config); + }); + } + + // backwards compat for now: also set the locale + // make sure we set the locale AFTER all child locales have been + // created, so we won't end up with the child locale set. + getSetGlobalLocale(name); + + return locales[name]; + } else { + // useful for testing + delete locales[name]; + return null; + } + } + + function updateLocale(name, config) { + if (config != null) { + var locale, + tmpLocale, + parentConfig = baseConfig; + + if (locales[name] != null && locales[name].parentLocale != null) { + // Update existing child locale in-place to avoid memory-leaks + locales[name].set(mergeConfigs(locales[name]._config, config)); + } else { + // MERGE + tmpLocale = loadLocale(name); + if (tmpLocale != null) { + parentConfig = tmpLocale._config; + } + config = mergeConfigs(parentConfig, config); + if (tmpLocale == null) { + // updateLocale is called for creating a new locale + // Set abbr so it will have a name (getters return + // undefined otherwise). + config.abbr = name; + } + locale = new Locale(config); + locale.parentLocale = locales[name]; + locales[name] = locale; + } + + // backwards compat for now: also set the locale + getSetGlobalLocale(name); + } else { + // pass null for config to unupdate, useful for tests + if (locales[name] != null) { + if (locales[name].parentLocale != null) { + locales[name] = locales[name].parentLocale; + if (name === getSetGlobalLocale()) { + getSetGlobalLocale(name); + } + } else if (locales[name] != null) { + delete locales[name]; + } + } + } + return locales[name]; + } + + // returns locale data + function getLocale(key) { + var locale; + + if (key && key._locale && key._locale._abbr) { + key = key._locale._abbr; + } + + if (!key) { + return globalLocale; + } + + if (!isArray(key)) { + //short-circuit everything else + locale = loadLocale(key); + if (locale) { + return locale; + } + key = [key]; + } + + return chooseLocale(key); + } + + function listLocales() { + return keys(locales); + } + + function checkOverflow(m) { + var overflow, + a = m._a; + + if (a && getParsingFlags(m).overflow === -2) { + overflow = + a[MONTH] < 0 || a[MONTH] > 11 + ? MONTH + : a[DATE] < 1 || a[DATE] > daysInMonth(a[YEAR], a[MONTH]) + ? DATE + : a[HOUR] < 0 || + a[HOUR] > 24 || + (a[HOUR] === 24 && + (a[MINUTE] !== 0 || + a[SECOND] !== 0 || + a[MILLISECOND] !== 0)) + ? HOUR + : a[MINUTE] < 0 || a[MINUTE] > 59 + ? MINUTE + : a[SECOND] < 0 || a[SECOND] > 59 + ? SECOND + : a[MILLISECOND] < 0 || a[MILLISECOND] > 999 + ? MILLISECOND + : -1; + + if ( + getParsingFlags(m)._overflowDayOfYear && + (overflow < YEAR || overflow > DATE) + ) { + overflow = DATE; + } + if (getParsingFlags(m)._overflowWeeks && overflow === -1) { + overflow = WEEK; + } + if (getParsingFlags(m)._overflowWeekday && overflow === -1) { + overflow = WEEKDAY; + } + + getParsingFlags(m).overflow = overflow; + } + + return m; + } + + // iso 8601 regex + // 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00) + var extendedIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([+-]\d\d(?::?\d\d)?|\s*Z)?)?$/, + basicIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d|))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([+-]\d\d(?::?\d\d)?|\s*Z)?)?$/, + tzRegex = /Z|[+-]\d\d(?::?\d\d)?/, + isoDates = [ + ['YYYYYY-MM-DD', /[+-]\d{6}-\d\d-\d\d/], + ['YYYY-MM-DD', /\d{4}-\d\d-\d\d/], + ['GGGG-[W]WW-E', /\d{4}-W\d\d-\d/], + ['GGGG-[W]WW', /\d{4}-W\d\d/, false], + ['YYYY-DDD', /\d{4}-\d{3}/], + ['YYYY-MM', /\d{4}-\d\d/, false], + ['YYYYYYMMDD', /[+-]\d{10}/], + ['YYYYMMDD', /\d{8}/], + ['GGGG[W]WWE', /\d{4}W\d{3}/], + ['GGGG[W]WW', /\d{4}W\d{2}/, false], + ['YYYYDDD', /\d{7}/], + ['YYYYMM', /\d{6}/, false], + ['YYYY', /\d{4}/, false], + ], + // iso time formats and regexes + isoTimes = [ + ['HH:mm:ss.SSSS', /\d\d:\d\d:\d\d\.\d+/], + ['HH:mm:ss,SSSS', /\d\d:\d\d:\d\d,\d+/], + ['HH:mm:ss', /\d\d:\d\d:\d\d/], + ['HH:mm', /\d\d:\d\d/], + ['HHmmss.SSSS', /\d\d\d\d\d\d\.\d+/], + ['HHmmss,SSSS', /\d\d\d\d\d\d,\d+/], + ['HHmmss', /\d\d\d\d\d\d/], + ['HHmm', /\d\d\d\d/], + ['HH', /\d\d/], + ], + aspNetJsonRegex = /^\/?Date\((-?\d+)/i, + // RFC 2822 regex: For details see https://tools.ietf.org/html/rfc2822#section-3.3 + rfc2822 = /^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d{1,2})\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\d{2,4})\s(\d\d):(\d\d)(?::(\d\d))?\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|([+-]\d{4}))$/, + obsOffsets = { + UT: 0, + GMT: 0, + EDT: -4 * 60, + EST: -5 * 60, + CDT: -5 * 60, + CST: -6 * 60, + MDT: -6 * 60, + MST: -7 * 60, + PDT: -7 * 60, + PST: -8 * 60, + }; + + // date from iso format + function configFromISO(config) { + var i, + l, + string = config._i, + match = extendedIsoRegex.exec(string) || basicIsoRegex.exec(string), + allowTime, + dateFormat, + timeFormat, + tzFormat; + + if (match) { + getParsingFlags(config).iso = true; + + for (i = 0, l = isoDates.length; i < l; i++) { + if (isoDates[i][1].exec(match[1])) { + dateFormat = isoDates[i][0]; + allowTime = isoDates[i][2] !== false; + break; + } + } + if (dateFormat == null) { + config._isValid = false; + return; + } + if (match[3]) { + for (i = 0, l = isoTimes.length; i < l; i++) { + if (isoTimes[i][1].exec(match[3])) { + // match[2] should be 'T' or space + timeFormat = (match[2] || ' ') + isoTimes[i][0]; + break; + } + } + if (timeFormat == null) { + config._isValid = false; + return; + } + } + if (!allowTime && timeFormat != null) { + config._isValid = false; + return; + } + if (match[4]) { + if (tzRegex.exec(match[4])) { + tzFormat = 'Z'; + } else { + config._isValid = false; + return; + } + } + config._f = dateFormat + (timeFormat || '') + (tzFormat || ''); + configFromStringAndFormat(config); + } else { + config._isValid = false; + } + } + + function extractFromRFC2822Strings( + yearStr, + monthStr, + dayStr, + hourStr, + minuteStr, + secondStr + ) { + var result = [ + untruncateYear(yearStr), + defaultLocaleMonthsShort.indexOf(monthStr), + parseInt(dayStr, 10), + parseInt(hourStr, 10), + parseInt(minuteStr, 10), + ]; + + if (secondStr) { + result.push(parseInt(secondStr, 10)); + } + + return result; + } + + function untruncateYear(yearStr) { + var year = parseInt(yearStr, 10); + if (year <= 49) { + return 2000 + year; + } else if (year <= 999) { + return 1900 + year; + } + return year; + } + + function preprocessRFC2822(s) { + // Remove comments and folding whitespace and replace multiple-spaces with a single space + return s + .replace(/\([^)]*\)|[\n\t]/g, ' ') + .replace(/(\s\s+)/g, ' ') + .replace(/^\s\s*/, '') + .replace(/\s\s*$/, ''); + } + + function checkWeekday(weekdayStr, parsedInput, config) { + if (weekdayStr) { + // TODO: Replace the vanilla JS Date object with an independent day-of-week check. + var weekdayProvided = defaultLocaleWeekdaysShort.indexOf(weekdayStr), + weekdayActual = new Date( + parsedInput[0], + parsedInput[1], + parsedInput[2] + ).getDay(); + if (weekdayProvided !== weekdayActual) { + getParsingFlags(config).weekdayMismatch = true; + config._isValid = false; + return false; + } + } + return true; + } + + function calculateOffset(obsOffset, militaryOffset, numOffset) { + if (obsOffset) { + return obsOffsets[obsOffset]; + } else if (militaryOffset) { + // the only allowed military tz is Z + return 0; + } else { + var hm = parseInt(numOffset, 10), + m = hm % 100, + h = (hm - m) / 100; + return h * 60 + m; + } + } + + // date and time from ref 2822 format + function configFromRFC2822(config) { + var match = rfc2822.exec(preprocessRFC2822(config._i)), + parsedArray; + if (match) { + parsedArray = extractFromRFC2822Strings( + match[4], + match[3], + match[2], + match[5], + match[6], + match[7] + ); + if (!checkWeekday(match[1], parsedArray, config)) { + return; + } + + config._a = parsedArray; + config._tzm = calculateOffset(match[8], match[9], match[10]); + + config._d = createUTCDate.apply(null, config._a); + config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm); + + getParsingFlags(config).rfc2822 = true; + } else { + config._isValid = false; + } + } + + // date from 1) ASP.NET, 2) ISO, 3) RFC 2822 formats, or 4) optional fallback if parsing isn't strict + function configFromString(config) { + var matched = aspNetJsonRegex.exec(config._i); + if (matched !== null) { + config._d = new Date(+matched[1]); + return; + } + + configFromISO(config); + if (config._isValid === false) { + delete config._isValid; + } else { + return; + } + + configFromRFC2822(config); + if (config._isValid === false) { + delete config._isValid; + } else { + return; + } + + if (config._strict) { + config._isValid = false; + } else { + // Final attempt, use Input Fallback + hooks.createFromInputFallback(config); + } + } + + hooks.createFromInputFallback = deprecate( + 'value provided is not in a recognized RFC2822 or ISO format. moment construction falls back to js Date(), ' + + 'which is not reliable across all browsers and versions. Non RFC2822/ISO date formats are ' + + 'discouraged. Please refer to http://momentjs.com/guides/#/warnings/js-date/ for more info.', + function (config) { + config._d = new Date(config._i + (config._useUTC ? ' UTC' : '')); + } + ); + + // Pick the first defined of two or three arguments. + function defaults(a, b, c) { + if (a != null) { + return a; + } + if (b != null) { + return b; + } + return c; + } + + function currentDateArray(config) { + // hooks is actually the exported moment object + var nowValue = new Date(hooks.now()); + if (config._useUTC) { + return [ + nowValue.getUTCFullYear(), + nowValue.getUTCMonth(), + nowValue.getUTCDate(), + ]; + } + return [nowValue.getFullYear(), nowValue.getMonth(), nowValue.getDate()]; + } + + // convert an array to a date. + // the array should mirror the parameters below + // note: all values past the year are optional and will default to the lowest possible value. + // [year, month, day , hour, minute, second, millisecond] + function configFromArray(config) { + var i, + date, + input = [], + currentDate, + expectedWeekday, + yearToUse; + + if (config._d) { + return; + } + + currentDate = currentDateArray(config); + + //compute day of the year from weeks and weekdays + if (config._w && config._a[DATE] == null && config._a[MONTH] == null) { + dayOfYearFromWeekInfo(config); + } + + //if the day of the year is set, figure out what it is + if (config._dayOfYear != null) { + yearToUse = defaults(config._a[YEAR], currentDate[YEAR]); + + if ( + config._dayOfYear > daysInYear(yearToUse) || + config._dayOfYear === 0 + ) { + getParsingFlags(config)._overflowDayOfYear = true; + } + + date = createUTCDate(yearToUse, 0, config._dayOfYear); + config._a[MONTH] = date.getUTCMonth(); + config._a[DATE] = date.getUTCDate(); + } + + // Default to current date. + // * if no year, month, day of month are given, default to today + // * if day of month is given, default month and year + // * if month is given, default only year + // * if year is given, don't default anything + for (i = 0; i < 3 && config._a[i] == null; ++i) { + config._a[i] = input[i] = currentDate[i]; + } + + // Zero out whatever was not defaulted, including time + for (; i < 7; i++) { + config._a[i] = input[i] = + config._a[i] == null ? (i === 2 ? 1 : 0) : config._a[i]; + } + + // Check for 24:00:00.000 + if ( + config._a[HOUR] === 24 && + config._a[MINUTE] === 0 && + config._a[SECOND] === 0 && + config._a[MILLISECOND] === 0 + ) { + config._nextDay = true; + config._a[HOUR] = 0; + } + + config._d = (config._useUTC ? createUTCDate : createDate).apply( + null, + input + ); + expectedWeekday = config._useUTC + ? config._d.getUTCDay() + : config._d.getDay(); + + // Apply timezone offset from input. The actual utcOffset can be changed + // with parseZone. + if (config._tzm != null) { + config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm); + } + + if (config._nextDay) { + config._a[HOUR] = 24; + } + + // check for mismatching day of week + if ( + config._w && + typeof config._w.d !== 'undefined' && + config._w.d !== expectedWeekday + ) { + getParsingFlags(config).weekdayMismatch = true; + } + } + + function dayOfYearFromWeekInfo(config) { + var w, weekYear, week, weekday, dow, doy, temp, weekdayOverflow, curWeek; + + w = config._w; + if (w.GG != null || w.W != null || w.E != null) { + dow = 1; + doy = 4; + + // TODO: We need to take the current isoWeekYear, but that depends on + // how we interpret now (local, utc, fixed offset). So create + // a now version of current config (take local/utc/offset flags, and + // create now). + weekYear = defaults( + w.GG, + config._a[YEAR], + weekOfYear(createLocal(), 1, 4).year + ); + week = defaults(w.W, 1); + weekday = defaults(w.E, 1); + if (weekday < 1 || weekday > 7) { + weekdayOverflow = true; + } + } else { + dow = config._locale._week.dow; + doy = config._locale._week.doy; + + curWeek = weekOfYear(createLocal(), dow, doy); + + weekYear = defaults(w.gg, config._a[YEAR], curWeek.year); + + // Default to current week. + week = defaults(w.w, curWeek.week); + + if (w.d != null) { + // weekday -- low day numbers are considered next week + weekday = w.d; + if (weekday < 0 || weekday > 6) { + weekdayOverflow = true; + } + } else if (w.e != null) { + // local weekday -- counting starts from beginning of week + weekday = w.e + dow; + if (w.e < 0 || w.e > 6) { + weekdayOverflow = true; + } + } else { + // default to beginning of week + weekday = dow; + } + } + if (week < 1 || week > weeksInYear(weekYear, dow, doy)) { + getParsingFlags(config)._overflowWeeks = true; + } else if (weekdayOverflow != null) { + getParsingFlags(config)._overflowWeekday = true; + } else { + temp = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy); + config._a[YEAR] = temp.year; + config._dayOfYear = temp.dayOfYear; + } + } + + // constant that refers to the ISO standard + hooks.ISO_8601 = function () {}; + + // constant that refers to the RFC 2822 form + hooks.RFC_2822 = function () {}; + + // date from string and format string + function configFromStringAndFormat(config) { + // TODO: Move this to another part of the creation flow to prevent circular deps + if (config._f === hooks.ISO_8601) { + configFromISO(config); + return; + } + if (config._f === hooks.RFC_2822) { + configFromRFC2822(config); + return; + } + config._a = []; + getParsingFlags(config).empty = true; + + // This array is used to make a Date, either with `new Date` or `Date.UTC` + var string = '' + config._i, + i, + parsedInput, + tokens, + token, + skipped, + stringLength = string.length, + totalParsedInputLength = 0, + era; + + tokens = + expandFormat(config._f, config._locale).match(formattingTokens) || []; + + for (i = 0; i < tokens.length; i++) { + token = tokens[i]; + parsedInput = (string.match(getParseRegexForToken(token, config)) || + [])[0]; + if (parsedInput) { + skipped = string.substr(0, string.indexOf(parsedInput)); + if (skipped.length > 0) { + getParsingFlags(config).unusedInput.push(skipped); + } + string = string.slice( + string.indexOf(parsedInput) + parsedInput.length + ); + totalParsedInputLength += parsedInput.length; + } + // don't parse if it's not a known token + if (formatTokenFunctions[token]) { + if (parsedInput) { + getParsingFlags(config).empty = false; + } else { + getParsingFlags(config).unusedTokens.push(token); + } + addTimeToArrayFromToken(token, parsedInput, config); + } else if (config._strict && !parsedInput) { + getParsingFlags(config).unusedTokens.push(token); + } + } + + // add remaining unparsed input length to the string + getParsingFlags(config).charsLeftOver = + stringLength - totalParsedInputLength; + if (string.length > 0) { + getParsingFlags(config).unusedInput.push(string); + } + + // clear _12h flag if hour is <= 12 + if ( + config._a[HOUR] <= 12 && + getParsingFlags(config).bigHour === true && + config._a[HOUR] > 0 + ) { + getParsingFlags(config).bigHour = undefined; + } + + getParsingFlags(config).parsedDateParts = config._a.slice(0); + getParsingFlags(config).meridiem = config._meridiem; + // handle meridiem + config._a[HOUR] = meridiemFixWrap( + config._locale, + config._a[HOUR], + config._meridiem + ); + + // handle era + era = getParsingFlags(config).era; + if (era !== null) { + config._a[YEAR] = config._locale.erasConvertYear(era, config._a[YEAR]); + } + + configFromArray(config); + checkOverflow(config); + } + + function meridiemFixWrap(locale, hour, meridiem) { + var isPm; + + if (meridiem == null) { + // nothing to do + return hour; + } + if (locale.meridiemHour != null) { + return locale.meridiemHour(hour, meridiem); + } else if (locale.isPM != null) { + // Fallback + isPm = locale.isPM(meridiem); + if (isPm && hour < 12) { + hour += 12; + } + if (!isPm && hour === 12) { + hour = 0; + } + return hour; + } else { + // this is not supposed to happen + return hour; + } + } + + // date from string and array of format strings + function configFromStringAndArray(config) { + var tempConfig, + bestMoment, + scoreToBeat, + i, + currentScore, + validFormatFound, + bestFormatIsValid = false; + + if (config._f.length === 0) { + getParsingFlags(config).invalidFormat = true; + config._d = new Date(NaN); + return; + } + + for (i = 0; i < config._f.length; i++) { + currentScore = 0; + validFormatFound = false; + tempConfig = copyConfig({}, config); + if (config._useUTC != null) { + tempConfig._useUTC = config._useUTC; + } + tempConfig._f = config._f[i]; + configFromStringAndFormat(tempConfig); + + if (isValid(tempConfig)) { + validFormatFound = true; + } + + // if there is any input that was not parsed add a penalty for that format + currentScore += getParsingFlags(tempConfig).charsLeftOver; + + //or tokens + currentScore += getParsingFlags(tempConfig).unusedTokens.length * 10; + + getParsingFlags(tempConfig).score = currentScore; + + if (!bestFormatIsValid) { + if ( + scoreToBeat == null || + currentScore < scoreToBeat || + validFormatFound + ) { + scoreToBeat = currentScore; + bestMoment = tempConfig; + if (validFormatFound) { + bestFormatIsValid = true; + } + } + } else { + if (currentScore < scoreToBeat) { + scoreToBeat = currentScore; + bestMoment = tempConfig; + } + } + } + + extend(config, bestMoment || tempConfig); + } + + function configFromObject(config) { + if (config._d) { + return; + } + + var i = normalizeObjectUnits(config._i), + dayOrDate = i.day === undefined ? i.date : i.day; + config._a = map( + [i.year, i.month, dayOrDate, i.hour, i.minute, i.second, i.millisecond], + function (obj) { + return obj && parseInt(obj, 10); + } + ); + + configFromArray(config); + } + + function createFromConfig(config) { + var res = new Moment(checkOverflow(prepareConfig(config))); + if (res._nextDay) { + // Adding is smart enough around DST + res.add(1, 'd'); + res._nextDay = undefined; + } + + return res; + } + + function prepareConfig(config) { + var input = config._i, + format = config._f; + + config._locale = config._locale || getLocale(config._l); + + if (input === null || (format === undefined && input === '')) { + return createInvalid({ nullInput: true }); + } + + if (typeof input === 'string') { + config._i = input = config._locale.preparse(input); + } + + if (isMoment(input)) { + return new Moment(checkOverflow(input)); + } else if (isDate(input)) { + config._d = input; + } else if (isArray(format)) { + configFromStringAndArray(config); + } else if (format) { + configFromStringAndFormat(config); + } else { + configFromInput(config); + } + + if (!isValid(config)) { + config._d = null; + } + + return config; + } + + function configFromInput(config) { + var input = config._i; + if (isUndefined(input)) { + config._d = new Date(hooks.now()); + } else if (isDate(input)) { + config._d = new Date(input.valueOf()); + } else if (typeof input === 'string') { + configFromString(config); + } else if (isArray(input)) { + config._a = map(input.slice(0), function (obj) { + return parseInt(obj, 10); + }); + configFromArray(config); + } else if (isObject(input)) { + configFromObject(config); + } else if (isNumber(input)) { + // from milliseconds + config._d = new Date(input); + } else { + hooks.createFromInputFallback(config); + } + } + + function createLocalOrUTC(input, format, locale, strict, isUTC) { + var c = {}; + + if (format === true || format === false) { + strict = format; + format = undefined; + } + + if (locale === true || locale === false) { + strict = locale; + locale = undefined; + } + + if ( + (isObject(input) && isObjectEmpty(input)) || + (isArray(input) && input.length === 0) + ) { + input = undefined; + } + // object construction must be done this way. + // https://github.com/moment/moment/issues/1423 + c._isAMomentObject = true; + c._useUTC = c._isUTC = isUTC; + c._l = locale; + c._i = input; + c._f = format; + c._strict = strict; + + return createFromConfig(c); + } + + function createLocal(input, format, locale, strict) { + return createLocalOrUTC(input, format, locale, strict, false); + } + + var prototypeMin = deprecate( + 'moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/', + function () { + var other = createLocal.apply(null, arguments); + if (this.isValid() && other.isValid()) { + return other < this ? this : other; + } else { + return createInvalid(); + } + } + ), + prototypeMax = deprecate( + 'moment().max is deprecated, use moment.min instead. http://momentjs.com/guides/#/warnings/min-max/', + function () { + var other = createLocal.apply(null, arguments); + if (this.isValid() && other.isValid()) { + return other > this ? this : other; + } else { + return createInvalid(); + } + } + ); + + // Pick a moment m from moments so that m[fn](other) is true for all + // other. This relies on the function fn to be transitive. + // + // moments should either be an array of moment objects or an array, whose + // first element is an array of moment objects. + function pickBy(fn, moments) { + var res, i; + if (moments.length === 1 && isArray(moments[0])) { + moments = moments[0]; + } + if (!moments.length) { + return createLocal(); + } + res = moments[0]; + for (i = 1; i < moments.length; ++i) { + if (!moments[i].isValid() || moments[i][fn](res)) { + res = moments[i]; + } + } + return res; + } + + // TODO: Use [].sort instead? + function min() { + var args = [].slice.call(arguments, 0); + + return pickBy('isBefore', args); + } + + function max() { + var args = [].slice.call(arguments, 0); + + return pickBy('isAfter', args); + } + + var now = function () { + return Date.now ? Date.now() : +new Date(); + }; + + var ordering = [ + 'year', + 'quarter', + 'month', + 'week', + 'day', + 'hour', + 'minute', + 'second', + 'millisecond', + ]; + + function isDurationValid(m) { + var key, + unitHasDecimal = false, + i; + for (key in m) { + if ( + hasOwnProp(m, key) && + !( + indexOf.call(ordering, key) !== -1 && + (m[key] == null || !isNaN(m[key])) + ) + ) { + return false; + } + } + + for (i = 0; i < ordering.length; ++i) { + if (m[ordering[i]]) { + if (unitHasDecimal) { + return false; // only allow non-integers for smallest unit + } + if (parseFloat(m[ordering[i]]) !== toInt(m[ordering[i]])) { + unitHasDecimal = true; + } + } + } + + return true; + } + + function isValid$1() { + return this._isValid; + } + + function createInvalid$1() { + return createDuration(NaN); + } + + function Duration(duration) { + var normalizedInput = normalizeObjectUnits(duration), + years = normalizedInput.year || 0, + quarters = normalizedInput.quarter || 0, + months = normalizedInput.month || 0, + weeks = normalizedInput.week || normalizedInput.isoWeek || 0, + days = normalizedInput.day || 0, + hours = normalizedInput.hour || 0, + minutes = normalizedInput.minute || 0, + seconds = normalizedInput.second || 0, + milliseconds = normalizedInput.millisecond || 0; + + this._isValid = isDurationValid(normalizedInput); + + // representation for dateAddRemove + this._milliseconds = + +milliseconds + + seconds * 1e3 + // 1000 + minutes * 6e4 + // 1000 * 60 + hours * 1000 * 60 * 60; //using 1000 * 60 * 60 instead of 36e5 to avoid floating point rounding errors https://github.com/moment/moment/issues/2978 + // Because of dateAddRemove treats 24 hours as different from a + // day when working around DST, we need to store them separately + this._days = +days + weeks * 7; + // It is impossible to translate months into days without knowing + // which months you are are talking about, so we have to store + // it separately. + this._months = +months + quarters * 3 + years * 12; + + this._data = {}; + + this._locale = getLocale(); + + this._bubble(); + } + + function isDuration(obj) { + return obj instanceof Duration; + } + + function absRound(number) { + if (number < 0) { + return Math.round(-1 * number) * -1; + } else { + return Math.round(number); + } + } + + // compare two arrays, return the number of differences + function compareArrays(array1, array2, dontConvert) { + var len = Math.min(array1.length, array2.length), + lengthDiff = Math.abs(array1.length - array2.length), + diffs = 0, + i; + for (i = 0; i < len; i++) { + if ( + (dontConvert && array1[i] !== array2[i]) || + (!dontConvert && toInt(array1[i]) !== toInt(array2[i])) + ) { + diffs++; + } + } + return diffs + lengthDiff; + } + + // FORMATTING + + function offset(token, separator) { + addFormatToken(token, 0, 0, function () { + var offset = this.utcOffset(), + sign = '+'; + if (offset < 0) { + offset = -offset; + sign = '-'; + } + return ( + sign + + zeroFill(~~(offset / 60), 2) + + separator + + zeroFill(~~offset % 60, 2) + ); + }); + } + + offset('Z', ':'); + offset('ZZ', ''); + + // PARSING + + addRegexToken('Z', matchShortOffset); + addRegexToken('ZZ', matchShortOffset); + addParseToken(['Z', 'ZZ'], function (input, array, config) { + config._useUTC = true; + config._tzm = offsetFromString(matchShortOffset, input); + }); + + // HELPERS + + // timezone chunker + // '+10:00' > ['10', '00'] + // '-1530' > ['-15', '30'] + var chunkOffset = /([\+\-]|\d\d)/gi; + + function offsetFromString(matcher, string) { + var matches = (string || '').match(matcher), + chunk, + parts, + minutes; + + if (matches === null) { + return null; + } + + chunk = matches[matches.length - 1] || []; + parts = (chunk + '').match(chunkOffset) || ['-', 0, 0]; + minutes = +(parts[1] * 60) + toInt(parts[2]); + + return minutes === 0 ? 0 : parts[0] === '+' ? minutes : -minutes; + } + + // Return a moment from input, that is local/utc/zone equivalent to model. + function cloneWithOffset(input, model) { + var res, diff; + if (model._isUTC) { + res = model.clone(); + diff = + (isMoment(input) || isDate(input) + ? input.valueOf() + : createLocal(input).valueOf()) - res.valueOf(); + // Use low-level api, because this fn is low-level api. + res._d.setTime(res._d.valueOf() + diff); + hooks.updateOffset(res, false); + return res; + } else { + return createLocal(input).local(); + } + } + + function getDateOffset(m) { + // On Firefox.24 Date#getTimezoneOffset returns a floating point. + // https://github.com/moment/moment/pull/1871 + return -Math.round(m._d.getTimezoneOffset()); + } + + // HOOKS + + // This function will be called whenever a moment is mutated. + // It is intended to keep the offset in sync with the timezone. + hooks.updateOffset = function () {}; + + // MOMENTS + + // keepLocalTime = true means only change the timezone, without + // affecting the local hour. So 5:31:26 +0300 --[utcOffset(2, true)]--> + // 5:31:26 +0200 It is possible that 5:31:26 doesn't exist with offset + // +0200, so we adjust the time as needed, to be valid. + // + // Keeping the time actually adds/subtracts (one hour) + // from the actual represented time. That is why we call updateOffset + // a second time. In case it wants us to change the offset again + // _changeInProgress == true case, then we have to adjust, because + // there is no such time in the given timezone. + function getSetOffset(input, keepLocalTime, keepMinutes) { + var offset = this._offset || 0, + localAdjust; + if (!this.isValid()) { + return input != null ? this : NaN; + } + if (input != null) { + if (typeof input === 'string') { + input = offsetFromString(matchShortOffset, input); + if (input === null) { + return this; + } + } else if (Math.abs(input) < 16 && !keepMinutes) { + input = input * 60; + } + if (!this._isUTC && keepLocalTime) { + localAdjust = getDateOffset(this); + } + this._offset = input; + this._isUTC = true; + if (localAdjust != null) { + this.add(localAdjust, 'm'); + } + if (offset !== input) { + if (!keepLocalTime || this._changeInProgress) { + addSubtract( + this, + createDuration(input - offset, 'm'), + 1, + false + ); + } else if (!this._changeInProgress) { + this._changeInProgress = true; + hooks.updateOffset(this, true); + this._changeInProgress = null; + } + } + return this; + } else { + return this._isUTC ? offset : getDateOffset(this); + } + } + + function getSetZone(input, keepLocalTime) { + if (input != null) { + if (typeof input !== 'string') { + input = -input; + } + + this.utcOffset(input, keepLocalTime); + + return this; + } else { + return -this.utcOffset(); + } + } + + function setOffsetToUTC(keepLocalTime) { + return this.utcOffset(0, keepLocalTime); + } + + function setOffsetToLocal(keepLocalTime) { + if (this._isUTC) { + this.utcOffset(0, keepLocalTime); + this._isUTC = false; + + if (keepLocalTime) { + this.subtract(getDateOffset(this), 'm'); + } + } + return this; + } + + function setOffsetToParsedOffset() { + if (this._tzm != null) { + this.utcOffset(this._tzm, false, true); + } else if (typeof this._i === 'string') { + var tZone = offsetFromString(matchOffset, this._i); + if (tZone != null) { + this.utcOffset(tZone); + } else { + this.utcOffset(0, true); + } + } + return this; + } + + function hasAlignedHourOffset(input) { + if (!this.isValid()) { + return false; + } + input = input ? createLocal(input).utcOffset() : 0; + + return (this.utcOffset() - input) % 60 === 0; + } + + function isDaylightSavingTime() { + return ( + this.utcOffset() > this.clone().month(0).utcOffset() || + this.utcOffset() > this.clone().month(5).utcOffset() + ); + } + + function isDaylightSavingTimeShifted() { + if (!isUndefined(this._isDSTShifted)) { + return this._isDSTShifted; + } + + var c = {}, + other; + + copyConfig(c, this); + c = prepareConfig(c); + + if (c._a) { + other = c._isUTC ? createUTC(c._a) : createLocal(c._a); + this._isDSTShifted = + this.isValid() && compareArrays(c._a, other.toArray()) > 0; + } else { + this._isDSTShifted = false; + } + + return this._isDSTShifted; + } + + function isLocal() { + return this.isValid() ? !this._isUTC : false; + } + + function isUtcOffset() { + return this.isValid() ? this._isUTC : false; + } + + function isUtc() { + return this.isValid() ? this._isUTC && this._offset === 0 : false; + } + + // ASP.NET json date format regex + var aspNetRegex = /^(-|\+)?(?:(\d*)[. ])?(\d+):(\d+)(?::(\d+)(\.\d*)?)?$/, + // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html + // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere + // and further modified to allow for strings containing both week and day + isoRegex = /^(-|\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/; + + function createDuration(input, key) { + var duration = input, + // matching against regexp is expensive, do it on demand + match = null, + sign, + ret, + diffRes; + + if (isDuration(input)) { + duration = { + ms: input._milliseconds, + d: input._days, + M: input._months, + }; + } else if (isNumber(input) || !isNaN(+input)) { + duration = {}; + if (key) { + duration[key] = +input; + } else { + duration.milliseconds = +input; + } + } else if ((match = aspNetRegex.exec(input))) { + sign = match[1] === '-' ? -1 : 1; + duration = { + y: 0, + d: toInt(match[DATE]) * sign, + h: toInt(match[HOUR]) * sign, + m: toInt(match[MINUTE]) * sign, + s: toInt(match[SECOND]) * sign, + ms: toInt(absRound(match[MILLISECOND] * 1000)) * sign, // the millisecond decimal point is included in the match + }; + } else if ((match = isoRegex.exec(input))) { + sign = match[1] === '-' ? -1 : 1; + duration = { + y: parseIso(match[2], sign), + M: parseIso(match[3], sign), + w: parseIso(match[4], sign), + d: parseIso(match[5], sign), + h: parseIso(match[6], sign), + m: parseIso(match[7], sign), + s: parseIso(match[8], sign), + }; + } else if (duration == null) { + // checks for null or undefined + duration = {}; + } else if ( + typeof duration === 'object' && + ('from' in duration || 'to' in duration) + ) { + diffRes = momentsDifference( + createLocal(duration.from), + createLocal(duration.to) + ); + + duration = {}; + duration.ms = diffRes.milliseconds; + duration.M = diffRes.months; + } + + ret = new Duration(duration); + + if (isDuration(input) && hasOwnProp(input, '_locale')) { + ret._locale = input._locale; + } + + if (isDuration(input) && hasOwnProp(input, '_isValid')) { + ret._isValid = input._isValid; + } + + return ret; + } + + createDuration.fn = Duration.prototype; + createDuration.invalid = createInvalid$1; + + function parseIso(inp, sign) { + // We'd normally use ~~inp for this, but unfortunately it also + // converts floats to ints. + // inp may be undefined, so careful calling replace on it. + var res = inp && parseFloat(inp.replace(',', '.')); + // apply sign while we're at it + return (isNaN(res) ? 0 : res) * sign; + } + + function positiveMomentsDifference(base, other) { + var res = {}; + + res.months = + other.month() - base.month() + (other.year() - base.year()) * 12; + if (base.clone().add(res.months, 'M').isAfter(other)) { + --res.months; + } + + res.milliseconds = +other - +base.clone().add(res.months, 'M'); + + return res; + } + + function momentsDifference(base, other) { + var res; + if (!(base.isValid() && other.isValid())) { + return { milliseconds: 0, months: 0 }; + } + + other = cloneWithOffset(other, base); + if (base.isBefore(other)) { + res = positiveMomentsDifference(base, other); + } else { + res = positiveMomentsDifference(other, base); + res.milliseconds = -res.milliseconds; + res.months = -res.months; + } + + return res; + } + + // TODO: remove 'name' arg after deprecation is removed + function createAdder(direction, name) { + return function (val, period) { + var dur, tmp; + //invert the arguments, but complain about it + if (period !== null && !isNaN(+period)) { + deprecateSimple( + name, + 'moment().' + + name + + '(period, number) is deprecated. Please use moment().' + + name + + '(number, period). ' + + 'See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info.' + ); + tmp = val; + val = period; + period = tmp; + } + + dur = createDuration(val, period); + addSubtract(this, dur, direction); + return this; + }; + } + + function addSubtract(mom, duration, isAdding, updateOffset) { + var milliseconds = duration._milliseconds, + days = absRound(duration._days), + months = absRound(duration._months); + + if (!mom.isValid()) { + // No op + return; + } + + updateOffset = updateOffset == null ? true : updateOffset; + + if (months) { + setMonth(mom, get(mom, 'Month') + months * isAdding); + } + if (days) { + set$1(mom, 'Date', get(mom, 'Date') + days * isAdding); + } + if (milliseconds) { + mom._d.setTime(mom._d.valueOf() + milliseconds * isAdding); + } + if (updateOffset) { + hooks.updateOffset(mom, days || months); + } + } + + var add = createAdder(1, 'add'), + subtract = createAdder(-1, 'subtract'); + + function isString(input) { + return typeof input === 'string' || input instanceof String; + } + + // type MomentInput = Moment | Date | string | number | (number | string)[] | MomentInputObject | void; // null | undefined + function isMomentInput(input) { + return ( + isMoment(input) || + isDate(input) || + isString(input) || + isNumber(input) || + isNumberOrStringArray(input) || + isMomentInputObject(input) || + input === null || + input === undefined + ); + } + + function isMomentInputObject(input) { + var objectTest = isObject(input) && !isObjectEmpty(input), + propertyTest = false, + properties = [ + 'years', + 'year', + 'y', + 'months', + 'month', + 'M', + 'days', + 'day', + 'd', + 'dates', + 'date', + 'D', + 'hours', + 'hour', + 'h', + 'minutes', + 'minute', + 'm', + 'seconds', + 'second', + 's', + 'milliseconds', + 'millisecond', + 'ms', + ], + i, + property; + + for (i = 0; i < properties.length; i += 1) { + property = properties[i]; + propertyTest = propertyTest || hasOwnProp(input, property); + } + + return objectTest && propertyTest; + } + + function isNumberOrStringArray(input) { + var arrayTest = isArray(input), + dataTypeTest = false; + if (arrayTest) { + dataTypeTest = + input.filter(function (item) { + return !isNumber(item) && isString(input); + }).length === 0; + } + return arrayTest && dataTypeTest; + } + + function isCalendarSpec(input) { + var objectTest = isObject(input) && !isObjectEmpty(input), + propertyTest = false, + properties = [ + 'sameDay', + 'nextDay', + 'lastDay', + 'nextWeek', + 'lastWeek', + 'sameElse', + ], + i, + property; + + for (i = 0; i < properties.length; i += 1) { + property = properties[i]; + propertyTest = propertyTest || hasOwnProp(input, property); + } + + return objectTest && propertyTest; + } + + function getCalendarFormat(myMoment, now) { + var diff = myMoment.diff(now, 'days', true); + return diff < -6 + ? 'sameElse' + : diff < -1 + ? 'lastWeek' + : diff < 0 + ? 'lastDay' + : diff < 1 + ? 'sameDay' + : diff < 2 + ? 'nextDay' + : diff < 7 + ? 'nextWeek' + : 'sameElse'; + } + + function calendar$1(time, formats) { + // Support for single parameter, formats only overload to the calendar function + if (arguments.length === 1) { + if (!arguments[0]) { + time = undefined; + formats = undefined; + } else if (isMomentInput(arguments[0])) { + time = arguments[0]; + formats = undefined; + } else if (isCalendarSpec(arguments[0])) { + formats = arguments[0]; + time = undefined; + } + } + // We want to compare the start of today, vs this. + // Getting start-of-today depends on whether we're local/utc/offset or not. + var now = time || createLocal(), + sod = cloneWithOffset(now, this).startOf('day'), + format = hooks.calendarFormat(this, sod) || 'sameElse', + output = + formats && + (isFunction(formats[format]) + ? formats[format].call(this, now) + : formats[format]); + + return this.format( + output || this.localeData().calendar(format, this, createLocal(now)) + ); + } + + function clone() { + return new Moment(this); + } + + function isAfter(input, units) { + var localInput = isMoment(input) ? input : createLocal(input); + if (!(this.isValid() && localInput.isValid())) { + return false; + } + units = normalizeUnits(units) || 'millisecond'; + if (units === 'millisecond') { + return this.valueOf() > localInput.valueOf(); + } else { + return localInput.valueOf() < this.clone().startOf(units).valueOf(); + } + } + + function isBefore(input, units) { + var localInput = isMoment(input) ? input : createLocal(input); + if (!(this.isValid() && localInput.isValid())) { + return false; + } + units = normalizeUnits(units) || 'millisecond'; + if (units === 'millisecond') { + return this.valueOf() < localInput.valueOf(); + } else { + return this.clone().endOf(units).valueOf() < localInput.valueOf(); + } + } + + function isBetween(from, to, units, inclusivity) { + var localFrom = isMoment(from) ? from : createLocal(from), + localTo = isMoment(to) ? to : createLocal(to); + if (!(this.isValid() && localFrom.isValid() && localTo.isValid())) { + return false; + } + inclusivity = inclusivity || '()'; + return ( + (inclusivity[0] === '(' + ? this.isAfter(localFrom, units) + : !this.isBefore(localFrom, units)) && + (inclusivity[1] === ')' + ? this.isBefore(localTo, units) + : !this.isAfter(localTo, units)) + ); + } + + function isSame(input, units) { + var localInput = isMoment(input) ? input : createLocal(input), + inputMs; + if (!(this.isValid() && localInput.isValid())) { + return false; + } + units = normalizeUnits(units) || 'millisecond'; + if (units === 'millisecond') { + return this.valueOf() === localInput.valueOf(); + } else { + inputMs = localInput.valueOf(); + return ( + this.clone().startOf(units).valueOf() <= inputMs && + inputMs <= this.clone().endOf(units).valueOf() + ); + } + } + + function isSameOrAfter(input, units) { + return this.isSame(input, units) || this.isAfter(input, units); + } + + function isSameOrBefore(input, units) { + return this.isSame(input, units) || this.isBefore(input, units); + } + + function diff(input, units, asFloat) { + var that, zoneDelta, output; + + if (!this.isValid()) { + return NaN; + } + + that = cloneWithOffset(input, this); + + if (!that.isValid()) { + return NaN; + } + + zoneDelta = (that.utcOffset() - this.utcOffset()) * 6e4; + + units = normalizeUnits(units); + + switch (units) { + case 'year': + output = monthDiff(this, that) / 12; + break; + case 'month': + output = monthDiff(this, that); + break; + case 'quarter': + output = monthDiff(this, that) / 3; + break; + case 'second': + output = (this - that) / 1e3; + break; // 1000 + case 'minute': + output = (this - that) / 6e4; + break; // 1000 * 60 + case 'hour': + output = (this - that) / 36e5; + break; // 1000 * 60 * 60 + case 'day': + output = (this - that - zoneDelta) / 864e5; + break; // 1000 * 60 * 60 * 24, negate dst + case 'week': + output = (this - that - zoneDelta) / 6048e5; + break; // 1000 * 60 * 60 * 24 * 7, negate dst + default: + output = this - that; + } + + return asFloat ? output : absFloor(output); + } + + function monthDiff(a, b) { + if (a.date() < b.date()) { + // end-of-month calculations work correct when the start month has more + // days than the end month. + return -monthDiff(b, a); + } + // difference in months + var wholeMonthDiff = (b.year() - a.year()) * 12 + (b.month() - a.month()), + // b is in (anchor - 1 month, anchor + 1 month) + anchor = a.clone().add(wholeMonthDiff, 'months'), + anchor2, + adjust; + + if (b - anchor < 0) { + anchor2 = a.clone().add(wholeMonthDiff - 1, 'months'); + // linear across the month + adjust = (b - anchor) / (anchor - anchor2); + } else { + anchor2 = a.clone().add(wholeMonthDiff + 1, 'months'); + // linear across the month + adjust = (b - anchor) / (anchor2 - anchor); + } + + //check for negative zero, return zero if negative zero + return -(wholeMonthDiff + adjust) || 0; + } + + hooks.defaultFormat = 'YYYY-MM-DDTHH:mm:ssZ'; + hooks.defaultFormatUtc = 'YYYY-MM-DDTHH:mm:ss[Z]'; + + function toString() { + return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ'); + } + + function toISOString(keepOffset) { + if (!this.isValid()) { + return null; + } + var utc = keepOffset !== true, + m = utc ? this.clone().utc() : this; + if (m.year() < 0 || m.year() > 9999) { + return formatMoment( + m, + utc + ? 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]' + : 'YYYYYY-MM-DD[T]HH:mm:ss.SSSZ' + ); + } + if (isFunction(Date.prototype.toISOString)) { + // native implementation is ~50x faster, use it when we can + if (utc) { + return this.toDate().toISOString(); + } else { + return new Date(this.valueOf() + this.utcOffset() * 60 * 1000) + .toISOString() + .replace('Z', formatMoment(m, 'Z')); + } + } + return formatMoment( + m, + utc ? 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]' : 'YYYY-MM-DD[T]HH:mm:ss.SSSZ' + ); + } + + /** + * Return a human readable representation of a moment that can + * also be evaluated to get a new moment which is the same + * + * @link https://nodejs.org/dist/latest/docs/api/util.html#util_custom_inspect_function_on_objects + */ + function inspect() { + if (!this.isValid()) { + return 'moment.invalid(/* ' + this._i + ' */)'; + } + var func = 'moment', + zone = '', + prefix, + year, + datetime, + suffix; + if (!this.isLocal()) { + func = this.utcOffset() === 0 ? 'moment.utc' : 'moment.parseZone'; + zone = 'Z'; + } + prefix = '[' + func + '("]'; + year = 0 <= this.year() && this.year() <= 9999 ? 'YYYY' : 'YYYYYY'; + datetime = '-MM-DD[T]HH:mm:ss.SSS'; + suffix = zone + '[")]'; + + return this.format(prefix + year + datetime + suffix); + } + + function format(inputString) { + if (!inputString) { + inputString = this.isUtc() + ? hooks.defaultFormatUtc + : hooks.defaultFormat; + } + var output = formatMoment(this, inputString); + return this.localeData().postformat(output); + } + + function from(time, withoutSuffix) { + if ( + this.isValid() && + ((isMoment(time) && time.isValid()) || createLocal(time).isValid()) + ) { + return createDuration({ to: this, from: time }) + .locale(this.locale()) + .humanize(!withoutSuffix); + } else { + return this.localeData().invalidDate(); + } + } + + function fromNow(withoutSuffix) { + return this.from(createLocal(), withoutSuffix); + } + + function to(time, withoutSuffix) { + if ( + this.isValid() && + ((isMoment(time) && time.isValid()) || createLocal(time).isValid()) + ) { + return createDuration({ from: this, to: time }) + .locale(this.locale()) + .humanize(!withoutSuffix); + } else { + return this.localeData().invalidDate(); + } + } + + function toNow(withoutSuffix) { + return this.to(createLocal(), withoutSuffix); + } + + // If passed a locale key, it will set the locale for this + // instance. Otherwise, it will return the locale configuration + // variables for this instance. + function locale(key) { + var newLocaleData; + + if (key === undefined) { + return this._locale._abbr; + } else { + newLocaleData = getLocale(key); + if (newLocaleData != null) { + this._locale = newLocaleData; + } + return this; + } + } + + var lang = deprecate( + 'moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.', + function (key) { + if (key === undefined) { + return this.localeData(); + } else { + return this.locale(key); + } + } + ); + + function localeData() { + return this._locale; + } + + var MS_PER_SECOND = 1000, + MS_PER_MINUTE = 60 * MS_PER_SECOND, + MS_PER_HOUR = 60 * MS_PER_MINUTE, + MS_PER_400_YEARS = (365 * 400 + 97) * 24 * MS_PER_HOUR; + + // actual modulo - handles negative numbers (for dates before 1970): + function mod$1(dividend, divisor) { + return ((dividend % divisor) + divisor) % divisor; + } + + function localStartOfDate(y, m, d) { + // the date constructor remaps years 0-99 to 1900-1999 + if (y < 100 && y >= 0) { + // preserve leap years using a full 400 year cycle, then reset + return new Date(y + 400, m, d) - MS_PER_400_YEARS; + } else { + return new Date(y, m, d).valueOf(); + } + } + + function utcStartOfDate(y, m, d) { + // Date.UTC remaps years 0-99 to 1900-1999 + if (y < 100 && y >= 0) { + // preserve leap years using a full 400 year cycle, then reset + return Date.UTC(y + 400, m, d) - MS_PER_400_YEARS; + } else { + return Date.UTC(y, m, d); + } + } + + function startOf(units) { + var time, startOfDate; + units = normalizeUnits(units); + if (units === undefined || units === 'millisecond' || !this.isValid()) { + return this; + } + + startOfDate = this._isUTC ? utcStartOfDate : localStartOfDate; + + switch (units) { + case 'year': + time = startOfDate(this.year(), 0, 1); + break; + case 'quarter': + time = startOfDate( + this.year(), + this.month() - (this.month() % 3), + 1 + ); + break; + case 'month': + time = startOfDate(this.year(), this.month(), 1); + break; + case 'week': + time = startOfDate( + this.year(), + this.month(), + this.date() - this.weekday() + ); + break; + case 'isoWeek': + time = startOfDate( + this.year(), + this.month(), + this.date() - (this.isoWeekday() - 1) + ); + break; + case 'day': + case 'date': + time = startOfDate(this.year(), this.month(), this.date()); + break; + case 'hour': + time = this._d.valueOf(); + time -= mod$1( + time + (this._isUTC ? 0 : this.utcOffset() * MS_PER_MINUTE), + MS_PER_HOUR + ); + break; + case 'minute': + time = this._d.valueOf(); + time -= mod$1(time, MS_PER_MINUTE); + break; + case 'second': + time = this._d.valueOf(); + time -= mod$1(time, MS_PER_SECOND); + break; + } + + this._d.setTime(time); + hooks.updateOffset(this, true); + return this; + } + + function endOf(units) { + var time, startOfDate; + units = normalizeUnits(units); + if (units === undefined || units === 'millisecond' || !this.isValid()) { + return this; + } + + startOfDate = this._isUTC ? utcStartOfDate : localStartOfDate; + + switch (units) { + case 'year': + time = startOfDate(this.year() + 1, 0, 1) - 1; + break; + case 'quarter': + time = + startOfDate( + this.year(), + this.month() - (this.month() % 3) + 3, + 1 + ) - 1; + break; + case 'month': + time = startOfDate(this.year(), this.month() + 1, 1) - 1; + break; + case 'week': + time = + startOfDate( + this.year(), + this.month(), + this.date() - this.weekday() + 7 + ) - 1; + break; + case 'isoWeek': + time = + startOfDate( + this.year(), + this.month(), + this.date() - (this.isoWeekday() - 1) + 7 + ) - 1; + break; + case 'day': + case 'date': + time = startOfDate(this.year(), this.month(), this.date() + 1) - 1; + break; + case 'hour': + time = this._d.valueOf(); + time += + MS_PER_HOUR - + mod$1( + time + (this._isUTC ? 0 : this.utcOffset() * MS_PER_MINUTE), + MS_PER_HOUR + ) - + 1; + break; + case 'minute': + time = this._d.valueOf(); + time += MS_PER_MINUTE - mod$1(time, MS_PER_MINUTE) - 1; + break; + case 'second': + time = this._d.valueOf(); + time += MS_PER_SECOND - mod$1(time, MS_PER_SECOND) - 1; + break; + } + + this._d.setTime(time); + hooks.updateOffset(this, true); + return this; + } + + function valueOf() { + return this._d.valueOf() - (this._offset || 0) * 60000; + } + + function unix() { + return Math.floor(this.valueOf() / 1000); + } + + function toDate() { + return new Date(this.valueOf()); + } + + function toArray() { + var m = this; + return [ + m.year(), + m.month(), + m.date(), + m.hour(), + m.minute(), + m.second(), + m.millisecond(), + ]; + } + + function toObject() { + var m = this; + return { + years: m.year(), + months: m.month(), + date: m.date(), + hours: m.hours(), + minutes: m.minutes(), + seconds: m.seconds(), + milliseconds: m.milliseconds(), + }; + } + + function toJSON() { + // new Date(NaN).toJSON() === null + return this.isValid() ? this.toISOString() : null; + } + + function isValid$2() { + return isValid(this); + } + + function parsingFlags() { + return extend({}, getParsingFlags(this)); + } + + function invalidAt() { + return getParsingFlags(this).overflow; + } + + function creationData() { + return { + input: this._i, + format: this._f, + locale: this._locale, + isUTC: this._isUTC, + strict: this._strict, + }; + } + + addFormatToken('N', 0, 0, 'eraAbbr'); + addFormatToken('NN', 0, 0, 'eraAbbr'); + addFormatToken('NNN', 0, 0, 'eraAbbr'); + addFormatToken('NNNN', 0, 0, 'eraName'); + addFormatToken('NNNNN', 0, 0, 'eraNarrow'); + + addFormatToken('y', ['y', 1], 'yo', 'eraYear'); + addFormatToken('y', ['yy', 2], 0, 'eraYear'); + addFormatToken('y', ['yyy', 3], 0, 'eraYear'); + addFormatToken('y', ['yyyy', 4], 0, 'eraYear'); + + addRegexToken('N', matchEraAbbr); + addRegexToken('NN', matchEraAbbr); + addRegexToken('NNN', matchEraAbbr); + addRegexToken('NNNN', matchEraName); + addRegexToken('NNNNN', matchEraNarrow); + + addParseToken(['N', 'NN', 'NNN', 'NNNN', 'NNNNN'], function ( + input, + array, + config, + token + ) { + var era = config._locale.erasParse(input, token, config._strict); + if (era) { + getParsingFlags(config).era = era; + } else { + getParsingFlags(config).invalidEra = input; + } + }); + + addRegexToken('y', matchUnsigned); + addRegexToken('yy', matchUnsigned); + addRegexToken('yyy', matchUnsigned); + addRegexToken('yyyy', matchUnsigned); + addRegexToken('yo', matchEraYearOrdinal); + + addParseToken(['y', 'yy', 'yyy', 'yyyy'], YEAR); + addParseToken(['yo'], function (input, array, config, token) { + var match; + if (config._locale._eraYearOrdinalRegex) { + match = input.match(config._locale._eraYearOrdinalRegex); + } + + if (config._locale.eraYearOrdinalParse) { + array[YEAR] = config._locale.eraYearOrdinalParse(input, match); + } else { + array[YEAR] = parseInt(input, 10); + } + }); + + function localeEras(m, format) { + var i, + l, + date, + eras = this._eras || getLocale('en')._eras; + for (i = 0, l = eras.length; i < l; ++i) { + switch (typeof eras[i].since) { + case 'string': + // truncate time + date = hooks(eras[i].since).startOf('day'); + eras[i].since = date.valueOf(); + break; + } + + switch (typeof eras[i].until) { + case 'undefined': + eras[i].until = +Infinity; + break; + case 'string': + // truncate time + date = hooks(eras[i].until).startOf('day').valueOf(); + eras[i].until = date.valueOf(); + break; + } + } + return eras; + } + + function localeErasParse(eraName, format, strict) { + var i, + l, + eras = this.eras(), + name, + abbr, + narrow; + eraName = eraName.toUpperCase(); + + for (i = 0, l = eras.length; i < l; ++i) { + name = eras[i].name.toUpperCase(); + abbr = eras[i].abbr.toUpperCase(); + narrow = eras[i].narrow.toUpperCase(); + + if (strict) { + switch (format) { + case 'N': + case 'NN': + case 'NNN': + if (abbr === eraName) { + return eras[i]; + } + break; + + case 'NNNN': + if (name === eraName) { + return eras[i]; + } + break; + + case 'NNNNN': + if (narrow === eraName) { + return eras[i]; + } + break; + } + } else if ([name, abbr, narrow].indexOf(eraName) >= 0) { + return eras[i]; + } + } + } + + function localeErasConvertYear(era, year) { + var dir = era.since <= era.until ? +1 : -1; + if (year === undefined) { + return hooks(era.since).year(); + } else { + return hooks(era.since).year() + (year - era.offset) * dir; + } + } + + function getEraName() { + var i, + l, + val, + eras = this.localeData().eras(); + for (i = 0, l = eras.length; i < l; ++i) { + // truncate time + val = this.clone().startOf('day').valueOf(); + + if (eras[i].since <= val && val <= eras[i].until) { + return eras[i].name; + } + if (eras[i].until <= val && val <= eras[i].since) { + return eras[i].name; + } + } + + return ''; + } + + function getEraNarrow() { + var i, + l, + val, + eras = this.localeData().eras(); + for (i = 0, l = eras.length; i < l; ++i) { + // truncate time + val = this.clone().startOf('day').valueOf(); + + if (eras[i].since <= val && val <= eras[i].until) { + return eras[i].narrow; + } + if (eras[i].until <= val && val <= eras[i].since) { + return eras[i].narrow; + } + } + + return ''; + } + + function getEraAbbr() { + var i, + l, + val, + eras = this.localeData().eras(); + for (i = 0, l = eras.length; i < l; ++i) { + // truncate time + val = this.clone().startOf('day').valueOf(); + + if (eras[i].since <= val && val <= eras[i].until) { + return eras[i].abbr; + } + if (eras[i].until <= val && val <= eras[i].since) { + return eras[i].abbr; + } + } + + return ''; + } + + function getEraYear() { + var i, + l, + dir, + val, + eras = this.localeData().eras(); + for (i = 0, l = eras.length; i < l; ++i) { + dir = eras[i].since <= eras[i].until ? +1 : -1; + + // truncate time + val = this.clone().startOf('day').valueOf(); + + if ( + (eras[i].since <= val && val <= eras[i].until) || + (eras[i].until <= val && val <= eras[i].since) + ) { + return ( + (this.year() - hooks(eras[i].since).year()) * dir + + eras[i].offset + ); + } + } + + return this.year(); + } + + function erasNameRegex(isStrict) { + if (!hasOwnProp(this, '_erasNameRegex')) { + computeErasParse.call(this); + } + return isStrict ? this._erasNameRegex : this._erasRegex; + } + + function erasAbbrRegex(isStrict) { + if (!hasOwnProp(this, '_erasAbbrRegex')) { + computeErasParse.call(this); + } + return isStrict ? this._erasAbbrRegex : this._erasRegex; + } + + function erasNarrowRegex(isStrict) { + if (!hasOwnProp(this, '_erasNarrowRegex')) { + computeErasParse.call(this); + } + return isStrict ? this._erasNarrowRegex : this._erasRegex; + } + + function matchEraAbbr(isStrict, locale) { + return locale.erasAbbrRegex(isStrict); + } + + function matchEraName(isStrict, locale) { + return locale.erasNameRegex(isStrict); + } + + function matchEraNarrow(isStrict, locale) { + return locale.erasNarrowRegex(isStrict); + } + + function matchEraYearOrdinal(isStrict, locale) { + return locale._eraYearOrdinalRegex || matchUnsigned; + } + + function computeErasParse() { + var abbrPieces = [], + namePieces = [], + narrowPieces = [], + mixedPieces = [], + i, + l, + eras = this.eras(); + + for (i = 0, l = eras.length; i < l; ++i) { + namePieces.push(regexEscape(eras[i].name)); + abbrPieces.push(regexEscape(eras[i].abbr)); + narrowPieces.push(regexEscape(eras[i].narrow)); + + mixedPieces.push(regexEscape(eras[i].name)); + mixedPieces.push(regexEscape(eras[i].abbr)); + mixedPieces.push(regexEscape(eras[i].narrow)); + } + + this._erasRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i'); + this._erasNameRegex = new RegExp('^(' + namePieces.join('|') + ')', 'i'); + this._erasAbbrRegex = new RegExp('^(' + abbrPieces.join('|') + ')', 'i'); + this._erasNarrowRegex = new RegExp( + '^(' + narrowPieces.join('|') + ')', + 'i' + ); + } + + // FORMATTING + + addFormatToken(0, ['gg', 2], 0, function () { + return this.weekYear() % 100; + }); + + addFormatToken(0, ['GG', 2], 0, function () { + return this.isoWeekYear() % 100; + }); + + function addWeekYearFormatToken(token, getter) { + addFormatToken(0, [token, token.length], 0, getter); + } + + addWeekYearFormatToken('gggg', 'weekYear'); + addWeekYearFormatToken('ggggg', 'weekYear'); + addWeekYearFormatToken('GGGG', 'isoWeekYear'); + addWeekYearFormatToken('GGGGG', 'isoWeekYear'); + + // ALIASES + + addUnitAlias('weekYear', 'gg'); + addUnitAlias('isoWeekYear', 'GG'); + + // PRIORITY + + addUnitPriority('weekYear', 1); + addUnitPriority('isoWeekYear', 1); + + // PARSING + + addRegexToken('G', matchSigned); + addRegexToken('g', matchSigned); + addRegexToken('GG', match1to2, match2); + addRegexToken('gg', match1to2, match2); + addRegexToken('GGGG', match1to4, match4); + addRegexToken('gggg', match1to4, match4); + addRegexToken('GGGGG', match1to6, match6); + addRegexToken('ggggg', match1to6, match6); + + addWeekParseToken(['gggg', 'ggggg', 'GGGG', 'GGGGG'], function ( + input, + week, + config, + token + ) { + week[token.substr(0, 2)] = toInt(input); + }); + + addWeekParseToken(['gg', 'GG'], function (input, week, config, token) { + week[token] = hooks.parseTwoDigitYear(input); + }); + + // MOMENTS + + function getSetWeekYear(input) { + return getSetWeekYearHelper.call( + this, + input, + this.week(), + this.weekday(), + this.localeData()._week.dow, + this.localeData()._week.doy + ); + } + + function getSetISOWeekYear(input) { + return getSetWeekYearHelper.call( + this, + input, + this.isoWeek(), + this.isoWeekday(), + 1, + 4 + ); + } + + function getISOWeeksInYear() { + return weeksInYear(this.year(), 1, 4); + } + + function getISOWeeksInISOWeekYear() { + return weeksInYear(this.isoWeekYear(), 1, 4); + } + + function getWeeksInYear() { + var weekInfo = this.localeData()._week; + return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy); + } + + function getWeeksInWeekYear() { + var weekInfo = this.localeData()._week; + return weeksInYear(this.weekYear(), weekInfo.dow, weekInfo.doy); + } + + function getSetWeekYearHelper(input, week, weekday, dow, doy) { + var weeksTarget; + if (input == null) { + return weekOfYear(this, dow, doy).year; + } else { + weeksTarget = weeksInYear(input, dow, doy); + if (week > weeksTarget) { + week = weeksTarget; + } + return setWeekAll.call(this, input, week, weekday, dow, doy); + } + } + + function setWeekAll(weekYear, week, weekday, dow, doy) { + var dayOfYearData = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy), + date = createUTCDate(dayOfYearData.year, 0, dayOfYearData.dayOfYear); + + this.year(date.getUTCFullYear()); + this.month(date.getUTCMonth()); + this.date(date.getUTCDate()); + return this; + } + + // FORMATTING + + addFormatToken('Q', 0, 'Qo', 'quarter'); + + // ALIASES + + addUnitAlias('quarter', 'Q'); + + // PRIORITY + + addUnitPriority('quarter', 7); + + // PARSING + + addRegexToken('Q', match1); + addParseToken('Q', function (input, array) { + array[MONTH] = (toInt(input) - 1) * 3; + }); + + // MOMENTS + + function getSetQuarter(input) { + return input == null + ? Math.ceil((this.month() + 1) / 3) + : this.month((input - 1) * 3 + (this.month() % 3)); + } + + // FORMATTING + + addFormatToken('D', ['DD', 2], 'Do', 'date'); + + // ALIASES + + addUnitAlias('date', 'D'); + + // PRIORITY + addUnitPriority('date', 9); + + // PARSING + + addRegexToken('D', match1to2); + addRegexToken('DD', match1to2, match2); + addRegexToken('Do', function (isStrict, locale) { + // TODO: Remove "ordinalParse" fallback in next major release. + return isStrict + ? locale._dayOfMonthOrdinalParse || locale._ordinalParse + : locale._dayOfMonthOrdinalParseLenient; + }); + + addParseToken(['D', 'DD'], DATE); + addParseToken('Do', function (input, array) { + array[DATE] = toInt(input.match(match1to2)[0]); + }); + + // MOMENTS + + var getSetDayOfMonth = makeGetSet('Date', true); + + // FORMATTING + + addFormatToken('DDD', ['DDDD', 3], 'DDDo', 'dayOfYear'); + + // ALIASES + + addUnitAlias('dayOfYear', 'DDD'); + + // PRIORITY + addUnitPriority('dayOfYear', 4); + + // PARSING + + addRegexToken('DDD', match1to3); + addRegexToken('DDDD', match3); + addParseToken(['DDD', 'DDDD'], function (input, array, config) { + config._dayOfYear = toInt(input); + }); + + // HELPERS + + // MOMENTS + + function getSetDayOfYear(input) { + var dayOfYear = + Math.round( + (this.clone().startOf('day') - this.clone().startOf('year')) / 864e5 + ) + 1; + return input == null ? dayOfYear : this.add(input - dayOfYear, 'd'); + } + + // FORMATTING + + addFormatToken('m', ['mm', 2], 0, 'minute'); + + // ALIASES + + addUnitAlias('minute', 'm'); + + // PRIORITY + + addUnitPriority('minute', 14); + + // PARSING + + addRegexToken('m', match1to2); + addRegexToken('mm', match1to2, match2); + addParseToken(['m', 'mm'], MINUTE); + + // MOMENTS + + var getSetMinute = makeGetSet('Minutes', false); + + // FORMATTING + + addFormatToken('s', ['ss', 2], 0, 'second'); + + // ALIASES + + addUnitAlias('second', 's'); + + // PRIORITY + + addUnitPriority('second', 15); + + // PARSING + + addRegexToken('s', match1to2); + addRegexToken('ss', match1to2, match2); + addParseToken(['s', 'ss'], SECOND); + + // MOMENTS + + var getSetSecond = makeGetSet('Seconds', false); + + // FORMATTING + + addFormatToken('S', 0, 0, function () { + return ~~(this.millisecond() / 100); + }); + + addFormatToken(0, ['SS', 2], 0, function () { + return ~~(this.millisecond() / 10); + }); + + addFormatToken(0, ['SSS', 3], 0, 'millisecond'); + addFormatToken(0, ['SSSS', 4], 0, function () { + return this.millisecond() * 10; + }); + addFormatToken(0, ['SSSSS', 5], 0, function () { + return this.millisecond() * 100; + }); + addFormatToken(0, ['SSSSSS', 6], 0, function () { + return this.millisecond() * 1000; + }); + addFormatToken(0, ['SSSSSSS', 7], 0, function () { + return this.millisecond() * 10000; + }); + addFormatToken(0, ['SSSSSSSS', 8], 0, function () { + return this.millisecond() * 100000; + }); + addFormatToken(0, ['SSSSSSSSS', 9], 0, function () { + return this.millisecond() * 1000000; + }); + + // ALIASES + + addUnitAlias('millisecond', 'ms'); + + // PRIORITY + + addUnitPriority('millisecond', 16); + + // PARSING + + addRegexToken('S', match1to3, match1); + addRegexToken('SS', match1to3, match2); + addRegexToken('SSS', match1to3, match3); + + var token, getSetMillisecond; + for (token = 'SSSS'; token.length <= 9; token += 'S') { + addRegexToken(token, matchUnsigned); + } + + function parseMs(input, array) { + array[MILLISECOND] = toInt(('0.' + input) * 1000); + } + + for (token = 'S'; token.length <= 9; token += 'S') { + addParseToken(token, parseMs); + } + + getSetMillisecond = makeGetSet('Milliseconds', false); + + // FORMATTING + + addFormatToken('z', 0, 0, 'zoneAbbr'); + addFormatToken('zz', 0, 0, 'zoneName'); + + // MOMENTS + + function getZoneAbbr() { + return this._isUTC ? 'UTC' : ''; + } + + function getZoneName() { + return this._isUTC ? 'Coordinated Universal Time' : ''; + } + + var proto = Moment.prototype; + + proto.add = add; + proto.calendar = calendar$1; + proto.clone = clone; + proto.diff = diff; + proto.endOf = endOf; + proto.format = format; + proto.from = from; + proto.fromNow = fromNow; + proto.to = to; + proto.toNow = toNow; + proto.get = stringGet; + proto.invalidAt = invalidAt; + proto.isAfter = isAfter; + proto.isBefore = isBefore; + proto.isBetween = isBetween; + proto.isSame = isSame; + proto.isSameOrAfter = isSameOrAfter; + proto.isSameOrBefore = isSameOrBefore; + proto.isValid = isValid$2; + proto.lang = lang; + proto.locale = locale; + proto.localeData = localeData; + proto.max = prototypeMax; + proto.min = prototypeMin; + proto.parsingFlags = parsingFlags; + proto.set = stringSet; + proto.startOf = startOf; + proto.subtract = subtract; + proto.toArray = toArray; + proto.toObject = toObject; + proto.toDate = toDate; + proto.toISOString = toISOString; + proto.inspect = inspect; + if (typeof Symbol !== 'undefined' && Symbol.for != null) { + proto[Symbol.for('nodejs.util.inspect.custom')] = function () { + return 'Moment<' + this.format() + '>'; + }; + } + proto.toJSON = toJSON; + proto.toString = toString; + proto.unix = unix; + proto.valueOf = valueOf; + proto.creationData = creationData; + proto.eraName = getEraName; + proto.eraNarrow = getEraNarrow; + proto.eraAbbr = getEraAbbr; + proto.eraYear = getEraYear; + proto.year = getSetYear; + proto.isLeapYear = getIsLeapYear; + proto.weekYear = getSetWeekYear; + proto.isoWeekYear = getSetISOWeekYear; + proto.quarter = proto.quarters = getSetQuarter; + proto.month = getSetMonth; + proto.daysInMonth = getDaysInMonth; + proto.week = proto.weeks = getSetWeek; + proto.isoWeek = proto.isoWeeks = getSetISOWeek; + proto.weeksInYear = getWeeksInYear; + proto.weeksInWeekYear = getWeeksInWeekYear; + proto.isoWeeksInYear = getISOWeeksInYear; + proto.isoWeeksInISOWeekYear = getISOWeeksInISOWeekYear; + proto.date = getSetDayOfMonth; + proto.day = proto.days = getSetDayOfWeek; + proto.weekday = getSetLocaleDayOfWeek; + proto.isoWeekday = getSetISODayOfWeek; + proto.dayOfYear = getSetDayOfYear; + proto.hour = proto.hours = getSetHour; + proto.minute = proto.minutes = getSetMinute; + proto.second = proto.seconds = getSetSecond; + proto.millisecond = proto.milliseconds = getSetMillisecond; + proto.utcOffset = getSetOffset; + proto.utc = setOffsetToUTC; + proto.local = setOffsetToLocal; + proto.parseZone = setOffsetToParsedOffset; + proto.hasAlignedHourOffset = hasAlignedHourOffset; + proto.isDST = isDaylightSavingTime; + proto.isLocal = isLocal; + proto.isUtcOffset = isUtcOffset; + proto.isUtc = isUtc; + proto.isUTC = isUtc; + proto.zoneAbbr = getZoneAbbr; + proto.zoneName = getZoneName; + proto.dates = deprecate( + 'dates accessor is deprecated. Use date instead.', + getSetDayOfMonth + ); + proto.months = deprecate( + 'months accessor is deprecated. Use month instead', + getSetMonth + ); + proto.years = deprecate( + 'years accessor is deprecated. Use year instead', + getSetYear + ); + proto.zone = deprecate( + 'moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/', + getSetZone + ); + proto.isDSTShifted = deprecate( + 'isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information', + isDaylightSavingTimeShifted + ); + + function createUnix(input) { + return createLocal(input * 1000); + } + + function createInZone() { + return createLocal.apply(null, arguments).parseZone(); + } + + function preParsePostFormat(string) { + return string; + } + + var proto$1 = Locale.prototype; + + proto$1.calendar = calendar; + proto$1.longDateFormat = longDateFormat; + proto$1.invalidDate = invalidDate; + proto$1.ordinal = ordinal; + proto$1.preparse = preParsePostFormat; + proto$1.postformat = preParsePostFormat; + proto$1.relativeTime = relativeTime; + proto$1.pastFuture = pastFuture; + proto$1.set = set; + proto$1.eras = localeEras; + proto$1.erasParse = localeErasParse; + proto$1.erasConvertYear = localeErasConvertYear; + proto$1.erasAbbrRegex = erasAbbrRegex; + proto$1.erasNameRegex = erasNameRegex; + proto$1.erasNarrowRegex = erasNarrowRegex; + + proto$1.months = localeMonths; + proto$1.monthsShort = localeMonthsShort; + proto$1.monthsParse = localeMonthsParse; + proto$1.monthsRegex = monthsRegex; + proto$1.monthsShortRegex = monthsShortRegex; + proto$1.week = localeWeek; + proto$1.firstDayOfYear = localeFirstDayOfYear; + proto$1.firstDayOfWeek = localeFirstDayOfWeek; + + proto$1.weekdays = localeWeekdays; + proto$1.weekdaysMin = localeWeekdaysMin; + proto$1.weekdaysShort = localeWeekdaysShort; + proto$1.weekdaysParse = localeWeekdaysParse; + + proto$1.weekdaysRegex = weekdaysRegex; + proto$1.weekdaysShortRegex = weekdaysShortRegex; + proto$1.weekdaysMinRegex = weekdaysMinRegex; + + proto$1.isPM = localeIsPM; + proto$1.meridiem = localeMeridiem; + + function get$1(format, index, field, setter) { + var locale = getLocale(), + utc = createUTC().set(setter, index); + return locale[field](utc, format); + } + + function listMonthsImpl(format, index, field) { + if (isNumber(format)) { + index = format; + format = undefined; + } + + format = format || ''; + + if (index != null) { + return get$1(format, index, field, 'month'); + } + + var i, + out = []; + for (i = 0; i < 12; i++) { + out[i] = get$1(format, i, field, 'month'); + } + return out; + } + + // () + // (5) + // (fmt, 5) + // (fmt) + // (true) + // (true, 5) + // (true, fmt, 5) + // (true, fmt) + function listWeekdaysImpl(localeSorted, format, index, field) { + if (typeof localeSorted === 'boolean') { + if (isNumber(format)) { + index = format; + format = undefined; + } + + format = format || ''; + } else { + format = localeSorted; + index = format; + localeSorted = false; + + if (isNumber(format)) { + index = format; + format = undefined; + } + + format = format || ''; + } + + var locale = getLocale(), + shift = localeSorted ? locale._week.dow : 0, + i, + out = []; + + if (index != null) { + return get$1(format, (index + shift) % 7, field, 'day'); + } + + for (i = 0; i < 7; i++) { + out[i] = get$1(format, (i + shift) % 7, field, 'day'); + } + return out; + } + + function listMonths(format, index) { + return listMonthsImpl(format, index, 'months'); + } + + function listMonthsShort(format, index) { + return listMonthsImpl(format, index, 'monthsShort'); + } + + function listWeekdays(localeSorted, format, index) { + return listWeekdaysImpl(localeSorted, format, index, 'weekdays'); + } + + function listWeekdaysShort(localeSorted, format, index) { + return listWeekdaysImpl(localeSorted, format, index, 'weekdaysShort'); + } + + function listWeekdaysMin(localeSorted, format, index) { + return listWeekdaysImpl(localeSorted, format, index, 'weekdaysMin'); + } + + getSetGlobalLocale('en', { + eras: [ + { + since: '0001-01-01', + until: +Infinity, + offset: 1, + name: 'Anno Domini', + narrow: 'AD', + abbr: 'AD', + }, + { + since: '0000-12-31', + until: -Infinity, + offset: 1, + name: 'Before Christ', + narrow: 'BC', + abbr: 'BC', + }, + ], + dayOfMonthOrdinalParse: /\d{1,2}(th|st|nd|rd)/, + ordinal: function (number) { + var b = number % 10, + output = + toInt((number % 100) / 10) === 1 + ? 'th' + : b === 1 + ? 'st' + : b === 2 + ? 'nd' + : b === 3 + ? 'rd' + : 'th'; + return number + output; + }, + }); + + // Side effect imports + + hooks.lang = deprecate( + 'moment.lang is deprecated. Use moment.locale instead.', + getSetGlobalLocale + ); + hooks.langData = deprecate( + 'moment.langData is deprecated. Use moment.localeData instead.', + getLocale + ); + + var mathAbs = Math.abs; + + function abs() { + var data = this._data; + + this._milliseconds = mathAbs(this._milliseconds); + this._days = mathAbs(this._days); + this._months = mathAbs(this._months); + + data.milliseconds = mathAbs(data.milliseconds); + data.seconds = mathAbs(data.seconds); + data.minutes = mathAbs(data.minutes); + data.hours = mathAbs(data.hours); + data.months = mathAbs(data.months); + data.years = mathAbs(data.years); + + return this; + } + + function addSubtract$1(duration, input, value, direction) { + var other = createDuration(input, value); + + duration._milliseconds += direction * other._milliseconds; + duration._days += direction * other._days; + duration._months += direction * other._months; + + return duration._bubble(); + } + + // supports only 2.0-style add(1, 's') or add(duration) + function add$1(input, value) { + return addSubtract$1(this, input, value, 1); + } + + // supports only 2.0-style subtract(1, 's') or subtract(duration) + function subtract$1(input, value) { + return addSubtract$1(this, input, value, -1); + } + + function absCeil(number) { + if (number < 0) { + return Math.floor(number); + } else { + return Math.ceil(number); + } + } + + function bubble() { + var milliseconds = this._milliseconds, + days = this._days, + months = this._months, + data = this._data, + seconds, + minutes, + hours, + years, + monthsFromDays; + + // if we have a mix of positive and negative values, bubble down first + // check: https://github.com/moment/moment/issues/2166 + if ( + !( + (milliseconds >= 0 && days >= 0 && months >= 0) || + (milliseconds <= 0 && days <= 0 && months <= 0) + ) + ) { + milliseconds += absCeil(monthsToDays(months) + days) * 864e5; + days = 0; + months = 0; + } + + // The following code bubbles up values, see the tests for + // examples of what that means. + data.milliseconds = milliseconds % 1000; + + seconds = absFloor(milliseconds / 1000); + data.seconds = seconds % 60; + + minutes = absFloor(seconds / 60); + data.minutes = minutes % 60; + + hours = absFloor(minutes / 60); + data.hours = hours % 24; + + days += absFloor(hours / 24); + + // convert days to months + monthsFromDays = absFloor(daysToMonths(days)); + months += monthsFromDays; + days -= absCeil(monthsToDays(monthsFromDays)); + + // 12 months -> 1 year + years = absFloor(months / 12); + months %= 12; + + data.days = days; + data.months = months; + data.years = years; + + return this; + } + + function daysToMonths(days) { + // 400 years have 146097 days (taking into account leap year rules) + // 400 years have 12 months === 4800 + return (days * 4800) / 146097; + } + + function monthsToDays(months) { + // the reverse of daysToMonths + return (months * 146097) / 4800; + } + + function as(units) { + if (!this.isValid()) { + return NaN; + } + var days, + months, + milliseconds = this._milliseconds; + + units = normalizeUnits(units); + + if (units === 'month' || units === 'quarter' || units === 'year') { + days = this._days + milliseconds / 864e5; + months = this._months + daysToMonths(days); + switch (units) { + case 'month': + return months; + case 'quarter': + return months / 3; + case 'year': + return months / 12; + } + } else { + // handle milliseconds separately because of floating point math errors (issue #1867) + days = this._days + Math.round(monthsToDays(this._months)); + switch (units) { + case 'week': + return days / 7 + milliseconds / 6048e5; + case 'day': + return days + milliseconds / 864e5; + case 'hour': + return days * 24 + milliseconds / 36e5; + case 'minute': + return days * 1440 + milliseconds / 6e4; + case 'second': + return days * 86400 + milliseconds / 1000; + // Math.floor prevents floating point math errors here + case 'millisecond': + return Math.floor(days * 864e5) + milliseconds; + default: + throw new Error('Unknown unit ' + units); + } + } + } + + // TODO: Use this.as('ms')? + function valueOf$1() { + if (!this.isValid()) { + return NaN; + } + return ( + this._milliseconds + + this._days * 864e5 + + (this._months % 12) * 2592e6 + + toInt(this._months / 12) * 31536e6 + ); + } + + function makeAs(alias) { + return function () { + return this.as(alias); + }; + } + + var asMilliseconds = makeAs('ms'), + asSeconds = makeAs('s'), + asMinutes = makeAs('m'), + asHours = makeAs('h'), + asDays = makeAs('d'), + asWeeks = makeAs('w'), + asMonths = makeAs('M'), + asQuarters = makeAs('Q'), + asYears = makeAs('y'); + + function clone$1() { + return createDuration(this); + } + + function get$2(units) { + units = normalizeUnits(units); + return this.isValid() ? this[units + 's']() : NaN; + } + + function makeGetter(name) { + return function () { + return this.isValid() ? this._data[name] : NaN; + }; + } + + var milliseconds = makeGetter('milliseconds'), + seconds = makeGetter('seconds'), + minutes = makeGetter('minutes'), + hours = makeGetter('hours'), + days = makeGetter('days'), + months = makeGetter('months'), + years = makeGetter('years'); + + function weeks() { + return absFloor(this.days() / 7); + } + + var round = Math.round, + thresholds = { + ss: 44, // a few seconds to seconds + s: 45, // seconds to minute + m: 45, // minutes to hour + h: 22, // hours to day + d: 26, // days to month/week + w: null, // weeks to month + M: 11, // months to year + }; + + // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize + function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) { + return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture); + } + + function relativeTime$1(posNegDuration, withoutSuffix, thresholds, locale) { + var duration = createDuration(posNegDuration).abs(), + seconds = round(duration.as('s')), + minutes = round(duration.as('m')), + hours = round(duration.as('h')), + days = round(duration.as('d')), + months = round(duration.as('M')), + weeks = round(duration.as('w')), + years = round(duration.as('y')), + a = + (seconds <= thresholds.ss && ['s', seconds]) || + (seconds < thresholds.s && ['ss', seconds]) || + (minutes <= 1 && ['m']) || + (minutes < thresholds.m && ['mm', minutes]) || + (hours <= 1 && ['h']) || + (hours < thresholds.h && ['hh', hours]) || + (days <= 1 && ['d']) || + (days < thresholds.d && ['dd', days]); + + if (thresholds.w != null) { + a = + a || + (weeks <= 1 && ['w']) || + (weeks < thresholds.w && ['ww', weeks]); + } + a = a || + (months <= 1 && ['M']) || + (months < thresholds.M && ['MM', months]) || + (years <= 1 && ['y']) || ['yy', years]; + + a[2] = withoutSuffix; + a[3] = +posNegDuration > 0; + a[4] = locale; + return substituteTimeAgo.apply(null, a); + } + + // This function allows you to set the rounding function for relative time strings + function getSetRelativeTimeRounding(roundingFunction) { + if (roundingFunction === undefined) { + return round; + } + if (typeof roundingFunction === 'function') { + round = roundingFunction; + return true; + } + return false; + } + + // This function allows you to set a threshold for relative time strings + function getSetRelativeTimeThreshold(threshold, limit) { + if (thresholds[threshold] === undefined) { + return false; + } + if (limit === undefined) { + return thresholds[threshold]; + } + thresholds[threshold] = limit; + if (threshold === 's') { + thresholds.ss = limit - 1; + } + return true; + } + + function humanize(argWithSuffix, argThresholds) { + if (!this.isValid()) { + return this.localeData().invalidDate(); + } + + var withSuffix = false, + th = thresholds, + locale, + output; + + if (typeof argWithSuffix === 'object') { + argThresholds = argWithSuffix; + argWithSuffix = false; + } + if (typeof argWithSuffix === 'boolean') { + withSuffix = argWithSuffix; + } + if (typeof argThresholds === 'object') { + th = Object.assign({}, thresholds, argThresholds); + if (argThresholds.s != null && argThresholds.ss == null) { + th.ss = argThresholds.s - 1; + } + } + + locale = this.localeData(); + output = relativeTime$1(this, !withSuffix, th, locale); + + if (withSuffix) { + output = locale.pastFuture(+this, output); + } + + return locale.postformat(output); + } + + var abs$1 = Math.abs; + + function sign(x) { + return (x > 0) - (x < 0) || +x; + } + + function toISOString$1() { + // for ISO strings we do not use the normal bubbling rules: + // * milliseconds bubble up until they become hours + // * days do not bubble at all + // * months bubble up until they become years + // This is because there is no context-free conversion between hours and days + // (think of clock changes) + // and also not between days and months (28-31 days per month) + if (!this.isValid()) { + return this.localeData().invalidDate(); + } + + var seconds = abs$1(this._milliseconds) / 1000, + days = abs$1(this._days), + months = abs$1(this._months), + minutes, + hours, + years, + s, + total = this.asSeconds(), + totalSign, + ymSign, + daysSign, + hmsSign; + + if (!total) { + // this is the same as C#'s (Noda) and python (isodate)... + // but not other JS (goog.date) + return 'P0D'; + } + + // 3600 seconds -> 60 minutes -> 1 hour + minutes = absFloor(seconds / 60); + hours = absFloor(minutes / 60); + seconds %= 60; + minutes %= 60; + + // 12 months -> 1 year + years = absFloor(months / 12); + months %= 12; + + // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js + s = seconds ? seconds.toFixed(3).replace(/\.?0+$/, '') : ''; + + totalSign = total < 0 ? '-' : ''; + ymSign = sign(this._months) !== sign(total) ? '-' : ''; + daysSign = sign(this._days) !== sign(total) ? '-' : ''; + hmsSign = sign(this._milliseconds) !== sign(total) ? '-' : ''; + + return ( + totalSign + + 'P' + + (years ? ymSign + years + 'Y' : '') + + (months ? ymSign + months + 'M' : '') + + (days ? daysSign + days + 'D' : '') + + (hours || minutes || seconds ? 'T' : '') + + (hours ? hmsSign + hours + 'H' : '') + + (minutes ? hmsSign + minutes + 'M' : '') + + (seconds ? hmsSign + s + 'S' : '') + ); + } + + var proto$2 = Duration.prototype; + + proto$2.isValid = isValid$1; + proto$2.abs = abs; + proto$2.add = add$1; + proto$2.subtract = subtract$1; + proto$2.as = as; + proto$2.asMilliseconds = asMilliseconds; + proto$2.asSeconds = asSeconds; + proto$2.asMinutes = asMinutes; + proto$2.asHours = asHours; + proto$2.asDays = asDays; + proto$2.asWeeks = asWeeks; + proto$2.asMonths = asMonths; + proto$2.asQuarters = asQuarters; + proto$2.asYears = asYears; + proto$2.valueOf = valueOf$1; + proto$2._bubble = bubble; + proto$2.clone = clone$1; + proto$2.get = get$2; + proto$2.milliseconds = milliseconds; + proto$2.seconds = seconds; + proto$2.minutes = minutes; + proto$2.hours = hours; + proto$2.days = days; + proto$2.weeks = weeks; + proto$2.months = months; + proto$2.years = years; + proto$2.humanize = humanize; + proto$2.toISOString = toISOString$1; + proto$2.toString = toISOString$1; + proto$2.toJSON = toISOString$1; + proto$2.locale = locale; + proto$2.localeData = localeData; + + proto$2.toIsoString = deprecate( + 'toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)', + toISOString$1 + ); + proto$2.lang = lang; + + // FORMATTING + + addFormatToken('X', 0, 0, 'unix'); + addFormatToken('x', 0, 0, 'valueOf'); + + // PARSING + + addRegexToken('x', matchSigned); + addRegexToken('X', matchTimestamp); + addParseToken('X', function (input, array, config) { + config._d = new Date(parseFloat(input) * 1000); + }); + addParseToken('x', function (input, array, config) { + config._d = new Date(toInt(input)); + }); + + //! moment.js + + hooks.version = '2.29.1'; + + setHookCallback(createLocal); + + hooks.fn = proto; + hooks.min = min; + hooks.max = max; + hooks.now = now; + hooks.utc = createUTC; + hooks.unix = createUnix; + hooks.months = listMonths; + hooks.isDate = isDate; + hooks.locale = getSetGlobalLocale; + hooks.invalid = createInvalid; + hooks.duration = createDuration; + hooks.isMoment = isMoment; + hooks.weekdays = listWeekdays; + hooks.parseZone = createInZone; + hooks.localeData = getLocale; + hooks.isDuration = isDuration; + hooks.monthsShort = listMonthsShort; + hooks.weekdaysMin = listWeekdaysMin; + hooks.defineLocale = defineLocale; + hooks.updateLocale = updateLocale; + hooks.locales = listLocales; + hooks.weekdaysShort = listWeekdaysShort; + hooks.normalizeUnits = normalizeUnits; + hooks.relativeTimeRounding = getSetRelativeTimeRounding; + hooks.relativeTimeThreshold = getSetRelativeTimeThreshold; + hooks.calendarFormat = getCalendarFormat; + hooks.prototype = proto; + + // currently HTML5 input type only supports 24-hour formats + hooks.HTML5_FMT = { + DATETIME_LOCAL: 'YYYY-MM-DDTHH:mm', // + DATETIME_LOCAL_SECONDS: 'YYYY-MM-DDTHH:mm:ss', // + DATETIME_LOCAL_MS: 'YYYY-MM-DDTHH:mm:ss.SSS', // + DATE: 'YYYY-MM-DD', // + TIME: 'HH:mm', // + TIME_SECONDS: 'HH:mm:ss', // + TIME_MS: 'HH:mm:ss.SSS', // + WEEK: 'GGGG-[W]WW', // + MONTH: 'YYYY-MM', // + }; + + return hooks; + +}))); From c55b9c241facb38343082d7c9097b6d5bba58875 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sun, 21 Nov 2021 21:09:31 -0700 Subject: [PATCH 057/169] Remove moment.js in favor of Temporal --- package-lock.json | 14 - package.json | 1 - public/scripts/moment.js | 5670 -------------------------------------- 3 files changed, 5685 deletions(-) delete mode 100644 public/scripts/moment.js diff --git a/package-lock.json b/package-lock.json index 821a54c..225db7b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,6 @@ "dotenv": "^10.0.0", "express": "~4.16.0", "http-errors": "~1.6.2", - "moment": "^2.29.1", "morgan": "~1.9.0", "nodemailer": "^6.6.5", "passport": "^0.5.0", @@ -910,14 +909,6 @@ "ms": "2.0.0" } }, - "node_modules/moment": { - "version": "2.29.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", - "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==", - "engines": { - "node": "*" - } - }, "node_modules/morgan": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.0.tgz", @@ -2409,11 +2400,6 @@ } } }, - "moment": { - "version": "2.29.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", - "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==" - }, "morgan": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.0.tgz", diff --git a/package.json b/package.json index 2e3323a..c94c979 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,6 @@ "dotenv": "^10.0.0", "express": "~4.16.0", "http-errors": "~1.6.2", - "moment": "^2.29.1", "morgan": "~1.9.0", "nodemailer": "^6.6.5", "passport": "^0.5.0", diff --git a/public/scripts/moment.js b/public/scripts/moment.js deleted file mode 100644 index 1484d6c..0000000 --- a/public/scripts/moment.js +++ /dev/null @@ -1,5670 +0,0 @@ -//! moment.js -//! version : 2.29.1 -//! authors : Tim Wood, Iskren Chernev, Moment.js contributors -//! license : MIT -//! momentjs.com - -;(function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : - typeof define === 'function' && define.amd ? define(factory) : - global.moment = factory() -}(this, (function () { 'use strict'; - - var hookCallback; - - function hooks() { - return hookCallback.apply(null, arguments); - } - - // This is done to register the method called with moment() - // without creating circular dependencies. - function setHookCallback(callback) { - hookCallback = callback; - } - - function isArray(input) { - return ( - input instanceof Array || - Object.prototype.toString.call(input) === '[object Array]' - ); - } - - function isObject(input) { - // IE8 will treat undefined and null as object if it wasn't for - // input != null - return ( - input != null && - Object.prototype.toString.call(input) === '[object Object]' - ); - } - - function hasOwnProp(a, b) { - return Object.prototype.hasOwnProperty.call(a, b); - } - - function isObjectEmpty(obj) { - if (Object.getOwnPropertyNames) { - return Object.getOwnPropertyNames(obj).length === 0; - } else { - var k; - for (k in obj) { - if (hasOwnProp(obj, k)) { - return false; - } - } - return true; - } - } - - function isUndefined(input) { - return input === void 0; - } - - function isNumber(input) { - return ( - typeof input === 'number' || - Object.prototype.toString.call(input) === '[object Number]' - ); - } - - function isDate(input) { - return ( - input instanceof Date || - Object.prototype.toString.call(input) === '[object Date]' - ); - } - - function map(arr, fn) { - var res = [], - i; - for (i = 0; i < arr.length; ++i) { - res.push(fn(arr[i], i)); - } - return res; - } - - function extend(a, b) { - for (var i in b) { - if (hasOwnProp(b, i)) { - a[i] = b[i]; - } - } - - if (hasOwnProp(b, 'toString')) { - a.toString = b.toString; - } - - if (hasOwnProp(b, 'valueOf')) { - a.valueOf = b.valueOf; - } - - return a; - } - - function createUTC(input, format, locale, strict) { - return createLocalOrUTC(input, format, locale, strict, true).utc(); - } - - function defaultParsingFlags() { - // We need to deep clone this object. - return { - empty: false, - unusedTokens: [], - unusedInput: [], - overflow: -2, - charsLeftOver: 0, - nullInput: false, - invalidEra: null, - invalidMonth: null, - invalidFormat: false, - userInvalidated: false, - iso: false, - parsedDateParts: [], - era: null, - meridiem: null, - rfc2822: false, - weekdayMismatch: false, - }; - } - - function getParsingFlags(m) { - if (m._pf == null) { - m._pf = defaultParsingFlags(); - } - return m._pf; - } - - var some; - if (Array.prototype.some) { - some = Array.prototype.some; - } else { - some = function (fun) { - var t = Object(this), - len = t.length >>> 0, - i; - - for (i = 0; i < len; i++) { - if (i in t && fun.call(this, t[i], i, t)) { - return true; - } - } - - return false; - }; - } - - function isValid(m) { - if (m._isValid == null) { - var flags = getParsingFlags(m), - parsedParts = some.call(flags.parsedDateParts, function (i) { - return i != null; - }), - isNowValid = - !isNaN(m._d.getTime()) && - flags.overflow < 0 && - !flags.empty && - !flags.invalidEra && - !flags.invalidMonth && - !flags.invalidWeekday && - !flags.weekdayMismatch && - !flags.nullInput && - !flags.invalidFormat && - !flags.userInvalidated && - (!flags.meridiem || (flags.meridiem && parsedParts)); - - if (m._strict) { - isNowValid = - isNowValid && - flags.charsLeftOver === 0 && - flags.unusedTokens.length === 0 && - flags.bigHour === undefined; - } - - if (Object.isFrozen == null || !Object.isFrozen(m)) { - m._isValid = isNowValid; - } else { - return isNowValid; - } - } - return m._isValid; - } - - function createInvalid(flags) { - var m = createUTC(NaN); - if (flags != null) { - extend(getParsingFlags(m), flags); - } else { - getParsingFlags(m).userInvalidated = true; - } - - return m; - } - - // Plugins that add properties should also add the key here (null value), - // so we can properly clone ourselves. - var momentProperties = (hooks.momentProperties = []), - updateInProgress = false; - - function copyConfig(to, from) { - var i, prop, val; - - if (!isUndefined(from._isAMomentObject)) { - to._isAMomentObject = from._isAMomentObject; - } - if (!isUndefined(from._i)) { - to._i = from._i; - } - if (!isUndefined(from._f)) { - to._f = from._f; - } - if (!isUndefined(from._l)) { - to._l = from._l; - } - if (!isUndefined(from._strict)) { - to._strict = from._strict; - } - if (!isUndefined(from._tzm)) { - to._tzm = from._tzm; - } - if (!isUndefined(from._isUTC)) { - to._isUTC = from._isUTC; - } - if (!isUndefined(from._offset)) { - to._offset = from._offset; - } - if (!isUndefined(from._pf)) { - to._pf = getParsingFlags(from); - } - if (!isUndefined(from._locale)) { - to._locale = from._locale; - } - - if (momentProperties.length > 0) { - for (i = 0; i < momentProperties.length; i++) { - prop = momentProperties[i]; - val = from[prop]; - if (!isUndefined(val)) { - to[prop] = val; - } - } - } - - return to; - } - - // Moment prototype object - function Moment(config) { - copyConfig(this, config); - this._d = new Date(config._d != null ? config._d.getTime() : NaN); - if (!this.isValid()) { - this._d = new Date(NaN); - } - // Prevent infinite loop in case updateOffset creates new moment - // objects. - if (updateInProgress === false) { - updateInProgress = true; - hooks.updateOffset(this); - updateInProgress = false; - } - } - - function isMoment(obj) { - return ( - obj instanceof Moment || (obj != null && obj._isAMomentObject != null) - ); - } - - function warn(msg) { - if ( - hooks.suppressDeprecationWarnings === false && - typeof console !== 'undefined' && - console.warn - ) { - console.warn('Deprecation warning: ' + msg); - } - } - - function deprecate(msg, fn) { - var firstTime = true; - - return extend(function () { - if (hooks.deprecationHandler != null) { - hooks.deprecationHandler(null, msg); - } - if (firstTime) { - var args = [], - arg, - i, - key; - for (i = 0; i < arguments.length; i++) { - arg = ''; - if (typeof arguments[i] === 'object') { - arg += '\n[' + i + '] '; - for (key in arguments[0]) { - if (hasOwnProp(arguments[0], key)) { - arg += key + ': ' + arguments[0][key] + ', '; - } - } - arg = arg.slice(0, -2); // Remove trailing comma and space - } else { - arg = arguments[i]; - } - args.push(arg); - } - warn( - msg + - '\nArguments: ' + - Array.prototype.slice.call(args).join('') + - '\n' + - new Error().stack - ); - firstTime = false; - } - return fn.apply(this, arguments); - }, fn); - } - - var deprecations = {}; - - function deprecateSimple(name, msg) { - if (hooks.deprecationHandler != null) { - hooks.deprecationHandler(name, msg); - } - if (!deprecations[name]) { - warn(msg); - deprecations[name] = true; - } - } - - hooks.suppressDeprecationWarnings = false; - hooks.deprecationHandler = null; - - function isFunction(input) { - return ( - (typeof Function !== 'undefined' && input instanceof Function) || - Object.prototype.toString.call(input) === '[object Function]' - ); - } - - function set(config) { - var prop, i; - for (i in config) { - if (hasOwnProp(config, i)) { - prop = config[i]; - if (isFunction(prop)) { - this[i] = prop; - } else { - this['_' + i] = prop; - } - } - } - this._config = config; - // Lenient ordinal parsing accepts just a number in addition to - // number + (possibly) stuff coming from _dayOfMonthOrdinalParse. - // TODO: Remove "ordinalParse" fallback in next major release. - this._dayOfMonthOrdinalParseLenient = new RegExp( - (this._dayOfMonthOrdinalParse.source || this._ordinalParse.source) + - '|' + - /\d{1,2}/.source - ); - } - - function mergeConfigs(parentConfig, childConfig) { - var res = extend({}, parentConfig), - prop; - for (prop in childConfig) { - if (hasOwnProp(childConfig, prop)) { - if (isObject(parentConfig[prop]) && isObject(childConfig[prop])) { - res[prop] = {}; - extend(res[prop], parentConfig[prop]); - extend(res[prop], childConfig[prop]); - } else if (childConfig[prop] != null) { - res[prop] = childConfig[prop]; - } else { - delete res[prop]; - } - } - } - for (prop in parentConfig) { - if ( - hasOwnProp(parentConfig, prop) && - !hasOwnProp(childConfig, prop) && - isObject(parentConfig[prop]) - ) { - // make sure changes to properties don't modify parent config - res[prop] = extend({}, res[prop]); - } - } - return res; - } - - function Locale(config) { - if (config != null) { - this.set(config); - } - } - - var keys; - - if (Object.keys) { - keys = Object.keys; - } else { - keys = function (obj) { - var i, - res = []; - for (i in obj) { - if (hasOwnProp(obj, i)) { - res.push(i); - } - } - return res; - }; - } - - var defaultCalendar = { - sameDay: '[Today at] LT', - nextDay: '[Tomorrow at] LT', - nextWeek: 'dddd [at] LT', - lastDay: '[Yesterday at] LT', - lastWeek: '[Last] dddd [at] LT', - sameElse: 'L', - }; - - function calendar(key, mom, now) { - var output = this._calendar[key] || this._calendar['sameElse']; - return isFunction(output) ? output.call(mom, now) : output; - } - - function zeroFill(number, targetLength, forceSign) { - var absNumber = '' + Math.abs(number), - zerosToFill = targetLength - absNumber.length, - sign = number >= 0; - return ( - (sign ? (forceSign ? '+' : '') : '-') + - Math.pow(10, Math.max(0, zerosToFill)).toString().substr(1) + - absNumber - ); - } - - var formattingTokens = /(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|N{1,5}|YYYYYY|YYYYY|YYYY|YY|y{2,4}|yo?|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g, - localFormattingTokens = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g, - formatFunctions = {}, - formatTokenFunctions = {}; - - // token: 'M' - // padded: ['MM', 2] - // ordinal: 'Mo' - // callback: function () { this.month() + 1 } - function addFormatToken(token, padded, ordinal, callback) { - var func = callback; - if (typeof callback === 'string') { - func = function () { - return this[callback](); - }; - } - if (token) { - formatTokenFunctions[token] = func; - } - if (padded) { - formatTokenFunctions[padded[0]] = function () { - return zeroFill(func.apply(this, arguments), padded[1], padded[2]); - }; - } - if (ordinal) { - formatTokenFunctions[ordinal] = function () { - return this.localeData().ordinal( - func.apply(this, arguments), - token - ); - }; - } - } - - function removeFormattingTokens(input) { - if (input.match(/\[[\s\S]/)) { - return input.replace(/^\[|\]$/g, ''); - } - return input.replace(/\\/g, ''); - } - - function makeFormatFunction(format) { - var array = format.match(formattingTokens), - i, - length; - - for (i = 0, length = array.length; i < length; i++) { - if (formatTokenFunctions[array[i]]) { - array[i] = formatTokenFunctions[array[i]]; - } else { - array[i] = removeFormattingTokens(array[i]); - } - } - - return function (mom) { - var output = '', - i; - for (i = 0; i < length; i++) { - output += isFunction(array[i]) - ? array[i].call(mom, format) - : array[i]; - } - return output; - }; - } - - // format date using native date object - function formatMoment(m, format) { - if (!m.isValid()) { - return m.localeData().invalidDate(); - } - - format = expandFormat(format, m.localeData()); - formatFunctions[format] = - formatFunctions[format] || makeFormatFunction(format); - - return formatFunctions[format](m); - } - - function expandFormat(format, locale) { - var i = 5; - - function replaceLongDateFormatTokens(input) { - return locale.longDateFormat(input) || input; - } - - localFormattingTokens.lastIndex = 0; - while (i >= 0 && localFormattingTokens.test(format)) { - format = format.replace( - localFormattingTokens, - replaceLongDateFormatTokens - ); - localFormattingTokens.lastIndex = 0; - i -= 1; - } - - return format; - } - - var defaultLongDateFormat = { - LTS: 'h:mm:ss A', - LT: 'h:mm A', - L: 'MM/DD/YYYY', - LL: 'MMMM D, YYYY', - LLL: 'MMMM D, YYYY h:mm A', - LLLL: 'dddd, MMMM D, YYYY h:mm A', - }; - - function longDateFormat(key) { - var format = this._longDateFormat[key], - formatUpper = this._longDateFormat[key.toUpperCase()]; - - if (format || !formatUpper) { - return format; - } - - this._longDateFormat[key] = formatUpper - .match(formattingTokens) - .map(function (tok) { - if ( - tok === 'MMMM' || - tok === 'MM' || - tok === 'DD' || - tok === 'dddd' - ) { - return tok.slice(1); - } - return tok; - }) - .join(''); - - return this._longDateFormat[key]; - } - - var defaultInvalidDate = 'Invalid date'; - - function invalidDate() { - return this._invalidDate; - } - - var defaultOrdinal = '%d', - defaultDayOfMonthOrdinalParse = /\d{1,2}/; - - function ordinal(number) { - return this._ordinal.replace('%d', number); - } - - var defaultRelativeTime = { - future: 'in %s', - past: '%s ago', - s: 'a few seconds', - ss: '%d seconds', - m: 'a minute', - mm: '%d minutes', - h: 'an hour', - hh: '%d hours', - d: 'a day', - dd: '%d days', - w: 'a week', - ww: '%d weeks', - M: 'a month', - MM: '%d months', - y: 'a year', - yy: '%d years', - }; - - function relativeTime(number, withoutSuffix, string, isFuture) { - var output = this._relativeTime[string]; - return isFunction(output) - ? output(number, withoutSuffix, string, isFuture) - : output.replace(/%d/i, number); - } - - function pastFuture(diff, output) { - var format = this._relativeTime[diff > 0 ? 'future' : 'past']; - return isFunction(format) ? format(output) : format.replace(/%s/i, output); - } - - var aliases = {}; - - function addUnitAlias(unit, shorthand) { - var lowerCase = unit.toLowerCase(); - aliases[lowerCase] = aliases[lowerCase + 's'] = aliases[shorthand] = unit; - } - - function normalizeUnits(units) { - return typeof units === 'string' - ? aliases[units] || aliases[units.toLowerCase()] - : undefined; - } - - function normalizeObjectUnits(inputObject) { - var normalizedInput = {}, - normalizedProp, - prop; - - for (prop in inputObject) { - if (hasOwnProp(inputObject, prop)) { - normalizedProp = normalizeUnits(prop); - if (normalizedProp) { - normalizedInput[normalizedProp] = inputObject[prop]; - } - } - } - - return normalizedInput; - } - - var priorities = {}; - - function addUnitPriority(unit, priority) { - priorities[unit] = priority; - } - - function getPrioritizedUnits(unitsObj) { - var units = [], - u; - for (u in unitsObj) { - if (hasOwnProp(unitsObj, u)) { - units.push({ unit: u, priority: priorities[u] }); - } - } - units.sort(function (a, b) { - return a.priority - b.priority; - }); - return units; - } - - function isLeapYear(year) { - return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; - } - - function absFloor(number) { - if (number < 0) { - // -0 -> 0 - return Math.ceil(number) || 0; - } else { - return Math.floor(number); - } - } - - function toInt(argumentForCoercion) { - var coercedNumber = +argumentForCoercion, - value = 0; - - if (coercedNumber !== 0 && isFinite(coercedNumber)) { - value = absFloor(coercedNumber); - } - - return value; - } - - function makeGetSet(unit, keepTime) { - return function (value) { - if (value != null) { - set$1(this, unit, value); - hooks.updateOffset(this, keepTime); - return this; - } else { - return get(this, unit); - } - }; - } - - function get(mom, unit) { - return mom.isValid() - ? mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]() - : NaN; - } - - function set$1(mom, unit, value) { - if (mom.isValid() && !isNaN(value)) { - if ( - unit === 'FullYear' && - isLeapYear(mom.year()) && - mom.month() === 1 && - mom.date() === 29 - ) { - value = toInt(value); - mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit]( - value, - mom.month(), - daysInMonth(value, mom.month()) - ); - } else { - mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value); - } - } - } - - // MOMENTS - - function stringGet(units) { - units = normalizeUnits(units); - if (isFunction(this[units])) { - return this[units](); - } - return this; - } - - function stringSet(units, value) { - if (typeof units === 'object') { - units = normalizeObjectUnits(units); - var prioritized = getPrioritizedUnits(units), - i; - for (i = 0; i < prioritized.length; i++) { - this[prioritized[i].unit](units[prioritized[i].unit]); - } - } else { - units = normalizeUnits(units); - if (isFunction(this[units])) { - return this[units](value); - } - } - return this; - } - - var match1 = /\d/, // 0 - 9 - match2 = /\d\d/, // 00 - 99 - match3 = /\d{3}/, // 000 - 999 - match4 = /\d{4}/, // 0000 - 9999 - match6 = /[+-]?\d{6}/, // -999999 - 999999 - match1to2 = /\d\d?/, // 0 - 99 - match3to4 = /\d\d\d\d?/, // 999 - 9999 - match5to6 = /\d\d\d\d\d\d?/, // 99999 - 999999 - match1to3 = /\d{1,3}/, // 0 - 999 - match1to4 = /\d{1,4}/, // 0 - 9999 - match1to6 = /[+-]?\d{1,6}/, // -999999 - 999999 - matchUnsigned = /\d+/, // 0 - inf - matchSigned = /[+-]?\d+/, // -inf - inf - matchOffset = /Z|[+-]\d\d:?\d\d/gi, // +00:00 -00:00 +0000 -0000 or Z - matchShortOffset = /Z|[+-]\d\d(?::?\d\d)?/gi, // +00 -00 +00:00 -00:00 +0000 -0000 or Z - matchTimestamp = /[+-]?\d+(\.\d{1,3})?/, // 123456789 123456789.123 - // any word (or two) characters or numbers including two/three word month in arabic. - // includes scottish gaelic two word and hyphenated months - matchWord = /[0-9]{0,256}['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFF07\uFF10-\uFFEF]{1,256}|[\u0600-\u06FF\/]{1,256}(\s*?[\u0600-\u06FF]{1,256}){1,2}/i, - regexes; - - regexes = {}; - - function addRegexToken(token, regex, strictRegex) { - regexes[token] = isFunction(regex) - ? regex - : function (isStrict, localeData) { - return isStrict && strictRegex ? strictRegex : regex; - }; - } - - function getParseRegexForToken(token, config) { - if (!hasOwnProp(regexes, token)) { - return new RegExp(unescapeFormat(token)); - } - - return regexes[token](config._strict, config._locale); - } - - // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript - function unescapeFormat(s) { - return regexEscape( - s - .replace('\\', '') - .replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function ( - matched, - p1, - p2, - p3, - p4 - ) { - return p1 || p2 || p3 || p4; - }) - ); - } - - function regexEscape(s) { - return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); - } - - var tokens = {}; - - function addParseToken(token, callback) { - var i, - func = callback; - if (typeof token === 'string') { - token = [token]; - } - if (isNumber(callback)) { - func = function (input, array) { - array[callback] = toInt(input); - }; - } - for (i = 0; i < token.length; i++) { - tokens[token[i]] = func; - } - } - - function addWeekParseToken(token, callback) { - addParseToken(token, function (input, array, config, token) { - config._w = config._w || {}; - callback(input, config._w, config, token); - }); - } - - function addTimeToArrayFromToken(token, input, config) { - if (input != null && hasOwnProp(tokens, token)) { - tokens[token](input, config._a, config, token); - } - } - - var YEAR = 0, - MONTH = 1, - DATE = 2, - HOUR = 3, - MINUTE = 4, - SECOND = 5, - MILLISECOND = 6, - WEEK = 7, - WEEKDAY = 8; - - function mod(n, x) { - return ((n % x) + x) % x; - } - - var indexOf; - - if (Array.prototype.indexOf) { - indexOf = Array.prototype.indexOf; - } else { - indexOf = function (o) { - // I know - var i; - for (i = 0; i < this.length; ++i) { - if (this[i] === o) { - return i; - } - } - return -1; - }; - } - - function daysInMonth(year, month) { - if (isNaN(year) || isNaN(month)) { - return NaN; - } - var modMonth = mod(month, 12); - year += (month - modMonth) / 12; - return modMonth === 1 - ? isLeapYear(year) - ? 29 - : 28 - : 31 - ((modMonth % 7) % 2); - } - - // FORMATTING - - addFormatToken('M', ['MM', 2], 'Mo', function () { - return this.month() + 1; - }); - - addFormatToken('MMM', 0, 0, function (format) { - return this.localeData().monthsShort(this, format); - }); - - addFormatToken('MMMM', 0, 0, function (format) { - return this.localeData().months(this, format); - }); - - // ALIASES - - addUnitAlias('month', 'M'); - - // PRIORITY - - addUnitPriority('month', 8); - - // PARSING - - addRegexToken('M', match1to2); - addRegexToken('MM', match1to2, match2); - addRegexToken('MMM', function (isStrict, locale) { - return locale.monthsShortRegex(isStrict); - }); - addRegexToken('MMMM', function (isStrict, locale) { - return locale.monthsRegex(isStrict); - }); - - addParseToken(['M', 'MM'], function (input, array) { - array[MONTH] = toInt(input) - 1; - }); - - addParseToken(['MMM', 'MMMM'], function (input, array, config, token) { - var month = config._locale.monthsParse(input, token, config._strict); - // if we didn't find a month name, mark the date as invalid. - if (month != null) { - array[MONTH] = month; - } else { - getParsingFlags(config).invalidMonth = input; - } - }); - - // LOCALES - - var defaultLocaleMonths = 'January_February_March_April_May_June_July_August_September_October_November_December'.split( - '_' - ), - defaultLocaleMonthsShort = 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split( - '_' - ), - MONTHS_IN_FORMAT = /D[oD]?(\[[^\[\]]*\]|\s)+MMMM?/, - defaultMonthsShortRegex = matchWord, - defaultMonthsRegex = matchWord; - - function localeMonths(m, format) { - if (!m) { - return isArray(this._months) - ? this._months - : this._months['standalone']; - } - return isArray(this._months) - ? this._months[m.month()] - : this._months[ - (this._months.isFormat || MONTHS_IN_FORMAT).test(format) - ? 'format' - : 'standalone' - ][m.month()]; - } - - function localeMonthsShort(m, format) { - if (!m) { - return isArray(this._monthsShort) - ? this._monthsShort - : this._monthsShort['standalone']; - } - return isArray(this._monthsShort) - ? this._monthsShort[m.month()] - : this._monthsShort[ - MONTHS_IN_FORMAT.test(format) ? 'format' : 'standalone' - ][m.month()]; - } - - function handleStrictParse(monthName, format, strict) { - var i, - ii, - mom, - llc = monthName.toLocaleLowerCase(); - if (!this._monthsParse) { - // this is not used - this._monthsParse = []; - this._longMonthsParse = []; - this._shortMonthsParse = []; - for (i = 0; i < 12; ++i) { - mom = createUTC([2000, i]); - this._shortMonthsParse[i] = this.monthsShort( - mom, - '' - ).toLocaleLowerCase(); - this._longMonthsParse[i] = this.months(mom, '').toLocaleLowerCase(); - } - } - - if (strict) { - if (format === 'MMM') { - ii = indexOf.call(this._shortMonthsParse, llc); - return ii !== -1 ? ii : null; - } else { - ii = indexOf.call(this._longMonthsParse, llc); - return ii !== -1 ? ii : null; - } - } else { - if (format === 'MMM') { - ii = indexOf.call(this._shortMonthsParse, llc); - if (ii !== -1) { - return ii; - } - ii = indexOf.call(this._longMonthsParse, llc); - return ii !== -1 ? ii : null; - } else { - ii = indexOf.call(this._longMonthsParse, llc); - if (ii !== -1) { - return ii; - } - ii = indexOf.call(this._shortMonthsParse, llc); - return ii !== -1 ? ii : null; - } - } - } - - function localeMonthsParse(monthName, format, strict) { - var i, mom, regex; - - if (this._monthsParseExact) { - return handleStrictParse.call(this, monthName, format, strict); - } - - if (!this._monthsParse) { - this._monthsParse = []; - this._longMonthsParse = []; - this._shortMonthsParse = []; - } - - // TODO: add sorting - // Sorting makes sure if one month (or abbr) is a prefix of another - // see sorting in computeMonthsParse - for (i = 0; i < 12; i++) { - // make the regex if we don't have it already - mom = createUTC([2000, i]); - if (strict && !this._longMonthsParse[i]) { - this._longMonthsParse[i] = new RegExp( - '^' + this.months(mom, '').replace('.', '') + '$', - 'i' - ); - this._shortMonthsParse[i] = new RegExp( - '^' + this.monthsShort(mom, '').replace('.', '') + '$', - 'i' - ); - } - if (!strict && !this._monthsParse[i]) { - regex = - '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, ''); - this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i'); - } - // test the regex - if ( - strict && - format === 'MMMM' && - this._longMonthsParse[i].test(monthName) - ) { - return i; - } else if ( - strict && - format === 'MMM' && - this._shortMonthsParse[i].test(monthName) - ) { - return i; - } else if (!strict && this._monthsParse[i].test(monthName)) { - return i; - } - } - } - - // MOMENTS - - function setMonth(mom, value) { - var dayOfMonth; - - if (!mom.isValid()) { - // No op - return mom; - } - - if (typeof value === 'string') { - if (/^\d+$/.test(value)) { - value = toInt(value); - } else { - value = mom.localeData().monthsParse(value); - // TODO: Another silent failure? - if (!isNumber(value)) { - return mom; - } - } - } - - dayOfMonth = Math.min(mom.date(), daysInMonth(mom.year(), value)); - mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth); - return mom; - } - - function getSetMonth(value) { - if (value != null) { - setMonth(this, value); - hooks.updateOffset(this, true); - return this; - } else { - return get(this, 'Month'); - } - } - - function getDaysInMonth() { - return daysInMonth(this.year(), this.month()); - } - - function monthsShortRegex(isStrict) { - if (this._monthsParseExact) { - if (!hasOwnProp(this, '_monthsRegex')) { - computeMonthsParse.call(this); - } - if (isStrict) { - return this._monthsShortStrictRegex; - } else { - return this._monthsShortRegex; - } - } else { - if (!hasOwnProp(this, '_monthsShortRegex')) { - this._monthsShortRegex = defaultMonthsShortRegex; - } - return this._monthsShortStrictRegex && isStrict - ? this._monthsShortStrictRegex - : this._monthsShortRegex; - } - } - - function monthsRegex(isStrict) { - if (this._monthsParseExact) { - if (!hasOwnProp(this, '_monthsRegex')) { - computeMonthsParse.call(this); - } - if (isStrict) { - return this._monthsStrictRegex; - } else { - return this._monthsRegex; - } - } else { - if (!hasOwnProp(this, '_monthsRegex')) { - this._monthsRegex = defaultMonthsRegex; - } - return this._monthsStrictRegex && isStrict - ? this._monthsStrictRegex - : this._monthsRegex; - } - } - - function computeMonthsParse() { - function cmpLenRev(a, b) { - return b.length - a.length; - } - - var shortPieces = [], - longPieces = [], - mixedPieces = [], - i, - mom; - for (i = 0; i < 12; i++) { - // make the regex if we don't have it already - mom = createUTC([2000, i]); - shortPieces.push(this.monthsShort(mom, '')); - longPieces.push(this.months(mom, '')); - mixedPieces.push(this.months(mom, '')); - mixedPieces.push(this.monthsShort(mom, '')); - } - // Sorting makes sure if one month (or abbr) is a prefix of another it - // will match the longer piece. - shortPieces.sort(cmpLenRev); - longPieces.sort(cmpLenRev); - mixedPieces.sort(cmpLenRev); - for (i = 0; i < 12; i++) { - shortPieces[i] = regexEscape(shortPieces[i]); - longPieces[i] = regexEscape(longPieces[i]); - } - for (i = 0; i < 24; i++) { - mixedPieces[i] = regexEscape(mixedPieces[i]); - } - - this._monthsRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i'); - this._monthsShortRegex = this._monthsRegex; - this._monthsStrictRegex = new RegExp( - '^(' + longPieces.join('|') + ')', - 'i' - ); - this._monthsShortStrictRegex = new RegExp( - '^(' + shortPieces.join('|') + ')', - 'i' - ); - } - - // FORMATTING - - addFormatToken('Y', 0, 0, function () { - var y = this.year(); - return y <= 9999 ? zeroFill(y, 4) : '+' + y; - }); - - addFormatToken(0, ['YY', 2], 0, function () { - return this.year() % 100; - }); - - addFormatToken(0, ['YYYY', 4], 0, 'year'); - addFormatToken(0, ['YYYYY', 5], 0, 'year'); - addFormatToken(0, ['YYYYYY', 6, true], 0, 'year'); - - // ALIASES - - addUnitAlias('year', 'y'); - - // PRIORITIES - - addUnitPriority('year', 1); - - // PARSING - - addRegexToken('Y', matchSigned); - addRegexToken('YY', match1to2, match2); - addRegexToken('YYYY', match1to4, match4); - addRegexToken('YYYYY', match1to6, match6); - addRegexToken('YYYYYY', match1to6, match6); - - addParseToken(['YYYYY', 'YYYYYY'], YEAR); - addParseToken('YYYY', function (input, array) { - array[YEAR] = - input.length === 2 ? hooks.parseTwoDigitYear(input) : toInt(input); - }); - addParseToken('YY', function (input, array) { - array[YEAR] = hooks.parseTwoDigitYear(input); - }); - addParseToken('Y', function (input, array) { - array[YEAR] = parseInt(input, 10); - }); - - // HELPERS - - function daysInYear(year) { - return isLeapYear(year) ? 366 : 365; - } - - // HOOKS - - hooks.parseTwoDigitYear = function (input) { - return toInt(input) + (toInt(input) > 68 ? 1900 : 2000); - }; - - // MOMENTS - - var getSetYear = makeGetSet('FullYear', true); - - function getIsLeapYear() { - return isLeapYear(this.year()); - } - - function createDate(y, m, d, h, M, s, ms) { - // can't just apply() to create a date: - // https://stackoverflow.com/q/181348 - var date; - // the date constructor remaps years 0-99 to 1900-1999 - if (y < 100 && y >= 0) { - // preserve leap years using a full 400 year cycle, then reset - date = new Date(y + 400, m, d, h, M, s, ms); - if (isFinite(date.getFullYear())) { - date.setFullYear(y); - } - } else { - date = new Date(y, m, d, h, M, s, ms); - } - - return date; - } - - function createUTCDate(y) { - var date, args; - // the Date.UTC function remaps years 0-99 to 1900-1999 - if (y < 100 && y >= 0) { - args = Array.prototype.slice.call(arguments); - // preserve leap years using a full 400 year cycle, then reset - args[0] = y + 400; - date = new Date(Date.UTC.apply(null, args)); - if (isFinite(date.getUTCFullYear())) { - date.setUTCFullYear(y); - } - } else { - date = new Date(Date.UTC.apply(null, arguments)); - } - - return date; - } - - // start-of-first-week - start-of-year - function firstWeekOffset(year, dow, doy) { - var // first-week day -- which january is always in the first week (4 for iso, 1 for other) - fwd = 7 + dow - doy, - // first-week day local weekday -- which local weekday is fwd - fwdlw = (7 + createUTCDate(year, 0, fwd).getUTCDay() - dow) % 7; - - return -fwdlw + fwd - 1; - } - - // https://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday - function dayOfYearFromWeeks(year, week, weekday, dow, doy) { - var localWeekday = (7 + weekday - dow) % 7, - weekOffset = firstWeekOffset(year, dow, doy), - dayOfYear = 1 + 7 * (week - 1) + localWeekday + weekOffset, - resYear, - resDayOfYear; - - if (dayOfYear <= 0) { - resYear = year - 1; - resDayOfYear = daysInYear(resYear) + dayOfYear; - } else if (dayOfYear > daysInYear(year)) { - resYear = year + 1; - resDayOfYear = dayOfYear - daysInYear(year); - } else { - resYear = year; - resDayOfYear = dayOfYear; - } - - return { - year: resYear, - dayOfYear: resDayOfYear, - }; - } - - function weekOfYear(mom, dow, doy) { - var weekOffset = firstWeekOffset(mom.year(), dow, doy), - week = Math.floor((mom.dayOfYear() - weekOffset - 1) / 7) + 1, - resWeek, - resYear; - - if (week < 1) { - resYear = mom.year() - 1; - resWeek = week + weeksInYear(resYear, dow, doy); - } else if (week > weeksInYear(mom.year(), dow, doy)) { - resWeek = week - weeksInYear(mom.year(), dow, doy); - resYear = mom.year() + 1; - } else { - resYear = mom.year(); - resWeek = week; - } - - return { - week: resWeek, - year: resYear, - }; - } - - function weeksInYear(year, dow, doy) { - var weekOffset = firstWeekOffset(year, dow, doy), - weekOffsetNext = firstWeekOffset(year + 1, dow, doy); - return (daysInYear(year) - weekOffset + weekOffsetNext) / 7; - } - - // FORMATTING - - addFormatToken('w', ['ww', 2], 'wo', 'week'); - addFormatToken('W', ['WW', 2], 'Wo', 'isoWeek'); - - // ALIASES - - addUnitAlias('week', 'w'); - addUnitAlias('isoWeek', 'W'); - - // PRIORITIES - - addUnitPriority('week', 5); - addUnitPriority('isoWeek', 5); - - // PARSING - - addRegexToken('w', match1to2); - addRegexToken('ww', match1to2, match2); - addRegexToken('W', match1to2); - addRegexToken('WW', match1to2, match2); - - addWeekParseToken(['w', 'ww', 'W', 'WW'], function ( - input, - week, - config, - token - ) { - week[token.substr(0, 1)] = toInt(input); - }); - - // HELPERS - - // LOCALES - - function localeWeek(mom) { - return weekOfYear(mom, this._week.dow, this._week.doy).week; - } - - var defaultLocaleWeek = { - dow: 0, // Sunday is the first day of the week. - doy: 6, // The week that contains Jan 6th is the first week of the year. - }; - - function localeFirstDayOfWeek() { - return this._week.dow; - } - - function localeFirstDayOfYear() { - return this._week.doy; - } - - // MOMENTS - - function getSetWeek(input) { - var week = this.localeData().week(this); - return input == null ? week : this.add((input - week) * 7, 'd'); - } - - function getSetISOWeek(input) { - var week = weekOfYear(this, 1, 4).week; - return input == null ? week : this.add((input - week) * 7, 'd'); - } - - // FORMATTING - - addFormatToken('d', 0, 'do', 'day'); - - addFormatToken('dd', 0, 0, function (format) { - return this.localeData().weekdaysMin(this, format); - }); - - addFormatToken('ddd', 0, 0, function (format) { - return this.localeData().weekdaysShort(this, format); - }); - - addFormatToken('dddd', 0, 0, function (format) { - return this.localeData().weekdays(this, format); - }); - - addFormatToken('e', 0, 0, 'weekday'); - addFormatToken('E', 0, 0, 'isoWeekday'); - - // ALIASES - - addUnitAlias('day', 'd'); - addUnitAlias('weekday', 'e'); - addUnitAlias('isoWeekday', 'E'); - - // PRIORITY - addUnitPriority('day', 11); - addUnitPriority('weekday', 11); - addUnitPriority('isoWeekday', 11); - - // PARSING - - addRegexToken('d', match1to2); - addRegexToken('e', match1to2); - addRegexToken('E', match1to2); - addRegexToken('dd', function (isStrict, locale) { - return locale.weekdaysMinRegex(isStrict); - }); - addRegexToken('ddd', function (isStrict, locale) { - return locale.weekdaysShortRegex(isStrict); - }); - addRegexToken('dddd', function (isStrict, locale) { - return locale.weekdaysRegex(isStrict); - }); - - addWeekParseToken(['dd', 'ddd', 'dddd'], function (input, week, config, token) { - var weekday = config._locale.weekdaysParse(input, token, config._strict); - // if we didn't get a weekday name, mark the date as invalid - if (weekday != null) { - week.d = weekday; - } else { - getParsingFlags(config).invalidWeekday = input; - } - }); - - addWeekParseToken(['d', 'e', 'E'], function (input, week, config, token) { - week[token] = toInt(input); - }); - - // HELPERS - - function parseWeekday(input, locale) { - if (typeof input !== 'string') { - return input; - } - - if (!isNaN(input)) { - return parseInt(input, 10); - } - - input = locale.weekdaysParse(input); - if (typeof input === 'number') { - return input; - } - - return null; - } - - function parseIsoWeekday(input, locale) { - if (typeof input === 'string') { - return locale.weekdaysParse(input) % 7 || 7; - } - return isNaN(input) ? null : input; - } - - // LOCALES - function shiftWeekdays(ws, n) { - return ws.slice(n, 7).concat(ws.slice(0, n)); - } - - var defaultLocaleWeekdays = 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split( - '_' - ), - defaultLocaleWeekdaysShort = 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'), - defaultLocaleWeekdaysMin = 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'), - defaultWeekdaysRegex = matchWord, - defaultWeekdaysShortRegex = matchWord, - defaultWeekdaysMinRegex = matchWord; - - function localeWeekdays(m, format) { - var weekdays = isArray(this._weekdays) - ? this._weekdays - : this._weekdays[ - m && m !== true && this._weekdays.isFormat.test(format) - ? 'format' - : 'standalone' - ]; - return m === true - ? shiftWeekdays(weekdays, this._week.dow) - : m - ? weekdays[m.day()] - : weekdays; - } - - function localeWeekdaysShort(m) { - return m === true - ? shiftWeekdays(this._weekdaysShort, this._week.dow) - : m - ? this._weekdaysShort[m.day()] - : this._weekdaysShort; - } - - function localeWeekdaysMin(m) { - return m === true - ? shiftWeekdays(this._weekdaysMin, this._week.dow) - : m - ? this._weekdaysMin[m.day()] - : this._weekdaysMin; - } - - function handleStrictParse$1(weekdayName, format, strict) { - var i, - ii, - mom, - llc = weekdayName.toLocaleLowerCase(); - if (!this._weekdaysParse) { - this._weekdaysParse = []; - this._shortWeekdaysParse = []; - this._minWeekdaysParse = []; - - for (i = 0; i < 7; ++i) { - mom = createUTC([2000, 1]).day(i); - this._minWeekdaysParse[i] = this.weekdaysMin( - mom, - '' - ).toLocaleLowerCase(); - this._shortWeekdaysParse[i] = this.weekdaysShort( - mom, - '' - ).toLocaleLowerCase(); - this._weekdaysParse[i] = this.weekdays(mom, '').toLocaleLowerCase(); - } - } - - if (strict) { - if (format === 'dddd') { - ii = indexOf.call(this._weekdaysParse, llc); - return ii !== -1 ? ii : null; - } else if (format === 'ddd') { - ii = indexOf.call(this._shortWeekdaysParse, llc); - return ii !== -1 ? ii : null; - } else { - ii = indexOf.call(this._minWeekdaysParse, llc); - return ii !== -1 ? ii : null; - } - } else { - if (format === 'dddd') { - ii = indexOf.call(this._weekdaysParse, llc); - if (ii !== -1) { - return ii; - } - ii = indexOf.call(this._shortWeekdaysParse, llc); - if (ii !== -1) { - return ii; - } - ii = indexOf.call(this._minWeekdaysParse, llc); - return ii !== -1 ? ii : null; - } else if (format === 'ddd') { - ii = indexOf.call(this._shortWeekdaysParse, llc); - if (ii !== -1) { - return ii; - } - ii = indexOf.call(this._weekdaysParse, llc); - if (ii !== -1) { - return ii; - } - ii = indexOf.call(this._minWeekdaysParse, llc); - return ii !== -1 ? ii : null; - } else { - ii = indexOf.call(this._minWeekdaysParse, llc); - if (ii !== -1) { - return ii; - } - ii = indexOf.call(this._weekdaysParse, llc); - if (ii !== -1) { - return ii; - } - ii = indexOf.call(this._shortWeekdaysParse, llc); - return ii !== -1 ? ii : null; - } - } - } - - function localeWeekdaysParse(weekdayName, format, strict) { - var i, mom, regex; - - if (this._weekdaysParseExact) { - return handleStrictParse$1.call(this, weekdayName, format, strict); - } - - if (!this._weekdaysParse) { - this._weekdaysParse = []; - this._minWeekdaysParse = []; - this._shortWeekdaysParse = []; - this._fullWeekdaysParse = []; - } - - for (i = 0; i < 7; i++) { - // make the regex if we don't have it already - - mom = createUTC([2000, 1]).day(i); - if (strict && !this._fullWeekdaysParse[i]) { - this._fullWeekdaysParse[i] = new RegExp( - '^' + this.weekdays(mom, '').replace('.', '\\.?') + '$', - 'i' - ); - this._shortWeekdaysParse[i] = new RegExp( - '^' + this.weekdaysShort(mom, '').replace('.', '\\.?') + '$', - 'i' - ); - this._minWeekdaysParse[i] = new RegExp( - '^' + this.weekdaysMin(mom, '').replace('.', '\\.?') + '$', - 'i' - ); - } - if (!this._weekdaysParse[i]) { - regex = - '^' + - this.weekdays(mom, '') + - '|^' + - this.weekdaysShort(mom, '') + - '|^' + - this.weekdaysMin(mom, ''); - this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i'); - } - // test the regex - if ( - strict && - format === 'dddd' && - this._fullWeekdaysParse[i].test(weekdayName) - ) { - return i; - } else if ( - strict && - format === 'ddd' && - this._shortWeekdaysParse[i].test(weekdayName) - ) { - return i; - } else if ( - strict && - format === 'dd' && - this._minWeekdaysParse[i].test(weekdayName) - ) { - return i; - } else if (!strict && this._weekdaysParse[i].test(weekdayName)) { - return i; - } - } - } - - // MOMENTS - - function getSetDayOfWeek(input) { - if (!this.isValid()) { - return input != null ? this : NaN; - } - var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay(); - if (input != null) { - input = parseWeekday(input, this.localeData()); - return this.add(input - day, 'd'); - } else { - return day; - } - } - - function getSetLocaleDayOfWeek(input) { - if (!this.isValid()) { - return input != null ? this : NaN; - } - var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7; - return input == null ? weekday : this.add(input - weekday, 'd'); - } - - function getSetISODayOfWeek(input) { - if (!this.isValid()) { - return input != null ? this : NaN; - } - - // behaves the same as moment#day except - // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6) - // as a setter, sunday should belong to the previous week. - - if (input != null) { - var weekday = parseIsoWeekday(input, this.localeData()); - return this.day(this.day() % 7 ? weekday : weekday - 7); - } else { - return this.day() || 7; - } - } - - function weekdaysRegex(isStrict) { - if (this._weekdaysParseExact) { - if (!hasOwnProp(this, '_weekdaysRegex')) { - computeWeekdaysParse.call(this); - } - if (isStrict) { - return this._weekdaysStrictRegex; - } else { - return this._weekdaysRegex; - } - } else { - if (!hasOwnProp(this, '_weekdaysRegex')) { - this._weekdaysRegex = defaultWeekdaysRegex; - } - return this._weekdaysStrictRegex && isStrict - ? this._weekdaysStrictRegex - : this._weekdaysRegex; - } - } - - function weekdaysShortRegex(isStrict) { - if (this._weekdaysParseExact) { - if (!hasOwnProp(this, '_weekdaysRegex')) { - computeWeekdaysParse.call(this); - } - if (isStrict) { - return this._weekdaysShortStrictRegex; - } else { - return this._weekdaysShortRegex; - } - } else { - if (!hasOwnProp(this, '_weekdaysShortRegex')) { - this._weekdaysShortRegex = defaultWeekdaysShortRegex; - } - return this._weekdaysShortStrictRegex && isStrict - ? this._weekdaysShortStrictRegex - : this._weekdaysShortRegex; - } - } - - function weekdaysMinRegex(isStrict) { - if (this._weekdaysParseExact) { - if (!hasOwnProp(this, '_weekdaysRegex')) { - computeWeekdaysParse.call(this); - } - if (isStrict) { - return this._weekdaysMinStrictRegex; - } else { - return this._weekdaysMinRegex; - } - } else { - if (!hasOwnProp(this, '_weekdaysMinRegex')) { - this._weekdaysMinRegex = defaultWeekdaysMinRegex; - } - return this._weekdaysMinStrictRegex && isStrict - ? this._weekdaysMinStrictRegex - : this._weekdaysMinRegex; - } - } - - function computeWeekdaysParse() { - function cmpLenRev(a, b) { - return b.length - a.length; - } - - var minPieces = [], - shortPieces = [], - longPieces = [], - mixedPieces = [], - i, - mom, - minp, - shortp, - longp; - for (i = 0; i < 7; i++) { - // make the regex if we don't have it already - mom = createUTC([2000, 1]).day(i); - minp = regexEscape(this.weekdaysMin(mom, '')); - shortp = regexEscape(this.weekdaysShort(mom, '')); - longp = regexEscape(this.weekdays(mom, '')); - minPieces.push(minp); - shortPieces.push(shortp); - longPieces.push(longp); - mixedPieces.push(minp); - mixedPieces.push(shortp); - mixedPieces.push(longp); - } - // Sorting makes sure if one weekday (or abbr) is a prefix of another it - // will match the longer piece. - minPieces.sort(cmpLenRev); - shortPieces.sort(cmpLenRev); - longPieces.sort(cmpLenRev); - mixedPieces.sort(cmpLenRev); - - this._weekdaysRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i'); - this._weekdaysShortRegex = this._weekdaysRegex; - this._weekdaysMinRegex = this._weekdaysRegex; - - this._weekdaysStrictRegex = new RegExp( - '^(' + longPieces.join('|') + ')', - 'i' - ); - this._weekdaysShortStrictRegex = new RegExp( - '^(' + shortPieces.join('|') + ')', - 'i' - ); - this._weekdaysMinStrictRegex = new RegExp( - '^(' + minPieces.join('|') + ')', - 'i' - ); - } - - // FORMATTING - - function hFormat() { - return this.hours() % 12 || 12; - } - - function kFormat() { - return this.hours() || 24; - } - - addFormatToken('H', ['HH', 2], 0, 'hour'); - addFormatToken('h', ['hh', 2], 0, hFormat); - addFormatToken('k', ['kk', 2], 0, kFormat); - - addFormatToken('hmm', 0, 0, function () { - return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2); - }); - - addFormatToken('hmmss', 0, 0, function () { - return ( - '' + - hFormat.apply(this) + - zeroFill(this.minutes(), 2) + - zeroFill(this.seconds(), 2) - ); - }); - - addFormatToken('Hmm', 0, 0, function () { - return '' + this.hours() + zeroFill(this.minutes(), 2); - }); - - addFormatToken('Hmmss', 0, 0, function () { - return ( - '' + - this.hours() + - zeroFill(this.minutes(), 2) + - zeroFill(this.seconds(), 2) - ); - }); - - function meridiem(token, lowercase) { - addFormatToken(token, 0, 0, function () { - return this.localeData().meridiem( - this.hours(), - this.minutes(), - lowercase - ); - }); - } - - meridiem('a', true); - meridiem('A', false); - - // ALIASES - - addUnitAlias('hour', 'h'); - - // PRIORITY - addUnitPriority('hour', 13); - - // PARSING - - function matchMeridiem(isStrict, locale) { - return locale._meridiemParse; - } - - addRegexToken('a', matchMeridiem); - addRegexToken('A', matchMeridiem); - addRegexToken('H', match1to2); - addRegexToken('h', match1to2); - addRegexToken('k', match1to2); - addRegexToken('HH', match1to2, match2); - addRegexToken('hh', match1to2, match2); - addRegexToken('kk', match1to2, match2); - - addRegexToken('hmm', match3to4); - addRegexToken('hmmss', match5to6); - addRegexToken('Hmm', match3to4); - addRegexToken('Hmmss', match5to6); - - addParseToken(['H', 'HH'], HOUR); - addParseToken(['k', 'kk'], function (input, array, config) { - var kInput = toInt(input); - array[HOUR] = kInput === 24 ? 0 : kInput; - }); - addParseToken(['a', 'A'], function (input, array, config) { - config._isPm = config._locale.isPM(input); - config._meridiem = input; - }); - addParseToken(['h', 'hh'], function (input, array, config) { - array[HOUR] = toInt(input); - getParsingFlags(config).bigHour = true; - }); - addParseToken('hmm', function (input, array, config) { - var pos = input.length - 2; - array[HOUR] = toInt(input.substr(0, pos)); - array[MINUTE] = toInt(input.substr(pos)); - getParsingFlags(config).bigHour = true; - }); - addParseToken('hmmss', function (input, array, config) { - var pos1 = input.length - 4, - pos2 = input.length - 2; - array[HOUR] = toInt(input.substr(0, pos1)); - array[MINUTE] = toInt(input.substr(pos1, 2)); - array[SECOND] = toInt(input.substr(pos2)); - getParsingFlags(config).bigHour = true; - }); - addParseToken('Hmm', function (input, array, config) { - var pos = input.length - 2; - array[HOUR] = toInt(input.substr(0, pos)); - array[MINUTE] = toInt(input.substr(pos)); - }); - addParseToken('Hmmss', function (input, array, config) { - var pos1 = input.length - 4, - pos2 = input.length - 2; - array[HOUR] = toInt(input.substr(0, pos1)); - array[MINUTE] = toInt(input.substr(pos1, 2)); - array[SECOND] = toInt(input.substr(pos2)); - }); - - // LOCALES - - function localeIsPM(input) { - // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays - // Using charAt should be more compatible. - return (input + '').toLowerCase().charAt(0) === 'p'; - } - - var defaultLocaleMeridiemParse = /[ap]\.?m?\.?/i, - // Setting the hour should keep the time, because the user explicitly - // specified which hour they want. So trying to maintain the same hour (in - // a new timezone) makes sense. Adding/subtracting hours does not follow - // this rule. - getSetHour = makeGetSet('Hours', true); - - function localeMeridiem(hours, minutes, isLower) { - if (hours > 11) { - return isLower ? 'pm' : 'PM'; - } else { - return isLower ? 'am' : 'AM'; - } - } - - var baseConfig = { - calendar: defaultCalendar, - longDateFormat: defaultLongDateFormat, - invalidDate: defaultInvalidDate, - ordinal: defaultOrdinal, - dayOfMonthOrdinalParse: defaultDayOfMonthOrdinalParse, - relativeTime: defaultRelativeTime, - - months: defaultLocaleMonths, - monthsShort: defaultLocaleMonthsShort, - - week: defaultLocaleWeek, - - weekdays: defaultLocaleWeekdays, - weekdaysMin: defaultLocaleWeekdaysMin, - weekdaysShort: defaultLocaleWeekdaysShort, - - meridiemParse: defaultLocaleMeridiemParse, - }; - - // internal storage for locale config files - var locales = {}, - localeFamilies = {}, - globalLocale; - - function commonPrefix(arr1, arr2) { - var i, - minl = Math.min(arr1.length, arr2.length); - for (i = 0; i < minl; i += 1) { - if (arr1[i] !== arr2[i]) { - return i; - } - } - return minl; - } - - function normalizeLocale(key) { - return key ? key.toLowerCase().replace('_', '-') : key; - } - - // pick the locale from the array - // try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each - // substring from most specific to least, but move to the next array item if it's a more specific variant than the current root - function chooseLocale(names) { - var i = 0, - j, - next, - locale, - split; - - while (i < names.length) { - split = normalizeLocale(names[i]).split('-'); - j = split.length; - next = normalizeLocale(names[i + 1]); - next = next ? next.split('-') : null; - while (j > 0) { - locale = loadLocale(split.slice(0, j).join('-')); - if (locale) { - return locale; - } - if ( - next && - next.length >= j && - commonPrefix(split, next) >= j - 1 - ) { - //the next array item is better than a shallower substring of this one - break; - } - j--; - } - i++; - } - return globalLocale; - } - - function loadLocale(name) { - var oldLocale = null, - aliasedRequire; - // TODO: Find a better way to register and load all the locales in Node - if ( - locales[name] === undefined && - typeof module !== 'undefined' && - module && - module.exports - ) { - try { - oldLocale = globalLocale._abbr; - aliasedRequire = require; - aliasedRequire('./locale/' + name); - getSetGlobalLocale(oldLocale); - } catch (e) { - // mark as not found to avoid repeating expensive file require call causing high CPU - // when trying to find en-US, en_US, en-us for every format call - locales[name] = null; // null means not found - } - } - return locales[name]; - } - - // This function will load locale and then set the global locale. If - // no arguments are passed in, it will simply return the current global - // locale key. - function getSetGlobalLocale(key, values) { - var data; - if (key) { - if (isUndefined(values)) { - data = getLocale(key); - } else { - data = defineLocale(key, values); - } - - if (data) { - // moment.duration._locale = moment._locale = data; - globalLocale = data; - } else { - if (typeof console !== 'undefined' && console.warn) { - //warn user if arguments are passed but the locale could not be set - console.warn( - 'Locale ' + key + ' not found. Did you forget to load it?' - ); - } - } - } - - return globalLocale._abbr; - } - - function defineLocale(name, config) { - if (config !== null) { - var locale, - parentConfig = baseConfig; - config.abbr = name; - if (locales[name] != null) { - deprecateSimple( - 'defineLocaleOverride', - 'use moment.updateLocale(localeName, config) to change ' + - 'an existing locale. moment.defineLocale(localeName, ' + - 'config) should only be used for creating a new locale ' + - 'See http://momentjs.com/guides/#/warnings/define-locale/ for more info.' - ); - parentConfig = locales[name]._config; - } else if (config.parentLocale != null) { - if (locales[config.parentLocale] != null) { - parentConfig = locales[config.parentLocale]._config; - } else { - locale = loadLocale(config.parentLocale); - if (locale != null) { - parentConfig = locale._config; - } else { - if (!localeFamilies[config.parentLocale]) { - localeFamilies[config.parentLocale] = []; - } - localeFamilies[config.parentLocale].push({ - name: name, - config: config, - }); - return null; - } - } - } - locales[name] = new Locale(mergeConfigs(parentConfig, config)); - - if (localeFamilies[name]) { - localeFamilies[name].forEach(function (x) { - defineLocale(x.name, x.config); - }); - } - - // backwards compat for now: also set the locale - // make sure we set the locale AFTER all child locales have been - // created, so we won't end up with the child locale set. - getSetGlobalLocale(name); - - return locales[name]; - } else { - // useful for testing - delete locales[name]; - return null; - } - } - - function updateLocale(name, config) { - if (config != null) { - var locale, - tmpLocale, - parentConfig = baseConfig; - - if (locales[name] != null && locales[name].parentLocale != null) { - // Update existing child locale in-place to avoid memory-leaks - locales[name].set(mergeConfigs(locales[name]._config, config)); - } else { - // MERGE - tmpLocale = loadLocale(name); - if (tmpLocale != null) { - parentConfig = tmpLocale._config; - } - config = mergeConfigs(parentConfig, config); - if (tmpLocale == null) { - // updateLocale is called for creating a new locale - // Set abbr so it will have a name (getters return - // undefined otherwise). - config.abbr = name; - } - locale = new Locale(config); - locale.parentLocale = locales[name]; - locales[name] = locale; - } - - // backwards compat for now: also set the locale - getSetGlobalLocale(name); - } else { - // pass null for config to unupdate, useful for tests - if (locales[name] != null) { - if (locales[name].parentLocale != null) { - locales[name] = locales[name].parentLocale; - if (name === getSetGlobalLocale()) { - getSetGlobalLocale(name); - } - } else if (locales[name] != null) { - delete locales[name]; - } - } - } - return locales[name]; - } - - // returns locale data - function getLocale(key) { - var locale; - - if (key && key._locale && key._locale._abbr) { - key = key._locale._abbr; - } - - if (!key) { - return globalLocale; - } - - if (!isArray(key)) { - //short-circuit everything else - locale = loadLocale(key); - if (locale) { - return locale; - } - key = [key]; - } - - return chooseLocale(key); - } - - function listLocales() { - return keys(locales); - } - - function checkOverflow(m) { - var overflow, - a = m._a; - - if (a && getParsingFlags(m).overflow === -2) { - overflow = - a[MONTH] < 0 || a[MONTH] > 11 - ? MONTH - : a[DATE] < 1 || a[DATE] > daysInMonth(a[YEAR], a[MONTH]) - ? DATE - : a[HOUR] < 0 || - a[HOUR] > 24 || - (a[HOUR] === 24 && - (a[MINUTE] !== 0 || - a[SECOND] !== 0 || - a[MILLISECOND] !== 0)) - ? HOUR - : a[MINUTE] < 0 || a[MINUTE] > 59 - ? MINUTE - : a[SECOND] < 0 || a[SECOND] > 59 - ? SECOND - : a[MILLISECOND] < 0 || a[MILLISECOND] > 999 - ? MILLISECOND - : -1; - - if ( - getParsingFlags(m)._overflowDayOfYear && - (overflow < YEAR || overflow > DATE) - ) { - overflow = DATE; - } - if (getParsingFlags(m)._overflowWeeks && overflow === -1) { - overflow = WEEK; - } - if (getParsingFlags(m)._overflowWeekday && overflow === -1) { - overflow = WEEKDAY; - } - - getParsingFlags(m).overflow = overflow; - } - - return m; - } - - // iso 8601 regex - // 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00) - var extendedIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([+-]\d\d(?::?\d\d)?|\s*Z)?)?$/, - basicIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d|))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([+-]\d\d(?::?\d\d)?|\s*Z)?)?$/, - tzRegex = /Z|[+-]\d\d(?::?\d\d)?/, - isoDates = [ - ['YYYYYY-MM-DD', /[+-]\d{6}-\d\d-\d\d/], - ['YYYY-MM-DD', /\d{4}-\d\d-\d\d/], - ['GGGG-[W]WW-E', /\d{4}-W\d\d-\d/], - ['GGGG-[W]WW', /\d{4}-W\d\d/, false], - ['YYYY-DDD', /\d{4}-\d{3}/], - ['YYYY-MM', /\d{4}-\d\d/, false], - ['YYYYYYMMDD', /[+-]\d{10}/], - ['YYYYMMDD', /\d{8}/], - ['GGGG[W]WWE', /\d{4}W\d{3}/], - ['GGGG[W]WW', /\d{4}W\d{2}/, false], - ['YYYYDDD', /\d{7}/], - ['YYYYMM', /\d{6}/, false], - ['YYYY', /\d{4}/, false], - ], - // iso time formats and regexes - isoTimes = [ - ['HH:mm:ss.SSSS', /\d\d:\d\d:\d\d\.\d+/], - ['HH:mm:ss,SSSS', /\d\d:\d\d:\d\d,\d+/], - ['HH:mm:ss', /\d\d:\d\d:\d\d/], - ['HH:mm', /\d\d:\d\d/], - ['HHmmss.SSSS', /\d\d\d\d\d\d\.\d+/], - ['HHmmss,SSSS', /\d\d\d\d\d\d,\d+/], - ['HHmmss', /\d\d\d\d\d\d/], - ['HHmm', /\d\d\d\d/], - ['HH', /\d\d/], - ], - aspNetJsonRegex = /^\/?Date\((-?\d+)/i, - // RFC 2822 regex: For details see https://tools.ietf.org/html/rfc2822#section-3.3 - rfc2822 = /^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d{1,2})\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\d{2,4})\s(\d\d):(\d\d)(?::(\d\d))?\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|([+-]\d{4}))$/, - obsOffsets = { - UT: 0, - GMT: 0, - EDT: -4 * 60, - EST: -5 * 60, - CDT: -5 * 60, - CST: -6 * 60, - MDT: -6 * 60, - MST: -7 * 60, - PDT: -7 * 60, - PST: -8 * 60, - }; - - // date from iso format - function configFromISO(config) { - var i, - l, - string = config._i, - match = extendedIsoRegex.exec(string) || basicIsoRegex.exec(string), - allowTime, - dateFormat, - timeFormat, - tzFormat; - - if (match) { - getParsingFlags(config).iso = true; - - for (i = 0, l = isoDates.length; i < l; i++) { - if (isoDates[i][1].exec(match[1])) { - dateFormat = isoDates[i][0]; - allowTime = isoDates[i][2] !== false; - break; - } - } - if (dateFormat == null) { - config._isValid = false; - return; - } - if (match[3]) { - for (i = 0, l = isoTimes.length; i < l; i++) { - if (isoTimes[i][1].exec(match[3])) { - // match[2] should be 'T' or space - timeFormat = (match[2] || ' ') + isoTimes[i][0]; - break; - } - } - if (timeFormat == null) { - config._isValid = false; - return; - } - } - if (!allowTime && timeFormat != null) { - config._isValid = false; - return; - } - if (match[4]) { - if (tzRegex.exec(match[4])) { - tzFormat = 'Z'; - } else { - config._isValid = false; - return; - } - } - config._f = dateFormat + (timeFormat || '') + (tzFormat || ''); - configFromStringAndFormat(config); - } else { - config._isValid = false; - } - } - - function extractFromRFC2822Strings( - yearStr, - monthStr, - dayStr, - hourStr, - minuteStr, - secondStr - ) { - var result = [ - untruncateYear(yearStr), - defaultLocaleMonthsShort.indexOf(monthStr), - parseInt(dayStr, 10), - parseInt(hourStr, 10), - parseInt(minuteStr, 10), - ]; - - if (secondStr) { - result.push(parseInt(secondStr, 10)); - } - - return result; - } - - function untruncateYear(yearStr) { - var year = parseInt(yearStr, 10); - if (year <= 49) { - return 2000 + year; - } else if (year <= 999) { - return 1900 + year; - } - return year; - } - - function preprocessRFC2822(s) { - // Remove comments and folding whitespace and replace multiple-spaces with a single space - return s - .replace(/\([^)]*\)|[\n\t]/g, ' ') - .replace(/(\s\s+)/g, ' ') - .replace(/^\s\s*/, '') - .replace(/\s\s*$/, ''); - } - - function checkWeekday(weekdayStr, parsedInput, config) { - if (weekdayStr) { - // TODO: Replace the vanilla JS Date object with an independent day-of-week check. - var weekdayProvided = defaultLocaleWeekdaysShort.indexOf(weekdayStr), - weekdayActual = new Date( - parsedInput[0], - parsedInput[1], - parsedInput[2] - ).getDay(); - if (weekdayProvided !== weekdayActual) { - getParsingFlags(config).weekdayMismatch = true; - config._isValid = false; - return false; - } - } - return true; - } - - function calculateOffset(obsOffset, militaryOffset, numOffset) { - if (obsOffset) { - return obsOffsets[obsOffset]; - } else if (militaryOffset) { - // the only allowed military tz is Z - return 0; - } else { - var hm = parseInt(numOffset, 10), - m = hm % 100, - h = (hm - m) / 100; - return h * 60 + m; - } - } - - // date and time from ref 2822 format - function configFromRFC2822(config) { - var match = rfc2822.exec(preprocessRFC2822(config._i)), - parsedArray; - if (match) { - parsedArray = extractFromRFC2822Strings( - match[4], - match[3], - match[2], - match[5], - match[6], - match[7] - ); - if (!checkWeekday(match[1], parsedArray, config)) { - return; - } - - config._a = parsedArray; - config._tzm = calculateOffset(match[8], match[9], match[10]); - - config._d = createUTCDate.apply(null, config._a); - config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm); - - getParsingFlags(config).rfc2822 = true; - } else { - config._isValid = false; - } - } - - // date from 1) ASP.NET, 2) ISO, 3) RFC 2822 formats, or 4) optional fallback if parsing isn't strict - function configFromString(config) { - var matched = aspNetJsonRegex.exec(config._i); - if (matched !== null) { - config._d = new Date(+matched[1]); - return; - } - - configFromISO(config); - if (config._isValid === false) { - delete config._isValid; - } else { - return; - } - - configFromRFC2822(config); - if (config._isValid === false) { - delete config._isValid; - } else { - return; - } - - if (config._strict) { - config._isValid = false; - } else { - // Final attempt, use Input Fallback - hooks.createFromInputFallback(config); - } - } - - hooks.createFromInputFallback = deprecate( - 'value provided is not in a recognized RFC2822 or ISO format. moment construction falls back to js Date(), ' + - 'which is not reliable across all browsers and versions. Non RFC2822/ISO date formats are ' + - 'discouraged. Please refer to http://momentjs.com/guides/#/warnings/js-date/ for more info.', - function (config) { - config._d = new Date(config._i + (config._useUTC ? ' UTC' : '')); - } - ); - - // Pick the first defined of two or three arguments. - function defaults(a, b, c) { - if (a != null) { - return a; - } - if (b != null) { - return b; - } - return c; - } - - function currentDateArray(config) { - // hooks is actually the exported moment object - var nowValue = new Date(hooks.now()); - if (config._useUTC) { - return [ - nowValue.getUTCFullYear(), - nowValue.getUTCMonth(), - nowValue.getUTCDate(), - ]; - } - return [nowValue.getFullYear(), nowValue.getMonth(), nowValue.getDate()]; - } - - // convert an array to a date. - // the array should mirror the parameters below - // note: all values past the year are optional and will default to the lowest possible value. - // [year, month, day , hour, minute, second, millisecond] - function configFromArray(config) { - var i, - date, - input = [], - currentDate, - expectedWeekday, - yearToUse; - - if (config._d) { - return; - } - - currentDate = currentDateArray(config); - - //compute day of the year from weeks and weekdays - if (config._w && config._a[DATE] == null && config._a[MONTH] == null) { - dayOfYearFromWeekInfo(config); - } - - //if the day of the year is set, figure out what it is - if (config._dayOfYear != null) { - yearToUse = defaults(config._a[YEAR], currentDate[YEAR]); - - if ( - config._dayOfYear > daysInYear(yearToUse) || - config._dayOfYear === 0 - ) { - getParsingFlags(config)._overflowDayOfYear = true; - } - - date = createUTCDate(yearToUse, 0, config._dayOfYear); - config._a[MONTH] = date.getUTCMonth(); - config._a[DATE] = date.getUTCDate(); - } - - // Default to current date. - // * if no year, month, day of month are given, default to today - // * if day of month is given, default month and year - // * if month is given, default only year - // * if year is given, don't default anything - for (i = 0; i < 3 && config._a[i] == null; ++i) { - config._a[i] = input[i] = currentDate[i]; - } - - // Zero out whatever was not defaulted, including time - for (; i < 7; i++) { - config._a[i] = input[i] = - config._a[i] == null ? (i === 2 ? 1 : 0) : config._a[i]; - } - - // Check for 24:00:00.000 - if ( - config._a[HOUR] === 24 && - config._a[MINUTE] === 0 && - config._a[SECOND] === 0 && - config._a[MILLISECOND] === 0 - ) { - config._nextDay = true; - config._a[HOUR] = 0; - } - - config._d = (config._useUTC ? createUTCDate : createDate).apply( - null, - input - ); - expectedWeekday = config._useUTC - ? config._d.getUTCDay() - : config._d.getDay(); - - // Apply timezone offset from input. The actual utcOffset can be changed - // with parseZone. - if (config._tzm != null) { - config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm); - } - - if (config._nextDay) { - config._a[HOUR] = 24; - } - - // check for mismatching day of week - if ( - config._w && - typeof config._w.d !== 'undefined' && - config._w.d !== expectedWeekday - ) { - getParsingFlags(config).weekdayMismatch = true; - } - } - - function dayOfYearFromWeekInfo(config) { - var w, weekYear, week, weekday, dow, doy, temp, weekdayOverflow, curWeek; - - w = config._w; - if (w.GG != null || w.W != null || w.E != null) { - dow = 1; - doy = 4; - - // TODO: We need to take the current isoWeekYear, but that depends on - // how we interpret now (local, utc, fixed offset). So create - // a now version of current config (take local/utc/offset flags, and - // create now). - weekYear = defaults( - w.GG, - config._a[YEAR], - weekOfYear(createLocal(), 1, 4).year - ); - week = defaults(w.W, 1); - weekday = defaults(w.E, 1); - if (weekday < 1 || weekday > 7) { - weekdayOverflow = true; - } - } else { - dow = config._locale._week.dow; - doy = config._locale._week.doy; - - curWeek = weekOfYear(createLocal(), dow, doy); - - weekYear = defaults(w.gg, config._a[YEAR], curWeek.year); - - // Default to current week. - week = defaults(w.w, curWeek.week); - - if (w.d != null) { - // weekday -- low day numbers are considered next week - weekday = w.d; - if (weekday < 0 || weekday > 6) { - weekdayOverflow = true; - } - } else if (w.e != null) { - // local weekday -- counting starts from beginning of week - weekday = w.e + dow; - if (w.e < 0 || w.e > 6) { - weekdayOverflow = true; - } - } else { - // default to beginning of week - weekday = dow; - } - } - if (week < 1 || week > weeksInYear(weekYear, dow, doy)) { - getParsingFlags(config)._overflowWeeks = true; - } else if (weekdayOverflow != null) { - getParsingFlags(config)._overflowWeekday = true; - } else { - temp = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy); - config._a[YEAR] = temp.year; - config._dayOfYear = temp.dayOfYear; - } - } - - // constant that refers to the ISO standard - hooks.ISO_8601 = function () {}; - - // constant that refers to the RFC 2822 form - hooks.RFC_2822 = function () {}; - - // date from string and format string - function configFromStringAndFormat(config) { - // TODO: Move this to another part of the creation flow to prevent circular deps - if (config._f === hooks.ISO_8601) { - configFromISO(config); - return; - } - if (config._f === hooks.RFC_2822) { - configFromRFC2822(config); - return; - } - config._a = []; - getParsingFlags(config).empty = true; - - // This array is used to make a Date, either with `new Date` or `Date.UTC` - var string = '' + config._i, - i, - parsedInput, - tokens, - token, - skipped, - stringLength = string.length, - totalParsedInputLength = 0, - era; - - tokens = - expandFormat(config._f, config._locale).match(formattingTokens) || []; - - for (i = 0; i < tokens.length; i++) { - token = tokens[i]; - parsedInput = (string.match(getParseRegexForToken(token, config)) || - [])[0]; - if (parsedInput) { - skipped = string.substr(0, string.indexOf(parsedInput)); - if (skipped.length > 0) { - getParsingFlags(config).unusedInput.push(skipped); - } - string = string.slice( - string.indexOf(parsedInput) + parsedInput.length - ); - totalParsedInputLength += parsedInput.length; - } - // don't parse if it's not a known token - if (formatTokenFunctions[token]) { - if (parsedInput) { - getParsingFlags(config).empty = false; - } else { - getParsingFlags(config).unusedTokens.push(token); - } - addTimeToArrayFromToken(token, parsedInput, config); - } else if (config._strict && !parsedInput) { - getParsingFlags(config).unusedTokens.push(token); - } - } - - // add remaining unparsed input length to the string - getParsingFlags(config).charsLeftOver = - stringLength - totalParsedInputLength; - if (string.length > 0) { - getParsingFlags(config).unusedInput.push(string); - } - - // clear _12h flag if hour is <= 12 - if ( - config._a[HOUR] <= 12 && - getParsingFlags(config).bigHour === true && - config._a[HOUR] > 0 - ) { - getParsingFlags(config).bigHour = undefined; - } - - getParsingFlags(config).parsedDateParts = config._a.slice(0); - getParsingFlags(config).meridiem = config._meridiem; - // handle meridiem - config._a[HOUR] = meridiemFixWrap( - config._locale, - config._a[HOUR], - config._meridiem - ); - - // handle era - era = getParsingFlags(config).era; - if (era !== null) { - config._a[YEAR] = config._locale.erasConvertYear(era, config._a[YEAR]); - } - - configFromArray(config); - checkOverflow(config); - } - - function meridiemFixWrap(locale, hour, meridiem) { - var isPm; - - if (meridiem == null) { - // nothing to do - return hour; - } - if (locale.meridiemHour != null) { - return locale.meridiemHour(hour, meridiem); - } else if (locale.isPM != null) { - // Fallback - isPm = locale.isPM(meridiem); - if (isPm && hour < 12) { - hour += 12; - } - if (!isPm && hour === 12) { - hour = 0; - } - return hour; - } else { - // this is not supposed to happen - return hour; - } - } - - // date from string and array of format strings - function configFromStringAndArray(config) { - var tempConfig, - bestMoment, - scoreToBeat, - i, - currentScore, - validFormatFound, - bestFormatIsValid = false; - - if (config._f.length === 0) { - getParsingFlags(config).invalidFormat = true; - config._d = new Date(NaN); - return; - } - - for (i = 0; i < config._f.length; i++) { - currentScore = 0; - validFormatFound = false; - tempConfig = copyConfig({}, config); - if (config._useUTC != null) { - tempConfig._useUTC = config._useUTC; - } - tempConfig._f = config._f[i]; - configFromStringAndFormat(tempConfig); - - if (isValid(tempConfig)) { - validFormatFound = true; - } - - // if there is any input that was not parsed add a penalty for that format - currentScore += getParsingFlags(tempConfig).charsLeftOver; - - //or tokens - currentScore += getParsingFlags(tempConfig).unusedTokens.length * 10; - - getParsingFlags(tempConfig).score = currentScore; - - if (!bestFormatIsValid) { - if ( - scoreToBeat == null || - currentScore < scoreToBeat || - validFormatFound - ) { - scoreToBeat = currentScore; - bestMoment = tempConfig; - if (validFormatFound) { - bestFormatIsValid = true; - } - } - } else { - if (currentScore < scoreToBeat) { - scoreToBeat = currentScore; - bestMoment = tempConfig; - } - } - } - - extend(config, bestMoment || tempConfig); - } - - function configFromObject(config) { - if (config._d) { - return; - } - - var i = normalizeObjectUnits(config._i), - dayOrDate = i.day === undefined ? i.date : i.day; - config._a = map( - [i.year, i.month, dayOrDate, i.hour, i.minute, i.second, i.millisecond], - function (obj) { - return obj && parseInt(obj, 10); - } - ); - - configFromArray(config); - } - - function createFromConfig(config) { - var res = new Moment(checkOverflow(prepareConfig(config))); - if (res._nextDay) { - // Adding is smart enough around DST - res.add(1, 'd'); - res._nextDay = undefined; - } - - return res; - } - - function prepareConfig(config) { - var input = config._i, - format = config._f; - - config._locale = config._locale || getLocale(config._l); - - if (input === null || (format === undefined && input === '')) { - return createInvalid({ nullInput: true }); - } - - if (typeof input === 'string') { - config._i = input = config._locale.preparse(input); - } - - if (isMoment(input)) { - return new Moment(checkOverflow(input)); - } else if (isDate(input)) { - config._d = input; - } else if (isArray(format)) { - configFromStringAndArray(config); - } else if (format) { - configFromStringAndFormat(config); - } else { - configFromInput(config); - } - - if (!isValid(config)) { - config._d = null; - } - - return config; - } - - function configFromInput(config) { - var input = config._i; - if (isUndefined(input)) { - config._d = new Date(hooks.now()); - } else if (isDate(input)) { - config._d = new Date(input.valueOf()); - } else if (typeof input === 'string') { - configFromString(config); - } else if (isArray(input)) { - config._a = map(input.slice(0), function (obj) { - return parseInt(obj, 10); - }); - configFromArray(config); - } else if (isObject(input)) { - configFromObject(config); - } else if (isNumber(input)) { - // from milliseconds - config._d = new Date(input); - } else { - hooks.createFromInputFallback(config); - } - } - - function createLocalOrUTC(input, format, locale, strict, isUTC) { - var c = {}; - - if (format === true || format === false) { - strict = format; - format = undefined; - } - - if (locale === true || locale === false) { - strict = locale; - locale = undefined; - } - - if ( - (isObject(input) && isObjectEmpty(input)) || - (isArray(input) && input.length === 0) - ) { - input = undefined; - } - // object construction must be done this way. - // https://github.com/moment/moment/issues/1423 - c._isAMomentObject = true; - c._useUTC = c._isUTC = isUTC; - c._l = locale; - c._i = input; - c._f = format; - c._strict = strict; - - return createFromConfig(c); - } - - function createLocal(input, format, locale, strict) { - return createLocalOrUTC(input, format, locale, strict, false); - } - - var prototypeMin = deprecate( - 'moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/', - function () { - var other = createLocal.apply(null, arguments); - if (this.isValid() && other.isValid()) { - return other < this ? this : other; - } else { - return createInvalid(); - } - } - ), - prototypeMax = deprecate( - 'moment().max is deprecated, use moment.min instead. http://momentjs.com/guides/#/warnings/min-max/', - function () { - var other = createLocal.apply(null, arguments); - if (this.isValid() && other.isValid()) { - return other > this ? this : other; - } else { - return createInvalid(); - } - } - ); - - // Pick a moment m from moments so that m[fn](other) is true for all - // other. This relies on the function fn to be transitive. - // - // moments should either be an array of moment objects or an array, whose - // first element is an array of moment objects. - function pickBy(fn, moments) { - var res, i; - if (moments.length === 1 && isArray(moments[0])) { - moments = moments[0]; - } - if (!moments.length) { - return createLocal(); - } - res = moments[0]; - for (i = 1; i < moments.length; ++i) { - if (!moments[i].isValid() || moments[i][fn](res)) { - res = moments[i]; - } - } - return res; - } - - // TODO: Use [].sort instead? - function min() { - var args = [].slice.call(arguments, 0); - - return pickBy('isBefore', args); - } - - function max() { - var args = [].slice.call(arguments, 0); - - return pickBy('isAfter', args); - } - - var now = function () { - return Date.now ? Date.now() : +new Date(); - }; - - var ordering = [ - 'year', - 'quarter', - 'month', - 'week', - 'day', - 'hour', - 'minute', - 'second', - 'millisecond', - ]; - - function isDurationValid(m) { - var key, - unitHasDecimal = false, - i; - for (key in m) { - if ( - hasOwnProp(m, key) && - !( - indexOf.call(ordering, key) !== -1 && - (m[key] == null || !isNaN(m[key])) - ) - ) { - return false; - } - } - - for (i = 0; i < ordering.length; ++i) { - if (m[ordering[i]]) { - if (unitHasDecimal) { - return false; // only allow non-integers for smallest unit - } - if (parseFloat(m[ordering[i]]) !== toInt(m[ordering[i]])) { - unitHasDecimal = true; - } - } - } - - return true; - } - - function isValid$1() { - return this._isValid; - } - - function createInvalid$1() { - return createDuration(NaN); - } - - function Duration(duration) { - var normalizedInput = normalizeObjectUnits(duration), - years = normalizedInput.year || 0, - quarters = normalizedInput.quarter || 0, - months = normalizedInput.month || 0, - weeks = normalizedInput.week || normalizedInput.isoWeek || 0, - days = normalizedInput.day || 0, - hours = normalizedInput.hour || 0, - minutes = normalizedInput.minute || 0, - seconds = normalizedInput.second || 0, - milliseconds = normalizedInput.millisecond || 0; - - this._isValid = isDurationValid(normalizedInput); - - // representation for dateAddRemove - this._milliseconds = - +milliseconds + - seconds * 1e3 + // 1000 - minutes * 6e4 + // 1000 * 60 - hours * 1000 * 60 * 60; //using 1000 * 60 * 60 instead of 36e5 to avoid floating point rounding errors https://github.com/moment/moment/issues/2978 - // Because of dateAddRemove treats 24 hours as different from a - // day when working around DST, we need to store them separately - this._days = +days + weeks * 7; - // It is impossible to translate months into days without knowing - // which months you are are talking about, so we have to store - // it separately. - this._months = +months + quarters * 3 + years * 12; - - this._data = {}; - - this._locale = getLocale(); - - this._bubble(); - } - - function isDuration(obj) { - return obj instanceof Duration; - } - - function absRound(number) { - if (number < 0) { - return Math.round(-1 * number) * -1; - } else { - return Math.round(number); - } - } - - // compare two arrays, return the number of differences - function compareArrays(array1, array2, dontConvert) { - var len = Math.min(array1.length, array2.length), - lengthDiff = Math.abs(array1.length - array2.length), - diffs = 0, - i; - for (i = 0; i < len; i++) { - if ( - (dontConvert && array1[i] !== array2[i]) || - (!dontConvert && toInt(array1[i]) !== toInt(array2[i])) - ) { - diffs++; - } - } - return diffs + lengthDiff; - } - - // FORMATTING - - function offset(token, separator) { - addFormatToken(token, 0, 0, function () { - var offset = this.utcOffset(), - sign = '+'; - if (offset < 0) { - offset = -offset; - sign = '-'; - } - return ( - sign + - zeroFill(~~(offset / 60), 2) + - separator + - zeroFill(~~offset % 60, 2) - ); - }); - } - - offset('Z', ':'); - offset('ZZ', ''); - - // PARSING - - addRegexToken('Z', matchShortOffset); - addRegexToken('ZZ', matchShortOffset); - addParseToken(['Z', 'ZZ'], function (input, array, config) { - config._useUTC = true; - config._tzm = offsetFromString(matchShortOffset, input); - }); - - // HELPERS - - // timezone chunker - // '+10:00' > ['10', '00'] - // '-1530' > ['-15', '30'] - var chunkOffset = /([\+\-]|\d\d)/gi; - - function offsetFromString(matcher, string) { - var matches = (string || '').match(matcher), - chunk, - parts, - minutes; - - if (matches === null) { - return null; - } - - chunk = matches[matches.length - 1] || []; - parts = (chunk + '').match(chunkOffset) || ['-', 0, 0]; - minutes = +(parts[1] * 60) + toInt(parts[2]); - - return minutes === 0 ? 0 : parts[0] === '+' ? minutes : -minutes; - } - - // Return a moment from input, that is local/utc/zone equivalent to model. - function cloneWithOffset(input, model) { - var res, diff; - if (model._isUTC) { - res = model.clone(); - diff = - (isMoment(input) || isDate(input) - ? input.valueOf() - : createLocal(input).valueOf()) - res.valueOf(); - // Use low-level api, because this fn is low-level api. - res._d.setTime(res._d.valueOf() + diff); - hooks.updateOffset(res, false); - return res; - } else { - return createLocal(input).local(); - } - } - - function getDateOffset(m) { - // On Firefox.24 Date#getTimezoneOffset returns a floating point. - // https://github.com/moment/moment/pull/1871 - return -Math.round(m._d.getTimezoneOffset()); - } - - // HOOKS - - // This function will be called whenever a moment is mutated. - // It is intended to keep the offset in sync with the timezone. - hooks.updateOffset = function () {}; - - // MOMENTS - - // keepLocalTime = true means only change the timezone, without - // affecting the local hour. So 5:31:26 +0300 --[utcOffset(2, true)]--> - // 5:31:26 +0200 It is possible that 5:31:26 doesn't exist with offset - // +0200, so we adjust the time as needed, to be valid. - // - // Keeping the time actually adds/subtracts (one hour) - // from the actual represented time. That is why we call updateOffset - // a second time. In case it wants us to change the offset again - // _changeInProgress == true case, then we have to adjust, because - // there is no such time in the given timezone. - function getSetOffset(input, keepLocalTime, keepMinutes) { - var offset = this._offset || 0, - localAdjust; - if (!this.isValid()) { - return input != null ? this : NaN; - } - if (input != null) { - if (typeof input === 'string') { - input = offsetFromString(matchShortOffset, input); - if (input === null) { - return this; - } - } else if (Math.abs(input) < 16 && !keepMinutes) { - input = input * 60; - } - if (!this._isUTC && keepLocalTime) { - localAdjust = getDateOffset(this); - } - this._offset = input; - this._isUTC = true; - if (localAdjust != null) { - this.add(localAdjust, 'm'); - } - if (offset !== input) { - if (!keepLocalTime || this._changeInProgress) { - addSubtract( - this, - createDuration(input - offset, 'm'), - 1, - false - ); - } else if (!this._changeInProgress) { - this._changeInProgress = true; - hooks.updateOffset(this, true); - this._changeInProgress = null; - } - } - return this; - } else { - return this._isUTC ? offset : getDateOffset(this); - } - } - - function getSetZone(input, keepLocalTime) { - if (input != null) { - if (typeof input !== 'string') { - input = -input; - } - - this.utcOffset(input, keepLocalTime); - - return this; - } else { - return -this.utcOffset(); - } - } - - function setOffsetToUTC(keepLocalTime) { - return this.utcOffset(0, keepLocalTime); - } - - function setOffsetToLocal(keepLocalTime) { - if (this._isUTC) { - this.utcOffset(0, keepLocalTime); - this._isUTC = false; - - if (keepLocalTime) { - this.subtract(getDateOffset(this), 'm'); - } - } - return this; - } - - function setOffsetToParsedOffset() { - if (this._tzm != null) { - this.utcOffset(this._tzm, false, true); - } else if (typeof this._i === 'string') { - var tZone = offsetFromString(matchOffset, this._i); - if (tZone != null) { - this.utcOffset(tZone); - } else { - this.utcOffset(0, true); - } - } - return this; - } - - function hasAlignedHourOffset(input) { - if (!this.isValid()) { - return false; - } - input = input ? createLocal(input).utcOffset() : 0; - - return (this.utcOffset() - input) % 60 === 0; - } - - function isDaylightSavingTime() { - return ( - this.utcOffset() > this.clone().month(0).utcOffset() || - this.utcOffset() > this.clone().month(5).utcOffset() - ); - } - - function isDaylightSavingTimeShifted() { - if (!isUndefined(this._isDSTShifted)) { - return this._isDSTShifted; - } - - var c = {}, - other; - - copyConfig(c, this); - c = prepareConfig(c); - - if (c._a) { - other = c._isUTC ? createUTC(c._a) : createLocal(c._a); - this._isDSTShifted = - this.isValid() && compareArrays(c._a, other.toArray()) > 0; - } else { - this._isDSTShifted = false; - } - - return this._isDSTShifted; - } - - function isLocal() { - return this.isValid() ? !this._isUTC : false; - } - - function isUtcOffset() { - return this.isValid() ? this._isUTC : false; - } - - function isUtc() { - return this.isValid() ? this._isUTC && this._offset === 0 : false; - } - - // ASP.NET json date format regex - var aspNetRegex = /^(-|\+)?(?:(\d*)[. ])?(\d+):(\d+)(?::(\d+)(\.\d*)?)?$/, - // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html - // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere - // and further modified to allow for strings containing both week and day - isoRegex = /^(-|\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/; - - function createDuration(input, key) { - var duration = input, - // matching against regexp is expensive, do it on demand - match = null, - sign, - ret, - diffRes; - - if (isDuration(input)) { - duration = { - ms: input._milliseconds, - d: input._days, - M: input._months, - }; - } else if (isNumber(input) || !isNaN(+input)) { - duration = {}; - if (key) { - duration[key] = +input; - } else { - duration.milliseconds = +input; - } - } else if ((match = aspNetRegex.exec(input))) { - sign = match[1] === '-' ? -1 : 1; - duration = { - y: 0, - d: toInt(match[DATE]) * sign, - h: toInt(match[HOUR]) * sign, - m: toInt(match[MINUTE]) * sign, - s: toInt(match[SECOND]) * sign, - ms: toInt(absRound(match[MILLISECOND] * 1000)) * sign, // the millisecond decimal point is included in the match - }; - } else if ((match = isoRegex.exec(input))) { - sign = match[1] === '-' ? -1 : 1; - duration = { - y: parseIso(match[2], sign), - M: parseIso(match[3], sign), - w: parseIso(match[4], sign), - d: parseIso(match[5], sign), - h: parseIso(match[6], sign), - m: parseIso(match[7], sign), - s: parseIso(match[8], sign), - }; - } else if (duration == null) { - // checks for null or undefined - duration = {}; - } else if ( - typeof duration === 'object' && - ('from' in duration || 'to' in duration) - ) { - diffRes = momentsDifference( - createLocal(duration.from), - createLocal(duration.to) - ); - - duration = {}; - duration.ms = diffRes.milliseconds; - duration.M = diffRes.months; - } - - ret = new Duration(duration); - - if (isDuration(input) && hasOwnProp(input, '_locale')) { - ret._locale = input._locale; - } - - if (isDuration(input) && hasOwnProp(input, '_isValid')) { - ret._isValid = input._isValid; - } - - return ret; - } - - createDuration.fn = Duration.prototype; - createDuration.invalid = createInvalid$1; - - function parseIso(inp, sign) { - // We'd normally use ~~inp for this, but unfortunately it also - // converts floats to ints. - // inp may be undefined, so careful calling replace on it. - var res = inp && parseFloat(inp.replace(',', '.')); - // apply sign while we're at it - return (isNaN(res) ? 0 : res) * sign; - } - - function positiveMomentsDifference(base, other) { - var res = {}; - - res.months = - other.month() - base.month() + (other.year() - base.year()) * 12; - if (base.clone().add(res.months, 'M').isAfter(other)) { - --res.months; - } - - res.milliseconds = +other - +base.clone().add(res.months, 'M'); - - return res; - } - - function momentsDifference(base, other) { - var res; - if (!(base.isValid() && other.isValid())) { - return { milliseconds: 0, months: 0 }; - } - - other = cloneWithOffset(other, base); - if (base.isBefore(other)) { - res = positiveMomentsDifference(base, other); - } else { - res = positiveMomentsDifference(other, base); - res.milliseconds = -res.milliseconds; - res.months = -res.months; - } - - return res; - } - - // TODO: remove 'name' arg after deprecation is removed - function createAdder(direction, name) { - return function (val, period) { - var dur, tmp; - //invert the arguments, but complain about it - if (period !== null && !isNaN(+period)) { - deprecateSimple( - name, - 'moment().' + - name + - '(period, number) is deprecated. Please use moment().' + - name + - '(number, period). ' + - 'See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info.' - ); - tmp = val; - val = period; - period = tmp; - } - - dur = createDuration(val, period); - addSubtract(this, dur, direction); - return this; - }; - } - - function addSubtract(mom, duration, isAdding, updateOffset) { - var milliseconds = duration._milliseconds, - days = absRound(duration._days), - months = absRound(duration._months); - - if (!mom.isValid()) { - // No op - return; - } - - updateOffset = updateOffset == null ? true : updateOffset; - - if (months) { - setMonth(mom, get(mom, 'Month') + months * isAdding); - } - if (days) { - set$1(mom, 'Date', get(mom, 'Date') + days * isAdding); - } - if (milliseconds) { - mom._d.setTime(mom._d.valueOf() + milliseconds * isAdding); - } - if (updateOffset) { - hooks.updateOffset(mom, days || months); - } - } - - var add = createAdder(1, 'add'), - subtract = createAdder(-1, 'subtract'); - - function isString(input) { - return typeof input === 'string' || input instanceof String; - } - - // type MomentInput = Moment | Date | string | number | (number | string)[] | MomentInputObject | void; // null | undefined - function isMomentInput(input) { - return ( - isMoment(input) || - isDate(input) || - isString(input) || - isNumber(input) || - isNumberOrStringArray(input) || - isMomentInputObject(input) || - input === null || - input === undefined - ); - } - - function isMomentInputObject(input) { - var objectTest = isObject(input) && !isObjectEmpty(input), - propertyTest = false, - properties = [ - 'years', - 'year', - 'y', - 'months', - 'month', - 'M', - 'days', - 'day', - 'd', - 'dates', - 'date', - 'D', - 'hours', - 'hour', - 'h', - 'minutes', - 'minute', - 'm', - 'seconds', - 'second', - 's', - 'milliseconds', - 'millisecond', - 'ms', - ], - i, - property; - - for (i = 0; i < properties.length; i += 1) { - property = properties[i]; - propertyTest = propertyTest || hasOwnProp(input, property); - } - - return objectTest && propertyTest; - } - - function isNumberOrStringArray(input) { - var arrayTest = isArray(input), - dataTypeTest = false; - if (arrayTest) { - dataTypeTest = - input.filter(function (item) { - return !isNumber(item) && isString(input); - }).length === 0; - } - return arrayTest && dataTypeTest; - } - - function isCalendarSpec(input) { - var objectTest = isObject(input) && !isObjectEmpty(input), - propertyTest = false, - properties = [ - 'sameDay', - 'nextDay', - 'lastDay', - 'nextWeek', - 'lastWeek', - 'sameElse', - ], - i, - property; - - for (i = 0; i < properties.length; i += 1) { - property = properties[i]; - propertyTest = propertyTest || hasOwnProp(input, property); - } - - return objectTest && propertyTest; - } - - function getCalendarFormat(myMoment, now) { - var diff = myMoment.diff(now, 'days', true); - return diff < -6 - ? 'sameElse' - : diff < -1 - ? 'lastWeek' - : diff < 0 - ? 'lastDay' - : diff < 1 - ? 'sameDay' - : diff < 2 - ? 'nextDay' - : diff < 7 - ? 'nextWeek' - : 'sameElse'; - } - - function calendar$1(time, formats) { - // Support for single parameter, formats only overload to the calendar function - if (arguments.length === 1) { - if (!arguments[0]) { - time = undefined; - formats = undefined; - } else if (isMomentInput(arguments[0])) { - time = arguments[0]; - formats = undefined; - } else if (isCalendarSpec(arguments[0])) { - formats = arguments[0]; - time = undefined; - } - } - // We want to compare the start of today, vs this. - // Getting start-of-today depends on whether we're local/utc/offset or not. - var now = time || createLocal(), - sod = cloneWithOffset(now, this).startOf('day'), - format = hooks.calendarFormat(this, sod) || 'sameElse', - output = - formats && - (isFunction(formats[format]) - ? formats[format].call(this, now) - : formats[format]); - - return this.format( - output || this.localeData().calendar(format, this, createLocal(now)) - ); - } - - function clone() { - return new Moment(this); - } - - function isAfter(input, units) { - var localInput = isMoment(input) ? input : createLocal(input); - if (!(this.isValid() && localInput.isValid())) { - return false; - } - units = normalizeUnits(units) || 'millisecond'; - if (units === 'millisecond') { - return this.valueOf() > localInput.valueOf(); - } else { - return localInput.valueOf() < this.clone().startOf(units).valueOf(); - } - } - - function isBefore(input, units) { - var localInput = isMoment(input) ? input : createLocal(input); - if (!(this.isValid() && localInput.isValid())) { - return false; - } - units = normalizeUnits(units) || 'millisecond'; - if (units === 'millisecond') { - return this.valueOf() < localInput.valueOf(); - } else { - return this.clone().endOf(units).valueOf() < localInput.valueOf(); - } - } - - function isBetween(from, to, units, inclusivity) { - var localFrom = isMoment(from) ? from : createLocal(from), - localTo = isMoment(to) ? to : createLocal(to); - if (!(this.isValid() && localFrom.isValid() && localTo.isValid())) { - return false; - } - inclusivity = inclusivity || '()'; - return ( - (inclusivity[0] === '(' - ? this.isAfter(localFrom, units) - : !this.isBefore(localFrom, units)) && - (inclusivity[1] === ')' - ? this.isBefore(localTo, units) - : !this.isAfter(localTo, units)) - ); - } - - function isSame(input, units) { - var localInput = isMoment(input) ? input : createLocal(input), - inputMs; - if (!(this.isValid() && localInput.isValid())) { - return false; - } - units = normalizeUnits(units) || 'millisecond'; - if (units === 'millisecond') { - return this.valueOf() === localInput.valueOf(); - } else { - inputMs = localInput.valueOf(); - return ( - this.clone().startOf(units).valueOf() <= inputMs && - inputMs <= this.clone().endOf(units).valueOf() - ); - } - } - - function isSameOrAfter(input, units) { - return this.isSame(input, units) || this.isAfter(input, units); - } - - function isSameOrBefore(input, units) { - return this.isSame(input, units) || this.isBefore(input, units); - } - - function diff(input, units, asFloat) { - var that, zoneDelta, output; - - if (!this.isValid()) { - return NaN; - } - - that = cloneWithOffset(input, this); - - if (!that.isValid()) { - return NaN; - } - - zoneDelta = (that.utcOffset() - this.utcOffset()) * 6e4; - - units = normalizeUnits(units); - - switch (units) { - case 'year': - output = monthDiff(this, that) / 12; - break; - case 'month': - output = monthDiff(this, that); - break; - case 'quarter': - output = monthDiff(this, that) / 3; - break; - case 'second': - output = (this - that) / 1e3; - break; // 1000 - case 'minute': - output = (this - that) / 6e4; - break; // 1000 * 60 - case 'hour': - output = (this - that) / 36e5; - break; // 1000 * 60 * 60 - case 'day': - output = (this - that - zoneDelta) / 864e5; - break; // 1000 * 60 * 60 * 24, negate dst - case 'week': - output = (this - that - zoneDelta) / 6048e5; - break; // 1000 * 60 * 60 * 24 * 7, negate dst - default: - output = this - that; - } - - return asFloat ? output : absFloor(output); - } - - function monthDiff(a, b) { - if (a.date() < b.date()) { - // end-of-month calculations work correct when the start month has more - // days than the end month. - return -monthDiff(b, a); - } - // difference in months - var wholeMonthDiff = (b.year() - a.year()) * 12 + (b.month() - a.month()), - // b is in (anchor - 1 month, anchor + 1 month) - anchor = a.clone().add(wholeMonthDiff, 'months'), - anchor2, - adjust; - - if (b - anchor < 0) { - anchor2 = a.clone().add(wholeMonthDiff - 1, 'months'); - // linear across the month - adjust = (b - anchor) / (anchor - anchor2); - } else { - anchor2 = a.clone().add(wholeMonthDiff + 1, 'months'); - // linear across the month - adjust = (b - anchor) / (anchor2 - anchor); - } - - //check for negative zero, return zero if negative zero - return -(wholeMonthDiff + adjust) || 0; - } - - hooks.defaultFormat = 'YYYY-MM-DDTHH:mm:ssZ'; - hooks.defaultFormatUtc = 'YYYY-MM-DDTHH:mm:ss[Z]'; - - function toString() { - return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ'); - } - - function toISOString(keepOffset) { - if (!this.isValid()) { - return null; - } - var utc = keepOffset !== true, - m = utc ? this.clone().utc() : this; - if (m.year() < 0 || m.year() > 9999) { - return formatMoment( - m, - utc - ? 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]' - : 'YYYYYY-MM-DD[T]HH:mm:ss.SSSZ' - ); - } - if (isFunction(Date.prototype.toISOString)) { - // native implementation is ~50x faster, use it when we can - if (utc) { - return this.toDate().toISOString(); - } else { - return new Date(this.valueOf() + this.utcOffset() * 60 * 1000) - .toISOString() - .replace('Z', formatMoment(m, 'Z')); - } - } - return formatMoment( - m, - utc ? 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]' : 'YYYY-MM-DD[T]HH:mm:ss.SSSZ' - ); - } - - /** - * Return a human readable representation of a moment that can - * also be evaluated to get a new moment which is the same - * - * @link https://nodejs.org/dist/latest/docs/api/util.html#util_custom_inspect_function_on_objects - */ - function inspect() { - if (!this.isValid()) { - return 'moment.invalid(/* ' + this._i + ' */)'; - } - var func = 'moment', - zone = '', - prefix, - year, - datetime, - suffix; - if (!this.isLocal()) { - func = this.utcOffset() === 0 ? 'moment.utc' : 'moment.parseZone'; - zone = 'Z'; - } - prefix = '[' + func + '("]'; - year = 0 <= this.year() && this.year() <= 9999 ? 'YYYY' : 'YYYYYY'; - datetime = '-MM-DD[T]HH:mm:ss.SSS'; - suffix = zone + '[")]'; - - return this.format(prefix + year + datetime + suffix); - } - - function format(inputString) { - if (!inputString) { - inputString = this.isUtc() - ? hooks.defaultFormatUtc - : hooks.defaultFormat; - } - var output = formatMoment(this, inputString); - return this.localeData().postformat(output); - } - - function from(time, withoutSuffix) { - if ( - this.isValid() && - ((isMoment(time) && time.isValid()) || createLocal(time).isValid()) - ) { - return createDuration({ to: this, from: time }) - .locale(this.locale()) - .humanize(!withoutSuffix); - } else { - return this.localeData().invalidDate(); - } - } - - function fromNow(withoutSuffix) { - return this.from(createLocal(), withoutSuffix); - } - - function to(time, withoutSuffix) { - if ( - this.isValid() && - ((isMoment(time) && time.isValid()) || createLocal(time).isValid()) - ) { - return createDuration({ from: this, to: time }) - .locale(this.locale()) - .humanize(!withoutSuffix); - } else { - return this.localeData().invalidDate(); - } - } - - function toNow(withoutSuffix) { - return this.to(createLocal(), withoutSuffix); - } - - // If passed a locale key, it will set the locale for this - // instance. Otherwise, it will return the locale configuration - // variables for this instance. - function locale(key) { - var newLocaleData; - - if (key === undefined) { - return this._locale._abbr; - } else { - newLocaleData = getLocale(key); - if (newLocaleData != null) { - this._locale = newLocaleData; - } - return this; - } - } - - var lang = deprecate( - 'moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.', - function (key) { - if (key === undefined) { - return this.localeData(); - } else { - return this.locale(key); - } - } - ); - - function localeData() { - return this._locale; - } - - var MS_PER_SECOND = 1000, - MS_PER_MINUTE = 60 * MS_PER_SECOND, - MS_PER_HOUR = 60 * MS_PER_MINUTE, - MS_PER_400_YEARS = (365 * 400 + 97) * 24 * MS_PER_HOUR; - - // actual modulo - handles negative numbers (for dates before 1970): - function mod$1(dividend, divisor) { - return ((dividend % divisor) + divisor) % divisor; - } - - function localStartOfDate(y, m, d) { - // the date constructor remaps years 0-99 to 1900-1999 - if (y < 100 && y >= 0) { - // preserve leap years using a full 400 year cycle, then reset - return new Date(y + 400, m, d) - MS_PER_400_YEARS; - } else { - return new Date(y, m, d).valueOf(); - } - } - - function utcStartOfDate(y, m, d) { - // Date.UTC remaps years 0-99 to 1900-1999 - if (y < 100 && y >= 0) { - // preserve leap years using a full 400 year cycle, then reset - return Date.UTC(y + 400, m, d) - MS_PER_400_YEARS; - } else { - return Date.UTC(y, m, d); - } - } - - function startOf(units) { - var time, startOfDate; - units = normalizeUnits(units); - if (units === undefined || units === 'millisecond' || !this.isValid()) { - return this; - } - - startOfDate = this._isUTC ? utcStartOfDate : localStartOfDate; - - switch (units) { - case 'year': - time = startOfDate(this.year(), 0, 1); - break; - case 'quarter': - time = startOfDate( - this.year(), - this.month() - (this.month() % 3), - 1 - ); - break; - case 'month': - time = startOfDate(this.year(), this.month(), 1); - break; - case 'week': - time = startOfDate( - this.year(), - this.month(), - this.date() - this.weekday() - ); - break; - case 'isoWeek': - time = startOfDate( - this.year(), - this.month(), - this.date() - (this.isoWeekday() - 1) - ); - break; - case 'day': - case 'date': - time = startOfDate(this.year(), this.month(), this.date()); - break; - case 'hour': - time = this._d.valueOf(); - time -= mod$1( - time + (this._isUTC ? 0 : this.utcOffset() * MS_PER_MINUTE), - MS_PER_HOUR - ); - break; - case 'minute': - time = this._d.valueOf(); - time -= mod$1(time, MS_PER_MINUTE); - break; - case 'second': - time = this._d.valueOf(); - time -= mod$1(time, MS_PER_SECOND); - break; - } - - this._d.setTime(time); - hooks.updateOffset(this, true); - return this; - } - - function endOf(units) { - var time, startOfDate; - units = normalizeUnits(units); - if (units === undefined || units === 'millisecond' || !this.isValid()) { - return this; - } - - startOfDate = this._isUTC ? utcStartOfDate : localStartOfDate; - - switch (units) { - case 'year': - time = startOfDate(this.year() + 1, 0, 1) - 1; - break; - case 'quarter': - time = - startOfDate( - this.year(), - this.month() - (this.month() % 3) + 3, - 1 - ) - 1; - break; - case 'month': - time = startOfDate(this.year(), this.month() + 1, 1) - 1; - break; - case 'week': - time = - startOfDate( - this.year(), - this.month(), - this.date() - this.weekday() + 7 - ) - 1; - break; - case 'isoWeek': - time = - startOfDate( - this.year(), - this.month(), - this.date() - (this.isoWeekday() - 1) + 7 - ) - 1; - break; - case 'day': - case 'date': - time = startOfDate(this.year(), this.month(), this.date() + 1) - 1; - break; - case 'hour': - time = this._d.valueOf(); - time += - MS_PER_HOUR - - mod$1( - time + (this._isUTC ? 0 : this.utcOffset() * MS_PER_MINUTE), - MS_PER_HOUR - ) - - 1; - break; - case 'minute': - time = this._d.valueOf(); - time += MS_PER_MINUTE - mod$1(time, MS_PER_MINUTE) - 1; - break; - case 'second': - time = this._d.valueOf(); - time += MS_PER_SECOND - mod$1(time, MS_PER_SECOND) - 1; - break; - } - - this._d.setTime(time); - hooks.updateOffset(this, true); - return this; - } - - function valueOf() { - return this._d.valueOf() - (this._offset || 0) * 60000; - } - - function unix() { - return Math.floor(this.valueOf() / 1000); - } - - function toDate() { - return new Date(this.valueOf()); - } - - function toArray() { - var m = this; - return [ - m.year(), - m.month(), - m.date(), - m.hour(), - m.minute(), - m.second(), - m.millisecond(), - ]; - } - - function toObject() { - var m = this; - return { - years: m.year(), - months: m.month(), - date: m.date(), - hours: m.hours(), - minutes: m.minutes(), - seconds: m.seconds(), - milliseconds: m.milliseconds(), - }; - } - - function toJSON() { - // new Date(NaN).toJSON() === null - return this.isValid() ? this.toISOString() : null; - } - - function isValid$2() { - return isValid(this); - } - - function parsingFlags() { - return extend({}, getParsingFlags(this)); - } - - function invalidAt() { - return getParsingFlags(this).overflow; - } - - function creationData() { - return { - input: this._i, - format: this._f, - locale: this._locale, - isUTC: this._isUTC, - strict: this._strict, - }; - } - - addFormatToken('N', 0, 0, 'eraAbbr'); - addFormatToken('NN', 0, 0, 'eraAbbr'); - addFormatToken('NNN', 0, 0, 'eraAbbr'); - addFormatToken('NNNN', 0, 0, 'eraName'); - addFormatToken('NNNNN', 0, 0, 'eraNarrow'); - - addFormatToken('y', ['y', 1], 'yo', 'eraYear'); - addFormatToken('y', ['yy', 2], 0, 'eraYear'); - addFormatToken('y', ['yyy', 3], 0, 'eraYear'); - addFormatToken('y', ['yyyy', 4], 0, 'eraYear'); - - addRegexToken('N', matchEraAbbr); - addRegexToken('NN', matchEraAbbr); - addRegexToken('NNN', matchEraAbbr); - addRegexToken('NNNN', matchEraName); - addRegexToken('NNNNN', matchEraNarrow); - - addParseToken(['N', 'NN', 'NNN', 'NNNN', 'NNNNN'], function ( - input, - array, - config, - token - ) { - var era = config._locale.erasParse(input, token, config._strict); - if (era) { - getParsingFlags(config).era = era; - } else { - getParsingFlags(config).invalidEra = input; - } - }); - - addRegexToken('y', matchUnsigned); - addRegexToken('yy', matchUnsigned); - addRegexToken('yyy', matchUnsigned); - addRegexToken('yyyy', matchUnsigned); - addRegexToken('yo', matchEraYearOrdinal); - - addParseToken(['y', 'yy', 'yyy', 'yyyy'], YEAR); - addParseToken(['yo'], function (input, array, config, token) { - var match; - if (config._locale._eraYearOrdinalRegex) { - match = input.match(config._locale._eraYearOrdinalRegex); - } - - if (config._locale.eraYearOrdinalParse) { - array[YEAR] = config._locale.eraYearOrdinalParse(input, match); - } else { - array[YEAR] = parseInt(input, 10); - } - }); - - function localeEras(m, format) { - var i, - l, - date, - eras = this._eras || getLocale('en')._eras; - for (i = 0, l = eras.length; i < l; ++i) { - switch (typeof eras[i].since) { - case 'string': - // truncate time - date = hooks(eras[i].since).startOf('day'); - eras[i].since = date.valueOf(); - break; - } - - switch (typeof eras[i].until) { - case 'undefined': - eras[i].until = +Infinity; - break; - case 'string': - // truncate time - date = hooks(eras[i].until).startOf('day').valueOf(); - eras[i].until = date.valueOf(); - break; - } - } - return eras; - } - - function localeErasParse(eraName, format, strict) { - var i, - l, - eras = this.eras(), - name, - abbr, - narrow; - eraName = eraName.toUpperCase(); - - for (i = 0, l = eras.length; i < l; ++i) { - name = eras[i].name.toUpperCase(); - abbr = eras[i].abbr.toUpperCase(); - narrow = eras[i].narrow.toUpperCase(); - - if (strict) { - switch (format) { - case 'N': - case 'NN': - case 'NNN': - if (abbr === eraName) { - return eras[i]; - } - break; - - case 'NNNN': - if (name === eraName) { - return eras[i]; - } - break; - - case 'NNNNN': - if (narrow === eraName) { - return eras[i]; - } - break; - } - } else if ([name, abbr, narrow].indexOf(eraName) >= 0) { - return eras[i]; - } - } - } - - function localeErasConvertYear(era, year) { - var dir = era.since <= era.until ? +1 : -1; - if (year === undefined) { - return hooks(era.since).year(); - } else { - return hooks(era.since).year() + (year - era.offset) * dir; - } - } - - function getEraName() { - var i, - l, - val, - eras = this.localeData().eras(); - for (i = 0, l = eras.length; i < l; ++i) { - // truncate time - val = this.clone().startOf('day').valueOf(); - - if (eras[i].since <= val && val <= eras[i].until) { - return eras[i].name; - } - if (eras[i].until <= val && val <= eras[i].since) { - return eras[i].name; - } - } - - return ''; - } - - function getEraNarrow() { - var i, - l, - val, - eras = this.localeData().eras(); - for (i = 0, l = eras.length; i < l; ++i) { - // truncate time - val = this.clone().startOf('day').valueOf(); - - if (eras[i].since <= val && val <= eras[i].until) { - return eras[i].narrow; - } - if (eras[i].until <= val && val <= eras[i].since) { - return eras[i].narrow; - } - } - - return ''; - } - - function getEraAbbr() { - var i, - l, - val, - eras = this.localeData().eras(); - for (i = 0, l = eras.length; i < l; ++i) { - // truncate time - val = this.clone().startOf('day').valueOf(); - - if (eras[i].since <= val && val <= eras[i].until) { - return eras[i].abbr; - } - if (eras[i].until <= val && val <= eras[i].since) { - return eras[i].abbr; - } - } - - return ''; - } - - function getEraYear() { - var i, - l, - dir, - val, - eras = this.localeData().eras(); - for (i = 0, l = eras.length; i < l; ++i) { - dir = eras[i].since <= eras[i].until ? +1 : -1; - - // truncate time - val = this.clone().startOf('day').valueOf(); - - if ( - (eras[i].since <= val && val <= eras[i].until) || - (eras[i].until <= val && val <= eras[i].since) - ) { - return ( - (this.year() - hooks(eras[i].since).year()) * dir + - eras[i].offset - ); - } - } - - return this.year(); - } - - function erasNameRegex(isStrict) { - if (!hasOwnProp(this, '_erasNameRegex')) { - computeErasParse.call(this); - } - return isStrict ? this._erasNameRegex : this._erasRegex; - } - - function erasAbbrRegex(isStrict) { - if (!hasOwnProp(this, '_erasAbbrRegex')) { - computeErasParse.call(this); - } - return isStrict ? this._erasAbbrRegex : this._erasRegex; - } - - function erasNarrowRegex(isStrict) { - if (!hasOwnProp(this, '_erasNarrowRegex')) { - computeErasParse.call(this); - } - return isStrict ? this._erasNarrowRegex : this._erasRegex; - } - - function matchEraAbbr(isStrict, locale) { - return locale.erasAbbrRegex(isStrict); - } - - function matchEraName(isStrict, locale) { - return locale.erasNameRegex(isStrict); - } - - function matchEraNarrow(isStrict, locale) { - return locale.erasNarrowRegex(isStrict); - } - - function matchEraYearOrdinal(isStrict, locale) { - return locale._eraYearOrdinalRegex || matchUnsigned; - } - - function computeErasParse() { - var abbrPieces = [], - namePieces = [], - narrowPieces = [], - mixedPieces = [], - i, - l, - eras = this.eras(); - - for (i = 0, l = eras.length; i < l; ++i) { - namePieces.push(regexEscape(eras[i].name)); - abbrPieces.push(regexEscape(eras[i].abbr)); - narrowPieces.push(regexEscape(eras[i].narrow)); - - mixedPieces.push(regexEscape(eras[i].name)); - mixedPieces.push(regexEscape(eras[i].abbr)); - mixedPieces.push(regexEscape(eras[i].narrow)); - } - - this._erasRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i'); - this._erasNameRegex = new RegExp('^(' + namePieces.join('|') + ')', 'i'); - this._erasAbbrRegex = new RegExp('^(' + abbrPieces.join('|') + ')', 'i'); - this._erasNarrowRegex = new RegExp( - '^(' + narrowPieces.join('|') + ')', - 'i' - ); - } - - // FORMATTING - - addFormatToken(0, ['gg', 2], 0, function () { - return this.weekYear() % 100; - }); - - addFormatToken(0, ['GG', 2], 0, function () { - return this.isoWeekYear() % 100; - }); - - function addWeekYearFormatToken(token, getter) { - addFormatToken(0, [token, token.length], 0, getter); - } - - addWeekYearFormatToken('gggg', 'weekYear'); - addWeekYearFormatToken('ggggg', 'weekYear'); - addWeekYearFormatToken('GGGG', 'isoWeekYear'); - addWeekYearFormatToken('GGGGG', 'isoWeekYear'); - - // ALIASES - - addUnitAlias('weekYear', 'gg'); - addUnitAlias('isoWeekYear', 'GG'); - - // PRIORITY - - addUnitPriority('weekYear', 1); - addUnitPriority('isoWeekYear', 1); - - // PARSING - - addRegexToken('G', matchSigned); - addRegexToken('g', matchSigned); - addRegexToken('GG', match1to2, match2); - addRegexToken('gg', match1to2, match2); - addRegexToken('GGGG', match1to4, match4); - addRegexToken('gggg', match1to4, match4); - addRegexToken('GGGGG', match1to6, match6); - addRegexToken('ggggg', match1to6, match6); - - addWeekParseToken(['gggg', 'ggggg', 'GGGG', 'GGGGG'], function ( - input, - week, - config, - token - ) { - week[token.substr(0, 2)] = toInt(input); - }); - - addWeekParseToken(['gg', 'GG'], function (input, week, config, token) { - week[token] = hooks.parseTwoDigitYear(input); - }); - - // MOMENTS - - function getSetWeekYear(input) { - return getSetWeekYearHelper.call( - this, - input, - this.week(), - this.weekday(), - this.localeData()._week.dow, - this.localeData()._week.doy - ); - } - - function getSetISOWeekYear(input) { - return getSetWeekYearHelper.call( - this, - input, - this.isoWeek(), - this.isoWeekday(), - 1, - 4 - ); - } - - function getISOWeeksInYear() { - return weeksInYear(this.year(), 1, 4); - } - - function getISOWeeksInISOWeekYear() { - return weeksInYear(this.isoWeekYear(), 1, 4); - } - - function getWeeksInYear() { - var weekInfo = this.localeData()._week; - return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy); - } - - function getWeeksInWeekYear() { - var weekInfo = this.localeData()._week; - return weeksInYear(this.weekYear(), weekInfo.dow, weekInfo.doy); - } - - function getSetWeekYearHelper(input, week, weekday, dow, doy) { - var weeksTarget; - if (input == null) { - return weekOfYear(this, dow, doy).year; - } else { - weeksTarget = weeksInYear(input, dow, doy); - if (week > weeksTarget) { - week = weeksTarget; - } - return setWeekAll.call(this, input, week, weekday, dow, doy); - } - } - - function setWeekAll(weekYear, week, weekday, dow, doy) { - var dayOfYearData = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy), - date = createUTCDate(dayOfYearData.year, 0, dayOfYearData.dayOfYear); - - this.year(date.getUTCFullYear()); - this.month(date.getUTCMonth()); - this.date(date.getUTCDate()); - return this; - } - - // FORMATTING - - addFormatToken('Q', 0, 'Qo', 'quarter'); - - // ALIASES - - addUnitAlias('quarter', 'Q'); - - // PRIORITY - - addUnitPriority('quarter', 7); - - // PARSING - - addRegexToken('Q', match1); - addParseToken('Q', function (input, array) { - array[MONTH] = (toInt(input) - 1) * 3; - }); - - // MOMENTS - - function getSetQuarter(input) { - return input == null - ? Math.ceil((this.month() + 1) / 3) - : this.month((input - 1) * 3 + (this.month() % 3)); - } - - // FORMATTING - - addFormatToken('D', ['DD', 2], 'Do', 'date'); - - // ALIASES - - addUnitAlias('date', 'D'); - - // PRIORITY - addUnitPriority('date', 9); - - // PARSING - - addRegexToken('D', match1to2); - addRegexToken('DD', match1to2, match2); - addRegexToken('Do', function (isStrict, locale) { - // TODO: Remove "ordinalParse" fallback in next major release. - return isStrict - ? locale._dayOfMonthOrdinalParse || locale._ordinalParse - : locale._dayOfMonthOrdinalParseLenient; - }); - - addParseToken(['D', 'DD'], DATE); - addParseToken('Do', function (input, array) { - array[DATE] = toInt(input.match(match1to2)[0]); - }); - - // MOMENTS - - var getSetDayOfMonth = makeGetSet('Date', true); - - // FORMATTING - - addFormatToken('DDD', ['DDDD', 3], 'DDDo', 'dayOfYear'); - - // ALIASES - - addUnitAlias('dayOfYear', 'DDD'); - - // PRIORITY - addUnitPriority('dayOfYear', 4); - - // PARSING - - addRegexToken('DDD', match1to3); - addRegexToken('DDDD', match3); - addParseToken(['DDD', 'DDDD'], function (input, array, config) { - config._dayOfYear = toInt(input); - }); - - // HELPERS - - // MOMENTS - - function getSetDayOfYear(input) { - var dayOfYear = - Math.round( - (this.clone().startOf('day') - this.clone().startOf('year')) / 864e5 - ) + 1; - return input == null ? dayOfYear : this.add(input - dayOfYear, 'd'); - } - - // FORMATTING - - addFormatToken('m', ['mm', 2], 0, 'minute'); - - // ALIASES - - addUnitAlias('minute', 'm'); - - // PRIORITY - - addUnitPriority('minute', 14); - - // PARSING - - addRegexToken('m', match1to2); - addRegexToken('mm', match1to2, match2); - addParseToken(['m', 'mm'], MINUTE); - - // MOMENTS - - var getSetMinute = makeGetSet('Minutes', false); - - // FORMATTING - - addFormatToken('s', ['ss', 2], 0, 'second'); - - // ALIASES - - addUnitAlias('second', 's'); - - // PRIORITY - - addUnitPriority('second', 15); - - // PARSING - - addRegexToken('s', match1to2); - addRegexToken('ss', match1to2, match2); - addParseToken(['s', 'ss'], SECOND); - - // MOMENTS - - var getSetSecond = makeGetSet('Seconds', false); - - // FORMATTING - - addFormatToken('S', 0, 0, function () { - return ~~(this.millisecond() / 100); - }); - - addFormatToken(0, ['SS', 2], 0, function () { - return ~~(this.millisecond() / 10); - }); - - addFormatToken(0, ['SSS', 3], 0, 'millisecond'); - addFormatToken(0, ['SSSS', 4], 0, function () { - return this.millisecond() * 10; - }); - addFormatToken(0, ['SSSSS', 5], 0, function () { - return this.millisecond() * 100; - }); - addFormatToken(0, ['SSSSSS', 6], 0, function () { - return this.millisecond() * 1000; - }); - addFormatToken(0, ['SSSSSSS', 7], 0, function () { - return this.millisecond() * 10000; - }); - addFormatToken(0, ['SSSSSSSS', 8], 0, function () { - return this.millisecond() * 100000; - }); - addFormatToken(0, ['SSSSSSSSS', 9], 0, function () { - return this.millisecond() * 1000000; - }); - - // ALIASES - - addUnitAlias('millisecond', 'ms'); - - // PRIORITY - - addUnitPriority('millisecond', 16); - - // PARSING - - addRegexToken('S', match1to3, match1); - addRegexToken('SS', match1to3, match2); - addRegexToken('SSS', match1to3, match3); - - var token, getSetMillisecond; - for (token = 'SSSS'; token.length <= 9; token += 'S') { - addRegexToken(token, matchUnsigned); - } - - function parseMs(input, array) { - array[MILLISECOND] = toInt(('0.' + input) * 1000); - } - - for (token = 'S'; token.length <= 9; token += 'S') { - addParseToken(token, parseMs); - } - - getSetMillisecond = makeGetSet('Milliseconds', false); - - // FORMATTING - - addFormatToken('z', 0, 0, 'zoneAbbr'); - addFormatToken('zz', 0, 0, 'zoneName'); - - // MOMENTS - - function getZoneAbbr() { - return this._isUTC ? 'UTC' : ''; - } - - function getZoneName() { - return this._isUTC ? 'Coordinated Universal Time' : ''; - } - - var proto = Moment.prototype; - - proto.add = add; - proto.calendar = calendar$1; - proto.clone = clone; - proto.diff = diff; - proto.endOf = endOf; - proto.format = format; - proto.from = from; - proto.fromNow = fromNow; - proto.to = to; - proto.toNow = toNow; - proto.get = stringGet; - proto.invalidAt = invalidAt; - proto.isAfter = isAfter; - proto.isBefore = isBefore; - proto.isBetween = isBetween; - proto.isSame = isSame; - proto.isSameOrAfter = isSameOrAfter; - proto.isSameOrBefore = isSameOrBefore; - proto.isValid = isValid$2; - proto.lang = lang; - proto.locale = locale; - proto.localeData = localeData; - proto.max = prototypeMax; - proto.min = prototypeMin; - proto.parsingFlags = parsingFlags; - proto.set = stringSet; - proto.startOf = startOf; - proto.subtract = subtract; - proto.toArray = toArray; - proto.toObject = toObject; - proto.toDate = toDate; - proto.toISOString = toISOString; - proto.inspect = inspect; - if (typeof Symbol !== 'undefined' && Symbol.for != null) { - proto[Symbol.for('nodejs.util.inspect.custom')] = function () { - return 'Moment<' + this.format() + '>'; - }; - } - proto.toJSON = toJSON; - proto.toString = toString; - proto.unix = unix; - proto.valueOf = valueOf; - proto.creationData = creationData; - proto.eraName = getEraName; - proto.eraNarrow = getEraNarrow; - proto.eraAbbr = getEraAbbr; - proto.eraYear = getEraYear; - proto.year = getSetYear; - proto.isLeapYear = getIsLeapYear; - proto.weekYear = getSetWeekYear; - proto.isoWeekYear = getSetISOWeekYear; - proto.quarter = proto.quarters = getSetQuarter; - proto.month = getSetMonth; - proto.daysInMonth = getDaysInMonth; - proto.week = proto.weeks = getSetWeek; - proto.isoWeek = proto.isoWeeks = getSetISOWeek; - proto.weeksInYear = getWeeksInYear; - proto.weeksInWeekYear = getWeeksInWeekYear; - proto.isoWeeksInYear = getISOWeeksInYear; - proto.isoWeeksInISOWeekYear = getISOWeeksInISOWeekYear; - proto.date = getSetDayOfMonth; - proto.day = proto.days = getSetDayOfWeek; - proto.weekday = getSetLocaleDayOfWeek; - proto.isoWeekday = getSetISODayOfWeek; - proto.dayOfYear = getSetDayOfYear; - proto.hour = proto.hours = getSetHour; - proto.minute = proto.minutes = getSetMinute; - proto.second = proto.seconds = getSetSecond; - proto.millisecond = proto.milliseconds = getSetMillisecond; - proto.utcOffset = getSetOffset; - proto.utc = setOffsetToUTC; - proto.local = setOffsetToLocal; - proto.parseZone = setOffsetToParsedOffset; - proto.hasAlignedHourOffset = hasAlignedHourOffset; - proto.isDST = isDaylightSavingTime; - proto.isLocal = isLocal; - proto.isUtcOffset = isUtcOffset; - proto.isUtc = isUtc; - proto.isUTC = isUtc; - proto.zoneAbbr = getZoneAbbr; - proto.zoneName = getZoneName; - proto.dates = deprecate( - 'dates accessor is deprecated. Use date instead.', - getSetDayOfMonth - ); - proto.months = deprecate( - 'months accessor is deprecated. Use month instead', - getSetMonth - ); - proto.years = deprecate( - 'years accessor is deprecated. Use year instead', - getSetYear - ); - proto.zone = deprecate( - 'moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/', - getSetZone - ); - proto.isDSTShifted = deprecate( - 'isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information', - isDaylightSavingTimeShifted - ); - - function createUnix(input) { - return createLocal(input * 1000); - } - - function createInZone() { - return createLocal.apply(null, arguments).parseZone(); - } - - function preParsePostFormat(string) { - return string; - } - - var proto$1 = Locale.prototype; - - proto$1.calendar = calendar; - proto$1.longDateFormat = longDateFormat; - proto$1.invalidDate = invalidDate; - proto$1.ordinal = ordinal; - proto$1.preparse = preParsePostFormat; - proto$1.postformat = preParsePostFormat; - proto$1.relativeTime = relativeTime; - proto$1.pastFuture = pastFuture; - proto$1.set = set; - proto$1.eras = localeEras; - proto$1.erasParse = localeErasParse; - proto$1.erasConvertYear = localeErasConvertYear; - proto$1.erasAbbrRegex = erasAbbrRegex; - proto$1.erasNameRegex = erasNameRegex; - proto$1.erasNarrowRegex = erasNarrowRegex; - - proto$1.months = localeMonths; - proto$1.monthsShort = localeMonthsShort; - proto$1.monthsParse = localeMonthsParse; - proto$1.monthsRegex = monthsRegex; - proto$1.monthsShortRegex = monthsShortRegex; - proto$1.week = localeWeek; - proto$1.firstDayOfYear = localeFirstDayOfYear; - proto$1.firstDayOfWeek = localeFirstDayOfWeek; - - proto$1.weekdays = localeWeekdays; - proto$1.weekdaysMin = localeWeekdaysMin; - proto$1.weekdaysShort = localeWeekdaysShort; - proto$1.weekdaysParse = localeWeekdaysParse; - - proto$1.weekdaysRegex = weekdaysRegex; - proto$1.weekdaysShortRegex = weekdaysShortRegex; - proto$1.weekdaysMinRegex = weekdaysMinRegex; - - proto$1.isPM = localeIsPM; - proto$1.meridiem = localeMeridiem; - - function get$1(format, index, field, setter) { - var locale = getLocale(), - utc = createUTC().set(setter, index); - return locale[field](utc, format); - } - - function listMonthsImpl(format, index, field) { - if (isNumber(format)) { - index = format; - format = undefined; - } - - format = format || ''; - - if (index != null) { - return get$1(format, index, field, 'month'); - } - - var i, - out = []; - for (i = 0; i < 12; i++) { - out[i] = get$1(format, i, field, 'month'); - } - return out; - } - - // () - // (5) - // (fmt, 5) - // (fmt) - // (true) - // (true, 5) - // (true, fmt, 5) - // (true, fmt) - function listWeekdaysImpl(localeSorted, format, index, field) { - if (typeof localeSorted === 'boolean') { - if (isNumber(format)) { - index = format; - format = undefined; - } - - format = format || ''; - } else { - format = localeSorted; - index = format; - localeSorted = false; - - if (isNumber(format)) { - index = format; - format = undefined; - } - - format = format || ''; - } - - var locale = getLocale(), - shift = localeSorted ? locale._week.dow : 0, - i, - out = []; - - if (index != null) { - return get$1(format, (index + shift) % 7, field, 'day'); - } - - for (i = 0; i < 7; i++) { - out[i] = get$1(format, (i + shift) % 7, field, 'day'); - } - return out; - } - - function listMonths(format, index) { - return listMonthsImpl(format, index, 'months'); - } - - function listMonthsShort(format, index) { - return listMonthsImpl(format, index, 'monthsShort'); - } - - function listWeekdays(localeSorted, format, index) { - return listWeekdaysImpl(localeSorted, format, index, 'weekdays'); - } - - function listWeekdaysShort(localeSorted, format, index) { - return listWeekdaysImpl(localeSorted, format, index, 'weekdaysShort'); - } - - function listWeekdaysMin(localeSorted, format, index) { - return listWeekdaysImpl(localeSorted, format, index, 'weekdaysMin'); - } - - getSetGlobalLocale('en', { - eras: [ - { - since: '0001-01-01', - until: +Infinity, - offset: 1, - name: 'Anno Domini', - narrow: 'AD', - abbr: 'AD', - }, - { - since: '0000-12-31', - until: -Infinity, - offset: 1, - name: 'Before Christ', - narrow: 'BC', - abbr: 'BC', - }, - ], - dayOfMonthOrdinalParse: /\d{1,2}(th|st|nd|rd)/, - ordinal: function (number) { - var b = number % 10, - output = - toInt((number % 100) / 10) === 1 - ? 'th' - : b === 1 - ? 'st' - : b === 2 - ? 'nd' - : b === 3 - ? 'rd' - : 'th'; - return number + output; - }, - }); - - // Side effect imports - - hooks.lang = deprecate( - 'moment.lang is deprecated. Use moment.locale instead.', - getSetGlobalLocale - ); - hooks.langData = deprecate( - 'moment.langData is deprecated. Use moment.localeData instead.', - getLocale - ); - - var mathAbs = Math.abs; - - function abs() { - var data = this._data; - - this._milliseconds = mathAbs(this._milliseconds); - this._days = mathAbs(this._days); - this._months = mathAbs(this._months); - - data.milliseconds = mathAbs(data.milliseconds); - data.seconds = mathAbs(data.seconds); - data.minutes = mathAbs(data.minutes); - data.hours = mathAbs(data.hours); - data.months = mathAbs(data.months); - data.years = mathAbs(data.years); - - return this; - } - - function addSubtract$1(duration, input, value, direction) { - var other = createDuration(input, value); - - duration._milliseconds += direction * other._milliseconds; - duration._days += direction * other._days; - duration._months += direction * other._months; - - return duration._bubble(); - } - - // supports only 2.0-style add(1, 's') or add(duration) - function add$1(input, value) { - return addSubtract$1(this, input, value, 1); - } - - // supports only 2.0-style subtract(1, 's') or subtract(duration) - function subtract$1(input, value) { - return addSubtract$1(this, input, value, -1); - } - - function absCeil(number) { - if (number < 0) { - return Math.floor(number); - } else { - return Math.ceil(number); - } - } - - function bubble() { - var milliseconds = this._milliseconds, - days = this._days, - months = this._months, - data = this._data, - seconds, - minutes, - hours, - years, - monthsFromDays; - - // if we have a mix of positive and negative values, bubble down first - // check: https://github.com/moment/moment/issues/2166 - if ( - !( - (milliseconds >= 0 && days >= 0 && months >= 0) || - (milliseconds <= 0 && days <= 0 && months <= 0) - ) - ) { - milliseconds += absCeil(monthsToDays(months) + days) * 864e5; - days = 0; - months = 0; - } - - // The following code bubbles up values, see the tests for - // examples of what that means. - data.milliseconds = milliseconds % 1000; - - seconds = absFloor(milliseconds / 1000); - data.seconds = seconds % 60; - - minutes = absFloor(seconds / 60); - data.minutes = minutes % 60; - - hours = absFloor(minutes / 60); - data.hours = hours % 24; - - days += absFloor(hours / 24); - - // convert days to months - monthsFromDays = absFloor(daysToMonths(days)); - months += monthsFromDays; - days -= absCeil(monthsToDays(monthsFromDays)); - - // 12 months -> 1 year - years = absFloor(months / 12); - months %= 12; - - data.days = days; - data.months = months; - data.years = years; - - return this; - } - - function daysToMonths(days) { - // 400 years have 146097 days (taking into account leap year rules) - // 400 years have 12 months === 4800 - return (days * 4800) / 146097; - } - - function monthsToDays(months) { - // the reverse of daysToMonths - return (months * 146097) / 4800; - } - - function as(units) { - if (!this.isValid()) { - return NaN; - } - var days, - months, - milliseconds = this._milliseconds; - - units = normalizeUnits(units); - - if (units === 'month' || units === 'quarter' || units === 'year') { - days = this._days + milliseconds / 864e5; - months = this._months + daysToMonths(days); - switch (units) { - case 'month': - return months; - case 'quarter': - return months / 3; - case 'year': - return months / 12; - } - } else { - // handle milliseconds separately because of floating point math errors (issue #1867) - days = this._days + Math.round(monthsToDays(this._months)); - switch (units) { - case 'week': - return days / 7 + milliseconds / 6048e5; - case 'day': - return days + milliseconds / 864e5; - case 'hour': - return days * 24 + milliseconds / 36e5; - case 'minute': - return days * 1440 + milliseconds / 6e4; - case 'second': - return days * 86400 + milliseconds / 1000; - // Math.floor prevents floating point math errors here - case 'millisecond': - return Math.floor(days * 864e5) + milliseconds; - default: - throw new Error('Unknown unit ' + units); - } - } - } - - // TODO: Use this.as('ms')? - function valueOf$1() { - if (!this.isValid()) { - return NaN; - } - return ( - this._milliseconds + - this._days * 864e5 + - (this._months % 12) * 2592e6 + - toInt(this._months / 12) * 31536e6 - ); - } - - function makeAs(alias) { - return function () { - return this.as(alias); - }; - } - - var asMilliseconds = makeAs('ms'), - asSeconds = makeAs('s'), - asMinutes = makeAs('m'), - asHours = makeAs('h'), - asDays = makeAs('d'), - asWeeks = makeAs('w'), - asMonths = makeAs('M'), - asQuarters = makeAs('Q'), - asYears = makeAs('y'); - - function clone$1() { - return createDuration(this); - } - - function get$2(units) { - units = normalizeUnits(units); - return this.isValid() ? this[units + 's']() : NaN; - } - - function makeGetter(name) { - return function () { - return this.isValid() ? this._data[name] : NaN; - }; - } - - var milliseconds = makeGetter('milliseconds'), - seconds = makeGetter('seconds'), - minutes = makeGetter('minutes'), - hours = makeGetter('hours'), - days = makeGetter('days'), - months = makeGetter('months'), - years = makeGetter('years'); - - function weeks() { - return absFloor(this.days() / 7); - } - - var round = Math.round, - thresholds = { - ss: 44, // a few seconds to seconds - s: 45, // seconds to minute - m: 45, // minutes to hour - h: 22, // hours to day - d: 26, // days to month/week - w: null, // weeks to month - M: 11, // months to year - }; - - // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize - function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) { - return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture); - } - - function relativeTime$1(posNegDuration, withoutSuffix, thresholds, locale) { - var duration = createDuration(posNegDuration).abs(), - seconds = round(duration.as('s')), - minutes = round(duration.as('m')), - hours = round(duration.as('h')), - days = round(duration.as('d')), - months = round(duration.as('M')), - weeks = round(duration.as('w')), - years = round(duration.as('y')), - a = - (seconds <= thresholds.ss && ['s', seconds]) || - (seconds < thresholds.s && ['ss', seconds]) || - (minutes <= 1 && ['m']) || - (minutes < thresholds.m && ['mm', minutes]) || - (hours <= 1 && ['h']) || - (hours < thresholds.h && ['hh', hours]) || - (days <= 1 && ['d']) || - (days < thresholds.d && ['dd', days]); - - if (thresholds.w != null) { - a = - a || - (weeks <= 1 && ['w']) || - (weeks < thresholds.w && ['ww', weeks]); - } - a = a || - (months <= 1 && ['M']) || - (months < thresholds.M && ['MM', months]) || - (years <= 1 && ['y']) || ['yy', years]; - - a[2] = withoutSuffix; - a[3] = +posNegDuration > 0; - a[4] = locale; - return substituteTimeAgo.apply(null, a); - } - - // This function allows you to set the rounding function for relative time strings - function getSetRelativeTimeRounding(roundingFunction) { - if (roundingFunction === undefined) { - return round; - } - if (typeof roundingFunction === 'function') { - round = roundingFunction; - return true; - } - return false; - } - - // This function allows you to set a threshold for relative time strings - function getSetRelativeTimeThreshold(threshold, limit) { - if (thresholds[threshold] === undefined) { - return false; - } - if (limit === undefined) { - return thresholds[threshold]; - } - thresholds[threshold] = limit; - if (threshold === 's') { - thresholds.ss = limit - 1; - } - return true; - } - - function humanize(argWithSuffix, argThresholds) { - if (!this.isValid()) { - return this.localeData().invalidDate(); - } - - var withSuffix = false, - th = thresholds, - locale, - output; - - if (typeof argWithSuffix === 'object') { - argThresholds = argWithSuffix; - argWithSuffix = false; - } - if (typeof argWithSuffix === 'boolean') { - withSuffix = argWithSuffix; - } - if (typeof argThresholds === 'object') { - th = Object.assign({}, thresholds, argThresholds); - if (argThresholds.s != null && argThresholds.ss == null) { - th.ss = argThresholds.s - 1; - } - } - - locale = this.localeData(); - output = relativeTime$1(this, !withSuffix, th, locale); - - if (withSuffix) { - output = locale.pastFuture(+this, output); - } - - return locale.postformat(output); - } - - var abs$1 = Math.abs; - - function sign(x) { - return (x > 0) - (x < 0) || +x; - } - - function toISOString$1() { - // for ISO strings we do not use the normal bubbling rules: - // * milliseconds bubble up until they become hours - // * days do not bubble at all - // * months bubble up until they become years - // This is because there is no context-free conversion between hours and days - // (think of clock changes) - // and also not between days and months (28-31 days per month) - if (!this.isValid()) { - return this.localeData().invalidDate(); - } - - var seconds = abs$1(this._milliseconds) / 1000, - days = abs$1(this._days), - months = abs$1(this._months), - minutes, - hours, - years, - s, - total = this.asSeconds(), - totalSign, - ymSign, - daysSign, - hmsSign; - - if (!total) { - // this is the same as C#'s (Noda) and python (isodate)... - // but not other JS (goog.date) - return 'P0D'; - } - - // 3600 seconds -> 60 minutes -> 1 hour - minutes = absFloor(seconds / 60); - hours = absFloor(minutes / 60); - seconds %= 60; - minutes %= 60; - - // 12 months -> 1 year - years = absFloor(months / 12); - months %= 12; - - // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js - s = seconds ? seconds.toFixed(3).replace(/\.?0+$/, '') : ''; - - totalSign = total < 0 ? '-' : ''; - ymSign = sign(this._months) !== sign(total) ? '-' : ''; - daysSign = sign(this._days) !== sign(total) ? '-' : ''; - hmsSign = sign(this._milliseconds) !== sign(total) ? '-' : ''; - - return ( - totalSign + - 'P' + - (years ? ymSign + years + 'Y' : '') + - (months ? ymSign + months + 'M' : '') + - (days ? daysSign + days + 'D' : '') + - (hours || minutes || seconds ? 'T' : '') + - (hours ? hmsSign + hours + 'H' : '') + - (minutes ? hmsSign + minutes + 'M' : '') + - (seconds ? hmsSign + s + 'S' : '') - ); - } - - var proto$2 = Duration.prototype; - - proto$2.isValid = isValid$1; - proto$2.abs = abs; - proto$2.add = add$1; - proto$2.subtract = subtract$1; - proto$2.as = as; - proto$2.asMilliseconds = asMilliseconds; - proto$2.asSeconds = asSeconds; - proto$2.asMinutes = asMinutes; - proto$2.asHours = asHours; - proto$2.asDays = asDays; - proto$2.asWeeks = asWeeks; - proto$2.asMonths = asMonths; - proto$2.asQuarters = asQuarters; - proto$2.asYears = asYears; - proto$2.valueOf = valueOf$1; - proto$2._bubble = bubble; - proto$2.clone = clone$1; - proto$2.get = get$2; - proto$2.milliseconds = milliseconds; - proto$2.seconds = seconds; - proto$2.minutes = minutes; - proto$2.hours = hours; - proto$2.days = days; - proto$2.weeks = weeks; - proto$2.months = months; - proto$2.years = years; - proto$2.humanize = humanize; - proto$2.toISOString = toISOString$1; - proto$2.toString = toISOString$1; - proto$2.toJSON = toISOString$1; - proto$2.locale = locale; - proto$2.localeData = localeData; - - proto$2.toIsoString = deprecate( - 'toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)', - toISOString$1 - ); - proto$2.lang = lang; - - // FORMATTING - - addFormatToken('X', 0, 0, 'unix'); - addFormatToken('x', 0, 0, 'valueOf'); - - // PARSING - - addRegexToken('x', matchSigned); - addRegexToken('X', matchTimestamp); - addParseToken('X', function (input, array, config) { - config._d = new Date(parseFloat(input) * 1000); - }); - addParseToken('x', function (input, array, config) { - config._d = new Date(toInt(input)); - }); - - //! moment.js - - hooks.version = '2.29.1'; - - setHookCallback(createLocal); - - hooks.fn = proto; - hooks.min = min; - hooks.max = max; - hooks.now = now; - hooks.utc = createUTC; - hooks.unix = createUnix; - hooks.months = listMonths; - hooks.isDate = isDate; - hooks.locale = getSetGlobalLocale; - hooks.invalid = createInvalid; - hooks.duration = createDuration; - hooks.isMoment = isMoment; - hooks.weekdays = listWeekdays; - hooks.parseZone = createInZone; - hooks.localeData = getLocale; - hooks.isDuration = isDuration; - hooks.monthsShort = listMonthsShort; - hooks.weekdaysMin = listWeekdaysMin; - hooks.defineLocale = defineLocale; - hooks.updateLocale = updateLocale; - hooks.locales = listLocales; - hooks.weekdaysShort = listWeekdaysShort; - hooks.normalizeUnits = normalizeUnits; - hooks.relativeTimeRounding = getSetRelativeTimeRounding; - hooks.relativeTimeThreshold = getSetRelativeTimeThreshold; - hooks.calendarFormat = getCalendarFormat; - hooks.prototype = proto; - - // currently HTML5 input type only supports 24-hour formats - hooks.HTML5_FMT = { - DATETIME_LOCAL: 'YYYY-MM-DDTHH:mm', // - DATETIME_LOCAL_SECONDS: 'YYYY-MM-DDTHH:mm:ss', // - DATETIME_LOCAL_MS: 'YYYY-MM-DDTHH:mm:ss.SSS', // - DATE: 'YYYY-MM-DD', // - TIME: 'HH:mm', // - TIME_SECONDS: 'HH:mm:ss', // - TIME_MS: 'HH:mm:ss.SSS', // - WEEK: 'GGGG-[W]WW', // - MONTH: 'YYYY-MM', // - }; - - return hooks; - -}))); From 8cd291ef2aa4876c45855ea592f845e2eadb501b Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sun, 21 Nov 2021 21:41:53 -0700 Subject: [PATCH 058/169] Store dates as strings --- database/scores/games.js | 4 ++-- routes/submit.js | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/database/scores/games.js b/database/scores/games.js index 96463ec..9af0b89 100644 --- a/database/scores/games.js +++ b/database/scores/games.js @@ -1,5 +1,4 @@ const database = require('./../database'); -const moment = require('moment'); @@ -51,8 +50,9 @@ async function retrieveByTeamDivisionAndSeason(teamID, divisionID, seasonID) { teamScore = opponentIsTeam2 ? row[6] : row[7]; opponentScore = opponentIsTeam2 ? row[7] : row[6]; - gamesList.push(new Game(row[0], moment(row[3]), teamID, opponentID, teamScore, opponentScore)); + gamesList.push(new Game(row[0], row[3].toISOString().slice(0,10), teamID, opponentID, teamScore, opponentScore)); }); + console.log(gamesList); return gamesList; } diff --git a/routes/submit.js b/routes/submit.js index 8510303..fcd660b 100644 --- a/routes/submit.js +++ b/routes/submit.js @@ -2,11 +2,10 @@ var express = require('express'); var router = express.Router(); var genders = require('../database/scores/genders'); var games = require('../database/scores/games'); -var moment = require('moment'); /* GET submit page. */ router.get('/', function(req, res, next) { - res.render('submit', { title: 'Submit Score', date: moment().format('YYYY-MM-DD') }); + res.render('submit', { title: 'Submit Score' }); }); /* POST submit page. */ From fc60e5186a0bd47aa1d28690b832e3876bf6cf26 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sun, 21 Nov 2021 21:42:03 -0700 Subject: [PATCH 059/169] Fix bug in teams.js --- database/scores/teams.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/database/scores/teams.js b/database/scores/teams.js index 7aa3d32..893cc34 100644 --- a/database/scores/teams.js +++ b/database/scores/teams.js @@ -55,8 +55,7 @@ async function getFromID(id) { const query = `SELECT team_name FROM scores.teams WHERE team_id = $1;`; - const name = await database.executeQuery(query, [teamID])[0][0]; - + const name = (await database.executeQuery(query, [id]))[0][0]; return new Team(id, name); } From 4f2b496cf2241c2acfeee3dcf3babc2cd65dd4f9 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sun, 21 Nov 2021 21:59:07 -0700 Subject: [PATCH 060/169] Add table of games to index page --- public/scripts/data.js | 12 +++++++ public/scripts/index.js | 68 ++++++++++++++++++++++++++++++++++-- public/stylesheets/index.css | 18 ++++++++++ routes/data.js | 13 ++++++- views/index.pug | 13 +++---- 5 files changed, 114 insertions(+), 10 deletions(-) diff --git a/public/scripts/data.js b/public/scripts/data.js index 7e21436..61f3923 100644 --- a/public/scripts/data.js +++ b/public/scripts/data.js @@ -26,4 +26,16 @@ export async function getTeams(sportID) { const response = await fetch(`/data/teams?sport=${+sportID}`); const teamsList = await response.json(); return teamsList; +} + +export async function getTeamName(teamID) { + const response = await fetch(`/data/team?team=${+teamID}`); + const team = await response.json(); + return team.name; +} + +export async function getGames(teamID, divisionID, seasonID) { + const response = await fetch(`/data/games?team=${+teamID}&division=${+divisionID}&season=${+seasonID}`); + const gamesList = await response.json(); + return gamesList; } \ No newline at end of file diff --git a/public/scripts/index.js b/public/scripts/index.js index 38627c0..4111353 100644 --- a/public/scripts/index.js +++ b/public/scripts/index.js @@ -1,11 +1,12 @@ import * as Data from "./data.js"; - const sportDropdown = document.getElementById('sport-dropdown'); const seasonDropdown = document.getElementById('year-dropdown'); const genderDropdown = document.getElementById('gender-dropdown'); const divisionDropdown = document.getElementById('division-dropdown'); const teamDropdown = document.getElementById('team-dropdown'); +const gamesTable = document.getElementById('games-table'); +const gamesTableHeader = document.getElementById('games-table-header'); @@ -38,7 +39,6 @@ async function listSports() { }); listGenders(); - listTeams(); } listSports(); @@ -76,6 +76,7 @@ async function listDivisions() { divisionDropdown.appendChild(option); }); } + listTeams(); } async function listTeams() { @@ -93,6 +94,66 @@ async function listTeams() { teamDropdown.appendChild(option); }); } + + listGames(); +} + +async function listGames() { + gamesTable.innerHTML = ""; + gamesTableHeader.textContent = ""; + + const selectedTeamID = teamDropdown.value; + const selectedDivisionID = divisionDropdown.value; + const selectedSeasonID = seasonDropdown.value; + + if(selectedTeamID && selectedDivisionID) { + await setupGamesTable(); + + const gamesList = await Data.getGames(selectedTeamID, selectedDivisionID, selectedSeasonID); + + gamesList.forEach((game) => { + const row = document.createElement('tr'); + + const scoreCell = document.createElement('td'); + const winLossLine = document.createElement('span'); + winLossLine.textContent = (game.team1Score > game.team2Score) ? "Win" : (game.team1Score < game.team2Score) ? "Loss" : "Tie"; + scoreCell.appendChild(winLossLine); + const scoreLine = document.createElement('span'); + scoreLine.textContent = game.team1Score + "-" + game.team2Score; + scoreCell.appendChild(scoreLine); + row.appendChild(scoreCell); + + const opponentCell = document.createElement('td'); + Data.getTeamName(game.team2ID) + .then(data => opponentCell.textContent = data); + row.appendChild(opponentCell); + + const dateCell = document.createElement('td'); + dateCell.textContent = game.date; + row.appendChild(dateCell); + + gamesTable.appendChild(row); + }); + } +} +async function setupGamesTable() { + gamesTableHeader.textContent = `Scores for ${teamDropdown.options[teamDropdown.selectedIndex].text}`; + + const row = document.createElement('tr'); + + const scoresHeader = document.createElement('th'); + scoresHeader.textContent = "Score" + row.appendChild(scoresHeader); + + const opponentHeader = document.createElement('th'); + opponentHeader.textContent = "Opponent"; + row.appendChild(opponentHeader); + + const dateHeader = document.createElement('th'); + dateHeader.textContent = "Date"; + row.appendChild(dateHeader); + + gamesTable.appendChild(row); } @@ -103,4 +164,5 @@ sportDropdown.onchange = (() => { listGenders(); listTeams(); }); -genderDropdown.onchange = listDivisions; \ No newline at end of file +genderDropdown.onchange = listDivisions; +teamDropdown.onchange = listGames; \ No newline at end of file diff --git a/public/stylesheets/index.css b/public/stylesheets/index.css index e4d4d83..2f8f252 100644 --- a/public/stylesheets/index.css +++ b/public/stylesheets/index.css @@ -1,3 +1,21 @@ h1 { text-align: left; +} + +th { + text-align: left; +} + +#score-column { + width: 20%; +} +#opponent-column{ + width: 60%; +} +#date-column { + width: 20%; +} + +tr { + height: 3em; } \ No newline at end of file diff --git a/routes/data.js b/routes/data.js index fc6c947..4344d2d 100644 --- a/routes/data.js +++ b/routes/data.js @@ -5,6 +5,7 @@ var seasons = require('../database/scores/seasons'); var genders = require('../database/scores/genders'); var divisions = require('../database/scores/divisions'); var teams = require('../database/scores/teams'); +var games = require('../database/scores/games'); router.get('/sports', function(req, res, next) { @@ -23,7 +24,7 @@ router.get('/genders', function(req, res, next) { }) router.get('/divisions', function(req, res, next) { - const gender = req.body.gender == 'female' ? genders.FEMALE : genders.MALE; + const gender = req.query.gender == 'female' ? genders.FEMALE : genders.MALE; divisions.retrieveBySportAndGender(req.query.sport, gender) .then(data => res.json(data)); @@ -34,4 +35,14 @@ router.get('/teams', function(req, res, next) { .then(data => res.json(data)); }) +router.get('/team', function(req, res, next) { + teams.getFromID(req.query.team) + .then(data => res.json(data)); +}) + +router.get('/games', function(req, res, next) { + games.retrieveByTeamDivisionAndSeason(req.query.team, req.query.division, req.query.season) + .then(data => res.json(data)); +}) + module.exports = router; \ No newline at end of file diff --git a/views/index.pug b/views/index.pug index 8a93bd0..6ec6e7c 100644 --- a/views/index.pug +++ b/views/index.pug @@ -24,12 +24,13 @@ block content select#team-dropdown(name="team" class="form-main-dropdown") span(class='form-section') div - table - tr - th Win? - th Opponent - th Score - th Date + h2#games-table-header + table + colgroup + col#score-column(span="1") + col#opponent-column(span="1") + col#date-column(span="1") + tbody#games-table block scripts From 8674781ba9cda6c08367c2dcae5cd75314594fbc Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sun, 21 Nov 2021 22:07:25 -0700 Subject: [PATCH 061/169] Tweak scores view page --- public/scripts/index.js | 62 +++++++++++++++++++++++------------------ views/index.pug | 1 + 2 files changed, 36 insertions(+), 27 deletions(-) diff --git a/public/scripts/index.js b/public/scripts/index.js index 4111353..4a4721c 100644 --- a/public/scripts/index.js +++ b/public/scripts/index.js @@ -7,6 +7,7 @@ const divisionDropdown = document.getElementById('division-dropdown'); const teamDropdown = document.getElementById('team-dropdown'); const gamesTable = document.getElementById('games-table'); const gamesTableHeader = document.getElementById('games-table-header'); +const noScoresMessage = document.getElementById('no-scores-message'); @@ -101,43 +102,49 @@ async function listTeams() { async function listGames() { gamesTable.innerHTML = ""; gamesTableHeader.textContent = ""; + noScoresMessage.textContent = ""; const selectedTeamID = teamDropdown.value; const selectedDivisionID = divisionDropdown.value; const selectedSeasonID = seasonDropdown.value; if(selectedTeamID && selectedDivisionID) { - await setupGamesTable(); + gamesTableHeader.textContent = `Scores for ${teamDropdown.options[teamDropdown.selectedIndex].text}`; const gamesList = await Data.getGames(selectedTeamID, selectedDivisionID, selectedSeasonID); + if(gamesList.length > 0) { + await setupGamesTableHeaders(); - gamesList.forEach((game) => { - const row = document.createElement('tr'); - - const scoreCell = document.createElement('td'); - const winLossLine = document.createElement('span'); - winLossLine.textContent = (game.team1Score > game.team2Score) ? "Win" : (game.team1Score < game.team2Score) ? "Loss" : "Tie"; - scoreCell.appendChild(winLossLine); - const scoreLine = document.createElement('span'); - scoreLine.textContent = game.team1Score + "-" + game.team2Score; - scoreCell.appendChild(scoreLine); - row.appendChild(scoreCell); - - const opponentCell = document.createElement('td'); - Data.getTeamName(game.team2ID) - .then(data => opponentCell.textContent = data); - row.appendChild(opponentCell); - - const dateCell = document.createElement('td'); - dateCell.textContent = game.date; - row.appendChild(dateCell); - - gamesTable.appendChild(row); - }); + gamesList.forEach((game) => { + const row = document.createElement('tr'); + + const scoreCell = document.createElement('td'); + const winLossLine = document.createElement('span'); + winLossLine.textContent = (game.team1Score > game.team2Score) ? "Win" : (game.team1Score < game.team2Score) ? "Loss" : "Tie"; + scoreCell.appendChild(winLossLine); + const scoreLine = document.createElement('span'); + scoreLine.textContent = game.team1Score + "-" + game.team2Score; + scoreCell.appendChild(scoreLine); + row.appendChild(scoreCell); + + const opponentCell = document.createElement('td'); + Data.getTeamName(game.team2ID) + .then(data => opponentCell.textContent = data); + row.appendChild(opponentCell); + + const dateCell = document.createElement('td'); + dateCell.textContent = game.date; + row.appendChild(dateCell); + + gamesTable.appendChild(row); + }); + } + else { + noScoresMessage.textContent = "No scores available"; + } } } -async function setupGamesTable() { - gamesTableHeader.textContent = `Scores for ${teamDropdown.options[teamDropdown.selectedIndex].text}`; +async function setupGamesTableHeaders() { const row = document.createElement('tr'); @@ -165,4 +172,5 @@ sportDropdown.onchange = (() => { listTeams(); }); genderDropdown.onchange = listDivisions; -teamDropdown.onchange = listGames; \ No newline at end of file +teamDropdown.onchange = listGames; +seasonDropdown.onchange = listGames; \ No newline at end of file diff --git a/views/index.pug b/views/index.pug index 6ec6e7c..1d3ffb2 100644 --- a/views/index.pug +++ b/views/index.pug @@ -25,6 +25,7 @@ block content span(class='form-section') div h2#games-table-header + span#no-scores-message table colgroup col#score-column(span="1") From 9db8ae51515f9c82fff957bb713217cbc192952e Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sun, 21 Nov 2021 23:16:29 -0700 Subject: [PATCH 062/169] remove require database from index.js --- routes/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/routes/index.js b/routes/index.js index 4cb7f20..3c536d2 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,6 +1,5 @@ var express = require('express'); var router = express.Router(); -var database = require('../database/database'); /* GET home page. */ router.get('/', function(req, res, next) { From dca907964843f2137e1f94e8e067af70eb48cca4 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sun, 21 Nov 2021 23:17:43 -0700 Subject: [PATCH 063/169] Create files for /manage page --- routes/manage.js | 9 +++++++++ views/manage.pug | 0 2 files changed, 9 insertions(+) create mode 100644 routes/manage.js create mode 100644 views/manage.pug diff --git a/routes/manage.js b/routes/manage.js new file mode 100644 index 0000000..796064b --- /dev/null +++ b/routes/manage.js @@ -0,0 +1,9 @@ +var express = require('express'); +var router = express.Router(); + +/* GET manage page. */ +router.get('/', function(req, res, next) { + res.render('manage', { title: 'Score Management' }); +}); + +module.exports = router; diff --git a/views/manage.pug b/views/manage.pug new file mode 100644 index 0000000..e69de29 From 41d9140863c5ea08abf7b88af456ecd6cab47914 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Sun, 21 Nov 2021 23:23:06 -0700 Subject: [PATCH 064/169] Remove console.log used for debugging --- database/scores/games.js | 1 - 1 file changed, 1 deletion(-) diff --git a/database/scores/games.js b/database/scores/games.js index 9af0b89..265dd56 100644 --- a/database/scores/games.js +++ b/database/scores/games.js @@ -52,7 +52,6 @@ async function retrieveByTeamDivisionAndSeason(teamID, divisionID, seasonID) { gamesList.push(new Game(row[0], row[3].toISOString().slice(0,10), teamID, opponentID, teamScore, opponentScore)); }); - console.log(gamesList); return gamesList; } From f7dfd24a68d96bef2d3ad71ce9b75dbacf9b1474 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Mon, 22 Nov 2021 13:44:19 -0700 Subject: [PATCH 065/169] Remove unnecessary div from index.pug --- views/index.pug | 1 - 1 file changed, 1 deletion(-) diff --git a/views/index.pug b/views/index.pug index 1d3ffb2..2e52919 100644 --- a/views/index.pug +++ b/views/index.pug @@ -22,7 +22,6 @@ block content label Team span(class='form-section-input') select#team-dropdown(name="team" class="form-main-dropdown") - span(class='form-section') div h2#games-table-header span#no-scores-message From a6e8e924e4459858eb2de01a9e5c7e0b6c7861b1 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Mon, 22 Nov 2021 13:56:57 -0700 Subject: [PATCH 066/169] Create basic layout for manage page --- views/manage.pug | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/views/manage.pug b/views/manage.pug index e69de29..79ac0b0 100644 --- a/views/manage.pug +++ b/views/manage.pug @@ -0,0 +1,31 @@ +extends layout + +block stylesheets + link(rel='stylesheet', href='/stylesheets/index.css') + link(rel='stylesheet', href='/stylesheets/form.css') + +block content + div#mobile-view + h1 Management Panel + div + span(class='form-section') + label Category + span(class='form-section-input') + select#category-dropdown(name="category" class="form-main-dropdown") + option(value="seasons") Seasons + option(value="sports") Sports + option(value="divisions") Divisions + option(value="teams") Teams + option(value="games") Games + div + h2#table-header + table + colgroup + col#score-column(span="1") + col#opponent-column(span="1") + col#date-column(span="1") + tbody#games-table + + +block scripts + script(src='/scripts/index.js' type="module") \ No newline at end of file From 7baa986a30093360ff704ddacf51fcdc1582a759 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Mon, 22 Nov 2021 13:57:13 -0700 Subject: [PATCH 067/169] Add manage route to app.js --- app.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app.js b/app.js index 29f7b4f..6bc76de 100644 --- a/app.js +++ b/app.js @@ -8,6 +8,7 @@ var indexRouter = require('./routes/index'); var usersRouter = require('./routes/users'); var submitRouter = require('./routes/submit'); var dataRouter = require('./routes/data'); +var manageRouter = require('./routes/manage'); var app = express(); @@ -25,6 +26,8 @@ app.use('/', indexRouter); app.use('/users', usersRouter); app.use('/submit', submitRouter); app.use('/data', dataRouter); +app.use('/manage', manageRouter); + // catch 404 and forward to error handler app.use(function(req, res, next) { From 77359ba3a7f99d1a58369a69e0619660dbe0960b Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Mon, 22 Nov 2021 14:07:06 -0700 Subject: [PATCH 068/169] Add formatting tweak to index.js --- public/scripts/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/scripts/index.js b/public/scripts/index.js index 4a4721c..41d9610 100644 --- a/public/scripts/index.js +++ b/public/scripts/index.js @@ -20,7 +20,7 @@ async function listSeasons() { seasonsList.forEach(season => { const option = document.createElement('option'); - option.text = season.year - 1 + "-" + season.year; + option.text = (season.year - 1) + "-" + season.year; option.value = season.id; seasonDropdown.appendChild(option); }); From 1ce1bdb27c4edc2921eeee241a138d12c3761158 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Mon, 22 Nov 2021 14:07:51 -0700 Subject: [PATCH 069/169] Add formatting tweak to submit.js --- public/scripts/submit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/scripts/submit.js b/public/scripts/submit.js index bd4d08a..8c3cf81 100644 --- a/public/scripts/submit.js +++ b/public/scripts/submit.js @@ -19,7 +19,7 @@ async function listSeasons() { seasonsList.forEach(season => { const option = document.createElement('option'); - option.text = season.year - 1 + "-" + season.year; + option.text = (season.year - 1) + "-" + season.year; option.value = season.id; seasonDropdown.appendChild(option); }); From 4aafccc5505fdd4d475854cf29a1cd8caf33b0e8 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Mon, 22 Nov 2021 14:50:32 -0700 Subject: [PATCH 070/169] Add functions to list seasons and sports in manage page --- public/scripts/manage.js | 116 +++++++++++++++++++++++++++++++++++++++ views/manage.pug | 13 ++--- 2 files changed, 120 insertions(+), 9 deletions(-) create mode 100644 public/scripts/manage.js diff --git a/public/scripts/manage.js b/public/scripts/manage.js new file mode 100644 index 0000000..19b700a --- /dev/null +++ b/public/scripts/manage.js @@ -0,0 +1,116 @@ +import * as Data from "./data.js"; + +const categoryDropdown = document.getElementById('category-dropdown'); +const itemsListTable = document.getElementById('items-list'); + +class Category { + constructor(name, getItems, listHeaders, listItem, addItem, submitItem, editItem) { + this.name = name; + this.getItems = getItems; + this.listHeaders = listHeaders; + this.listItem = listItem; + this.addItem = addItem; + this.submitItem = submitItem; + this.editItem = editItem; + } +} + +const CATEGORIES = []; + +CATEGORIES.push(new Category( + "seasons", + async function getSeasons() { + return await Data.getSeasons(); + }, + async function listSeasonHeaders() { + const headerRow = document.createElement('tr'); + + const yearsHeader = document.createElement('th'); + yearsHeader.textContent = "Years"; + + headerRow.appendChild(yearsHeader); + + itemsListTable.appendChild(headerRow); + }, + function listSeason(season, row) { + const yearCell = document.createElement('td'); + yearCell.textContent = (season.year - 1) + "-" + season.year; + row.appendChild(yearCell); + }, + async function addSeason() { + // + }, + async function submitSeason() { + // + }, + async function editSeason() { + + } +)); + +CATEGORIES.push(new Category( + "sports", + async function getSports() { + return await Data.getSports(); + }, + async function listSportHeaders() { + const headerRow = document.createElement('tr'); + + const yearsHeader = document.createElement('th'); + yearsHeader.textContent = "Name"; + headerRow.appendChild(yearsHeader); + + itemsListTable.appendChild(headerRow); + }, + function listSport(sport, row) { + const nameCell = document.createElement('td'); + nameCell.textContent = sport.name; + row.appendChild(nameCell); + }, + async function addSeason() { + // + }, + async function submitSeason() { + // + }, + async function editSeason() { + + } +)); + + + +async function listItems(category) { + itemsListTable.innerHTML = ""; + + const itemsList = await category.getItems(); + + await category.listHeaders(); + + itemsList.forEach(item => { + const row = document.createElement('tr'); + + category.listItem(item, row); + + const editCell = document.createElement('td'); + const editButton = document.createElement('button'); + editButton.textContent = "edit"; + editCell.appendChild(editButton); + row.appendChild(editCell); + + const removeCell = document.createElement('td'); + const removeButton = document.createElement('button'); + removeButton.textContent = "remove"; + removeCell.appendChild(removeButton); + row.appendChild(removeCell); + + itemsListTable.appendChild(row); + }); +} +listItems(CATEGORIES[0]); + + + +categoryDropdown.onchange = () => { + listItems(CATEGORIES[categoryDropdown.selectedIndex]); +}; \ No newline at end of file diff --git a/views/manage.pug b/views/manage.pug index 79ac0b0..7977824 100644 --- a/views/manage.pug +++ b/views/manage.pug @@ -1,7 +1,7 @@ extends layout block stylesheets - link(rel='stylesheet', href='/stylesheets/index.css') + link(rel='stylesheet', href='/stylesheets/manage.css') link(rel='stylesheet', href='/stylesheets/form.css') block content @@ -19,13 +19,8 @@ block content option(value="games") Games div h2#table-header - table - colgroup - col#score-column(span="1") - col#opponent-column(span="1") - col#date-column(span="1") - tbody#games-table - + table#items-list + button Add new... block scripts - script(src='/scripts/index.js' type="module") \ No newline at end of file + script(src='/scripts/manage.js' type="module") \ No newline at end of file From 75594a81a6497272941e897485797a9b9728ff74 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Mon, 22 Nov 2021 14:58:43 -0700 Subject: [PATCH 071/169] Add option to fetch all divisions --- public/scripts/data.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/public/scripts/data.js b/public/scripts/data.js index 61f3923..502bbb8 100644 --- a/public/scripts/data.js +++ b/public/scripts/data.js @@ -16,8 +16,12 @@ export async function getGenders(sportID) { return gendersList; } -export async function getDivisions(sportID, gender) { - const response = await fetch(`/data/divisions?sport=${+sportID}&gender=${gender}`); +export async function getDivisions(sportID = undefined, gender = undefined) { + let URL = '/data/divisions?'; + if(sportID) URL += `sport=${+sportID}&`; + if(gender) URL += `gender=${gender}&`; + + const response = await fetch(URL); const divisionsList = await response.json(); return divisionsList; } From 435748ae7663ea3af7219d76396ccfce24a22c4f Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Mon, 22 Nov 2021 15:20:59 -0700 Subject: [PATCH 072/169] Add functionality to return all divisions in database --- database/scores/divisions.js | 30 ++++++++++++++++++++++-------- routes/data.js | 5 +++-- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/database/scores/divisions.js b/database/scores/divisions.js index 38fa8be..80fe35e 100644 --- a/database/scores/divisions.js +++ b/database/scores/divisions.js @@ -6,9 +6,11 @@ const genders = require('./genders'); class Division { - constructor(id, name) { + constructor(id, name, gender, sportID) { this.id = id; this.name = name; + this.gender = gender; + this.sportID = sportID; } } @@ -29,7 +31,7 @@ async function add(name, gender, sportID) { return new Division(id, name); } -async function rename(id, division) { +async function rename(id, name) { const query = `UPDATE scores.divisions SET division_name = $2 WHERE division_id = $1;`; @@ -45,17 +47,29 @@ async function remove(id) { return new Division(id, name); } -async function retrieveBySportAndGender(sportID, gender) { - const query = `SELECT * +async function retrieve(sportID = undefined, gender = undefined) { + let table; + + if(sportID && gender) { + const query = `SELECT division_id, division_name, gender, sport_id FROM scores.divisions WHERE sport_id = $1 AND gender = $2 ORDER BY division_name;`; - const genderID = getGenderID(gender); - const table = await database.executeQuery(query, [sportID, genderID]); + const genderID = getGenderID(gender); + table = await database.executeQuery(query, [sportID, genderID]); + } + else { + const query = `SELECT division_id, division_name, gender, sport_id + FROM scores.divisions + ORDER BY division_name, + gender;`; + table = await database.executeQuery(query); + } const divisionsList = []; table.forEach((row) => { - divisionsList.push(new Division(row[0], row[1])); + let gender = (row[2] == "F") ? genders.FEMALE : genders.MALE; + divisionsList.push(new Division(row[0], row[1], gender, row[3])); }); return divisionsList; } @@ -67,4 +81,4 @@ async function retrieveBySportAndGender(sportID, gender) { exports.add = add; exports.rename = rename; exports.remove = remove; -exports.retrieveBySportAndGender = retrieveBySportAndGender; \ No newline at end of file +exports.retrieve = retrieve; \ No newline at end of file diff --git a/routes/data.js b/routes/data.js index 4344d2d..588ed2f 100644 --- a/routes/data.js +++ b/routes/data.js @@ -24,9 +24,10 @@ router.get('/genders', function(req, res, next) { }) router.get('/divisions', function(req, res, next) { - const gender = req.query.gender == 'female' ? genders.FEMALE : genders.MALE; + let gender; + if(req.query.gender) gender = (req.query.gender == 'female' ? genders.FEMALE : genders.MALE); - divisions.retrieveBySportAndGender(req.query.sport, gender) + divisions.retrieve(req.query.sport, gender) .then(data => res.json(data)); }) From b6f8b6bdaa78a57bdbba80ea08e0d0be3029a457 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Mon, 22 Nov 2021 15:30:02 -0700 Subject: [PATCH 073/169] Add functions to retrieve sport by ID --- database/scores/sports.js | 11 ++++++++++- public/scripts/data.js | 10 ++++++++-- routes/data.js | 5 +++++ 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/database/scores/sports.js b/database/scores/sports.js index 14a6e45..134009d 100644 --- a/database/scores/sports.js +++ b/database/scores/sports.js @@ -48,6 +48,14 @@ async function retrieveAll() { return sportsList; } +async function getFromID(id) { + const query = `SELECT sport_name + FROM scores.sports + WHERE sport_id = $1;`; + const name = (await database.executeQuery(query, [id]))[0][0]; + return new Sport(id, name); +} + @@ -55,4 +63,5 @@ async function retrieveAll() { exports.add = add; exports.rename = rename; exports.remove = remove; -exports.retrieveAll = retrieveAll; \ No newline at end of file +exports.retrieveAll = retrieveAll; +exports.getFromID = getFromID; \ No newline at end of file diff --git a/public/scripts/data.js b/public/scripts/data.js index 502bbb8..567c1ac 100644 --- a/public/scripts/data.js +++ b/public/scripts/data.js @@ -1,11 +1,17 @@ export async function getSports() { - const response = await fetch('/data/sports'); + const response = await fetch(`/data/sports`); const sportsList = await response.json(); return sportsList; } +export async function getSportName(sportID) { + const response = await fetch(`/data/sport?sport=${sportID}`); + const sport = await response.json(); + return sport.name; +} + export async function getSeasons() { - const response = await fetch('/data/seasons'); + const response = await fetch(`/data/seasons`); const seasonsList = await response.json(); return seasonsList; } diff --git a/routes/data.js b/routes/data.js index 588ed2f..fc61052 100644 --- a/routes/data.js +++ b/routes/data.js @@ -13,6 +13,11 @@ router.get('/sports', function(req, res, next) { .then(data => res.json(data)); }); +router.get('/sport', function(req, res, next) { + sports.getFromID(req.query.sport) + .then(data => res.json(data)); +}); + router.get('/seasons', function(req, res, next) { seasons.retrieveAll() .then(data => res.json(data)); From 18f840183fad1627fe59c2d1b621505c3b27c93a Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Mon, 22 Nov 2021 15:32:44 -0700 Subject: [PATCH 074/169] Add functionality to list divisions in management page --- public/scripts/manage.js | 53 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/public/scripts/manage.js b/public/scripts/manage.js index 19b700a..8958f1c 100644 --- a/public/scripts/manage.js +++ b/public/scripts/manage.js @@ -56,9 +56,9 @@ CATEGORIES.push(new Category( async function listSportHeaders() { const headerRow = document.createElement('tr'); - const yearsHeader = document.createElement('th'); - yearsHeader.textContent = "Name"; - headerRow.appendChild(yearsHeader); + const nameHeader = document.createElement('th'); + nameHeader.textContent = "Name"; + headerRow.appendChild(nameHeader); itemsListTable.appendChild(headerRow); }, @@ -67,6 +67,53 @@ CATEGORIES.push(new Category( nameCell.textContent = sport.name; row.appendChild(nameCell); }, + async function addSport() { + // + }, + async function submitSport() { + // + }, + async function editSport() { + + } +)); + +CATEGORIES.push(new Category( + "divisions", + async function getDivisions() { + return await Data.getDivisions(); + }, + async function listDivisionHeaders() { + const headerRow = document.createElement('tr'); + + const nameHeader = document.createElement('th'); + nameHeader.textContent = "Name"; + headerRow.appendChild(nameHeader); + + const genderHeader = document.createElement('th'); + headerRow.appendChild(genderHeader); + + const sportHeader = document.createElement('th'); + sportHeader.textContent = "Sport"; + headerRow.appendChild(sportHeader); + + itemsListTable.appendChild(headerRow); + }, + function listDivision(division, row) { + const nameCell = document.createElement('td'); + nameCell.textContent = division.name; + row.appendChild(nameCell); + + const genderCell = document.createElement('td'); + const gender = division.gender.name == "female" ? "F" : "M"; + genderCell.textContent = gender; + row.appendChild(genderCell); + + const sportCell = document.createElement('td'); + Data.getSportName(division.sportID) + .then(data => sportCell.textContent = data); + row.appendChild(sportCell); + }, async function addSeason() { // }, From 28b11b7266689063c9b112fa284a4c661119b3b6 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Mon, 22 Nov 2021 15:38:20 -0700 Subject: [PATCH 075/169] Group divisions by sport_id --- database/scores/divisions.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/database/scores/divisions.js b/database/scores/divisions.js index 80fe35e..a3cebd2 100644 --- a/database/scores/divisions.js +++ b/database/scores/divisions.js @@ -61,7 +61,8 @@ async function retrieve(sportID = undefined, gender = undefined) { else { const query = `SELECT division_id, division_name, gender, sport_id FROM scores.divisions - ORDER BY division_name, + ORDER BY sport_id, + division_name, gender;`; table = await database.executeQuery(query); } From 90ef696b717ec05e92ffd55b840b377ec44e2488 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Mon, 22 Nov 2021 15:45:19 -0700 Subject: [PATCH 076/169] Add retrieve all teams in database functionality --- database/scores/teams.js | 25 +++++++++++++++++++------ public/scripts/data.js | 7 +++++-- routes/data.js | 2 +- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/database/scores/teams.js b/database/scores/teams.js index 893cc34..3717841 100644 --- a/database/scores/teams.js +++ b/database/scores/teams.js @@ -5,9 +5,10 @@ const database = require('./../database'); class Team { - constructor(id, name) { + constructor(id, name, sportID) { this.id = id; this.name = name; + this.sportID = sportID; } } @@ -37,16 +38,28 @@ async function remove(id) { return new Team(id, name); } -async function retrieveBySport(sportID) { - const query = `SELECT * +async function retrieve(sportID = undefined) { + let table; + + if(sportID) { + const query = `SELECT team_id, team_name, sport_id FROM scores.teams WHERE sport_id = $1 ORDER BY team_name;`; - const table = await database.executeQuery(query, [sportID]); + table = await database.executeQuery(query, [sportID]); + } + else { + const query = `SELECT team_id, team_name, sport_id + FROM scores.teams + ORDER BY + sport_id, + team_name;`; + table = await database.executeQuery(query); + } const teamsList = []; table.forEach((row) => { - teamsList.push(new Team(row[0], row[1])); + teamsList.push(new Team(row[0], row[1], row[2])); }); return teamsList; } @@ -66,5 +79,5 @@ async function getFromID(id) { exports.add = add; exports.rename = rename; exports.remove = remove; -exports.retrieveBySport = retrieveBySport; +exports.retrieve = retrieve; exports.getFromID = getFromID; \ No newline at end of file diff --git a/public/scripts/data.js b/public/scripts/data.js index 567c1ac..b83fdd0 100644 --- a/public/scripts/data.js +++ b/public/scripts/data.js @@ -32,8 +32,11 @@ export async function getDivisions(sportID = undefined, gender = undefined) { return divisionsList; } -export async function getTeams(sportID) { - const response = await fetch(`/data/teams?sport=${+sportID}`); +export async function getTeams(sportID = undefined) { + let URL = '/data/teams?'; + if(sportID) URL += `sport=${+sportID}&`; + + const response = await fetch(URL); const teamsList = await response.json(); return teamsList; } diff --git a/routes/data.js b/routes/data.js index fc61052..f9c6431 100644 --- a/routes/data.js +++ b/routes/data.js @@ -37,7 +37,7 @@ router.get('/divisions', function(req, res, next) { }) router.get('/teams', function(req, res, next) { - teams.retrieveBySport(req.query.sport) + teams.retrieve(req.query.sport) .then(data => res.json(data)); }) From 6fdbaedc791db514e5f61ab9318690c952fd0976 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Mon, 22 Nov 2021 15:48:30 -0700 Subject: [PATCH 077/169] Add functionality to list teams on manage page --- public/scripts/manage.js | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/public/scripts/manage.js b/public/scripts/manage.js index 8958f1c..f6bcda6 100644 --- a/public/scripts/manage.js +++ b/public/scripts/manage.js @@ -125,6 +125,45 @@ CATEGORIES.push(new Category( } )); +CATEGORIES.push(new Category( + "teams", + async function getTeams() { + return await Data.getTeams(); + }, + async function listTeamHeaders() { + const headerRow = document.createElement('tr'); + + const nameHeader = document.createElement('th'); + nameHeader.textContent = "Name"; + headerRow.appendChild(nameHeader); + + const sportHeader = document.createElement('th'); + sportHeader.textContent = "Sport"; + headerRow.appendChild(sportHeader); + + itemsListTable.appendChild(headerRow); + }, + function listTeam(team, row) { + const nameCell = document.createElement('td'); + nameCell.textContent = team.name; + row.appendChild(nameCell); + + const sportCell = document.createElement('td'); + Data.getSportName(team.sportID) + .then(data => sportCell.textContent = data); + row.appendChild(sportCell); + }, + async function addSeason() { + // + }, + async function submitSeason() { + // + }, + async function editSeason() { + + } +)); + async function listItems(category) { From 17f14573e94647befee47ca1190902f46103b484 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Mon, 22 Nov 2021 16:00:12 -0700 Subject: [PATCH 078/169] Add functionality to get all games from database --- database/scores/games.js | 25 +++++++++++++++++++------ public/scripts/data.js | 10 ++++++++-- routes/data.js | 2 +- 3 files changed, 28 insertions(+), 9 deletions(-) diff --git a/database/scores/games.js b/database/scores/games.js index 265dd56..9f27bab 100644 --- a/database/scores/games.js +++ b/database/scores/games.js @@ -5,13 +5,15 @@ const database = require('./../database'); class Game { - constructor(id, date, team1ID, team2ID, team1Score, team2Score) { + constructor(id, date, team1ID, team2ID, team1Score, team2Score, divisionID, seasonID) { this.id = id; this.date = date; this.team1ID = team1ID; this.team2ID = team2ID; this.team1Score = team1Score; this.team2Score = team2Score; + this.divisionID = divisionID; + this.seasonID = seasonID; } } @@ -36,12 +38,23 @@ async function remove(id) { return new Game(id, row[3], row[4], row[5], row[6], row[7]); } -async function retrieveByTeamDivisionAndSeason(teamID, divisionID, seasonID) { - const query = `SELECT * +async function retrieve(teamID, divisionID, seasonID) { + let table; + + if(teamID && divisionID && seasonID) { + const query = `SELECT game_id, division_id, season_id, game_date, team1_id, team2_id, team1_score, team2_score FROM scores.games WHERE (team1_id = $1 OR team2_id = $1) AND division_id = $2 AND season_id = $3 ORDER BY game_date DESC;`; - const table = (await database.executeQuery(query, [teamID,divisionID,seasonID])); + table = await database.executeQuery(query, [teamID,divisionID,seasonID]); + } + else { + const query = `SELECT game_id, division_id, season_id, game_date, team1_id, team2_id, team1_score, team2_score + FROM scores.games + ORDER BY game_date DESC;`; + table = await database.executeQuery(query); + } + const gamesList = []; table.forEach((row) => { @@ -50,7 +63,7 @@ async function retrieveByTeamDivisionAndSeason(teamID, divisionID, seasonID) { teamScore = opponentIsTeam2 ? row[6] : row[7]; opponentScore = opponentIsTeam2 ? row[7] : row[6]; - gamesList.push(new Game(row[0], row[3].toISOString().slice(0,10), teamID, opponentID, teamScore, opponentScore)); + gamesList.push(new Game(row[0], row[3].toISOString().slice(0,10), teamID, opponentID, teamScore, opponentScore, row[1], row[2])); }); return gamesList; } @@ -61,4 +74,4 @@ async function retrieveByTeamDivisionAndSeason(teamID, divisionID, seasonID) { exports.add = add; exports.remove = remove; -exports.retrieveByTeamDivisionAndSeason = retrieveByTeamDivisionAndSeason; \ No newline at end of file +exports.retrieve = retrieve; \ No newline at end of file diff --git a/public/scripts/data.js b/public/scripts/data.js index b83fdd0..9866321 100644 --- a/public/scripts/data.js +++ b/public/scripts/data.js @@ -47,8 +47,14 @@ export async function getTeamName(teamID) { return team.name; } -export async function getGames(teamID, divisionID, seasonID) { - const response = await fetch(`/data/games?team=${+teamID}&division=${+divisionID}&season=${+seasonID}`); +export async function getGames(teamID = undefined, divisionID = undefined, seasonID = undefined) { + let URL = '/data/games?'; + if(teamID) URL += `team=${+teamID}&`; + if(divisionID) URL += `division=${+divisionID}&`; + if(seasonID) URL += `season=${+seasonID}`; + + + const response = await fetch(URL); const gamesList = await response.json(); return gamesList; } \ No newline at end of file diff --git a/routes/data.js b/routes/data.js index f9c6431..76dd2ac 100644 --- a/routes/data.js +++ b/routes/data.js @@ -47,7 +47,7 @@ router.get('/team', function(req, res, next) { }) router.get('/games', function(req, res, next) { - games.retrieveByTeamDivisionAndSeason(req.query.team, req.query.division, req.query.season) + games.retrieve(req.query.team, req.query.division, req.query.season) .then(data => res.json(data)); }) From fe659184f41838ec53313d7d9b9ef84eee3f426b Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Mon, 22 Nov 2021 16:13:21 -0700 Subject: [PATCH 079/169] Fix bug in games.js --- database/scores/games.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/database/scores/games.js b/database/scores/games.js index 9f27bab..4055523 100644 --- a/database/scores/games.js +++ b/database/scores/games.js @@ -58,12 +58,17 @@ async function retrieve(teamID, divisionID, seasonID) { const gamesList = []; table.forEach((row) => { - opponentIsTeam2 = teamID != row[5]; - opponentID = opponentIsTeam2 ? row[5] : row[4]; - teamScore = opponentIsTeam2 ? row[6] : row[7]; - opponentScore = opponentIsTeam2 ? row[7] : row[6]; - - gamesList.push(new Game(row[0], row[3].toISOString().slice(0,10), teamID, opponentID, teamScore, opponentScore, row[1], row[2])); + if(teamID) { + const opponentIsTeam2 = teamID != row[5]; + const opponentID = opponentIsTeam2 ? row[5] : row[4]; + const teamScore = opponentIsTeam2 ? row[6] : row[7]; + const opponentScore = opponentIsTeam2 ? row[7] : row[6]; + + gamesList.push(new Game(row[0], row[3].toISOString().slice(0,10), teamID, opponentID, teamScore, opponentScore, row[1], row[2])); + } + else { + gamesList.push(new Game(row[0], row[3].toISOString().slice(0,10), row[4], row[5], row[6], row[7], row[1], row[2])); + } }); return gamesList; } From 1f55ba3ff9921942a17cf2d5f120e206ce6666ab Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Mon, 22 Nov 2021 16:27:50 -0700 Subject: [PATCH 080/169] Add functions to get division by ID --- database/scores/divisions.js | 20 +++++++++-- public/scripts/data.js | 6 ++++ public/scripts/manage.js | 68 ++++++++++++++++++++++++++++++++++++ routes/data.js | 5 +++ 4 files changed, 96 insertions(+), 3 deletions(-) diff --git a/database/scores/divisions.js b/database/scores/divisions.js index a3cebd2..7da86dd 100644 --- a/database/scores/divisions.js +++ b/database/scores/divisions.js @@ -20,6 +20,10 @@ function getGenderID(gender) { return (gender == genders.MALE) ? "M" : "F"; } +function getGenderFromID(genderID) { + return (genderID == "F") ? genders.FEMALE : genders.MALE; +} + async function add(name, gender, sportID) { @@ -69,12 +73,21 @@ async function retrieve(sportID = undefined, gender = undefined) { const divisionsList = []; table.forEach((row) => { - let gender = (row[2] == "F") ? genders.FEMALE : genders.MALE; - divisionsList.push(new Division(row[0], row[1], gender, row[3])); + divisionsList.push(new Division(row[0], row[1], getGenderFromID(row[2]), row[3])); }); return divisionsList; } +async function getFromID(id) { + const query = `SELECT division_id, division_name, gender, sport_id + FROM scores.divisions + WHERE division_id = $1;`; + const row = (await database.executeQuery(query, [id]))[0]; + + + return new Division(id, row[1], getGenderFromID(row[2]), row[3]); +} + @@ -82,4 +95,5 @@ async function retrieve(sportID = undefined, gender = undefined) { exports.add = add; exports.rename = rename; exports.remove = remove; -exports.retrieve = retrieve; \ No newline at end of file +exports.retrieve = retrieve; +exports.getFromID = getFromID; \ No newline at end of file diff --git a/public/scripts/data.js b/public/scripts/data.js index 9866321..8ef7479 100644 --- a/public/scripts/data.js +++ b/public/scripts/data.js @@ -32,6 +32,12 @@ export async function getDivisions(sportID = undefined, gender = undefined) { return divisionsList; } +export async function getDivision(divisionID) { + const response = await fetch(`/data/division?division=${divisionID}`); + const division = await response.json(); + return division; +} + export async function getTeams(sportID = undefined) { let URL = '/data/teams?'; if(sportID) URL += `sport=${+sportID}&`; diff --git a/public/scripts/manage.js b/public/scripts/manage.js index f6bcda6..83920f0 100644 --- a/public/scripts/manage.js +++ b/public/scripts/manage.js @@ -164,6 +164,74 @@ CATEGORIES.push(new Category( } )); +CATEGORIES.push(new Category( + "games", + async function getGames() { + return await Data.getGames(); + }, + async function listGameHeaders() { + const headerRow = document.createElement('tr'); + + const teamsHeader = document.createElement('th'); + teamsHeader.textContent = "Teams"; + headerRow.appendChild(teamsHeader); + + const scoreHeader = document.createElement('th'); + headerRow.appendChild(scoreHeader); + + const sportNameHeader = document.createElement('th'); + sportNameHeader.textContent = "Sport"; + headerRow.appendChild(sportNameHeader); + + const divisionNameHeader = document.createElement('th'); + headerRow.appendChild(divisionNameHeader); + + const divisionGenderHeader = document.createElement('th'); + headerRow.appendChild(divisionGenderHeader); + + const dateHeader = document.createElement('th'); + dateHeader.textContent = "Date"; + headerRow.appendChild(dateHeader); + + itemsListTable.appendChild(headerRow); + }, + function listGame(game, row) { + const teamsCell = document.createElement('td'); + const team1NameSpan = document.createElement('span'); + Data.getTeamName(game.team1ID) + .then(data => team1NameSpan.textContent = data); + teamsCell.appendChild(team1NameSpan); + const team2NameSpan = document.createElement('span'); + Data.getTeamName(game.team2ID) + .then(data => team2NameSpan.textContent = data); + teamsCell.appendChild(team2NameSpan); + row.appendChild(teamsCell); + + const scoresCell = document.createElement('td'); + const team1ScoreSpan = document.createElement('span'); + team1ScoreSpan.textContent = game.team1Score; + scoresCell.appendChild(team1ScoreSpan); + const team2ScoreSpan = document.createElement('span'); + team2ScoreSpan.textContent = game.team2Score; + scoresCell.appendChild(team2ScoreSpan); + row.appendChild(scoresCell); + + const sportCell = document.createElement('td'); + Data.getSportName(team.sportID) + .then(data => sportCell.textContent = data); + row.appendChild(sportCell); + }, + async function addSeason() { + // + }, + async function submitSeason() { + // + }, + async function editSeason() { + + } +)); + async function listItems(category) { diff --git a/routes/data.js b/routes/data.js index 76dd2ac..917b36b 100644 --- a/routes/data.js +++ b/routes/data.js @@ -36,6 +36,11 @@ router.get('/divisions', function(req, res, next) { .then(data => res.json(data)); }) +router.get('/division', function(req, res, next) { + divisions.getFromID(req.query.division) + .then(data => res.json(data)); +}) + router.get('/teams', function(req, res, next) { teams.retrieve(req.query.sport) .then(data => res.json(data)); From 54bef983121f6e467a7eaf0198da0338a873a5b7 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Mon, 22 Nov 2021 16:58:36 -0700 Subject: [PATCH 081/169] Add games list to manage page --- public/scripts/manage.js | 64 ++++++++++++++++++++++++++++------------ 1 file changed, 45 insertions(+), 19 deletions(-) diff --git a/public/scripts/manage.js b/public/scripts/manage.js index 83920f0..b609171 100644 --- a/public/scripts/manage.js +++ b/public/scripts/manage.js @@ -3,6 +3,11 @@ import * as Data from "./data.js"; const categoryDropdown = document.getElementById('category-dropdown'); const itemsListTable = document.getElementById('items-list'); + +function getGenderLetter(genderName) { + return genderName == "female" ? "F" : "M"; +} + class Category { constructor(name, getItems, listHeaders, listItem, addItem, submitItem, editItem) { this.name = name; @@ -105,7 +110,7 @@ CATEGORIES.push(new Category( row.appendChild(nameCell); const genderCell = document.createElement('td'); - const gender = division.gender.name == "female" ? "F" : "M"; + const gender = getGenderLetter(division.gender.name); genderCell.textContent = gender; row.appendChild(genderCell); @@ -183,12 +188,6 @@ CATEGORIES.push(new Category( sportNameHeader.textContent = "Sport"; headerRow.appendChild(sportNameHeader); - const divisionNameHeader = document.createElement('th'); - headerRow.appendChild(divisionNameHeader); - - const divisionGenderHeader = document.createElement('th'); - headerRow.appendChild(divisionGenderHeader); - const dateHeader = document.createElement('th'); dateHeader.textContent = "Date"; headerRow.appendChild(dateHeader); @@ -217,9 +216,32 @@ CATEGORIES.push(new Category( row.appendChild(scoresCell); const sportCell = document.createElement('td'); - Data.getSportName(team.sportID) - .then(data => sportCell.textContent = data); + const sportSpan = document.createElement('span'); + const divisionSpan = document.createElement('span'); + divisionSpan.classList.add('division-span'); + const divisionNameSpan = document.createElement('span'); + const divisionGenderSpan = document.createElement('span'); + divisionSpan.appendChild(divisionNameSpan); + divisionSpan.appendChild(divisionGenderSpan); + Data.getDivision(game.divisionID) + .then(data => { + Data.getSportName(data.sportID) + .then(data => sportSpan.textContent = data); + divisionNameSpan.textContent = data.name; + divisionGenderSpan.textContent = getGenderLetter(data.gender.name); + }); + sportCell.appendChild(sportSpan); + sportCell.appendChild(divisionSpan); row.appendChild(sportCell); + + const dateCell = document.createElement('td'); + const yearSpan = document.createElement('span'); + yearSpan.textContent = game.date.slice(0,4); + dateCell.appendChild(yearSpan); + const dateSpan = document.createElement('span'); + dateSpan.textContent = game.date.slice(5); + dateCell.appendChild(dateSpan); + row.appendChild(dateCell); }, async function addSeason() { // @@ -246,22 +268,26 @@ async function listItems(category) { category.listItem(item, row); - const editCell = document.createElement('td'); - const editButton = document.createElement('button'); - editButton.textContent = "edit"; - editCell.appendChild(editButton); - row.appendChild(editCell); + const manageCell = document.createElement('td'); - const removeCell = document.createElement('td'); + const editSpan = document.createElement('span'); + const editButton = document.createElement('button'); + editButton.textContent = "E"; + editSpan.appendChild(editButton); + manageCell.appendChild(editSpan); + + const removeSpan = document.createElement('remove'); const removeButton = document.createElement('button'); - removeButton.textContent = "remove"; - removeCell.appendChild(removeButton); - row.appendChild(removeCell); + removeButton.textContent = "D"; + removeSpan.appendChild(removeButton); + manageCell.appendChild(removeSpan); + + row.appendChild(manageCell); itemsListTable.appendChild(row); }); } -listItems(CATEGORIES[0]); +listItems(CATEGORIES[categoryDropdown.selectedIndex]); From 4d5669dab79a3b8b1be0320f762a0e93d978c0a0 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Mon, 22 Nov 2021 17:13:29 -0700 Subject: [PATCH 082/169] Tweak table layout --- public/scripts/manage.js | 37 ++++++++++++++++++++++++++++++++++- public/stylesheets/manage.css | 15 ++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 public/stylesheets/manage.css diff --git a/public/scripts/manage.js b/public/scripts/manage.js index b609171..80bc534 100644 --- a/public/scripts/manage.js +++ b/public/scripts/manage.js @@ -35,12 +35,19 @@ CATEGORIES.push(new Category( headerRow.appendChild(yearsHeader); + const spacerHeader = document.createElement('th'); + spacerHeader.classList.add('spacer-column'); + headerRow.appendChild(spacerHeader); + itemsListTable.appendChild(headerRow); }, function listSeason(season, row) { const yearCell = document.createElement('td'); yearCell.textContent = (season.year - 1) + "-" + season.year; row.appendChild(yearCell); + + const spacerCell = document.createElement('td'); + row.appendChild(spacerCell); }, async function addSeason() { // @@ -65,12 +72,19 @@ CATEGORIES.push(new Category( nameHeader.textContent = "Name"; headerRow.appendChild(nameHeader); + const spacerHeader = document.createElement('th'); + spacerHeader.classList.add('spacer-column'); + headerRow.appendChild(spacerHeader); + itemsListTable.appendChild(headerRow); }, function listSport(sport, row) { const nameCell = document.createElement('td'); nameCell.textContent = sport.name; row.appendChild(nameCell); + + const spacerCell = document.createElement('td'); + row.appendChild(spacerCell); }, async function addSport() { // @@ -97,6 +111,10 @@ CATEGORIES.push(new Category( const genderHeader = document.createElement('th'); headerRow.appendChild(genderHeader); + + const spacerHeader = document.createElement('th'); + spacerHeader.classList.add('spacer-column'); + headerRow.appendChild(spacerHeader); const sportHeader = document.createElement('th'); sportHeader.textContent = "Sport"; @@ -114,6 +132,9 @@ CATEGORIES.push(new Category( genderCell.textContent = gender; row.appendChild(genderCell); + const spacerCell = document.createElement('td'); + row.appendChild(spacerCell); + const sportCell = document.createElement('td'); Data.getSportName(division.sportID) .then(data => sportCell.textContent = data); @@ -141,6 +162,10 @@ CATEGORIES.push(new Category( const nameHeader = document.createElement('th'); nameHeader.textContent = "Name"; headerRow.appendChild(nameHeader); + + const spacerHeader = document.createElement('th'); + spacerHeader.classList.add('spacer-column'); + headerRow.appendChild(spacerHeader); const sportHeader = document.createElement('th'); sportHeader.textContent = "Sport"; @@ -153,6 +178,9 @@ CATEGORIES.push(new Category( nameCell.textContent = team.name; row.appendChild(nameCell); + const spacerCell = document.createElement('td'); + row.appendChild(spacerCell); + const sportCell = document.createElement('td'); Data.getSportName(team.sportID) .then(data => sportCell.textContent = data); @@ -184,6 +212,10 @@ CATEGORIES.push(new Category( const scoreHeader = document.createElement('th'); headerRow.appendChild(scoreHeader); + const spacerHeader = document.createElement('th'); + spacerHeader.classList.add('spacer-column'); + headerRow.appendChild(spacerHeader); + const sportNameHeader = document.createElement('th'); sportNameHeader.textContent = "Sport"; headerRow.appendChild(sportNameHeader); @@ -214,11 +246,14 @@ CATEGORIES.push(new Category( team2ScoreSpan.textContent = game.team2Score; scoresCell.appendChild(team2ScoreSpan); row.appendChild(scoresCell); + + const spacerCell = document.createElement('td'); + row.appendChild(spacerCell); const sportCell = document.createElement('td'); const sportSpan = document.createElement('span'); const divisionSpan = document.createElement('span'); - divisionSpan.classList.add('division-span'); + divisionSpan.classList.add('flat-content'); const divisionNameSpan = document.createElement('span'); const divisionGenderSpan = document.createElement('span'); divisionSpan.appendChild(divisionNameSpan); diff --git a/public/stylesheets/manage.css b/public/stylesheets/manage.css new file mode 100644 index 0000000..95ecca1 --- /dev/null +++ b/public/stylesheets/manage.css @@ -0,0 +1,15 @@ +.flat-content { + flex-direction: row; +} + +th { + text-align: left; +} + +td { + white-space: nowrap; +} + +.spacer-column { + width: 100%; +} \ No newline at end of file From 722bb4fce6933e9fc3a789a71c58f213bfc28980 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Mon, 22 Nov 2021 17:38:45 -0700 Subject: [PATCH 083/169] Edit css --- public/stylesheets/style.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/public/stylesheets/style.css b/public/stylesheets/style.css index b6a5c49..ec6953e 100644 --- a/public/stylesheets/style.css +++ b/public/stylesheets/style.css @@ -15,3 +15,6 @@ a { flex-direction: column; } +.flat-content { + flex-direction: row; +} \ No newline at end of file From 8587212af921870075817c9ee08e3e252b6842af Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Mon, 22 Nov 2021 17:45:58 -0700 Subject: [PATCH 084/169] Fix bug where games.js uses nonexistent moment.js module for formatting --- database/scores/games.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/database/scores/games.js b/database/scores/games.js index 4055523..922d600 100644 --- a/database/scores/games.js +++ b/database/scores/games.js @@ -23,10 +23,8 @@ async function add(divisionID, seasonID, date, team1ID, team2ID, team1Score, tea const query = `INSERT INTO scores.games(division_id, season_id, game_date, team1_id, team2_id, team1_score, team2_score) VALUES($1, $2, $3, $4, $5, $6, $7) RETURNING game_id;`; - - const dateISO = date.format('YYYY-MM-DD'); - const id = (await database.executeQuery(query, [divisionID, seasonID, dateISO, team1ID, team2ID, team1Score, team2Score]))[0][0]; + const id = (await database.executeQuery(query, [divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score]))[0][0]; return new Game(id, date, team1ID, team2ID, team1Score, team2Score); } From e257e9732087192151602008027a62c4ead3b297 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Mon, 22 Nov 2021 17:48:21 -0700 Subject: [PATCH 085/169] Edit CSS --- public/stylesheets/manage.css | 4 ---- 1 file changed, 4 deletions(-) diff --git a/public/stylesheets/manage.css b/public/stylesheets/manage.css index 95ecca1..6a5359f 100644 --- a/public/stylesheets/manage.css +++ b/public/stylesheets/manage.css @@ -1,7 +1,3 @@ -.flat-content { - flex-direction: row; -} - th { text-align: left; } From 9f2572336fc897e758bc6cdff00902936e72f837 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Mon, 22 Nov 2021 17:48:51 -0700 Subject: [PATCH 086/169] Move game submit page from /submit to /manage/addgame --- public/scripts/manage.js | 17 +-------------- routes/manage.js | 23 +++++++++++++++++++- routes/submit.js | 27 ------------------------ views/{submit.pug => manage/addgame.pug} | 2 +- views/manage/layout.pug | 10 +++++++++ 5 files changed, 34 insertions(+), 45 deletions(-) delete mode 100644 routes/submit.js rename views/{submit.pug => manage/addgame.pug} (93%) create mode 100644 views/manage/layout.pug diff --git a/public/scripts/manage.js b/public/scripts/manage.js index 80bc534..04656eb 100644 --- a/public/scripts/manage.js +++ b/public/scripts/manage.js @@ -52,9 +52,6 @@ CATEGORIES.push(new Category( async function addSeason() { // }, - async function submitSeason() { - // - }, async function editSeason() { } @@ -89,9 +86,6 @@ CATEGORIES.push(new Category( async function addSport() { // }, - async function submitSport() { - // - }, async function editSport() { } @@ -143,9 +137,6 @@ CATEGORIES.push(new Category( async function addSeason() { // }, - async function submitSeason() { - // - }, async function editSeason() { } @@ -189,9 +180,6 @@ CATEGORIES.push(new Category( async function addSeason() { // }, - async function submitSeason() { - // - }, async function editSeason() { } @@ -278,10 +266,7 @@ CATEGORIES.push(new Category( dateCell.appendChild(dateSpan); row.appendChild(dateCell); }, - async function addSeason() { - // - }, - async function submitSeason() { + async function addGame() { // }, async function editSeason() { diff --git a/routes/manage.js b/routes/manage.js index 796064b..454c3c9 100644 --- a/routes/manage.js +++ b/routes/manage.js @@ -1,9 +1,30 @@ var express = require('express'); var router = express.Router(); +var genders = require('../database/scores/genders'); +var games = require('../database/scores/games'); + -/* GET manage page. */ router.get('/', function(req, res, next) { res.render('manage', { title: 'Score Management' }); }); +router.get('/addgame', function(req, res, next) { + res.render('manage/addgame', { title: 'Submit Score' }); +}); + +router.post('/submitgame', function(req, res, next) { + const seasonID = req.body['year']; + const sportID = req.body['sport']; + const gender = (req.body['gender'] == "female") ? genders.FEMALE : genders.MALE; + const divisionID = req.body['division']; + const date = req.body['date']; + const team1ID = req.body['team1']; + const team1Score = req.body['team1-score']; + const team2ID = req.body['team2']; + const team2Score = req.body['team2-score']; + + games.add(divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score) + .then(res.send("SUCCESS")); +}); + module.exports = router; diff --git a/routes/submit.js b/routes/submit.js deleted file mode 100644 index fcd660b..0000000 --- a/routes/submit.js +++ /dev/null @@ -1,27 +0,0 @@ -var express = require('express'); -var router = express.Router(); -var genders = require('../database/scores/genders'); -var games = require('../database/scores/games'); - -/* GET submit page. */ -router.get('/', function(req, res, next) { - res.render('submit', { title: 'Submit Score' }); -}); - -/* POST submit page. */ -router.post('/', function(req, res, next) { - const seasonID = req.body['year']; - const sportID = req.body['sport']; - const gender = (req.body['gender'] == "female") ? genders.FEMALE : genders.MALE; - const divisionID = req.body['division']; - const date = moment(req.body['date']); - const team1ID = req.body['team1']; - const team1Score = req.body['team1-score']; - const team2ID = req.body['team2']; - const team2Score = req.body['team2-score']; - - games.add(divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score) - .then(res.send("SUCCESS")); -}); - -module.exports = router; diff --git a/views/submit.pug b/views/manage/addgame.pug similarity index 93% rename from views/submit.pug rename to views/manage/addgame.pug index 85f352f..04026f4 100644 --- a/views/submit.pug +++ b/views/manage/addgame.pug @@ -7,7 +7,7 @@ block stylesheets block content div#mobile-view h1 Submit Score - form(action='/submit', method='POST') + form(action='./submitgame', method='POST') span(class='form-section') label Year span(class='form-section-input') diff --git a/views/manage/layout.pug b/views/manage/layout.pug new file mode 100644 index 0000000..e4f85eb --- /dev/null +++ b/views/manage/layout.pug @@ -0,0 +1,10 @@ +doctype html +html + head + title= title + meta(name='viewport', content='width=device-width, initial-scale=1') + link(rel='stylesheet', href='/stylesheets/style.css') + block stylesheets + body + block content + block scripts From 7c5c059e09ca215048caaf71d53788032773eb01 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Mon, 22 Nov 2021 17:55:15 -0700 Subject: [PATCH 087/169] Remove submit router from app.js --- app.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/app.js b/app.js index 6bc76de..c773280 100644 --- a/app.js +++ b/app.js @@ -6,7 +6,6 @@ var logger = require('morgan'); var indexRouter = require('./routes/index'); var usersRouter = require('./routes/users'); -var submitRouter = require('./routes/submit'); var dataRouter = require('./routes/data'); var manageRouter = require('./routes/manage'); @@ -24,7 +23,6 @@ app.use(express.static(path.join(__dirname, 'public'))); app.use('/', indexRouter); app.use('/users', usersRouter); -app.use('/submit', submitRouter); app.use('/data', dataRouter); app.use('/manage', manageRouter); From 4133758bbafbb2c9085fd6d2eb7b2c3770796566 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Mon, 22 Nov 2021 18:04:36 -0700 Subject: [PATCH 088/169] Create page to add season --- public/scripts/manage/season.js | 0 routes/manage.js | 12 ++++++++++++ views/manage/addseason.pug | 19 +++++++++++++++++++ 3 files changed, 31 insertions(+) create mode 100644 public/scripts/manage/season.js create mode 100644 views/manage/addseason.pug diff --git a/public/scripts/manage/season.js b/public/scripts/manage/season.js new file mode 100644 index 0000000..e69de29 diff --git a/routes/manage.js b/routes/manage.js index 454c3c9..72d0784 100644 --- a/routes/manage.js +++ b/routes/manage.js @@ -2,6 +2,7 @@ var express = require('express'); var router = express.Router(); var genders = require('../database/scores/genders'); var games = require('../database/scores/games'); +var seasons = require('../database/scores/seasons'); router.get('/', function(req, res, next) { @@ -27,4 +28,15 @@ router.post('/submitgame', function(req, res, next) { .then(res.send("SUCCESS")); }); +router.get('/addseason', function(req, res, next) { + res.render('manage/addseason', { title: 'Add season', currentYear : (new Date()).getFullYear() }); +}); + +router.post('/submitseason', function(req, res, next) { + const year = req.body['year']; + + seasons.add(year) + .then(res.send("SUCCESS")); +}); + module.exports = router; diff --git a/views/manage/addseason.pug b/views/manage/addseason.pug new file mode 100644 index 0000000..e82937c --- /dev/null +++ b/views/manage/addseason.pug @@ -0,0 +1,19 @@ +extends layout + +block stylesheets + link(rel='stylesheet', href='/stylesheets/submit.css') + link(rel='stylesheet', href='/stylesheets/form.css') + +block content + div#mobile-view + h1 Add Season + form(action='./submitseason', method='POST') + span(class='form-section') + label Ending year + span(class='form-section-input') + input(type="number", name="year", value=currentYear) + span(class='form-section') + button#submit-button(type="submit") Submit + +block scripts + script(src='/scripts/season.js' type="module") \ No newline at end of file From 31575212ad0ef0e733ac8007b5a407282e3b62b6 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Mon, 22 Nov 2021 18:42:02 -0700 Subject: [PATCH 089/169] Allow "Add new..." button to redirect to webpages per category --- public/scripts/manage.js | 8 +++++--- views/manage.pug | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/public/scripts/manage.js b/public/scripts/manage.js index 04656eb..2f24fc4 100644 --- a/public/scripts/manage.js +++ b/public/scripts/manage.js @@ -2,6 +2,7 @@ import * as Data from "./data.js"; const categoryDropdown = document.getElementById('category-dropdown'); const itemsListTable = document.getElementById('items-list'); +const addNewButton = document.getElementById('add-new-button'); function getGenderLetter(genderName) { @@ -50,7 +51,7 @@ CATEGORIES.push(new Category( row.appendChild(spacerCell); }, async function addSeason() { - // + window.location.href = "manage/addseason"; }, async function editSeason() { @@ -267,7 +268,7 @@ CATEGORIES.push(new Category( row.appendChild(dateCell); }, async function addGame() { - // + window.location.href = "manage/addgame"; }, async function editSeason() { @@ -313,4 +314,5 @@ listItems(CATEGORIES[categoryDropdown.selectedIndex]); categoryDropdown.onchange = () => { listItems(CATEGORIES[categoryDropdown.selectedIndex]); -}; \ No newline at end of file +}; +addNewButton.addEventListener('click', () => CATEGORIES[categoryDropdown.selectedIndex].addItem()); \ No newline at end of file diff --git a/views/manage.pug b/views/manage.pug index 7977824..c43dcf9 100644 --- a/views/manage.pug +++ b/views/manage.pug @@ -20,7 +20,7 @@ block content div h2#table-header table#items-list - button Add new... + button#add-new-button Add new... block scripts script(src='/scripts/manage.js' type="module") \ No newline at end of file From f95015095d425d28147d16c4ff52bdbea0144ab2 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Mon, 22 Nov 2021 18:47:40 -0700 Subject: [PATCH 090/169] Add page for adding sports --- public/scripts/manage.js | 2 +- routes/manage.js | 12 ++++++++++++ views/manage/addsport.pug | 19 +++++++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 views/manage/addsport.pug diff --git a/public/scripts/manage.js b/public/scripts/manage.js index 2f24fc4..2017341 100644 --- a/public/scripts/manage.js +++ b/public/scripts/manage.js @@ -85,7 +85,7 @@ CATEGORIES.push(new Category( row.appendChild(spacerCell); }, async function addSport() { - // + window.location.href = "manage/addsport"; }, async function editSport() { diff --git a/routes/manage.js b/routes/manage.js index 72d0784..4582727 100644 --- a/routes/manage.js +++ b/routes/manage.js @@ -3,6 +3,7 @@ var router = express.Router(); var genders = require('../database/scores/genders'); var games = require('../database/scores/games'); var seasons = require('../database/scores/seasons'); +var sports = require('../database/scores/sports'); router.get('/', function(req, res, next) { @@ -39,4 +40,15 @@ router.post('/submitseason', function(req, res, next) { .then(res.send("SUCCESS")); }); +router.get('/addsport', function(req, res, next) { + res.render('manage/addsport', { title: 'Add sport' }); +}); + +router.post('/submitsport', function(req, res, next) { + const name = req.body['name']; + + sports.add(name) + .then(res.send("SUCCESS")); +}); + module.exports = router; diff --git a/views/manage/addsport.pug b/views/manage/addsport.pug new file mode 100644 index 0000000..7f7e5ad --- /dev/null +++ b/views/manage/addsport.pug @@ -0,0 +1,19 @@ +extends layout + +block stylesheets + link(rel='stylesheet', href='/stylesheets/submit.css') + link(rel='stylesheet', href='/stylesheets/form.css') + +block content + div#mobile-view + h1 Add Sport + form(action='./submitsport', method='POST') + span(class='form-section') + label Sport name + span(class='form-section-input') + input(type="text", name="name") + span(class='form-section') + button#submit-button(type="submit") Submit + +block scripts + script(src='/scripts/sport.js' type="module") \ No newline at end of file From 1a3b013a0f93f9c12e8d4a170173636c5704e005 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Mon, 22 Nov 2021 19:11:16 -0700 Subject: [PATCH 091/169] Add page to add divisions --- public/scripts/manage.js | 6 +++--- public/scripts/manage/division.js | 18 ++++++++++++++++++ routes/manage.js | 23 +++++++++++++++++++++++ views/manage/adddivision.pug | 30 ++++++++++++++++++++++++++++++ 4 files changed, 74 insertions(+), 3 deletions(-) create mode 100644 public/scripts/manage/division.js create mode 100644 views/manage/adddivision.pug diff --git a/public/scripts/manage.js b/public/scripts/manage.js index 2017341..770a517 100644 --- a/public/scripts/manage.js +++ b/public/scripts/manage.js @@ -135,10 +135,10 @@ CATEGORIES.push(new Category( .then(data => sportCell.textContent = data); row.appendChild(sportCell); }, - async function addSeason() { - // + async function addDivision() { + window.location.href = "manage/adddivision"; }, - async function editSeason() { + async function editDivision() { } )); diff --git a/public/scripts/manage/division.js b/public/scripts/manage/division.js new file mode 100644 index 0000000..6b37492 --- /dev/null +++ b/public/scripts/manage/division.js @@ -0,0 +1,18 @@ +import * as Data from "../data.js"; + + +const sportDropdown = document.getElementById('sport-dropdown'); + +async function listSports() { + sportDropdown.innerHTML = ""; + + const sportsList = await Data.getSports(); + + sportsList.forEach(sport => { + const option = document.createElement('option'); + option.text = sport.name; + option.value = sport.id; + sportDropdown.appendChild(option); + }); +} +listSports(); \ No newline at end of file diff --git a/routes/manage.js b/routes/manage.js index 4582727..72f17e8 100644 --- a/routes/manage.js +++ b/routes/manage.js @@ -4,6 +4,8 @@ var genders = require('../database/scores/genders'); var games = require('../database/scores/games'); var seasons = require('../database/scores/seasons'); var sports = require('../database/scores/sports'); +var divisions = require('../database/scores/divisions'); +var genders = require('../database/scores/genders'); router.get('/', function(req, res, next) { @@ -51,4 +53,25 @@ router.post('/submitsport', function(req, res, next) { .then(res.send("SUCCESS")); }); +router.get('/adddivision', function(req, res, next) { + res.render('manage/adddivision', { title: 'Add division' }); +}); + +router.post('/submitdivision', function(req, res, next) { + const name = req.body['name']; + const sport = req.body['sport']; + const genderName = req.body['gender']; + + if(genderName == "both") { + divisions.add(name, genders.FEMALE, sport) + .then(divisions.add(name, genders.MALE, sport) + .then(res.send("SUCCESS"))); + } + else { + const gender = (genderName == "female") ? genders.FEMALE : genders.MALE; + divisions.add(name, gender, sport) + .then(res.send("SUCCESS")); + } +}); + module.exports = router; diff --git a/views/manage/adddivision.pug b/views/manage/adddivision.pug new file mode 100644 index 0000000..4b8d7d1 --- /dev/null +++ b/views/manage/adddivision.pug @@ -0,0 +1,30 @@ +extends layout + +block stylesheets + link(rel='stylesheet', href='/stylesheets/submit.css') + link(rel='stylesheet', href='/stylesheets/form.css') + +block content + div#mobile-view + h1 Add Division + form(action='./submitdivision', method='POST') + span(class='form-section') + label Sport + span(class='form-section-input') + select#sport-dropdown(name="sport" class="form-main-dropdown") + span(class='form-section') + label Gender + span(class='form-section-input') + select#gender-dropdown(name="gender" class="form-main-dropdown") + option(value="both") Both + option(value="female") Female + option(value="male") Male + span(class='form-section') + label Division name + span(class='form-section-input') + input(type="text", name="name") + span(class='form-section') + button#submit-button(type="submit") Submit + +block scripts + script(src='/scripts/manage/division.js' type="module") \ No newline at end of file From aacff9cca1121b4afd4156fa7f5fa9d966b76620 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Mon, 22 Nov 2021 19:17:23 -0700 Subject: [PATCH 092/169] Add page to add teams --- public/scripts/manage.js | 6 +++--- public/scripts/manage/team.js | 18 ++++++++++++++++++ routes/manage.js | 13 +++++++++++++ views/manage/addteam.pug | 23 +++++++++++++++++++++++ 4 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 public/scripts/manage/team.js create mode 100644 views/manage/addteam.pug diff --git a/public/scripts/manage.js b/public/scripts/manage.js index 770a517..651047e 100644 --- a/public/scripts/manage.js +++ b/public/scripts/manage.js @@ -178,10 +178,10 @@ CATEGORIES.push(new Category( .then(data => sportCell.textContent = data); row.appendChild(sportCell); }, - async function addSeason() { - // + async function addTeam() { + window.location.href = "manage/addteam"; }, - async function editSeason() { + async function editTeam() { } )); diff --git a/public/scripts/manage/team.js b/public/scripts/manage/team.js new file mode 100644 index 0000000..6b37492 --- /dev/null +++ b/public/scripts/manage/team.js @@ -0,0 +1,18 @@ +import * as Data from "../data.js"; + + +const sportDropdown = document.getElementById('sport-dropdown'); + +async function listSports() { + sportDropdown.innerHTML = ""; + + const sportsList = await Data.getSports(); + + sportsList.forEach(sport => { + const option = document.createElement('option'); + option.text = sport.name; + option.value = sport.id; + sportDropdown.appendChild(option); + }); +} +listSports(); \ No newline at end of file diff --git a/routes/manage.js b/routes/manage.js index 72f17e8..04b2b9a 100644 --- a/routes/manage.js +++ b/routes/manage.js @@ -6,6 +6,7 @@ var seasons = require('../database/scores/seasons'); var sports = require('../database/scores/sports'); var divisions = require('../database/scores/divisions'); var genders = require('../database/scores/genders'); +var teams = require('../database/scores/teams'); router.get('/', function(req, res, next) { @@ -74,4 +75,16 @@ router.post('/submitdivision', function(req, res, next) { } }); +router.get('/addteam', function(req, res, next) { + res.render('manage/addteam', { title: 'Add team' }); +}); + +router.post('/submitteam', function(req, res, next) { + const name = req.body['name']; + const sport = req.body['sport']; + + teams.add(name, sport) + .then(res.send("SUCCESS")); +}); + module.exports = router; diff --git a/views/manage/addteam.pug b/views/manage/addteam.pug new file mode 100644 index 0000000..c3d422b --- /dev/null +++ b/views/manage/addteam.pug @@ -0,0 +1,23 @@ +extends layout + +block stylesheets + link(rel='stylesheet', href='/stylesheets/submit.css') + link(rel='stylesheet', href='/stylesheets/form.css') + +block content + div#mobile-view + h1 Add Team + form(action='./submitteam', method='POST') + span(class='form-section') + label Sport + span(class='form-section-input') + select#sport-dropdown(name="sport" class="form-main-dropdown") + span(class='form-section') + label Team name + span(class='form-section-input') + input(type="text", name="name") + span(class='form-section') + button#submit-button(type="submit") Submit + +block scripts + script(src='/scripts/manage/team.js' type="module") \ No newline at end of file From 5c7875382818d5725b9a7d2c928336e48f60ae78 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Mon, 22 Nov 2021 19:23:20 -0700 Subject: [PATCH 093/169] Fix bug regarding page redirection --- public/scripts/manage.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/public/scripts/manage.js b/public/scripts/manage.js index 651047e..5482569 100644 --- a/public/scripts/manage.js +++ b/public/scripts/manage.js @@ -51,7 +51,7 @@ CATEGORIES.push(new Category( row.appendChild(spacerCell); }, async function addSeason() { - window.location.href = "manage/addseason"; + window.location.href = "/manage/addseason"; }, async function editSeason() { @@ -85,7 +85,7 @@ CATEGORIES.push(new Category( row.appendChild(spacerCell); }, async function addSport() { - window.location.href = "manage/addsport"; + window.location.href = "/manage/addsport"; }, async function editSport() { @@ -136,7 +136,7 @@ CATEGORIES.push(new Category( row.appendChild(sportCell); }, async function addDivision() { - window.location.href = "manage/adddivision"; + window.location.href = "/manage/adddivision"; }, async function editDivision() { @@ -179,7 +179,7 @@ CATEGORIES.push(new Category( row.appendChild(sportCell); }, async function addTeam() { - window.location.href = "manage/addteam"; + window.location.href = "/manage/addteam"; }, async function editTeam() { @@ -268,7 +268,7 @@ CATEGORIES.push(new Category( row.appendChild(dateCell); }, async function addGame() { - window.location.href = "manage/addgame"; + window.location.href = "/manage/addgame"; }, async function editSeason() { From 6ef9096cb792e5bd4a0aaebf1391574ead25d267 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Mon, 22 Nov 2021 19:25:44 -0700 Subject: [PATCH 094/169] Remove delete button on manage page --- public/scripts/manage.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/public/scripts/manage.js b/public/scripts/manage.js index 5482569..1f4ff7e 100644 --- a/public/scripts/manage.js +++ b/public/scripts/manage.js @@ -297,12 +297,6 @@ async function listItems(category) { editSpan.appendChild(editButton); manageCell.appendChild(editSpan); - const removeSpan = document.createElement('remove'); - const removeButton = document.createElement('button'); - removeButton.textContent = "D"; - removeSpan.appendChild(removeButton); - manageCell.appendChild(removeSpan); - row.appendChild(manageCell); itemsListTable.appendChild(row); From 4fec3a45c800a07b999624ce7e7338d456ccfd61 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Mon, 22 Nov 2021 19:40:33 -0700 Subject: [PATCH 095/169] Change header in addseason.pug to match title --- views/manage/addseason.pug | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/manage/addseason.pug b/views/manage/addseason.pug index e82937c..96d4080 100644 --- a/views/manage/addseason.pug +++ b/views/manage/addseason.pug @@ -6,7 +6,7 @@ block stylesheets block content div#mobile-view - h1 Add Season + h1 #{title} form(action='./submitseason', method='POST') span(class='form-section') label Ending year From 35af76c90f7e4d4acf1979c1c8ce259881ea68e3 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Mon, 22 Nov 2021 22:03:02 -0700 Subject: [PATCH 096/169] Add ability to edit sports in manage page --- public/scripts/manage.js | 12 ++++++---- public/scripts/manage/sport.js | 43 ++++++++++++++++++++++++++++++++++ public/stylesheets/form.css | 4 ++++ routes/manage.js | 13 +++++----- views/manage/addsport.pug | 13 ++++++---- 5 files changed, 69 insertions(+), 16 deletions(-) create mode 100644 public/scripts/manage/sport.js diff --git a/public/scripts/manage.js b/public/scripts/manage.js index 1f4ff7e..8b0221b 100644 --- a/public/scripts/manage.js +++ b/public/scripts/manage.js @@ -10,13 +10,12 @@ function getGenderLetter(genderName) { } class Category { - constructor(name, getItems, listHeaders, listItem, addItem, submitItem, editItem) { + constructor(name, getItems, listHeaders, listItem, addItem, editItem) { this.name = name; this.getItems = getItems; this.listHeaders = listHeaders; this.listItem = listItem; this.addItem = addItem; - this.submitItem = submitItem; this.editItem = editItem; } } @@ -85,10 +84,10 @@ CATEGORIES.push(new Category( row.appendChild(spacerCell); }, async function addSport() { - window.location.href = "/manage/addsport"; + window.location.href = `/manage/sport`; }, - async function editSport() { - + async function editSport(id) { + window.location.href = `/manage/sport?sport=${id}` } )); @@ -294,6 +293,9 @@ async function listItems(category) { const editSpan = document.createElement('span'); const editButton = document.createElement('button'); editButton.textContent = "E"; + editButton.addEventListener('click', () => { + CATEGORIES[categoryDropdown.selectedIndex].editItem(item.id); + }); editSpan.appendChild(editButton); manageCell.appendChild(editSpan); diff --git a/public/scripts/manage/sport.js b/public/scripts/manage/sport.js new file mode 100644 index 0000000..2b12593 --- /dev/null +++ b/public/scripts/manage/sport.js @@ -0,0 +1,43 @@ +import * as Data from "../data.js"; + + +const mainHeader = document.getElementById('main-header'); +const nameTextbox = document.getElementById('name-textbox'); +const submitButton = document.getElementById('submit-button'); +const deleteButton = document.getElementById('delete-button'); +const submissionForm = document.getElementById('submission-form'); + + +async function initializeForm() { + let params = new URLSearchParams(location.search); + let sportID = params.get('sport') + if(sportID) { + mainHeader.textContent = "Edit Sport"; + + const sportName = await Data.getSportName(sportID); + + nameTextbox.value = sportName; + deleteButton.style.visibility = "visible"; + deleteButton.disabled = false; + + const sportIDInput = document.createElement('input'); + sportIDInput.setAttribute('name', 'sport'); + sportIDInput.setAttribute('value', sportID); + sportIDInput.setAttribute('type', 'hidden'); + submissionForm.appendChild(sportIDInput); + } + nameTextbox.disabled = false; + + nameTextbox.addEventListener('keyup', checkDataValidity); +} +initializeForm(); + +async function checkDataValidity() { + let dataIsValid = true; + + if(!nameTextbox.value) dataIsValid = false; + + + if(dataIsValid) submitButton.disabled = false; + else submitButton.disabled = true; +} \ No newline at end of file diff --git a/public/stylesheets/form.css b/public/stylesheets/form.css index 85b34bc..21abe10 100644 --- a/public/stylesheets/form.css +++ b/public/stylesheets/form.css @@ -31,4 +31,8 @@ form { #submit-button { margin-top: 1.5em; + } + + #delete-button { + visibility: hidden; } \ No newline at end of file diff --git a/routes/manage.js b/routes/manage.js index 04b2b9a..a42f734 100644 --- a/routes/manage.js +++ b/routes/manage.js @@ -33,7 +33,7 @@ router.post('/submitgame', function(req, res, next) { }); router.get('/addseason', function(req, res, next) { - res.render('manage/addseason', { title: 'Add season', currentYear : (new Date()).getFullYear() }); + res.render('manage/addseason', { title: 'Add Season', currentYear : (new Date()).getFullYear() }); }); router.post('/submitseason', function(req, res, next) { @@ -43,15 +43,16 @@ router.post('/submitseason', function(req, res, next) { .then(res.send("SUCCESS")); }); -router.get('/addsport', function(req, res, next) { - res.render('manage/addsport', { title: 'Add sport' }); +router.get('/sport', function(req, res, next) { + res.render('manage/addsport', { title: 'Add Sport' }); }); -router.post('/submitsport', function(req, res, next) { +router.post('/sport', function(req, res, next) { const name = req.body['name']; + const id = req.body['sport']; - sports.add(name) - .then(res.send("SUCCESS")); + if(id) sports.rename(id, name).then(res.redirect('/manage')); + else sports.add(name).then(res.redirect('/manage')); }); router.get('/adddivision', function(req, res, next) { diff --git a/views/manage/addsport.pug b/views/manage/addsport.pug index 7f7e5ad..5251b7e 100644 --- a/views/manage/addsport.pug +++ b/views/manage/addsport.pug @@ -6,14 +6,17 @@ block stylesheets block content div#mobile-view - h1 Add Sport - form(action='./submitsport', method='POST') + h1#main-header Add Sport + form#submission-form(action='./sport', method='POST') span(class='form-section') label Sport name span(class='form-section-input') - input(type="text", name="name") + input#name-textbox(type="text" name="name" disabled) span(class='form-section') - button#submit-button(type="submit") Submit + button#submit-button(type="submit" disabled) Submit + span(class='form-section') + button#delete-button(disabled) Delete + block scripts - script(src='/scripts/sport.js' type="module") \ No newline at end of file + script(src='/scripts/manage/sport.js' type="module") \ No newline at end of file From cf91548daca5c4b4b174adca65204ab9d57e18a4 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Mon, 22 Nov 2021 22:10:47 -0700 Subject: [PATCH 097/169] Add ability to remove sports --- public/scripts/manage/sport.js | 17 ++++++++++++++++- routes/manage.js | 4 +++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/public/scripts/manage/sport.js b/public/scripts/manage/sport.js index 2b12593..c30fbce 100644 --- a/public/scripts/manage/sport.js +++ b/public/scripts/manage/sport.js @@ -40,4 +40,19 @@ async function checkDataValidity() { if(dataIsValid) submitButton.disabled = false; else submitButton.disabled = true; -} \ No newline at end of file +} + +async function removeSport() { + const removeInput = document.createElement('input'); + removeInput.setAttribute('name', 'remove'); + removeInput.setAttribute('value', 1); + removeInput.setAttribute('type', 'hidden'); + submissionForm.appendChild(removeInput); + submissionForm.submit(); +} + +deleteButton.addEventListener('click', () => { + const verified = confirm("This sport will be removed."); + + if(verified) removeSport(); +}); \ No newline at end of file diff --git a/routes/manage.js b/routes/manage.js index a42f734..145dfa0 100644 --- a/routes/manage.js +++ b/routes/manage.js @@ -50,8 +50,10 @@ router.get('/sport', function(req, res, next) { router.post('/sport', function(req, res, next) { const name = req.body['name']; const id = req.body['sport']; + const remove = req.body['remove']; - if(id) sports.rename(id, name).then(res.redirect('/manage')); + if(remove) sports.remove(id).then(res.redirect('/manage')); + else if(id) sports.rename(id, name).then(res.redirect('/manage')); else sports.add(name).then(res.redirect('/manage')); }); From a2b9c5f50ec8e5e258a38b99e71a15dbaf130688 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Mon, 22 Nov 2021 23:23:57 -0700 Subject: [PATCH 098/169] Add ability to edit divisions --- public/scripts/form.js | 40 ++++++++++++++++++++ public/scripts/manage.js | 6 +-- public/scripts/manage/division.js | 61 +++++++++++++++++++++++++------ routes/manage.js | 32 ++++++++++------ views/manage/adddivision.pug | 14 ++++--- 5 files changed, 122 insertions(+), 31 deletions(-) create mode 100644 public/scripts/form.js diff --git a/public/scripts/form.js b/public/scripts/form.js new file mode 100644 index 0000000..9d4ee93 --- /dev/null +++ b/public/scripts/form.js @@ -0,0 +1,40 @@ +import * as Data from "./data.js"; + +export async function populateSports(sportDropdown, selectedSportID = undefined) { + sportDropdown.innerHTML = ""; + + const sportsList = await Data.getSports(); + + let currentIndex = 0; + let selectedSportIndex; + sportsList.forEach(sport => { + const option = document.createElement('option'); + option.text = sport.name; + option.value = sport.id; + sportDropdown.appendChild(option); + + if(sport.id == selectedSportID) selectedSportIndex = currentIndex; + currentIndex++; + }); + + if(selectedSportIndex) sportDropdown.selectedIndex = selectedSportIndex; +} + +export async function addHiddenValue(name, value, form) { + const valueInput = document.createElement('input'); + valueInput.setAttribute('name', name); + valueInput.setAttribute('value', value); + valueInput.setAttribute('type', 'hidden'); + form.appendChild(valueInput); +} + +export async function addRemoveFunction(removeButton, form, objectTitle) { + removeButton.addEventListener('click', async () => { + const verified = confirm(`This ${objectTitle} will be removed.`); + + if(verified) { + await addHiddenValue('remove', 1, form); + form.submit(); + } + }); +} \ No newline at end of file diff --git a/public/scripts/manage.js b/public/scripts/manage.js index 8b0221b..a30c179 100644 --- a/public/scripts/manage.js +++ b/public/scripts/manage.js @@ -135,10 +135,10 @@ CATEGORIES.push(new Category( row.appendChild(sportCell); }, async function addDivision() { - window.location.href = "/manage/adddivision"; + window.location.href = "/manage/division"; }, - async function editDivision() { - + async function editDivision(id) { + window.location.href = `/manage/division?division=${id}` } )); diff --git a/public/scripts/manage/division.js b/public/scripts/manage/division.js index 6b37492..fb623c5 100644 --- a/public/scripts/manage/division.js +++ b/public/scripts/manage/division.js @@ -1,18 +1,57 @@ import * as Data from "../data.js"; +import * as Form from "../form.js"; +const submissionForm = document.getElementById('submission-form'); const sportDropdown = document.getElementById('sport-dropdown'); +const genderDropdown = document.getElementById('gender-dropdown'); +const nameTextbox = document.getElementById('name-textbox'); +const submitButton = document.getElementById('submit-button'); +const deleteButton = document.getElementById('delete-button'); -async function listSports() { - sportDropdown.innerHTML = ""; - const sportsList = await Data.getSports(); - - sportsList.forEach(sport => { - const option = document.createElement('option'); - option.text = sport.name; - option.value = sport.id; - sportDropdown.appendChild(option); - }); +async function initializeForm() { + let params = new URLSearchParams(location.search); + let divisionID = params.get('division'); + if(divisionID) { + const division = await Data.getDivision(divisionID); + + nameTextbox.value = division.name; + + deleteButton.style.visibility = "visible"; + deleteButton.disabled = false; + + const gender = division.gender.name; + + if(gender == 'female') genderDropdown.selectedIndex = 1; + else genderDropdown.selectedIndex = 2; + + Form.populateSports(sportDropdown, division.sportID); + + Form.addHiddenValue('division', divisionID, submissionForm); + } + else { + Form.populateSports(sportDropdown); + + genderDropdown.disabled = false; + + sportDropdown.disabled = false; + } + + nameTextbox.disabled = false; + nameTextbox.addEventListener('keyup', checkDataValidity); } -listSports(); \ No newline at end of file +initializeForm(); + +async function checkDataValidity() { + let dataIsValid = true; + + if(!nameTextbox.value) dataIsValid = false; + + + if(dataIsValid) submitButton.disabled = false; + else submitButton.disabled = true; +} + +Form.addRemoveFunction(deleteButton, submissionForm, "division"); + diff --git a/routes/manage.js b/routes/manage.js index 145dfa0..4b6683f 100644 --- a/routes/manage.js +++ b/routes/manage.js @@ -57,24 +57,34 @@ router.post('/sport', function(req, res, next) { else sports.add(name).then(res.redirect('/manage')); }); -router.get('/adddivision', function(req, res, next) { - res.render('manage/adddivision', { title: 'Add division' }); +router.get('/division', function(req, res, next) { + let title = req.query.division ? 'Edit Division' : 'Add Division' + + res.render('manage/adddivision', { title }); }); -router.post('/submitdivision', function(req, res, next) { +router.post('/division', function(req, res, next) { const name = req.body['name']; const sport = req.body['sport']; const genderName = req.body['gender']; - if(genderName == "both") { - divisions.add(name, genders.FEMALE, sport) - .then(divisions.add(name, genders.MALE, sport) - .then(res.send("SUCCESS"))); - } + const id = req.body['division']; + const remove = req.body['remove']; + + + if(remove) divisions.remove(id).then(res.redirect('/manage')); + else if(id) divisions.rename(id, name).then(res.redirect('/manage')); else { - const gender = (genderName == "female") ? genders.FEMALE : genders.MALE; - divisions.add(name, gender, sport) - .then(res.send("SUCCESS")); + if(genderName == "both") { + divisions.add(name, genders.FEMALE, sport) + .then(divisions.add(name, genders.MALE, sport) + .then(res.redirect("/manage"))); + } + else { + const gender = (genderName == "female") ? genders.FEMALE : genders.MALE; + divisions.add(name, gender, sport) + .then(res.redirect("/manage")); + } } }); diff --git a/views/manage/adddivision.pug b/views/manage/adddivision.pug index 4b8d7d1..5db737c 100644 --- a/views/manage/adddivision.pug +++ b/views/manage/adddivision.pug @@ -6,25 +6,27 @@ block stylesheets block content div#mobile-view - h1 Add Division - form(action='./submitdivision', method='POST') + h1 #{title} + form#submission-form(action='./division', method='POST') span(class='form-section') label Sport span(class='form-section-input') - select#sport-dropdown(name="sport" class="form-main-dropdown") + select#sport-dropdown(name="sport" class="form-main-dropdown" disabled) span(class='form-section') label Gender span(class='form-section-input') - select#gender-dropdown(name="gender" class="form-main-dropdown") + select#gender-dropdown(name="gender" class="form-main-dropdown" disabled) option(value="both") Both option(value="female") Female option(value="male") Male span(class='form-section') label Division name span(class='form-section-input') - input(type="text", name="name") + input#name-textbox(type="text", name="name" disabled) span(class='form-section') - button#submit-button(type="submit") Submit + button#submit-button(type="submit" disabled) Submit + span(class='form-section') + button#delete-button(disabled) Delete block scripts script(src='/scripts/manage/division.js' type="module") \ No newline at end of file From 5d88f0ac4dd393ba9a2181ceda8127454d2af140 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Mon, 22 Nov 2021 23:45:24 -0700 Subject: [PATCH 099/169] Add ability to edit teams --- database/scores/teams.js | 7 +++-- public/scripts/data.js | 4 +-- public/scripts/index.js | 4 +-- public/scripts/manage.js | 14 +++++----- public/scripts/manage/team.js | 52 +++++++++++++++++++++++++++-------- routes/manage.js | 16 +++++++---- views/manage/addteam.pug | 12 ++++---- 7 files changed, 74 insertions(+), 35 deletions(-) diff --git a/database/scores/teams.js b/database/scores/teams.js index 3717841..592796f 100644 --- a/database/scores/teams.js +++ b/database/scores/teams.js @@ -65,11 +65,12 @@ async function retrieve(sportID = undefined) { } async function getFromID(id) { - const query = `SELECT team_name + const query = `SELECT team_name, sport_id FROM scores.teams WHERE team_id = $1;`; - const name = (await database.executeQuery(query, [id]))[0][0]; - return new Team(id, name); + const row = (await database.executeQuery(query, [id]))[0]; + console.log(row); + return new Team(id, row[0], row[1]); } diff --git a/public/scripts/data.js b/public/scripts/data.js index 8ef7479..f2eead9 100644 --- a/public/scripts/data.js +++ b/public/scripts/data.js @@ -47,10 +47,10 @@ export async function getTeams(sportID = undefined) { return teamsList; } -export async function getTeamName(teamID) { +export async function getTeam(teamID) { const response = await fetch(`/data/team?team=${+teamID}`); const team = await response.json(); - return team.name; + return team; } export async function getGames(teamID = undefined, divisionID = undefined, seasonID = undefined) { diff --git a/public/scripts/index.js b/public/scripts/index.js index 41d9610..d752ff1 100644 --- a/public/scripts/index.js +++ b/public/scripts/index.js @@ -128,8 +128,8 @@ async function listGames() { row.appendChild(scoreCell); const opponentCell = document.createElement('td'); - Data.getTeamName(game.team2ID) - .then(data => opponentCell.textContent = data); + Data.getTeam(game.team2ID) + .then(data => opponentCell.textContent = data.name); row.appendChild(opponentCell); const dateCell = document.createElement('td'); diff --git a/public/scripts/manage.js b/public/scripts/manage.js index a30c179..ca2f4b2 100644 --- a/public/scripts/manage.js +++ b/public/scripts/manage.js @@ -178,10 +178,10 @@ CATEGORIES.push(new Category( row.appendChild(sportCell); }, async function addTeam() { - window.location.href = "/manage/addteam"; + window.location.href = "/manage/team"; }, - async function editTeam() { - + async function editTeam(id) { + window.location.href = `/manage/team?team=${id}`; } )); @@ -217,12 +217,12 @@ CATEGORIES.push(new Category( function listGame(game, row) { const teamsCell = document.createElement('td'); const team1NameSpan = document.createElement('span'); - Data.getTeamName(game.team1ID) - .then(data => team1NameSpan.textContent = data); + Data.getTeam(game.team1ID) + .then(data => team1NameSpan.textContent = data.name); teamsCell.appendChild(team1NameSpan); const team2NameSpan = document.createElement('span'); - Data.getTeamName(game.team2ID) - .then(data => team2NameSpan.textContent = data); + Data.getTeam(game.team2ID) + .then(data => team2NameSpan.textContent = data.name); teamsCell.appendChild(team2NameSpan); row.appendChild(teamsCell); diff --git a/public/scripts/manage/team.js b/public/scripts/manage/team.js index 6b37492..d5d8291 100644 --- a/public/scripts/manage/team.js +++ b/public/scripts/manage/team.js @@ -1,18 +1,48 @@ import * as Data from "../data.js"; +import * as Form from "../form.js"; +const submissionForm = document.getElementById('submission-form'); const sportDropdown = document.getElementById('sport-dropdown'); +const nameTextbox = document.getElementById('name-textbox'); +const submitButton = document.getElementById('submit-button'); +const deleteButton = document.getElementById('delete-button'); -async function listSports() { - sportDropdown.innerHTML = ""; +async function initializeForm() { + let params = new URLSearchParams(location.search); + let teamID = params.get('team'); + if(teamID) { + const team = await Data.getTeam(teamID); - const sportsList = await Data.getSports(); - - sportsList.forEach(sport => { - const option = document.createElement('option'); - option.text = sport.name; - option.value = sport.id; - sportDropdown.appendChild(option); - }); + nameTextbox.value = team.name; + + deleteButton.style.visibility = "visible"; + deleteButton.disabled = false; + + Form.populateSports(sportDropdown, team.sportID); + + console.log(team.sportID); + Form.addHiddenValue('team', teamID, submissionForm); + } + else { + sportDropdown.disabled = false; + + Form.populateSports(sportDropdown); + } + + nameTextbox.disabled = false; + nameTextbox.addEventListener('keyup', checkDataValidity); } -listSports(); \ No newline at end of file +initializeForm(); + +async function checkDataValidity() { + let dataIsValid = true; + + if(!nameTextbox.value) dataIsValid = false; + + + if(dataIsValid) submitButton.disabled = false; + else submitButton.disabled = true; +} + +Form.addRemoveFunction(deleteButton, submissionForm, "team"); \ No newline at end of file diff --git a/routes/manage.js b/routes/manage.js index 4b6683f..4114790 100644 --- a/routes/manage.js +++ b/routes/manage.js @@ -88,16 +88,22 @@ router.post('/division', function(req, res, next) { } }); -router.get('/addteam', function(req, res, next) { - res.render('manage/addteam', { title: 'Add team' }); +router.get('/team', function(req, res, next) { + let title = req.query.team ? 'Edit Team' : 'Add Team' + + res.render('manage/addteam', { title }); }); -router.post('/submitteam', function(req, res, next) { +router.post('/team', function(req, res, next) { const name = req.body['name']; const sport = req.body['sport']; - teams.add(name, sport) - .then(res.send("SUCCESS")); + const id = req.body['team']; + const remove = req.body['remove']; + + if(remove) teams.remove(id).then(res.redirect('/manage')); + else if(id) teams.rename(id, name).then(res.redirect('/manage')); + else teams.add(name, sport).then(res.redirect("/manage")); }); module.exports = router; diff --git a/views/manage/addteam.pug b/views/manage/addteam.pug index c3d422b..3cb6ccb 100644 --- a/views/manage/addteam.pug +++ b/views/manage/addteam.pug @@ -6,18 +6,20 @@ block stylesheets block content div#mobile-view - h1 Add Team - form(action='./submitteam', method='POST') + h1 #{title} + form#submission-form(action='./team', method='POST') span(class='form-section') label Sport span(class='form-section-input') - select#sport-dropdown(name="sport" class="form-main-dropdown") + select#sport-dropdown(name="sport" class="form-main-dropdown" disabled) span(class='form-section') label Team name span(class='form-section-input') - input(type="text", name="name") + input#name-textbox(type="text", name="name" disabled) span(class='form-section') - button#submit-button(type="submit") Submit + button#submit-button(type="submit" disabled) Submit + span(class='form-section') + button#delete-button(disabled) Delete block scripts script(src='/scripts/manage/team.js' type="module") \ No newline at end of file From b5495b1f57c51fab126e4244af1420466d242236 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Tue, 23 Nov 2021 00:49:11 -0700 Subject: [PATCH 100/169] Add ability to edit games --- database/scores/games.js | 26 ++++++++++- public/scripts/data.js | 6 +++ public/scripts/form.js | 86 +++++++++++++++++++++++++++++++++++ public/scripts/manage.js | 6 +-- public/scripts/manage/game.js | 71 +++++++++++++++++++++++++++++ routes/data.js | 5 ++ routes/manage.js | 19 ++++++-- views/manage/addgame.pug | 28 ++++++------ views/manage/addseason.pug | 2 +- 9 files changed, 226 insertions(+), 23 deletions(-) create mode 100644 public/scripts/manage/game.js diff --git a/database/scores/games.js b/database/scores/games.js index 922d600..270941b 100644 --- a/database/scores/games.js +++ b/database/scores/games.js @@ -71,10 +71,34 @@ async function retrieve(teamID, divisionID, seasonID) { return gamesList; } +async function edit(gameID, divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score) { + const query = `UPDATE scores.games + SET division_id = $2, + season_id = $3, + game_date = $4, + team1_id = $5, + team2_id = $6, + team1_score = $7, + team2_score = $8 + WHERE game_id = $1;`; + await database.executeQuery(query, [gameID, divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score]); + return new Game(gameID, date, team1ID, team2ID, team1Score, team2Score, divisionID, seasonID); +} + +async function getFromID(gameID) { + const query = `SELECT game_id, division_id, season_id, game_date, team1_id, team2_id, team1_score, team2_score + FROM scores.games + WHERE game_id = $1;`; + const row = (await database.executeQuery(query, [gameID]))[0]; + return new Game(row[0], row[3].toISOString().slice(0,10), row[4], row[5], row[6], row[7], row[1], row[2]); +} + exports.add = add; exports.remove = remove; -exports.retrieve = retrieve; \ No newline at end of file +exports.retrieve = retrieve; +exports.edit = edit; +exports.getFromID = getFromID; \ No newline at end of file diff --git a/public/scripts/data.js b/public/scripts/data.js index f2eead9..873d4d6 100644 --- a/public/scripts/data.js +++ b/public/scripts/data.js @@ -63,4 +63,10 @@ export async function getGames(teamID = undefined, divisionID = undefined, seaso const response = await fetch(URL); const gamesList = await response.json(); return gamesList; +} + +export async function getGame(gameID) { + const response = await fetch(`/data/game?game=${gameID}`); + const game = await response.json(); + return game; } \ No newline at end of file diff --git a/public/scripts/form.js b/public/scripts/form.js index 9d4ee93..5132ea9 100644 --- a/public/scripts/form.js +++ b/public/scripts/form.js @@ -20,6 +20,92 @@ export async function populateSports(sportDropdown, selectedSportID = undefined) if(selectedSportIndex) sportDropdown.selectedIndex = selectedSportIndex; } +export async function populateSeasons(seasonDropdown, selectedSeasonID = undefined) { + seasonDropdown.innerHTML = ""; + + const seasonsList = await Data.getSeasons(); + + let currentIndex = 0; + let selectedSeasonIndex; + seasonsList.forEach(season => { + const option = document.createElement('option'); + option.text = (season.year - 1) + "-" + season.year; + option.value = season.id; + seasonDropdown.appendChild(option); + + if(season.id == selectedSeasonID) selectedSeasonIndex = currentIndex; + currentIndex++; + }); + + if(selectedSeasonIndex) seasonDropdown.selectedIndex = selectedSeasonIndex; +} + +export async function populateGenders(genderDropdown, selectedSportID, selectedGender = undefined) { + genderDropdown.innerHTML = ""; + + const gendersList = await Data.getGenders(selectedSportID); + + if(selectedSportID) { + let currentIndex = 0; + let selectedGenderIndex; + gendersList.forEach(gender => { + const option = document.createElement('option'); + option.text = (gender.name == "female") ? "Female" : (gender.name == "male") ? "Male" : ""; + option.value = gender.name; + genderDropdown.appendChild(option); + + if(gender.name == selectedGender) selectedGenderIndex = currentIndex; + currentIndex++; + }); + + if(selectedGenderIndex) genderDropdown.selectedIndex = selectedGenderIndex; + } +} + +export async function populateDivisions (divisionDropdown, selectedSportID, selectedGender, selectedDivisionID = undefined) { + divisionDropdown.innerHTML = ""; + + if(selectedSportID && selectedGender) { + const divisionsList = await Data.getDivisions(selectedSportID, selectedGender); + + let currentIndex = 0; + let selectedDivisionIndex; + divisionsList.forEach(division => { + const option = document.createElement('option'); + option.text = division.name; + option.value = division.id; + divisionDropdown.appendChild(option); + + if(division.id == selectedDivisionID) selectedDivisionIndex = currentIndex; + currentIndex++; + }); + + if(selectedDivisionIndex) divisionDropdown.selectedIndex = selectedDivisionIndex; + } +} + +export async function populateTeams(teamDropdown, selectedSportID, selectedTeamID) { + teamDropdown.innerHTML = ""; + + if(selectedSportID) { + const teamsList = await Data.getTeams(selectedSportID); + + let currentIndex = 0; + let selectedTeamIndex; + teamsList.forEach(team => { + const option = document.createElement('option'); + option.text = team.name; + option.value = team.id; + teamDropdown.appendChild(option); + + if(team.id == selectedTeamID) selectedTeamIndex = currentIndex; + currentIndex++; + }); + + if(selectedTeamIndex) teamDropdown.selectedIndex = selectedTeamIndex; + } +} + export async function addHiddenValue(name, value, form) { const valueInput = document.createElement('input'); valueInput.setAttribute('name', name); diff --git a/public/scripts/manage.js b/public/scripts/manage.js index ca2f4b2..27ad375 100644 --- a/public/scripts/manage.js +++ b/public/scripts/manage.js @@ -267,10 +267,10 @@ CATEGORIES.push(new Category( row.appendChild(dateCell); }, async function addGame() { - window.location.href = "/manage/addgame"; + window.location.href = "/manage/game"; }, - async function editSeason() { - + async function editGame(id) { + window.location.href = `/manage/game?game=${id}`; } )); diff --git a/public/scripts/manage/game.js b/public/scripts/manage/game.js new file mode 100644 index 0000000..0cc134b --- /dev/null +++ b/public/scripts/manage/game.js @@ -0,0 +1,71 @@ +import * as Data from "./../data.js"; +import * as Form from "./../form.js"; + + +const submissionForm = document.getElementById('submission-form'); +const sportDropdown = document.getElementById('sport-dropdown'); +const seasonDropdown = document.getElementById('year-dropdown'); +const genderDropdown = document.getElementById('gender-dropdown'); +const divisionDropdown = document.getElementById('division-dropdown'); +const dateInput = document.getElementById('date-input'); +const team1Dropdown = document.getElementById('team1-dropdown'); +const team2Dropdown = document.getElementById('team2-dropdown'); +const team1ScoreTextbox = document.getElementById('team1-score-textbox'); +const team2ScoreTextbox = document.getElementById('team2-score-textbox'); +const submitButton = document.getElementById('submit-button'); +const deleteButton = document.getElementById('delete-button'); + + +async function initializeForm() { + let params = new URLSearchParams(location.search); + let gameID = params.get('game'); + if(gameID) { + deleteButton.style.visibility = "visible"; + deleteButton.disabled = false; + + const game = await Data.getGame(gameID); + + Form.populateSeasons(seasonDropdown, game.seasonID); + Data.getDivision(game.divisionID) + .then(data => { + Form.populateSports(sportDropdown, data.sportID) + .then(() => { + Form.populateGenders(genderDropdown, sportDropdown.value, data.gender.name) + .then(() => { + Form.populateDivisions(divisionDropdown, sportDropdown.value, genderDropdown.value, game.divisionID); + }); + Form.populateTeams(team1Dropdown, sportDropdown.value, game.team1ID); + Form.populateTeams(team2Dropdown, sportDropdown.value, game.team2ID); + }); + }); + dateInput.value = game.date; + team1ScoreTextbox.value = game.team1Score; + team2ScoreTextbox.value = game.team2Score; + } + else { + Form.populateSeasons(seasonDropdown); + Form.populateSports(sportDropdown) + .then(() => { + Form.populateGenders(genderDropdown, sportDropdown.value) + .then(() => { + Form.populateDivisions(divisionDropdown, sportDropdown.value, genderDropdown.value); + }); + Form.populateTeams(team1Dropdown, sportDropdown.value); + Form.populateTeams(team2Dropdown, sportDropdown.value); + }); + dateInput.value = (new Date()).toISOString().slice(0,10); + } + seasonDropdown.disabled = false; + sportDropdown.disabled = false; + genderDropdown.disabled = false; + divisionDropdown.disabled = false; + dateInput.disabled = false; + team1Dropdown.disabled = false; + team2Dropdown.disabled = false; + team1ScoreTextbox.disabled = false; + team2ScoreTextbox.disabled = false; + submitButton.disabled = false; +} +initializeForm(); + +Form.addRemoveFunction(deleteButton, submissionForm, "game"); diff --git a/routes/data.js b/routes/data.js index 917b36b..6ad845a 100644 --- a/routes/data.js +++ b/routes/data.js @@ -56,4 +56,9 @@ router.get('/games', function(req, res, next) { .then(data => res.json(data)); }) +router.get('/game', function(req, res, next) { + games.getFromID(req.query.game) + .then(data => res.json(data)); +}) + module.exports = router; \ No newline at end of file diff --git a/routes/manage.js b/routes/manage.js index 4114790..e7c77ed 100644 --- a/routes/manage.js +++ b/routes/manage.js @@ -13,11 +13,13 @@ router.get('/', function(req, res, next) { res.render('manage', { title: 'Score Management' }); }); -router.get('/addgame', function(req, res, next) { - res.render('manage/addgame', { title: 'Submit Score' }); +router.get('/game', function(req, res, next) { + let title = req.query.game ? 'Edit Game' : 'Submit Score' + + res.render('manage/addgame', { title }); }); -router.post('/submitgame', function(req, res, next) { +router.post('/game', function(req, res, next) { const seasonID = req.body['year']; const sportID = req.body['sport']; const gender = (req.body['gender'] == "female") ? genders.FEMALE : genders.MALE; @@ -28,8 +30,15 @@ router.post('/submitgame', function(req, res, next) { const team2ID = req.body['team2']; const team2Score = req.body['team2-score']; - games.add(divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score) - .then(res.send("SUCCESS")); + const id = req.body['game']; + const remove = req.body['delete']; + + if(remove) games.remove(id) + .then(res.redirect("/manage")); + else if(id) games.edit(id, divisionId, seasonID, date, team1ID, team2ID, team1Score, team2Score) + .then(res.redirect('/manage')); + else games.add(divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score) + .then(res.redirect("/manage")); }); router.get('/addseason', function(req, res, next) { diff --git a/views/manage/addgame.pug b/views/manage/addgame.pug index 04026f4..039bfd1 100644 --- a/views/manage/addgame.pug +++ b/views/manage/addgame.pug @@ -6,34 +6,36 @@ block stylesheets block content div#mobile-view - h1 Submit Score - form(action='./submitgame', method='POST') + h1 #{title} + form#submission-form(action='./submitgame', method='POST') span(class='form-section') label Year span(class='form-section-input') - select#year-dropdown(name="year" class="form-main-dropdown") + select#year-dropdown(name="year" class="form-main-dropdown" disabled) span(class='form-section') label Sport span(class='form-section-input') - select#sport-dropdown(name="sport" class="form-main-dropdown") - select#gender-dropdown(name="gender") - select#division-dropdown(name="division") + select#sport-dropdown(name="sport" class="form-main-dropdown" disabled) + select#gender-dropdown(name="gender" disabled) + select#division-dropdown(name="division" disabled) span(class='form-section') label Date of match span(class='form-section-input') - input(type="date", name="date", value=date) + input#date-input(type="date", name="date" value=date disabled) span(class='form-section') label Your team span(class='form-section-input') - select#team1-dropdown(name="team1" class="form-main-dropdown") - input(class="form-score-input", type="number", name="team1-score", value="0") + select#team1-dropdown(name="team1" class="form-main-dropdown" disabled) + input#team1-score-textbox(class="form-score-input", type="number", name="team1-score", value="0" disabled) span(class='form-section') label Opponent span(class='form-section-input') - select#team2-dropdown(name="team2" class="form-main-dropdown") - input(class="form-score-input", type="number", name="team2-score", value="0") + select#team2-dropdown(name="team2" class="form-main-dropdown" disabled) + input#team2-score-textbox(class="form-score-input", type="number", name="team2-score", value="0" disabled) span(class='form-section') - button#submit-button(type="submit") Submit + button#submit-button(type="submit" disabled) Submit + span(class='form-section') + button#delete-button(disabled) Delete block scripts - script(src='/scripts/submit.js' type="module") \ No newline at end of file + script(src='/scripts/manage/game.js' type="module") \ No newline at end of file diff --git a/views/manage/addseason.pug b/views/manage/addseason.pug index 96d4080..6539a76 100644 --- a/views/manage/addseason.pug +++ b/views/manage/addseason.pug @@ -16,4 +16,4 @@ block content button#submit-button(type="submit") Submit block scripts - script(src='/scripts/season.js' type="module") \ No newline at end of file + script(src='/scripts/manage/season.js' type="module") \ No newline at end of file From bd0ae3bdfd0246a504477c6022094553e869bbd7 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Tue, 23 Nov 2021 14:48:19 -0700 Subject: [PATCH 101/169] Add ability to delete seasons --- public/scripts/manage.js | 24 ++++++++++++++++++++++-- routes/manage.js | 11 +++++++---- views/manage/addseason.pug | 2 +- 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/public/scripts/manage.js b/public/scripts/manage.js index 27ad375..8c89bfd 100644 --- a/public/scripts/manage.js +++ b/public/scripts/manage.js @@ -50,10 +50,30 @@ CATEGORIES.push(new Category( row.appendChild(spacerCell); }, async function addSeason() { - window.location.href = "/manage/addseason"; + window.location.href = "/manage/season"; }, - async function editSeason() { + async function editSeason(id) { + const verified = confirm(`This season will be removed.`); + + if(verified) { + const form = document.createElement('form'); + form.action = "/manage/season"; + form.method = "POST"; + form.style.visibility = "hidden"; + itemsListTable.appendChild(form); + const remove = document.createElement('input'); + remove.setAttribute('name', 'remove'); + remove.setAttribute('value', 1); + form.appendChild(remove); + + const seasonID = document.createElement('input'); + seasonID.setAttribute('name', 'season'); + seasonID.setAttribute('value', id); + form.appendChild(seasonID); + + form.submit(); + } } )); diff --git a/routes/manage.js b/routes/manage.js index e7c77ed..9757e69 100644 --- a/routes/manage.js +++ b/routes/manage.js @@ -41,15 +41,18 @@ router.post('/game', function(req, res, next) { .then(res.redirect("/manage")); }); -router.get('/addseason', function(req, res, next) { +router.get('/season', function(req, res, next) { res.render('manage/addseason', { title: 'Add Season', currentYear : (new Date()).getFullYear() }); }); -router.post('/submitseason', function(req, res, next) { +router.post('/season', function(req, res, next) { const year = req.body['year']; - seasons.add(year) - .then(res.send("SUCCESS")); + const seasonID = req.body['season']; + const remove = req.body['remove']; + + if(remove) seasons.remove(seasonID).then(res.redirect('/manage')); + else seasons.add(year).then(res.redirect("/manage")); }); router.get('/sport', function(req, res, next) { diff --git a/views/manage/addseason.pug b/views/manage/addseason.pug index 6539a76..bdb13fd 100644 --- a/views/manage/addseason.pug +++ b/views/manage/addseason.pug @@ -7,7 +7,7 @@ block stylesheets block content div#mobile-view h1 #{title} - form(action='./submitseason', method='POST') + form(action='./season', method='POST') span(class='form-section') label Ending year span(class='form-section-input') From fb63ef6b416c148e3b1d37a5ed009fd44c99dfc8 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Tue, 23 Nov 2021 14:48:30 -0700 Subject: [PATCH 102/169] Add buttons to access add score and management pages --- public/scripts/index.js | 13 ++++++++++++- public/stylesheets/index.css | 11 +++++++++++ views/index.pug | 6 +++++- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/public/scripts/index.js b/public/scripts/index.js index d752ff1..843ed4b 100644 --- a/public/scripts/index.js +++ b/public/scripts/index.js @@ -8,6 +8,8 @@ const teamDropdown = document.getElementById('team-dropdown'); const gamesTable = document.getElementById('games-table'); const gamesTableHeader = document.getElementById('games-table-header'); const noScoresMessage = document.getElementById('no-scores-message'); +const addScoreButton = document.getElementById('add-score-button'); +const manageButton = document.getElementById('manage-button'); @@ -173,4 +175,13 @@ sportDropdown.onchange = (() => { }); genderDropdown.onchange = listDivisions; teamDropdown.onchange = listGames; -seasonDropdown.onchange = listGames; \ No newline at end of file +seasonDropdown.onchange = listGames; + + +addScoreButton.addEventListener('click', () => { + window.location.href = '/manage/game'; +}); + +manageButton.addEventListener('click', () => { + window.location.href = '/manage' +}); \ No newline at end of file diff --git a/public/stylesheets/index.css b/public/stylesheets/index.css index 2f8f252..7a60f19 100644 --- a/public/stylesheets/index.css +++ b/public/stylesheets/index.css @@ -18,4 +18,15 @@ th { tr { height: 3em; +} + +#header-div { + display: flex; + flex-direction: row; +} + +#actions-div { + display: flex; + flex-direction: column; + margin-left: auto; } \ No newline at end of file diff --git a/views/index.pug b/views/index.pug index 2e52919..674d874 100644 --- a/views/index.pug +++ b/views/index.pug @@ -6,7 +6,11 @@ block stylesheets block content div#mobile-view - h1 Score Tracker + div#header-div + h1 Score Tracker + div#actions-div + button#add-score-button + + button#manage-button Manage div span(class='form-section') label Year From 64026a791e9c67efffe81c050cb7b2ef11f55608 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Wed, 24 Nov 2021 12:46:21 -0700 Subject: [PATCH 103/169] Add packages for user authentication --- package-lock.json | 142 ++++++++++++++++++++++++++++++++++++++++++---- package.json | 1 + 2 files changed, 131 insertions(+), 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index 225db7b..9ec6365 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "debug": "~2.6.9", "dotenv": "^10.0.0", "express": "~4.16.0", + "express-session": "^1.17.2", "http-errors": "~1.6.2", "morgan": "~1.9.0", "nodemailer": "^6.6.5", @@ -520,6 +521,59 @@ "node": ">= 0.10.0" } }, + "node_modules/express-session": { + "version": "1.17.2", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.2.tgz", + "integrity": "sha512-mPcYcLA0lvh7D4Oqr5aNJFMtBMKPLl++OKKxkHzZ0U0oDq1rpKBnkR5f5vCHR26VeArlTOEF9td4x5IjICksRQ==", + "dependencies": { + "cookie": "0.4.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-headers": "~1.0.2", + "parseurl": "~1.3.3", + "safe-buffer": "5.2.1", + "uid-safe": "~2.1.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/express-session/node_modules/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express-session/node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express-session/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -965,9 +1019,9 @@ } }, "node_modules/on-headers": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz", - "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", "engines": { "node": ">= 0.8" } @@ -987,9 +1041,9 @@ "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" }, "node_modules/parseurl": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", - "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "engines": { "node": ">= 0.8" } @@ -1299,6 +1353,14 @@ "node": ">=0.6" } }, + "node_modules/random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/range-parser": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", @@ -1602,6 +1664,17 @@ "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", "optional": true }, + "node_modules/uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "dependencies": { + "random-bytes": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -2091,6 +2164,38 @@ "vary": "~1.1.2" } }, + "express-session": { + "version": "1.17.2", + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.2.tgz", + "integrity": "sha512-mPcYcLA0lvh7D4Oqr5aNJFMtBMKPLl++OKKxkHzZ0U0oDq1rpKBnkR5f5vCHR26VeArlTOEF9td4x5IjICksRQ==", + "requires": { + "cookie": "0.4.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-headers": "~1.0.2", + "parseurl": "~1.3.3", + "safe-buffer": "5.2.1", + "uid-safe": "~2.1.5" + }, + "dependencies": { + "cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==" + }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + } + } + }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -2441,9 +2546,9 @@ } }, "on-headers": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.1.tgz", - "integrity": "sha1-ko9dD0cNSTQmUepnlLCFfBAGk/c=" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" }, "once": { "version": "1.4.0", @@ -2460,9 +2565,9 @@ "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==" }, "parseurl": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", - "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" }, "passport": { "version": "0.5.0", @@ -2719,6 +2824,11 @@ "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" }, + "random-bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", + "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" + }, "range-parser": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", @@ -2971,6 +3081,14 @@ "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", "optional": true }, + "uid-safe": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", + "requires": { + "random-bytes": "~1.0.0" + } + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", diff --git a/package.json b/package.json index c94c979..eb6d387 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "debug": "~2.6.9", "dotenv": "^10.0.0", "express": "~4.16.0", + "express-session": "^1.17.2", "http-errors": "~1.6.2", "morgan": "~1.9.0", "nodemailer": "^6.6.5", From 9c9c106acd1ea730fb3877d49662fc14b456d0cb Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Wed, 24 Nov 2021 15:53:42 -0700 Subject: [PATCH 104/169] Database init script now creates accounts table --- database/init_database.sql | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/database/init_database.sql b/database/init_database.sql index ad4f0e7..ea8decf 100644 --- a/database/init_database.sql +++ b/database/init_database.sql @@ -22,10 +22,7 @@ scores: accounts: users: - *user_id* | email | salt | password_hash | role | approved - - sessions: - *session_id* | ~user_id~ | expiration_date + *user_id* | email | password | admin */ @@ -102,4 +99,13 @@ CREATE TABLE IF NOT EXISTS scores.games( ); +CREATE SCHEMA IF NOT EXISTS accounts; + +CREATE TABLE IF NOT EXISTS accounts.users( + user_id BIGINT GENERATED ALWAYS AS IDENTITY, + email TEXT UNIQUE NOT NULL, + password TEXT NOT NULL, + admin BOOLEAN NOT NULL DEFAULT FALSE + ); + COMMIT; \ No newline at end of file From fbfa500b8a513d198683f436ec02c384b70c448d Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Wed, 24 Nov 2021 15:55:44 -0700 Subject: [PATCH 105/169] Install bcrypt for managing passwords --- package-lock.json | 857 ++++++++++++++++++++++++++++++++++++++++++++-- package.json | 1 + 2 files changed, 831 insertions(+), 27 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9ec6365..b41b189 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.0", "dependencies": { "async": "^3.2.2", + "bcrypt": "^5.0.1", "cookie-parser": "~1.4.3", "debug": "~2.6.9", "dotenv": "^10.0.0", @@ -26,6 +27,25 @@ "supertest": "^3.0.0" } }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.7.tgz", + "integrity": "sha512-PplSvl4pJ5N3BkVjAdDzpPhVUPdC73JgttkR+LnBx2OORC1GCQsBjUeEuipf9uOaAM1SbxcdZFfR3KDTKm2S0A==", + "dependencies": { + "detect-libc": "^1.0.3", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.5", + "nopt": "^5.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, "node_modules/@types/babel-types": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/@types/babel-types/-/babel-types-7.0.4.tgz", @@ -39,6 +59,11 @@ "@types/babel-types": "*" } }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, "node_modules/accepts": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", @@ -81,6 +106,38 @@ "node": ">=0.4.0" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agent-base/node_modules/debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/agent-base/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, "node_modules/align-text": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", @@ -102,6 +159,44 @@ "node": ">=0.4.2" } }, + "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==", + "engines": { + "node": ">=8" + } + }, + "node_modules/aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" + }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/are-we-there-yet/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -154,8 +249,7 @@ "node_modules/balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "node_modules/basic-auth": { "version": "2.0.0", @@ -168,6 +262,19 @@ "node": ">= 0.8" } }, + "node_modules/bcrypt": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.0.1.tgz", + "integrity": "sha512-9BTgmrhZM2t1bNuDtrtIMVSmmxZBrJ71n8Wg+YgdjHuIWYF7SjjmCPZFB+/5i/o/PIeRpwVJR3P+NrpIItUjqw==", + "hasInstallScript": true, + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.0", + "node-addon-api": "^3.1.0" + }, + "engines": { + "node": ">= 10.0.0" + } + }, "node_modules/body-parser": { "version": "1.18.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", @@ -192,7 +299,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -248,6 +354,14 @@ "is-regex": "^1.0.3" } }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "engines": { + "node": ">=10" + } + }, "node_modules/clean-css": { "version": "3.4.28", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-3.4.28.tgz", @@ -273,6 +387,14 @@ "wordwrap": "0.0.2" } }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "bin": { + "color-support": "bin.js" + } + }, "node_modules/combined-stream": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", @@ -305,8 +427,12 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" }, "node_modules/constantinople": { "version": "3.1.2", @@ -403,6 +529,11 @@ "node": ">=0.4.0" } }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + }, "node_modules/depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -416,6 +547,17 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/diff": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", @@ -443,6 +585,11 @@ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, + "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==" + }, "node_modules/encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -634,17 +781,46 @@ "node": ">= 0.6" } }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, + "node_modules/gauge": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.0.tgz", + "integrity": "sha512-F8sU45yQpjQjxKkm1UOAhf0U/O0aFt//Fl7hsrNVto+patMHjs7dPI9mFOGUKbhrgKm0S3EjW3scMFuQmWSROw==", + "dependencies": { + "ansi-regex": "^5.0.1", + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, "node_modules/glob": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", @@ -696,6 +872,11 @@ "node": ">=4" } }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + }, "node_modules/he": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", @@ -719,6 +900,39 @@ "node": ">= 0.6" } }, + "node_modules/https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent/node_modules/debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/https-proxy-agent/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, "node_modules/iconv-lite": { "version": "0.4.19", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", @@ -731,7 +945,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -775,6 +988,14 @@ "node": ">=0.4.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==", + "engines": { + "node": ">=8" + } + }, "node_modules/is-promise": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", @@ -843,6 +1064,39 @@ "node": ">=0.10.0" } }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -895,7 +1149,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -909,6 +1162,29 @@ "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true }, + "node_modules/minipass": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.5.tgz", + "integrity": "sha512-+8NzxD82XQoNKNrl1d/FSi+X8wAEWR+sbYAfIvub4Nz0d22plFG72CEVVaufV8PNf4qSslFTD8VMOxNVhHCjTw==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/mkdirp": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", @@ -991,6 +1267,22 @@ "node": ">= 0.6" } }, + "node_modules/node-addon-api": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", + "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==" + }, + "node_modules/node-fetch": { + "version": "2.6.6", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz", + "integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + } + }, "node_modules/nodemailer": { "version": "6.6.5", "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.6.5.tgz", @@ -999,6 +1291,34 @@ "node": ">=6.0.0" } }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/npmlog": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.0.tgz", + "integrity": "sha512-03ppFRGlsyUaQFbGC2C8QWJN/C/K7PsfyD9aQdhVKAQIH4sQBc8WASqFBP7O+Ut4d2oo5LoeoboB3cGdBZSp6Q==", + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.0", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -1030,7 +1350,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "dependencies": { "wrappy": "1" } @@ -1076,7 +1395,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -1457,11 +1775,58 @@ "node": ">=0.10.0" } }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rimraf/node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/safe-buffer": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" }, + "node_modules/semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/send": { "version": "0.16.2", "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", @@ -1499,11 +1864,21 @@ "node": ">= 0.8.0" } }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, "node_modules/setprototypeof": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" }, + "node_modules/signal-exit": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz", + "integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==" + }, "node_modules/source-map": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", @@ -1552,6 +1927,30 @@ "safe-buffer": "~5.1.0" } }, + "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==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/superagent": { "version": "3.8.2", "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.2.tgz", @@ -1607,6 +2006,33 @@ "node": ">=4" } }, + "node_modules/tar": { + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", + "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/tar/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/to-fast-properties": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", @@ -1620,6 +2046,11 @@ "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-0.0.1.tgz", "integrity": "sha1-zu78cXp2xDFvEm0LnbqlXX598Bo=" }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, "node_modules/type-is": { "version": "1.6.16", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", @@ -1712,6 +2143,28 @@ "node": ">=0.10.0" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, "node_modules/window-size": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", @@ -1740,8 +2193,7 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "node_modules/xtend": { "version": "4.0.2", @@ -1751,6 +2203,11 @@ "node": ">=0.4" } }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, "node_modules/yargs": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", @@ -1764,6 +2221,22 @@ } }, "dependencies": { + "@mapbox/node-pre-gyp": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.7.tgz", + "integrity": "sha512-PplSvl4pJ5N3BkVjAdDzpPhVUPdC73JgttkR+LnBx2OORC1GCQsBjUeEuipf9uOaAM1SbxcdZFfR3KDTKm2S0A==", + "requires": { + "detect-libc": "^1.0.3", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.5", + "nopt": "^5.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + } + }, "@types/babel-types": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/@types/babel-types/-/babel-types-7.0.4.tgz", @@ -1777,6 +2250,11 @@ "@types/babel-types": "*" } }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, "accepts": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", @@ -1806,6 +2284,29 @@ } } }, + "agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "requires": { + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, "align-text": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", @@ -1821,6 +2322,37 @@ "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" + }, + "are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -1870,8 +2402,7 @@ "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "basic-auth": { "version": "2.0.0", @@ -1881,6 +2412,15 @@ "safe-buffer": "5.1.1" } }, + "bcrypt": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-5.0.1.tgz", + "integrity": "sha512-9BTgmrhZM2t1bNuDtrtIMVSmmxZBrJ71n8Wg+YgdjHuIWYF7SjjmCPZFB+/5i/o/PIeRpwVJR3P+NrpIItUjqw==", + "requires": { + "@mapbox/node-pre-gyp": "^1.0.0", + "node-addon-api": "^3.1.0" + } + }, "body-parser": { "version": "1.18.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", @@ -1902,7 +2442,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1946,6 +2485,11 @@ "is-regex": "^1.0.3" } }, + "chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" + }, "clean-css": { "version": "3.4.28", "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-3.4.28.tgz", @@ -1965,6 +2509,11 @@ "wordwrap": "0.0.2" } }, + "color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==" + }, "combined-stream": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", @@ -1991,8 +2540,12 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" }, "constantinople": { "version": "3.1.2", @@ -2070,6 +2623,11 @@ "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", "dev": true }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + }, "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -2080,6 +2638,11 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" + }, "diff": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", @@ -2101,6 +2664,11 @@ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, + "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==" + }, "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", @@ -2243,17 +2811,40 @@ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" }, + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "requires": { + "minipass": "^3.0.0" + } + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, + "gauge": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.0.tgz", + "integrity": "sha512-F8sU45yQpjQjxKkm1UOAhf0U/O0aFt//Fl7hsrNVto+patMHjs7dPI9mFOGUKbhrgKm0S3EjW3scMFuQmWSROw==", + "requires": { + "ansi-regex": "^5.0.1", + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + } + }, "glob": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", @@ -2293,6 +2884,11 @@ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + }, "he": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", @@ -2310,6 +2906,30 @@ "statuses": ">= 1.4.0 < 2" } }, + "https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "requires": { + "agent-base": "6", + "debug": "4" + }, + "dependencies": { + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, "iconv-lite": { "version": "0.4.19", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", @@ -2319,7 +2939,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -2356,6 +2975,11 @@ } } }, + "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==" + }, "is-promise": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", @@ -2412,6 +3036,29 @@ "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=" }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "requires": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -2449,7 +3096,6 @@ "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -2460,6 +3106,23 @@ "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true }, + "minipass": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.5.tgz", + "integrity": "sha512-+8NzxD82XQoNKNrl1d/FSi+X8wAEWR+sbYAfIvub4Nz0d22plFG72CEVVaufV8PNf4qSslFTD8VMOxNVhHCjTw==", + "requires": { + "yallist": "^4.0.0" + } + }, + "minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "requires": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + } + }, "mkdirp": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", @@ -2527,11 +3190,43 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" }, + "node-addon-api": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", + "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==" + }, + "node-fetch": { + "version": "2.6.6", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz", + "integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==", + "requires": { + "whatwg-url": "^5.0.0" + } + }, "nodemailer": { "version": "6.6.5", "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.6.5.tgz", "integrity": "sha512-C/v856DBijUzHcHIgGpQoTrfsH3suKIRAGliIzCstatM2cAa+MYX3LuyCrABiO/cdJTxgBBHXxV1ztiqUwst5A==" }, + "nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "requires": { + "abbrev": "1" + } + }, + "npmlog": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.0.tgz", + "integrity": "sha512-03ppFRGlsyUaQFbGC2C8QWJN/C/K7PsfyD9aQdhVKAQIH4sQBc8WASqFBP7O+Ut4d2oo5LoeoboB3cGdBZSp6Q==", + "requires": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.0", + "set-blocking": "^2.0.0" + } + }, "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -2554,7 +3249,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "requires": { "wrappy": "1" } @@ -2586,8 +3280,7 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-parse": { "version": "1.0.5", @@ -2909,11 +3602,42 @@ "align-text": "^0.1.1" } }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "requires": { + "glob": "^7.1.3" + }, + "dependencies": { + "glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, "safe-buffer": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "requires": { + "lru-cache": "^6.0.0" + } + }, "send": { "version": "0.16.2", "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", @@ -2945,11 +3669,21 @@ "send": "0.16.2" } }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + }, "setprototypeof": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" }, + "signal-exit": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz", + "integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==" + }, "source-map": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", @@ -2991,6 +3725,24 @@ "safe-buffer": "~5.1.0" } }, + "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==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + }, "superagent": { "version": "3.8.2", "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.2.tgz", @@ -3039,6 +3791,26 @@ "has-flag": "^3.0.0" } }, + "tar": { + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", + "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", + "requires": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^3.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "dependencies": { + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + } + } + }, "to-fast-properties": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", @@ -3049,6 +3821,11 @@ "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-0.0.1.tgz", "integrity": "sha1-zu78cXp2xDFvEm0LnbqlXX598Bo=" }, + "tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + }, "type-is": { "version": "1.6.16", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", @@ -3114,6 +3891,28 @@ "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=" }, + "webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + }, + "whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", + "requires": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "requires": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, "window-size": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", @@ -3136,14 +3935,18 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, "yargs": { "version": "3.10.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", diff --git a/package.json b/package.json index eb6d387..79eeb84 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ }, "dependencies": { "async": "^3.2.2", + "bcrypt": "^5.0.1", "cookie-parser": "~1.4.3", "debug": "~2.6.9", "dotenv": "^10.0.0", From 7a62885e8f908970c29d85b078db026bdf9a93c4 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Wed, 24 Nov 2021 17:16:46 -0700 Subject: [PATCH 106/169] Install passport-local --- package-lock.json | 20 ++++++++++++++++++++ package.json | 1 + 2 files changed, 21 insertions(+) diff --git a/package-lock.json b/package-lock.json index b41b189..c402529 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "morgan": "~1.9.0", "nodemailer": "^6.6.5", "passport": "^0.5.0", + "passport-local": "^1.0.0", "pg": "^8.7.1", "pug": "2.0.0-beta11" }, @@ -1383,6 +1384,17 @@ "url": "https://github.com/sponsors/jaredhanson" } }, + "node_modules/passport-local": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz", + "integrity": "sha1-H+YyaMkudWBmJkN+O5BmYsFbpu4=", + "dependencies": { + "passport-strategy": "1.x.x" + }, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/passport-strategy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", @@ -3272,6 +3284,14 @@ "pause": "0.0.1" } }, + "passport-local": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz", + "integrity": "sha1-H+YyaMkudWBmJkN+O5BmYsFbpu4=", + "requires": { + "passport-strategy": "1.x.x" + } + }, "passport-strategy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", diff --git a/package.json b/package.json index 79eeb84..2959fbd 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "morgan": "~1.9.0", "nodemailer": "^6.6.5", "passport": "^0.5.0", + "passport-local": "^1.0.0", "pg": "^8.7.1", "pug": "2.0.0-beta11" }, From 33617a82ecea0c55286986d4093b5f082cdddcab Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Wed, 24 Nov 2021 17:56:04 -0700 Subject: [PATCH 107/169] Add passport functions --- app.js | 22 +++++++++++++++++++++ database/accounts/accounts.js | 37 +++++++++++++++++++++++++++++++++++ database/accounts/random.js | 12 ++++++++++++ routes/accounts.js | 11 +++++++++++ 4 files changed, 82 insertions(+) create mode 100644 database/accounts/accounts.js create mode 100644 database/accounts/random.js create mode 100644 routes/accounts.js diff --git a/app.js b/app.js index c773280..0ad96f4 100644 --- a/app.js +++ b/app.js @@ -3,6 +3,11 @@ var express = require('express'); var path = require('path'); var cookieParser = require('cookie-parser'); var logger = require('morgan'); +var random = require('./database/accounts/random'); +const passport = require('passport'); +const session = require('express-session'); +const accounts = require('./database/accounts/accounts'); + var indexRouter = require('./routes/index'); var usersRouter = require('./routes/users'); @@ -11,6 +16,23 @@ var manageRouter = require('./routes/manage'); var app = express(); +// session setup +app.use( + session({ + secret: random.makeid(20), + resave: false, + saveUninitialized: true, + }) +); + +// passport setup +app.use(passport.initialize()); +app.use(passport.session()); + +//passport.use(accounts.createStrategy()); +//passport.serializeUser(accounts.serializeUser()); +//passport.deserializeUser(accounts.deserializeUser()); + // view engine setup app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'pug'); diff --git a/database/accounts/accounts.js b/database/accounts/accounts.js new file mode 100644 index 0000000..67d277a --- /dev/null +++ b/database/accounts/accounts.js @@ -0,0 +1,37 @@ +const database = require('./../database'); +const passport = require('passport'); +const passportLocal = require('passport-local'); + +passport.use(new passportLocal.Strategy((email, password, cb) => { + query = `SELECT id, email, password, admin + FROM accounts.users + WHERE email = $1`; + const result = database.executeQuery(query, [email]); + if(result.length > 0) { + const first = result[0]; + bcrypt.compare(password, first[2], function(err, res) { + if(res) { + cb(null, { id: first[0], email: first[1], admin: first[3] }) + } + else + { + cb(null, false) + } + }) + } else { + cb(null, false) + } +})); + +passport.serializeUser((user, done) => { + done(null, user.id) +}) + +passport.deserializeUser((id, cb) => { + query = `SELECT id, email, admin + FROM accounts.users + WHERE id = $1`; + const result = database.executeQuery(query, [parseInt(id, 10)]); + + cb(null, result[0]); +}); \ No newline at end of file diff --git a/database/accounts/random.js b/database/accounts/random.js new file mode 100644 index 0000000..6d6a57d --- /dev/null +++ b/database/accounts/random.js @@ -0,0 +1,12 @@ +function makeid(length) { + var result = ''; + var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + var charactersLength = characters.length; + for ( var i = 0; i < length; i++ ) { + result += characters.charAt(Math.floor(Math.random() * + charactersLength)); + } + return result; +} + +exports.makeid = makeid; \ No newline at end of file diff --git a/routes/accounts.js b/routes/accounts.js new file mode 100644 index 0000000..5a72c2f --- /dev/null +++ b/routes/accounts.js @@ -0,0 +1,11 @@ +const passport = require('passport'); +var router = express.Router(); +const app = require('../app'); + +router.post('/login', passport.authenticate('local'), (req, res, next) => { + const { user } = req; + + res.json(user); +}); + +module.exports = router; \ No newline at end of file From c5f05eebff4da9a8eecad0d955f6e266d32514d8 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Wed, 24 Nov 2021 17:58:28 -0700 Subject: [PATCH 108/169] Add auth router to app.js --- app.js | 2 ++ routes/{accounts.js => auth.js} | 0 2 files changed, 2 insertions(+) rename routes/{accounts.js => auth.js} (100%) diff --git a/app.js b/app.js index 0ad96f4..f657970 100644 --- a/app.js +++ b/app.js @@ -13,6 +13,7 @@ var indexRouter = require('./routes/index'); var usersRouter = require('./routes/users'); var dataRouter = require('./routes/data'); var manageRouter = require('./routes/manage'); +var authRouter = require('./routes/auth'); var app = express(); @@ -47,6 +48,7 @@ app.use('/', indexRouter); app.use('/users', usersRouter); app.use('/data', dataRouter); app.use('/manage', manageRouter); +app.user('/auth', authRouter); // catch 404 and forward to error handler diff --git a/routes/accounts.js b/routes/auth.js similarity index 100% rename from routes/accounts.js rename to routes/auth.js From 7520580d66e5a136203ade7e7f928b33d7696764 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Wed, 24 Nov 2021 18:13:33 -0700 Subject: [PATCH 109/169] Add function to create user --- app.js | 2 +- database/accounts/accounts.js | 52 ++++++++++++++++++++++------------- database/database.js | 1 - 3 files changed, 34 insertions(+), 21 deletions(-) diff --git a/app.js b/app.js index f657970..f8cb47b 100644 --- a/app.js +++ b/app.js @@ -48,7 +48,7 @@ app.use('/', indexRouter); app.use('/users', usersRouter); app.use('/data', dataRouter); app.use('/manage', manageRouter); -app.user('/auth', authRouter); +app.use('/auth', authRouter); // catch 404 and forward to error handler diff --git a/database/accounts/accounts.js b/database/accounts/accounts.js index 67d277a..d65de3c 100644 --- a/database/accounts/accounts.js +++ b/database/accounts/accounts.js @@ -1,26 +1,28 @@ const database = require('./../database'); const passport = require('passport'); const passportLocal = require('passport-local'); +const bcrypt = require('bcrypt'); passport.use(new passportLocal.Strategy((email, password, cb) => { - query = `SELECT id, email, password, admin + query = `SELECT user_id, email, password, admin FROM accounts.users WHERE email = $1`; - const result = database.executeQuery(query, [email]); - if(result.length > 0) { - const first = result[0]; - bcrypt.compare(password, first[2], function(err, res) { - if(res) { - cb(null, { id: first[0], email: first[1], admin: first[3] }) - } - else - { + database.executeQuery(query, [email]) + .then(result => { + if(result.length > 0) { + const first = result[0]; + const matches = bcrypt.compareSync(password, first[2]); + if(matches) { + cb(null, { id: first[0], email: first[1], admin: first[3] }) + } + else + { + cb(null, false) + } + } else { cb(null, false) } - }) - } else { - cb(null, false) - } + }); })); passport.serializeUser((user, done) => { @@ -28,10 +30,22 @@ passport.serializeUser((user, done) => { }) passport.deserializeUser((id, cb) => { - query = `SELECT id, email, admin + query = `SELECT user_id, email, admin FROM accounts.users WHERE id = $1`; - const result = database.executeQuery(query, [parseInt(id, 10)]); - - cb(null, result[0]); -}); \ No newline at end of file + database.executeQuery(query, [parseInt(id, 10)]) + .then(result => { + cb(null, result[0]); + }); +}); + + + +async function createUser(email, password) { + const salt = bcrypt.genSaltSync(); + const hash = bcrypt.hashSync(password, salt); + + const query = `INSERT INTO accounts.users(email, password) + VALUES($1, $2)`; + await database.executeQuery(query, [email, hash]); +} \ No newline at end of file diff --git a/database/database.js b/database/database.js index f166980..1d5bc56 100644 --- a/database/database.js +++ b/database/database.js @@ -27,7 +27,6 @@ async function Initialize() { - async function checkForDatabaseInitialization() { const query = `SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'scores'`; let result = await executeQuery(query); From 48c2ee67bd9b5cc9e7292c382bb172606e226c40 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Wed, 24 Nov 2021 18:25:27 -0700 Subject: [PATCH 110/169] Add page to create user --- app.js | 2 ++ database/accounts/accounts.js | 4 +++- routes/admin.js | 12 ++++++++++++ routes/auth.js | 9 ++++++++- views/accounts/createuser.pug | 20 ++++++++++++++++++++ 5 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 routes/admin.js create mode 100644 views/accounts/createuser.pug diff --git a/app.js b/app.js index f8cb47b..3efd585 100644 --- a/app.js +++ b/app.js @@ -14,6 +14,7 @@ var usersRouter = require('./routes/users'); var dataRouter = require('./routes/data'); var manageRouter = require('./routes/manage'); var authRouter = require('./routes/auth'); +var adminRouter = require('./routes/admin'); var app = express(); @@ -49,6 +50,7 @@ app.use('/users', usersRouter); app.use('/data', dataRouter); app.use('/manage', manageRouter); app.use('/auth', authRouter); +app.use('/admin', adminRouter); // catch 404 and forward to error handler diff --git a/database/accounts/accounts.js b/database/accounts/accounts.js index d65de3c..5ae6cc9 100644 --- a/database/accounts/accounts.js +++ b/database/accounts/accounts.js @@ -48,4 +48,6 @@ async function createUser(email, password) { const query = `INSERT INTO accounts.users(email, password) VALUES($1, $2)`; await database.executeQuery(query, [email, hash]); -} \ No newline at end of file +} + +exports.createUser = createUser; \ No newline at end of file diff --git a/routes/admin.js b/routes/admin.js new file mode 100644 index 0000000..d032cd1 --- /dev/null +++ b/routes/admin.js @@ -0,0 +1,12 @@ +var express = require('express'); +var router = express.Router(); +const passport = require('passport'); +const app = require('../app'); +const accounts = require('./../database/accounts/accounts'); + + +router.get('/createuser', (req, res, next) => { + res.render('accounts/createuser', { title: 'Create user' }); +}); + +module.exports = router; \ No newline at end of file diff --git a/routes/auth.js b/routes/auth.js index 5a72c2f..38426ea 100644 --- a/routes/auth.js +++ b/routes/auth.js @@ -1,5 +1,7 @@ -const passport = require('passport'); +var express = require('express'); var router = express.Router(); +const passport = require('passport'); +const accounts = require('./../database/accounts/accounts'); const app = require('../app'); router.post('/login', passport.authenticate('local'), (req, res, next) => { @@ -8,4 +10,9 @@ router.post('/login', passport.authenticate('local'), (req, res, next) => { res.json(user); }); +router.post('/register', (req, res, next) => { + accounts.createUser(req.body.email, req.body.password) + .then(res.redirect('/')); +}); + module.exports = router; \ No newline at end of file diff --git a/views/accounts/createuser.pug b/views/accounts/createuser.pug new file mode 100644 index 0000000..c5630c8 --- /dev/null +++ b/views/accounts/createuser.pug @@ -0,0 +1,20 @@ +extends ../layout + +block stylesheets + link(rel='stylesheet', href='/stylesheets/submit.css') + link(rel='stylesheet', href='/stylesheets/form.css') + +block content + div#mobile-view + h1 #{title} + form(action='/auth/register', method='POST') + span(class='form-section') + label Email + span(class='form-section-input') + input(type="email", name="email") + span(class='form-section') + label Password + span(class='form-section-input') + input(type="password", name="password") + span(class='form-section') + button#submit-button(type="submit") Submit \ No newline at end of file From 0a6c56e103d94f45a9260f334e537f5125c24901 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Wed, 24 Nov 2021 20:36:00 -0700 Subject: [PATCH 111/169] Fix issues involving editing and submitting games --- public/scripts/manage/game.js | 2 ++ routes/manage.js | 2 +- views/manage/addgame.pug | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/public/scripts/manage/game.js b/public/scripts/manage/game.js index 0cc134b..82d4cb8 100644 --- a/public/scripts/manage/game.js +++ b/public/scripts/manage/game.js @@ -25,6 +25,8 @@ async function initializeForm() { const game = await Data.getGame(gameID); + Form.addHiddenValue('game', gameID, submissionForm); + Form.populateSeasons(seasonDropdown, game.seasonID); Data.getDivision(game.divisionID) .then(data => { diff --git a/routes/manage.js b/routes/manage.js index 9757e69..16f8ca4 100644 --- a/routes/manage.js +++ b/routes/manage.js @@ -35,7 +35,7 @@ router.post('/game', function(req, res, next) { if(remove) games.remove(id) .then(res.redirect("/manage")); - else if(id) games.edit(id, divisionId, seasonID, date, team1ID, team2ID, team1Score, team2Score) + else if(id) games.edit(id, divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score) .then(res.redirect('/manage')); else games.add(divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score) .then(res.redirect("/manage")); diff --git a/views/manage/addgame.pug b/views/manage/addgame.pug index 039bfd1..7981148 100644 --- a/views/manage/addgame.pug +++ b/views/manage/addgame.pug @@ -7,7 +7,7 @@ block stylesheets block content div#mobile-view h1 #{title} - form#submission-form(action='./submitgame', method='POST') + form#submission-form(action='./game', method='POST') span(class='form-section') label Year span(class='form-section-input') From 9c7241e7e6c0b58e0eb9748b42059755241879ca Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Wed, 24 Nov 2021 20:46:16 -0700 Subject: [PATCH 112/169] Remove console.log from teams.js --- database/scores/teams.js | 1 - 1 file changed, 1 deletion(-) diff --git a/database/scores/teams.js b/database/scores/teams.js index 592796f..20034ef 100644 --- a/database/scores/teams.js +++ b/database/scores/teams.js @@ -69,7 +69,6 @@ async function getFromID(id) { FROM scores.teams WHERE team_id = $1;`; const row = (await database.executeQuery(query, [id]))[0]; - console.log(row); return new Team(id, row[0], row[1]); } From a6fe6d6c7280d639ed9284c381b05c88e1102a1d Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Wed, 24 Nov 2021 20:58:49 -0700 Subject: [PATCH 113/169] Create login page --- database/accounts/accounts.js | 21 +++++++++++++-------- routes/auth.js | 14 ++++++++++---- views/accounts/login.pug | 20 ++++++++++++++++++++ 3 files changed, 43 insertions(+), 12 deletions(-) create mode 100644 views/accounts/login.pug diff --git a/database/accounts/accounts.js b/database/accounts/accounts.js index 5ae6cc9..6f000c9 100644 --- a/database/accounts/accounts.js +++ b/database/accounts/accounts.js @@ -1,26 +1,30 @@ const database = require('./../database'); const passport = require('passport'); -const passportLocal = require('passport-local'); +const localStrategy = require('passport-local').Strategy; const bcrypt = require('bcrypt'); -passport.use(new passportLocal.Strategy((email, password, cb) => { + +passport.use(new localStrategy({ + usernameField: 'email', + passwordField: 'password'}, + (username, password, cb) => { query = `SELECT user_id, email, password, admin FROM accounts.users WHERE email = $1`; - database.executeQuery(query, [email]) + database.executeQuery(query, [username]) .then(result => { if(result.length > 0) { const first = result[0]; const matches = bcrypt.compareSync(password, first[2]); if(matches) { - cb(null, { id: first[0], email: first[1], admin: first[3] }) + return cb(null, { id: first[0], email: first[1], admin: first[3] }) } else { - cb(null, false) + return cb(null, false) } } else { - cb(null, false) + return cb(null, false) } }); })); @@ -32,7 +36,7 @@ passport.serializeUser((user, done) => { passport.deserializeUser((id, cb) => { query = `SELECT user_id, email, admin FROM accounts.users - WHERE id = $1`; + WHERE user_id = $1`; database.executeQuery(query, [parseInt(id, 10)]) .then(result => { cb(null, result[0]); @@ -50,4 +54,5 @@ async function createUser(email, password) { await database.executeQuery(query, [email, hash]); } -exports.createUser = createUser; \ No newline at end of file +exports.createUser = createUser; +exports.passport = passport; \ No newline at end of file diff --git a/routes/auth.js b/routes/auth.js index 38426ea..026728c 100644 --- a/routes/auth.js +++ b/routes/auth.js @@ -1,13 +1,19 @@ var express = require('express'); var router = express.Router(); -const passport = require('passport'); const accounts = require('./../database/accounts/accounts'); const app = require('../app'); -router.post('/login', passport.authenticate('local'), (req, res, next) => { - const { user } = req; +router.get('/login', (req, res, next) => { + res.render('accounts/login', { title : "Login" }); +}); - res.json(user); +router.post('/login', + accounts.passport.authenticate('local', { + failureRedirect: '/fail', + successRedirect: '/success', + }), + (req, res, next) => { + console.log(req.user); }); router.post('/register', (req, res, next) => { diff --git a/views/accounts/login.pug b/views/accounts/login.pug new file mode 100644 index 0000000..fb58202 --- /dev/null +++ b/views/accounts/login.pug @@ -0,0 +1,20 @@ +extends ../layout + +block stylesheets + link(rel='stylesheet', href='/stylesheets/submit.css') + link(rel='stylesheet', href='/stylesheets/form.css') + +block content + div#mobile-view + h1 #{title} + form(action='/auth/login', method='POST') + span(class='form-section') + label Email + span(class='form-section-input') + input(type="email", name="email") + span(class='form-section') + label Password + span(class='form-section-input') + input(type="password", name="password") + span(class='form-section') + button#submit-button(type="submit") Submit \ No newline at end of file From 8e8f0e3e7ced5b82b3926e3c8472a682386cd423 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Wed, 24 Nov 2021 21:05:33 -0700 Subject: [PATCH 114/169] Change passport require --- routes/auth.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/routes/auth.js b/routes/auth.js index 026728c..53242d5 100644 --- a/routes/auth.js +++ b/routes/auth.js @@ -1,5 +1,6 @@ var express = require('express'); var router = express.Router(); +const passport = require('passport'); const accounts = require('./../database/accounts/accounts'); const app = require('../app'); @@ -8,7 +9,7 @@ router.get('/login', (req, res, next) => { }); router.post('/login', - accounts.passport.authenticate('local', { + passport.authenticate('local', { failureRedirect: '/fail', successRedirect: '/success', }), From ebef80e061a11777fd48d304dc5a2a5d37003fa3 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Wed, 24 Nov 2021 21:07:13 -0700 Subject: [PATCH 115/169] Add logout function --- routes/auth.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/routes/auth.js b/routes/auth.js index 53242d5..9b400e5 100644 --- a/routes/auth.js +++ b/routes/auth.js @@ -8,6 +8,11 @@ router.get('/login', (req, res, next) => { res.render('accounts/login', { title : "Login" }); }); +router.get('/logout', (req, res, next) => { + req.logout(); + res.redirect("/"); +}); + router.post('/login', passport.authenticate('local', { failureRedirect: '/fail', From 24197d9bffb9b9dd8881b8ca110b12851175f6cb Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Wed, 24 Nov 2021 21:22:17 -0700 Subject: [PATCH 116/169] Add invalid email or password message --- app.js | 4 ++++ package-lock.json | 14 ++++++++++++++ package.json | 1 + routes/auth.js | 7 ++++--- views/accounts/login.pug | 1 + 5 files changed, 24 insertions(+), 3 deletions(-) diff --git a/app.js b/app.js index 3efd585..a379166 100644 --- a/app.js +++ b/app.js @@ -7,6 +7,7 @@ var random = require('./database/accounts/random'); const passport = require('passport'); const session = require('express-session'); const accounts = require('./database/accounts/accounts'); +var flash = require('connect-flash'); var indexRouter = require('./routes/index'); @@ -18,6 +19,9 @@ var adminRouter = require('./routes/admin'); var app = express(); +// flash setup +app.use(flash()); + // session setup app.use( session({ diff --git a/package-lock.json b/package-lock.json index c402529..1903fa7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "async": "^3.2.2", "bcrypt": "^5.0.1", + "connect-flash": "^0.1.1", "cookie-parser": "~1.4.3", "debug": "~2.6.9", "dotenv": "^10.0.0", @@ -430,6 +431,14 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, + "node_modules/connect-flash": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/connect-flash/-/connect-flash-0.1.1.tgz", + "integrity": "sha1-2GMPJtlaf4UfmVax6MxnMvO2qjA=", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", @@ -2554,6 +2563,11 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, + "connect-flash": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/connect-flash/-/connect-flash-0.1.1.tgz", + "integrity": "sha1-2GMPJtlaf4UfmVax6MxnMvO2qjA=" + }, "console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", diff --git a/package.json b/package.json index 2959fbd..c3c687d 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "dependencies": { "async": "^3.2.2", "bcrypt": "^5.0.1", + "connect-flash": "^0.1.1", "cookie-parser": "~1.4.3", "debug": "~2.6.9", "dotenv": "^10.0.0", diff --git a/routes/auth.js b/routes/auth.js index 9b400e5..5d2e910 100644 --- a/routes/auth.js +++ b/routes/auth.js @@ -5,7 +5,7 @@ const accounts = require('./../database/accounts/accounts'); const app = require('../app'); router.get('/login', (req, res, next) => { - res.render('accounts/login', { title : "Login" }); + res.render('accounts/login', { title : "Login", message: req.flash('error') }); }); router.get('/logout', (req, res, next) => { @@ -15,8 +15,9 @@ router.get('/logout', (req, res, next) => { router.post('/login', passport.authenticate('local', { - failureRedirect: '/fail', - successRedirect: '/success', + failureRedirect: '/auth/login', + successRedirect: '/', + failureFlash: "Invalid email or password.", }), (req, res, next) => { console.log(req.user); diff --git a/views/accounts/login.pug b/views/accounts/login.pug index fb58202..13f707e 100644 --- a/views/accounts/login.pug +++ b/views/accounts/login.pug @@ -16,5 +16,6 @@ block content label Password span(class='form-section-input') input(type="password", name="password") + .error #{message} span(class='form-section') button#submit-button(type="submit") Submit \ No newline at end of file From 4828ef2c7c77fa27542e83bff753bd093a285ea1 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Wed, 24 Nov 2021 21:40:33 -0700 Subject: [PATCH 117/169] Require admin for certain pages --- database/accounts/accounts.js | 8 +++---- routes/manage.js | 40 +++++++++++++++++++++++++---------- 2 files changed, 33 insertions(+), 15 deletions(-) diff --git a/database/accounts/accounts.js b/database/accounts/accounts.js index 6f000c9..6fc0d62 100644 --- a/database/accounts/accounts.js +++ b/database/accounts/accounts.js @@ -45,13 +45,13 @@ passport.deserializeUser((id, cb) => { -async function createUser(email, password) { +async function createUser(email, password, isAdmin) { const salt = bcrypt.genSaltSync(); const hash = bcrypt.hashSync(password, salt); - const query = `INSERT INTO accounts.users(email, password) - VALUES($1, $2)`; - await database.executeQuery(query, [email, hash]); + const query = `INSERT INTO accounts.users(email, password, admin) + VALUES($1, $2, $3)`; + await database.executeQuery(query, [email, hash, isAdmin]); } exports.createUser = createUser; diff --git a/routes/manage.js b/routes/manage.js index 16f8ca4..7fbd517 100644 --- a/routes/manage.js +++ b/routes/manage.js @@ -8,18 +8,36 @@ var divisions = require('../database/scores/divisions'); var genders = require('../database/scores/genders'); var teams = require('../database/scores/teams'); +function userLoggedIn(req, res, next) { + if (req.user) { + next(); + } + else { + res.redirect('/auth/login'); + } +} -router.get('/', function(req, res, next) { +function adminLoggedIn(req, res, next) { + if (req.user && req.user[2]) { + next(); + } + else { + res.send('UNAUTHORIZED'); + } +} + + +router.get('/' ,userLoggedIn, function(req, res, next) { res.render('manage', { title: 'Score Management' }); }); -router.get('/game', function(req, res, next) { +router.get('/game', userLoggedIn, function(req, res, next) { let title = req.query.game ? 'Edit Game' : 'Submit Score' res.render('manage/addgame', { title }); }); -router.post('/game', function(req, res, next) { +router.post('/game', userLoggedIn, function(req, res, next) { const seasonID = req.body['year']; const sportID = req.body['sport']; const gender = (req.body['gender'] == "female") ? genders.FEMALE : genders.MALE; @@ -41,11 +59,11 @@ router.post('/game', function(req, res, next) { .then(res.redirect("/manage")); }); -router.get('/season', function(req, res, next) { +router.get('/season', adminLoggedIn, function(req, res, next) { res.render('manage/addseason', { title: 'Add Season', currentYear : (new Date()).getFullYear() }); }); -router.post('/season', function(req, res, next) { +router.post('/season', adminLoggedIn, function(req, res, next) { const year = req.body['year']; const seasonID = req.body['season']; @@ -55,11 +73,11 @@ router.post('/season', function(req, res, next) { else seasons.add(year).then(res.redirect("/manage")); }); -router.get('/sport', function(req, res, next) { +router.get('/sport', adminLoggedIn, function(req, res, next) { res.render('manage/addsport', { title: 'Add Sport' }); }); -router.post('/sport', function(req, res, next) { +router.post('/sport', adminLoggedIn, function(req, res, next) { const name = req.body['name']; const id = req.body['sport']; const remove = req.body['remove']; @@ -69,13 +87,13 @@ router.post('/sport', function(req, res, next) { else sports.add(name).then(res.redirect('/manage')); }); -router.get('/division', function(req, res, next) { +router.get('/division', adminLoggedIn, function(req, res, next) { let title = req.query.division ? 'Edit Division' : 'Add Division' res.render('manage/adddivision', { title }); }); -router.post('/division', function(req, res, next) { +router.post('/division', adminLoggedIn, function(req, res, next) { const name = req.body['name']; const sport = req.body['sport']; const genderName = req.body['gender']; @@ -100,13 +118,13 @@ router.post('/division', function(req, res, next) { } }); -router.get('/team', function(req, res, next) { +router.get('/team', adminLoggedIn, function(req, res, next) { let title = req.query.team ? 'Edit Team' : 'Add Team' res.render('manage/addteam', { title }); }); -router.post('/team', function(req, res, next) { +router.post('/team', adminLoggedIn, function(req, res, next) { const name = req.body['name']; const sport = req.body['sport']; From 173c075aa335d00e11432b60d9c2b4b0b58f1904 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Wed, 24 Nov 2021 22:29:29 -0700 Subject: [PATCH 118/169] Add ability to create user from admin panel --- database/accounts/accounts.js | 22 ++++++++++++++++++ public/scripts/data.js | 6 +++++ public/scripts/manage.js | 43 +++++++++++++++++++++++++++++++++++ public/stylesheets/form.css | 9 ++++++++ routes/admin.js | 16 +++++++++++-- routes/auth.js | 17 +++++++++++--- routes/data.js | 15 ++++++++++++ routes/manage.js | 7 +++++- views/accounts/createuser.pug | 4 ++++ views/manage.pug | 1 + 10 files changed, 134 insertions(+), 6 deletions(-) diff --git a/database/accounts/accounts.js b/database/accounts/accounts.js index 6fc0d62..147cadd 100644 --- a/database/accounts/accounts.js +++ b/database/accounts/accounts.js @@ -3,6 +3,14 @@ const passport = require('passport'); const localStrategy = require('passport-local').Strategy; const bcrypt = require('bcrypt'); +class User { + constructor(id, email, isAdmin) { + this.id = id; + this.email = email; + this.isAdmin = isAdmin; + } +} + passport.use(new localStrategy({ usernameField: 'email', @@ -54,5 +62,19 @@ async function createUser(email, password, isAdmin) { await database.executeQuery(query, [email, hash, isAdmin]); } +async function retrieveAll() { + const query = `SELECT user_id, email, admin + FROM accounts.users + ORDER BY email;` + const table = await database.executeQuery(query); + + const accountsList = []; + table.forEach((row) => { + accountsList.push(new User(row[0], row[1], row[2])); + }); + return accountsList; +} + exports.createUser = createUser; +exports.retrieveAll = retrieveAll; exports.passport = passport; \ No newline at end of file diff --git a/public/scripts/data.js b/public/scripts/data.js index 873d4d6..2360aed 100644 --- a/public/scripts/data.js +++ b/public/scripts/data.js @@ -69,4 +69,10 @@ export async function getGame(gameID) { const response = await fetch(`/data/game?game=${gameID}`); const game = await response.json(); return game; +} + +export async function getAccounts() { + const response = await fetch(`/data/accounts`); + const accounts = await response.json(); + return accounts; } \ No newline at end of file diff --git a/public/scripts/manage.js b/public/scripts/manage.js index 8c89bfd..de4a6e2 100644 --- a/public/scripts/manage.js +++ b/public/scripts/manage.js @@ -294,6 +294,49 @@ CATEGORIES.push(new Category( } )); +CATEGORIES.push(new Category( + "accounts", + async function getAccounts() { + return await Data.getAccounts(); + }, + async function listAccountHeaders() { + const headerRow = document.createElement('tr'); + + const emailHeader = document.createElement('th'); + emailHeader.textContent = "Email"; + headerRow.appendChild(emailHeader); + + const spacerHeader = document.createElement('th'); + spacerHeader.classList.add('spacer-column'); + headerRow.appendChild(spacerHeader); + + const adminHeader = document.createElement('th'); + adminHeader.textContent = "Admin?"; + headerRow.appendChild(adminHeader); + + itemsListTable.appendChild(headerRow); + }, + function listAccount(account, row) { + const emailCell = document.createElement('td'); + emailCell.textContent = account.email; + row.appendChild(emailCell); + + const spacerCell = document.createElement('td'); + row.appendChild(spacerCell); + + const adminCell = document.createElement('td'); + adminCell.textContent = account.isAdmin; + row.appendChild(adminCell); + }, + async function addAccount() { + window.location.href = "/manage/account"; + }, + async function editAccount(id) { + window.location.href = `/manage/account?account=${id}`; + } +)); + + async function listItems(category) { diff --git a/public/stylesheets/form.css b/public/stylesheets/form.css index 21abe10..32123e7 100644 --- a/public/stylesheets/form.css +++ b/public/stylesheets/form.css @@ -35,4 +35,13 @@ form { #delete-button { visibility: hidden; + } + + .form-section-checkbox { + flex-direction: row; + align-items: center; + } + + #admin { + width: auto; } \ No newline at end of file diff --git a/routes/admin.js b/routes/admin.js index d032cd1..9e9cfc9 100644 --- a/routes/admin.js +++ b/routes/admin.js @@ -5,8 +5,20 @@ const app = require('../app'); const accounts = require('./../database/accounts/accounts'); -router.get('/createuser', (req, res, next) => { - res.render('accounts/createuser', { title: 'Create user' }); +function adminLoggedIn(req, res, next) { + if (req.user && req.user[2]) { + next(); + } + else { + req.flash('error', 'An admin account is required to access this page.'); + res.redirect('/auth/login'); + } + } + +router.get('/', adminLoggedIn, (req, res, next) => { + res.render }); + + module.exports = router; \ No newline at end of file diff --git a/routes/auth.js b/routes/auth.js index 5d2e910..47a9b09 100644 --- a/routes/auth.js +++ b/routes/auth.js @@ -4,6 +4,17 @@ const passport = require('passport'); const accounts = require('./../database/accounts/accounts'); const app = require('../app'); + +function adminLoggedIn(req, res, next) { + if (req.user && req.user[2]) { + next(); + } + else { + req.flash('error', 'An admin account is required to access this page.'); + res.redirect('/auth/login'); + } + } + router.get('/login', (req, res, next) => { res.render('accounts/login', { title : "Login", message: req.flash('error') }); }); @@ -23,9 +34,9 @@ router.post('/login', console.log(req.user); }); -router.post('/register', (req, res, next) => { - accounts.createUser(req.body.email, req.body.password) - .then(res.redirect('/')); +router.post('/register', adminLoggedIn, (req, res, next) => { + accounts.createUser(req.body.email, req.body.password, !!req.body.admin) + .then(res.redirect('/manage')); }); module.exports = router; \ No newline at end of file diff --git a/routes/data.js b/routes/data.js index 6ad845a..fcb4920 100644 --- a/routes/data.js +++ b/routes/data.js @@ -6,7 +6,17 @@ var genders = require('../database/scores/genders'); var divisions = require('../database/scores/divisions'); var teams = require('../database/scores/teams'); var games = require('../database/scores/games'); +var accounts = require('../database/accounts/accounts'); +function adminLoggedIn(req, res, next) { + if (req.user && req.user[2]) { + next(); + } + else { + req.flash('error', 'An admin account is required to access this page.'); + res.redirect('/auth/login'); + } + } router.get('/sports', function(req, res, next) { sports.retrieveAll() @@ -61,4 +71,9 @@ router.get('/game', function(req, res, next) { .then(data => res.json(data)); }) +router.get('/accounts', adminLoggedIn, function(req, res, next) { + accounts.retrieveAll() + .then(data => res.json(data)); +}) + module.exports = router; \ No newline at end of file diff --git a/routes/manage.js b/routes/manage.js index 7fbd517..756c58d 100644 --- a/routes/manage.js +++ b/routes/manage.js @@ -22,7 +22,8 @@ function adminLoggedIn(req, res, next) { next(); } else { - res.send('UNAUTHORIZED'); + req.flash('error', 'An admin account is required to access this page.'); + res.redirect('/auth/login'); } } @@ -136,4 +137,8 @@ router.post('/team', adminLoggedIn, function(req, res, next) { else teams.add(name, sport).then(res.redirect("/manage")); }); +router.get('/account', adminLoggedIn, (req, res, next) => { + res.render('accounts/createuser', { title: 'Create user' }); +}); + module.exports = router; diff --git a/views/accounts/createuser.pug b/views/accounts/createuser.pug index c5630c8..02560ec 100644 --- a/views/accounts/createuser.pug +++ b/views/accounts/createuser.pug @@ -16,5 +16,9 @@ block content label Password span(class='form-section-input') input(type="password", name="password") + span(class='form-section') + span(class='form-section-checkbox') + input#admin(type="checkbox", name="admin") + label(for="admin") Grant admin privileges span(class='form-section') button#submit-button(type="submit") Submit \ No newline at end of file diff --git a/views/manage.pug b/views/manage.pug index c43dcf9..454d1ed 100644 --- a/views/manage.pug +++ b/views/manage.pug @@ -17,6 +17,7 @@ block content option(value="divisions") Divisions option(value="teams") Teams option(value="games") Games + option(value="accounts") Accounts div h2#table-header table#items-list From 4d3b6989e4905f317b0bb293cd7467139f4c0efa Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Thu, 25 Nov 2021 12:04:18 -0700 Subject: [PATCH 119/169] Add function to get user information --- database/accounts/accounts.js | 10 ++++++++++ public/scripts/data.js | 6 ++++++ routes/data.js | 5 +++++ 3 files changed, 21 insertions(+) diff --git a/database/accounts/accounts.js b/database/accounts/accounts.js index 147cadd..ef0d040 100644 --- a/database/accounts/accounts.js +++ b/database/accounts/accounts.js @@ -75,6 +75,16 @@ async function retrieveAll() { return accountsList; } +async function getFromID(id) { + const query = `SELECT user_id, email, admin + FROM accounts.users + WHERE user_id = $1;`; + const row = (await database.executeQuery(query, [id]))[0]; + + return new User(id, row[1], row[2]); +} + exports.createUser = createUser; exports.retrieveAll = retrieveAll; +exports.getFromID = getFromID; exports.passport = passport; \ No newline at end of file diff --git a/public/scripts/data.js b/public/scripts/data.js index 2360aed..ae8dc7f 100644 --- a/public/scripts/data.js +++ b/public/scripts/data.js @@ -75,4 +75,10 @@ export async function getAccounts() { const response = await fetch(`/data/accounts`); const accounts = await response.json(); return accounts; +} + +export async function getAccount(accountID) { + const response = await fetch(`/data/account?account=${accountID}`); + const account = await response.json(); + return account; } \ No newline at end of file diff --git a/routes/data.js b/routes/data.js index fcb4920..d010bd1 100644 --- a/routes/data.js +++ b/routes/data.js @@ -76,4 +76,9 @@ router.get('/accounts', adminLoggedIn, function(req, res, next) { .then(data => res.json(data)); }) +router.get('/account', adminLoggedIn, function(req, res, next) { + accounts.getFromID(req.query.account) + .then(data => res.json(data)); +}) + module.exports = router; \ No newline at end of file From d43aba3da1e7cd270b75af4c1febaecc7e947597 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Thu, 25 Nov 2021 12:13:46 -0700 Subject: [PATCH 120/169] Remove console.log from teams.js --- public/scripts/manage/team.js | 1 - 1 file changed, 1 deletion(-) diff --git a/public/scripts/manage/team.js b/public/scripts/manage/team.js index d5d8291..10cd386 100644 --- a/public/scripts/manage/team.js +++ b/public/scripts/manage/team.js @@ -21,7 +21,6 @@ async function initializeForm() { Form.populateSports(sportDropdown, team.sportID); - console.log(team.sportID); Form.addHiddenValue('team', teamID, submissionForm); } else { From 1901f33851f1e37682a3e8d26028f09b7ca5fc7f Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Thu, 25 Nov 2021 12:17:53 -0700 Subject: [PATCH 121/169] Create client layout for managing users --- public/scripts/manage/account.js | 33 ++++++++++++++++++++++++++++++++ public/stylesheets/form.css | 2 +- routes/manage.js | 4 +++- views/accounts/createuser.pug | 19 +++++++++++------- 4 files changed, 49 insertions(+), 9 deletions(-) create mode 100644 public/scripts/manage/account.js diff --git a/public/scripts/manage/account.js b/public/scripts/manage/account.js new file mode 100644 index 0000000..6b51c2f --- /dev/null +++ b/public/scripts/manage/account.js @@ -0,0 +1,33 @@ +import * as Data from "../data.js"; +import * as Form from "../form.js"; + +const submissionForm = document.getElementById('submission-form'); +const emailTextbox = document.getElementById('email-textbox'); +const passwordTextbox = document.getElementById('password-textbox'); +const adminCheckbox = document.getElementById('admin-checkbox'); +const submitButton = document.getElementById('submit-button'); +const deleteButton = document.getElementById('delete-button'); + +async function Initialize() { + let params = new URLSearchParams(location.search); + let accountID = params.get('account'); + if(accountID) { + const account = await Data.getAccount(accountID); + console.log(account); + + emailTextbox.value = account.email; + + passwordTextbox.placeholder = "leave unchanged"; + + adminCheckbox.checked = account.isAdmin; + + Form.addHiddenValue('account', accountID, submissionForm); + + deleteButton.style.visibility = "visible"; + deleteButton.disabled = false; + } + emailTextbox.disabled = false; + passwordTextbox.disabled = false; + adminCheckbox.disabled = false; +} +Initialize(); \ No newline at end of file diff --git a/public/stylesheets/form.css b/public/stylesheets/form.css index 32123e7..389028c 100644 --- a/public/stylesheets/form.css +++ b/public/stylesheets/form.css @@ -42,6 +42,6 @@ form { align-items: center; } - #admin { + #admin-checkbox { width: auto; } \ No newline at end of file diff --git a/routes/manage.js b/routes/manage.js index 756c58d..e9d4377 100644 --- a/routes/manage.js +++ b/routes/manage.js @@ -138,7 +138,9 @@ router.post('/team', adminLoggedIn, function(req, res, next) { }); router.get('/account', adminLoggedIn, (req, res, next) => { - res.render('accounts/createuser', { title: 'Create user' }); + let title = req.query.account ? 'Manage User' : 'Create User' + + res.render('accounts/createuser', { title }); }); module.exports = router; diff --git a/views/accounts/createuser.pug b/views/accounts/createuser.pug index 02560ec..cc5324d 100644 --- a/views/accounts/createuser.pug +++ b/views/accounts/createuser.pug @@ -7,18 +7,23 @@ block stylesheets block content div#mobile-view h1 #{title} - form(action='/auth/register', method='POST') + form#submission-form(action='/auth/register', method='POST') span(class='form-section') label Email span(class='form-section-input') - input(type="email", name="email") + input#email-textbox(type="email", name="email" disabled) span(class='form-section') label Password - span(class='form-section-input') - input(type="password", name="password") + span(class='form-section-input' ) + input#password-textbox(type="password" name="password" disabled) span(class='form-section') span(class='form-section-checkbox') - input#admin(type="checkbox", name="admin") - label(for="admin") Grant admin privileges + input#admin-checkbox(type="checkbox" name="admin" disabled) + label(for="admin-checkbox") Grant admin privileges span(class='form-section') - button#submit-button(type="submit") Submit \ No newline at end of file + button#submit-button(type="submit" disabled) Submit + span(class='form-section') + button#delete-button(type="delete" disabled) Delete + +block scripts + script(src='/scripts/manage/account.js' type="module") \ No newline at end of file From 06765455f62ccba8c4743287d07fe0f7502e445c Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Thu, 25 Nov 2021 12:40:19 -0700 Subject: [PATCH 122/169] Add functionality to edit accounts --- database/accounts/accounts.js | 30 +++++++++++++++++++++++++++--- public/scripts/manage/account.js | 16 +++++++++++++++- routes/manage.js | 12 ++++++++++++ views/accounts/createuser.pug | 2 +- 4 files changed, 55 insertions(+), 5 deletions(-) diff --git a/database/accounts/accounts.js b/database/accounts/accounts.js index ef0d040..ce0be9f 100644 --- a/database/accounts/accounts.js +++ b/database/accounts/accounts.js @@ -52,16 +52,39 @@ passport.deserializeUser((id, cb) => { }); +async function generateHash(password) { + return bcrypt.hashSync(password, salt); +} -async function createUser(email, password, isAdmin) { +async function create(email, password, isAdmin) { const salt = bcrypt.genSaltSync(); - const hash = bcrypt.hashSync(password, salt); + const hash = await generateHash(password); const query = `INSERT INTO accounts.users(email, password, admin) VALUES($1, $2, $3)`; await database.executeQuery(query, [email, hash, isAdmin]); } +async function edit(id, email, password, isAdmin) { + if(password) { + const hash = await generateHash(password); + + const query = `UPDATE accounts.users + SET email = $2, + password = $3, + admin = $4 + WHERE user_id = $1;`; + await database.executeQuery(query, [id, email, hash, isAdmin]); + } else { + const query = `UPDATE accounts.users + SET email = $2, + admin = $3 + WHERE user_id = $1;`; + await database.executeQuery(query, [id, email, isAdmin]); + } + return new User(id, email, isAdmin); +} + async function retrieveAll() { const query = `SELECT user_id, email, admin FROM accounts.users @@ -84,7 +107,8 @@ async function getFromID(id) { return new User(id, row[1], row[2]); } -exports.createUser = createUser; +exports.create = create; +exports.edit = edit; exports.retrieveAll = retrieveAll; exports.getFromID = getFromID; exports.passport = passport; \ No newline at end of file diff --git a/public/scripts/manage/account.js b/public/scripts/manage/account.js index 6b51c2f..65c9f4e 100644 --- a/public/scripts/manage/account.js +++ b/public/scripts/manage/account.js @@ -27,7 +27,21 @@ async function Initialize() { deleteButton.disabled = false; } emailTextbox.disabled = false; + emailTextbox.addEventListener('keyup', checkDataValidity); passwordTextbox.disabled = false; + passwordTextbox.addEventListener('keyup', checkDataValidity); adminCheckbox.disabled = false; + checkDataValidity(); } -Initialize(); \ No newline at end of file +Initialize(); + +async function checkDataValidity() { + let dataIsValid = true; + + if(!passwordTextbox.value && !passwordTextbox.placeholder) dataIsValid = false; + if(!emailTextbox.value) dataIsValid = false; + + if(dataIsValid) submitButton.disabled = false; + else submitButton.disabled = true; +} + diff --git a/routes/manage.js b/routes/manage.js index e9d4377..9e038f0 100644 --- a/routes/manage.js +++ b/routes/manage.js @@ -7,6 +7,7 @@ var sports = require('../database/scores/sports'); var divisions = require('../database/scores/divisions'); var genders = require('../database/scores/genders'); var teams = require('../database/scores/teams'); +var accounts = require('../database/accounts/accounts'); function userLoggedIn(req, res, next) { if (req.user) { @@ -143,4 +144,15 @@ router.get('/account', adminLoggedIn, (req, res, next) => { res.render('accounts/createuser', { title }); }); +router.post('/account', adminLoggedIn, (req, res, next) => { + const email = req.body.email; + const password = req.body.password; + const isAdmin = !!req.body.admin; + + const accountID = req.body.account; + + if(accountID) accounts.edit(accountID, email, password, isAdmin).then(res.redirect('/manage')); + else accounts.create(req.body.email, req.body.password, !!req.body.admin).then(res.redirect('/manage')); +}); + module.exports = router; diff --git a/views/accounts/createuser.pug b/views/accounts/createuser.pug index cc5324d..36e0acb 100644 --- a/views/accounts/createuser.pug +++ b/views/accounts/createuser.pug @@ -7,7 +7,7 @@ block stylesheets block content div#mobile-view h1 #{title} - form#submission-form(action='/auth/register', method='POST') + form#submission-form(action='/manage/account', method='POST') span(class='form-section') label Email span(class='form-section-input') From 23e85b7af3109f5c7b9ef31a204626f390a4c901 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Thu, 25 Nov 2021 12:41:22 -0700 Subject: [PATCH 123/169] Fix bug in accounts.js --- database/accounts/accounts.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/accounts/accounts.js b/database/accounts/accounts.js index ce0be9f..869810e 100644 --- a/database/accounts/accounts.js +++ b/database/accounts/accounts.js @@ -53,11 +53,11 @@ passport.deserializeUser((id, cb) => { async function generateHash(password) { + const salt = bcrypt.genSaltSync(); return bcrypt.hashSync(password, salt); } async function create(email, password, isAdmin) { - const salt = bcrypt.genSaltSync(); const hash = await generateHash(password); const query = `INSERT INTO accounts.users(email, password, admin) From 08545bfb5cefb941e93caa051fb352ac927937ef Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Thu, 25 Nov 2021 12:49:31 -0700 Subject: [PATCH 124/169] Add functions to remove account --- database/accounts/accounts.js | 9 +++++++++ public/scripts/manage/account.js | 1 + routes/manage.js | 2 ++ 3 files changed, 12 insertions(+) diff --git a/database/accounts/accounts.js b/database/accounts/accounts.js index 869810e..a8bab35 100644 --- a/database/accounts/accounts.js +++ b/database/accounts/accounts.js @@ -85,6 +85,14 @@ async function edit(id, email, password, isAdmin) { return new User(id, email, isAdmin); } +async function remove(id) { + const query = `DELETE FROM accounts.users + WHERE user_id = $1 + RETURNING email, admin;`; + const row = (await database.executeQuery(query, [id]))[0]; + return new User(id, row[0], row[1]); +} + async function retrieveAll() { const query = `SELECT user_id, email, admin FROM accounts.users @@ -109,6 +117,7 @@ async function getFromID(id) { exports.create = create; exports.edit = edit; +exports.remove = remove; exports.retrieveAll = retrieveAll; exports.getFromID = getFromID; exports.passport = passport; \ No newline at end of file diff --git a/public/scripts/manage/account.js b/public/scripts/manage/account.js index 65c9f4e..0ab23a3 100644 --- a/public/scripts/manage/account.js +++ b/public/scripts/manage/account.js @@ -45,3 +45,4 @@ async function checkDataValidity() { else submitButton.disabled = true; } +Form.addRemoveFunction(deleteButton, submissionForm, "account"); \ No newline at end of file diff --git a/routes/manage.js b/routes/manage.js index 9e038f0..fe6f204 100644 --- a/routes/manage.js +++ b/routes/manage.js @@ -150,7 +150,9 @@ router.post('/account', adminLoggedIn, (req, res, next) => { const isAdmin = !!req.body.admin; const accountID = req.body.account; + const remove = req.body.remove; + if(remove) accounts.remove(accountID).then(res.redirect('/manage')); if(accountID) accounts.edit(accountID, email, password, isAdmin).then(res.redirect('/manage')); else accounts.create(req.body.email, req.body.password, !!req.body.admin).then(res.redirect('/manage')); }); From d5be13955a0194400eea33f77b2e4100d9fb876a Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Thu, 25 Nov 2021 12:55:54 -0700 Subject: [PATCH 125/169] Fix bug regarding deleting games --- routes/manage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes/manage.js b/routes/manage.js index fe6f204..c3202f1 100644 --- a/routes/manage.js +++ b/routes/manage.js @@ -51,7 +51,7 @@ router.post('/game', userLoggedIn, function(req, res, next) { const team2Score = req.body['team2-score']; const id = req.body['game']; - const remove = req.body['delete']; + const remove = req.body['remove']; if(remove) games.remove(id) .then(res.redirect("/manage")); From b7f032754bb27f718ef1933e0edc298bcc2f7c1d Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Thu, 25 Nov 2021 13:03:54 -0700 Subject: [PATCH 126/169] Fix bug in games.js where dropdowns are not being updated --- public/scripts/manage/game.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/public/scripts/manage/game.js b/public/scripts/manage/game.js index 82d4cb8..8338d4c 100644 --- a/public/scripts/manage/game.js +++ b/public/scripts/manage/game.js @@ -67,6 +67,19 @@ async function initializeForm() { team1ScoreTextbox.disabled = false; team2ScoreTextbox.disabled = false; submitButton.disabled = false; + + sportDropdown.onchange = () => { + Form.populateGenders(genderDropdown, sportDropdown.value) + .then(() => { + Form.populateDivisions(divisionDropdown, sportDropdown.value, genderDropdown.value); + }); + Form.populateTeams(team1Dropdown, sportDropdown.value); + Form.populateTeams(team2Dropdown, sportDropdown.value); + }; + + genderDropdown.onchange = () => { + Form.populateDivisions(divisionDropdown, sportDropdown.value, genderDropdown.value); + }; } initializeForm(); From 4a108f606b46832c61289aaea9a967e645d24abb Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Thu, 25 Nov 2021 15:41:10 -0700 Subject: [PATCH 127/169] Move register route from auth to manage --- routes/auth.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/routes/auth.js b/routes/auth.js index 47a9b09..25159ab 100644 --- a/routes/auth.js +++ b/routes/auth.js @@ -34,9 +34,6 @@ router.post('/login', console.log(req.user); }); -router.post('/register', adminLoggedIn, (req, res, next) => { - accounts.createUser(req.body.email, req.body.password, !!req.body.admin) - .then(res.redirect('/manage')); -}); + module.exports = router; \ No newline at end of file From d35188537540e7040cbbb1ac0f3ea9998038d7d1 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Thu, 25 Nov 2021 18:29:18 -0700 Subject: [PATCH 128/169] Add page with only scores for non-admins --- public/scripts/manage/manage-nonadmin.js | 127 +++++++++++++++++++++++ routes/manage.js | 3 +- views/manage/manage-nonadmin.pug | 16 +++ 3 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 public/scripts/manage/manage-nonadmin.js create mode 100644 views/manage/manage-nonadmin.pug diff --git a/public/scripts/manage/manage-nonadmin.js b/public/scripts/manage/manage-nonadmin.js new file mode 100644 index 0000000..4fc4b02 --- /dev/null +++ b/public/scripts/manage/manage-nonadmin.js @@ -0,0 +1,127 @@ +import * as Data from "./../data.js"; + +const gamesListTable = document.getElementById('games-list'); +const addNewButton = document.getElementById('add-new-button'); + + +function getGenderLetter(genderName) { + return genderName == "female" ? "F" : "M"; +} + +async function listGameHeaders() { + const headerRow = document.createElement('tr'); + + const teamsHeader = document.createElement('th'); + teamsHeader.textContent = "Teams"; + headerRow.appendChild(teamsHeader); + + const scoreHeader = document.createElement('th'); + headerRow.appendChild(scoreHeader); + + const spacerHeader = document.createElement('th'); + spacerHeader.classList.add('spacer-column'); + headerRow.appendChild(spacerHeader); + + const sportNameHeader = document.createElement('th'); + sportNameHeader.textContent = "Sport"; + headerRow.appendChild(sportNameHeader); + + const dateHeader = document.createElement('th'); + dateHeader.textContent = "Date"; + headerRow.appendChild(dateHeader); + + gamesListTable.appendChild(headerRow); +} + + +function listGame(game, row) { + const teamsCell = document.createElement('td'); + const team1NameSpan = document.createElement('span'); + Data.getTeam(game.team1ID) + .then(data => team1NameSpan.textContent = data.name); + teamsCell.appendChild(team1NameSpan); + const team2NameSpan = document.createElement('span'); + Data.getTeam(game.team2ID) + .then(data => team2NameSpan.textContent = data.name); + teamsCell.appendChild(team2NameSpan); + row.appendChild(teamsCell); + + const scoresCell = document.createElement('td'); + const team1ScoreSpan = document.createElement('span'); + team1ScoreSpan.textContent = game.team1Score; + scoresCell.appendChild(team1ScoreSpan); + const team2ScoreSpan = document.createElement('span'); + team2ScoreSpan.textContent = game.team2Score; + scoresCell.appendChild(team2ScoreSpan); + row.appendChild(scoresCell); + + const spacerCell = document.createElement('td'); + row.appendChild(spacerCell); + + const sportCell = document.createElement('td'); + const sportSpan = document.createElement('span'); + const divisionSpan = document.createElement('span'); + divisionSpan.classList.add('flat-content'); + const divisionNameSpan = document.createElement('span'); + const divisionGenderSpan = document.createElement('span'); + divisionSpan.appendChild(divisionNameSpan); + divisionSpan.appendChild(divisionGenderSpan); + Data.getDivision(game.divisionID) + .then(data => { + Data.getSportName(data.sportID) + .then(data => sportSpan.textContent = data); + divisionNameSpan.textContent = data.name; + divisionGenderSpan.textContent = getGenderLetter(data.gender.name); + }); + sportCell.appendChild(sportSpan); + sportCell.appendChild(divisionSpan); + row.appendChild(sportCell); + + const dateCell = document.createElement('td'); + const yearSpan = document.createElement('span'); + yearSpan.textContent = game.date.slice(0,4); + dateCell.appendChild(yearSpan); + const dateSpan = document.createElement('span'); + dateSpan.textContent = game.date.slice(5); + dateCell.appendChild(dateSpan); + row.appendChild(dateCell); +} + +async function addGame() { + window.location.href = "/manage/game"; +} + +async function editGame(id) { + window.location.href = `/manage/game?game=${id}`; +} + + +async function listItems() { + const gamesList = await Data.getGames(); + + await listGameHeaders(); + + gamesList.forEach(game => { + const row = document.createElement('tr'); + + listGame(game, row); + + const manageCell = document.createElement('td'); + + const editSpan = document.createElement('span'); + const editButton = document.createElement('button'); + editButton.textContent = "E"; + editButton.addEventListener('click', () => { + editGame(game.id); + }); + editSpan.appendChild(editButton); + manageCell.appendChild(editSpan); + + row.appendChild(manageCell); + + gamesListTable.appendChild(row); + }); +} +listItems(); + +addNewButton.addEventListener('click', () => addGame()); \ No newline at end of file diff --git a/routes/manage.js b/routes/manage.js index c3202f1..aece4fc 100644 --- a/routes/manage.js +++ b/routes/manage.js @@ -30,7 +30,8 @@ function adminLoggedIn(req, res, next) { router.get('/' ,userLoggedIn, function(req, res, next) { - res.render('manage', { title: 'Score Management' }); + if(req.user[2]) res.render('manage', { title: 'Score Management' }); + else res.render('manage/manage-nonadmin', { title: "Manage Games" }); }); router.get('/game', userLoggedIn, function(req, res, next) { diff --git a/views/manage/manage-nonadmin.pug b/views/manage/manage-nonadmin.pug new file mode 100644 index 0000000..f3bb4e6 --- /dev/null +++ b/views/manage/manage-nonadmin.pug @@ -0,0 +1,16 @@ +extends layout + +block stylesheets + link(rel='stylesheet', href='/stylesheets/manage.css') + link(rel='stylesheet', href='/stylesheets/form.css') + +block content + div#mobile-view + h1 #{title} + div + h2#table-header + table#games-list + button#add-new-button Add new... + +block scripts + script(src='/scripts/manage/manage-nonadmin.js' type="module") \ No newline at end of file From 3515be836d0a4e429cdf438c66dbb8abd0c06b8b Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Thu, 25 Nov 2021 18:49:31 -0700 Subject: [PATCH 129/169] Add submitter_id column to init_database.sql script --- database/database.js | 1 - database/init_database.sql | 30 ++++++++++++++++-------------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/database/database.js b/database/database.js index 1d5bc56..2f9277e 100644 --- a/database/database.js +++ b/database/database.js @@ -26,7 +26,6 @@ async function Initialize() { } - async function checkForDatabaseInitialization() { const query = `SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'scores'`; let result = await executeQuery(query); diff --git a/database/init_database.sql b/database/init_database.sql index ea8decf..c7de265 100644 --- a/database/init_database.sql +++ b/database/init_database.sql @@ -30,6 +30,18 @@ accounts: BEGIN; +CREATE SCHEMA IF NOT EXISTS accounts; + +CREATE TABLE IF NOT EXISTS accounts.users( + user_id BIGINT GENERATED ALWAYS AS IDENTITY, + email TEXT UNIQUE NOT NULL, + password TEXT NOT NULL, + admin BOOLEAN NOT NULL DEFAULT FALSE, + PRIMARY KEY(user_id) + ); + + + CREATE SCHEMA IF NOT EXISTS scores; @@ -78,7 +90,7 @@ CREATE TABLE IF NOT EXISTS scores.games( team2_id BIGINT, team1_score INTEGER, team2_score INTEGER, -/* submitter_id BIGINT,*/ + submitter_id BIGINT, updated_timestamp TIMESTAMP WITH TIME ZONE DEFAULT now(), PRIMARY KEY(game_id), CONSTRAINT fk_division @@ -92,20 +104,10 @@ CREATE TABLE IF NOT EXISTS scores.games( REFERENCES scores.teams(team_id), CONSTRAINT fk_team2 FOREIGN KEY(team2_id) - REFERENCES scores.teams(team_id) -/* CONSTRAINT fk_submitter + REFERENCES scores.teams(team_id), + CONSTRAINT fk_submitter FOREIGN KEY(submitter_id) - REFERENCES accounts.users(user_id)*/ - ); - - -CREATE SCHEMA IF NOT EXISTS accounts; - -CREATE TABLE IF NOT EXISTS accounts.users( - user_id BIGINT GENERATED ALWAYS AS IDENTITY, - email TEXT UNIQUE NOT NULL, - password TEXT NOT NULL, - admin BOOLEAN NOT NULL DEFAULT FALSE + REFERENCES accounts.users(user_id) ); COMMIT; \ No newline at end of file From bd5b393a278154bde1f6a6f9b0210073b38c4cd6 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Thu, 25 Nov 2021 19:21:21 -0700 Subject: [PATCH 130/169] Add panel for non-admins to edit their own games --- database/accounts/accounts.js | 18 ++++++++++++++++++ database/database.js | 6 +++--- database/scores/games.js | 23 +++++++++++++++++++---- public/scripts/data.js | 7 +++++++ public/scripts/manage/manage-nonadmin.js | 2 +- routes/data.js | 5 +++-- routes/manage.js | 5 +++-- 7 files changed, 54 insertions(+), 12 deletions(-) diff --git a/database/accounts/accounts.js b/database/accounts/accounts.js index a8bab35..84c7a35 100644 --- a/database/accounts/accounts.js +++ b/database/accounts/accounts.js @@ -12,6 +12,24 @@ class User { } +async function checkForAdminAccount() { + + const adminUsersQuery = `SELECT * + FROM accounts.users + WHERE admin = true;`; + const adminUsers = await database.executeQuery(adminUsersQuery); + + if(adminUsers.length == 0) { + const passwordHash = await generateHash('admin'); + const createTempAdminQuery = `INSERT INTO accounts.users(email, password, admin) + VALUES('admin@example.com', $1, true);`; + database.executeQuery(createTempAdminQuery, [passwordHash]); + console.log("Created temp admin account 'admin@example.com' with password 'admin'."); + } +} +checkForAdminAccount(); + + passport.use(new localStrategy({ usernameField: 'email', passwordField: 'password'}, diff --git a/database/database.js b/database/database.js index 2f9277e..349e2bc 100644 --- a/database/database.js +++ b/database/database.js @@ -27,13 +27,13 @@ async function Initialize() { async function checkForDatabaseInitialization() { - const query = `SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'scores'`; - let result = await executeQuery(query); + const scoresSchemaExistsQuery = `SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'scores'`; + let result = await executeQuery(scoresSchemaExistsQuery); const scoresSchemaExists = result.length !== 0; if(!scoresSchemaExists) { - Initialize(); + await Initialize(); } } checkForDatabaseInitialization(); diff --git a/database/scores/games.js b/database/scores/games.js index 270941b..3506356 100644 --- a/database/scores/games.js +++ b/database/scores/games.js @@ -19,12 +19,12 @@ class Game { -async function add(divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score) { - const query = `INSERT INTO scores.games(division_id, season_id, game_date, team1_id, team2_id, team1_score, team2_score) - VALUES($1, $2, $3, $4, $5, $6, $7) +async function add(divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score, userID) { + const query = `INSERT INTO scores.games(division_id, season_id, game_date, team1_id, team2_id, team1_score, team2_score, submitter_id) + VALUES($1, $2, $3, $4, $5, $6, $7, $8) RETURNING game_id;`; - const id = (await database.executeQuery(query, [divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score]))[0][0]; + const id = (await database.executeQuery(query, [divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score, userID]))[0][0]; return new Game(id, date, team1ID, team2ID, team1Score, team2Score); } @@ -71,6 +71,20 @@ async function retrieve(teamID, divisionID, seasonID) { return gamesList; } +async function retrieveByUser(userID) { + const query = `SELECT game_id, division_id, season_id, game_date, team1_id, team2_id, team1_score, team2_score + FROM scores.games + WHERE submitter_id = $1 + ORDER BY game_date DESC;`; + const table = await database.executeQuery(query, [userID]); + + const gamesList = []; + table.forEach((row) => { + gamesList.push(new Game(row[0], row[3].toISOString().slice(0,10), row[4], row[5], row[6], row[7], row[1], row[2])); + }); + return gamesList; +} + async function edit(gameID, divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score) { const query = `UPDATE scores.games SET division_id = $2, @@ -100,5 +114,6 @@ async function getFromID(gameID) { exports.add = add; exports.remove = remove; exports.retrieve = retrieve; +exports.retrieveByUser = retrieveByUser; exports.edit = edit; exports.getFromID = getFromID; \ No newline at end of file diff --git a/public/scripts/data.js b/public/scripts/data.js index ae8dc7f..e8c5898 100644 --- a/public/scripts/data.js +++ b/public/scripts/data.js @@ -65,6 +65,13 @@ export async function getGames(teamID = undefined, divisionID = undefined, seaso return gamesList; } +export async function getGamesByUser() { + let URL = '/data/games?user=1'; + const response = await fetch(URL); + const gamesList = await response.json(); + return gamesList; +} + export async function getGame(gameID) { const response = await fetch(`/data/game?game=${gameID}`); const game = await response.json(); diff --git a/public/scripts/manage/manage-nonadmin.js b/public/scripts/manage/manage-nonadmin.js index 4fc4b02..d549030 100644 --- a/public/scripts/manage/manage-nonadmin.js +++ b/public/scripts/manage/manage-nonadmin.js @@ -97,7 +97,7 @@ async function editGame(id) { async function listItems() { - const gamesList = await Data.getGames(); + const gamesList = await Data.getGamesByUser(); await listGameHeaders(); diff --git a/routes/data.js b/routes/data.js index d010bd1..3d71892 100644 --- a/routes/data.js +++ b/routes/data.js @@ -62,8 +62,9 @@ router.get('/team', function(req, res, next) { }) router.get('/games', function(req, res, next) { - games.retrieve(req.query.team, req.query.division, req.query.season) - .then(data => res.json(data)); + const userID = req.user[0]; + if(req.query.user) games.retrieveByUser(userID).then(data => res.json(data)); + else games.retrieve(req.query.team, req.query.division, req.query.season).then(data => res.json(data)); }) router.get('/game', function(req, res, next) { diff --git a/routes/manage.js b/routes/manage.js index aece4fc..b01b477 100644 --- a/routes/manage.js +++ b/routes/manage.js @@ -31,7 +31,7 @@ function adminLoggedIn(req, res, next) { router.get('/' ,userLoggedIn, function(req, res, next) { if(req.user[2]) res.render('manage', { title: 'Score Management' }); - else res.render('manage/manage-nonadmin', { title: "Manage Games" }); + else res.render('manage/manage-nonadmin', { title: "My Games" }); }); router.get('/game', userLoggedIn, function(req, res, next) { @@ -50,6 +50,7 @@ router.post('/game', userLoggedIn, function(req, res, next) { const team1Score = req.body['team1-score']; const team2ID = req.body['team2']; const team2Score = req.body['team2-score']; + const userID = req.user[0]; const id = req.body['game']; const remove = req.body['remove']; @@ -58,7 +59,7 @@ router.post('/game', userLoggedIn, function(req, res, next) { .then(res.redirect("/manage")); else if(id) games.edit(id, divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score) .then(res.redirect('/manage')); - else games.add(divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score) + else games.add(divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score, userID) .then(res.redirect("/manage")); }); From 79123c14bd0b4944bc653118a0b7e40efaaebbcc Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Thu, 25 Nov 2021 19:33:53 -0700 Subject: [PATCH 131/169] Only allow non-admin users to edit their own games --- database/scores/games.js | 7 ++++--- routes/manage.js | 21 +++++++++++++++------ 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/database/scores/games.js b/database/scores/games.js index 3506356..8792b2e 100644 --- a/database/scores/games.js +++ b/database/scores/games.js @@ -5,7 +5,7 @@ const database = require('./../database'); class Game { - constructor(id, date, team1ID, team2ID, team1Score, team2Score, divisionID, seasonID) { + constructor(id, date, team1ID, team2ID, team1Score, team2Score, divisionID, seasonID, submitterID) { this.id = id; this.date = date; this.team1ID = team1ID; @@ -14,6 +14,7 @@ class Game { this.team2Score = team2Score; this.divisionID = divisionID; this.seasonID = seasonID; + this.submitterID = submitterID; } } @@ -100,11 +101,11 @@ async function edit(gameID, divisionID, seasonID, date, team1ID, team2ID, team1S } async function getFromID(gameID) { - const query = `SELECT game_id, division_id, season_id, game_date, team1_id, team2_id, team1_score, team2_score + const query = `SELECT game_id, division_id, season_id, game_date, team1_id, team2_id, team1_score, team2_score, submitter_id FROM scores.games WHERE game_id = $1;`; const row = (await database.executeQuery(query, [gameID]))[0]; - return new Game(row[0], row[3].toISOString().slice(0,10), row[4], row[5], row[6], row[7], row[1], row[2]); + return new Game(row[0], row[3].toISOString().slice(0,10), row[4], row[5], row[6], row[7], row[1], row[2], row[8]); } diff --git a/routes/manage.js b/routes/manage.js index b01b477..0a9c16e 100644 --- a/routes/manage.js +++ b/routes/manage.js @@ -55,12 +55,21 @@ router.post('/game', userLoggedIn, function(req, res, next) { const id = req.body['game']; const remove = req.body['remove']; - if(remove) games.remove(id) - .then(res.redirect("/manage")); - else if(id) games.edit(id, divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score) - .then(res.redirect('/manage')); - else games.add(divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score, userID) - .then(res.redirect("/manage")); + const loggedInUserID = req.user[0]; + const loggedInUserIsAdmin = req.user[2]; + + games.getFromID(id) + .then(game => { + if(!loggedInUserIsAdmin && loggedInUserID != game.submitterID) { + res.status(403).send("ACCESS DENIED"); + } + else if(remove) games.remove(id) + .then(res.redirect("/manage")); + else if(id) games.edit(id, divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score) + .then(res.redirect('/manage')); + else games.add(divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score, userID) + .then(res.redirect("/manage")); + }); }); router.get('/season', adminLoggedIn, function(req, res, next) { From 54978d3c35dba2a22fd2403df5d236342aca46ca Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Thu, 25 Nov 2021 19:46:29 -0700 Subject: [PATCH 132/169] Add manage account button for non-admin manage page --- public/stylesheets/style.css | 5 +++++ views/manage/manage-nonadmin.pug | 5 ++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/public/stylesheets/style.css b/public/stylesheets/style.css index ec6953e..06b919e 100644 --- a/public/stylesheets/style.css +++ b/public/stylesheets/style.css @@ -17,4 +17,9 @@ a { .flat-content { flex-direction: row; + justify-content: space-between; +} + +.send-to-right { + margin-left: auto; } \ No newline at end of file diff --git a/views/manage/manage-nonadmin.pug b/views/manage/manage-nonadmin.pug index f3bb4e6..4f9df3a 100644 --- a/views/manage/manage-nonadmin.pug +++ b/views/manage/manage-nonadmin.pug @@ -7,10 +7,13 @@ block stylesheets block content div#mobile-view h1 #{title} + span(class="flat-content") + button#add-new-button Add new... + button#manage-account-button(class="send-to-right") Manage account... + div h2#table-header table#games-list - button#add-new-button Add new... block scripts script(src='/scripts/manage/manage-nonadmin.js' type="module") \ No newline at end of file From e277107b10fe0d01f72686ff876c044d26044f0a Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Fri, 26 Nov 2021 12:08:45 -0700 Subject: [PATCH 133/169] Add ability for non-admin users to manage their account --- public/scripts/manage/account.js | 16 ++++++++-- public/scripts/manage/manage-nonadmin.js | 7 ++++- public/stylesheets/submit.css | 4 +++ routes/data.js | 27 +++++++++++++--- routes/manage.js | 39 +++++++++++++++++++----- views/accounts/createuser.pug | 4 ++- 6 files changed, 79 insertions(+), 18 deletions(-) diff --git a/public/scripts/manage/account.js b/public/scripts/manage/account.js index 0ab23a3..40b252f 100644 --- a/public/scripts/manage/account.js +++ b/public/scripts/manage/account.js @@ -4,13 +4,14 @@ import * as Form from "../form.js"; const submissionForm = document.getElementById('submission-form'); const emailTextbox = document.getElementById('email-textbox'); const passwordTextbox = document.getElementById('password-textbox'); +const adminCheckboxSection = document.getElementById('admin-checkbox-section'); const adminCheckbox = document.getElementById('admin-checkbox'); const submitButton = document.getElementById('submit-button'); const deleteButton = document.getElementById('delete-button'); async function Initialize() { let params = new URLSearchParams(location.search); - let accountID = params.get('account'); + let accountID = params.get('account') || (document.getElementById('account-id') ? document.getElementById('account-id').value : null); if(accountID) { const account = await Data.getAccount(accountID); console.log(account); @@ -21,16 +22,25 @@ async function Initialize() { adminCheckbox.checked = account.isAdmin; - Form.addHiddenValue('account', accountID, submissionForm); + if(!document.getElementById('account-id')) { + adminCheckboxSection.style.visibility = "visible"; + adminCheckbox.disabled = false; + + Form.addHiddenValue('account', accountID, submissionForm); + } deleteButton.style.visibility = "visible"; deleteButton.disabled = false; } + else + { + adminCheckboxSection.style.visibility = "visible"; + adminCheckbox.disabled = false; + } emailTextbox.disabled = false; emailTextbox.addEventListener('keyup', checkDataValidity); passwordTextbox.disabled = false; passwordTextbox.addEventListener('keyup', checkDataValidity); - adminCheckbox.disabled = false; checkDataValidity(); } Initialize(); diff --git a/public/scripts/manage/manage-nonadmin.js b/public/scripts/manage/manage-nonadmin.js index d549030..86782aa 100644 --- a/public/scripts/manage/manage-nonadmin.js +++ b/public/scripts/manage/manage-nonadmin.js @@ -2,6 +2,8 @@ import * as Data from "./../data.js"; const gamesListTable = document.getElementById('games-list'); const addNewButton = document.getElementById('add-new-button'); +const manageAccountButton = document.getElementById('manage-account-button'); + function getGenderLetter(genderName) { @@ -124,4 +126,7 @@ async function listItems() { } listItems(); -addNewButton.addEventListener('click', () => addGame()); \ No newline at end of file +addNewButton.addEventListener('click', () => addGame()); +manageAccountButton.addEventListener('click', () => { + window.location.href = '/manage/account'; +}); \ No newline at end of file diff --git a/public/stylesheets/submit.css b/public/stylesheets/submit.css index af4e239..30e64a5 100644 --- a/public/stylesheets/submit.css +++ b/public/stylesheets/submit.css @@ -1,3 +1,7 @@ h1 { text-align: center; +} + +#admin-checkbox-section { + visibility: hidden; } \ No newline at end of file diff --git a/routes/data.js b/routes/data.js index 3d71892..5981784 100644 --- a/routes/data.js +++ b/routes/data.js @@ -10,13 +10,22 @@ var accounts = require('../database/accounts/accounts'); function adminLoggedIn(req, res, next) { if (req.user && req.user[2]) { + next(); + } + else { + req.flash('error', 'An admin account is required to access this page.'); + res.redirect('/auth/login'); + } +} + +function userLoggedIn(req, res, next) { + if (req.user) { next(); } else { - req.flash('error', 'An admin account is required to access this page.'); res.redirect('/auth/login'); } - } +} router.get('/sports', function(req, res, next) { sports.retrieveAll() @@ -77,9 +86,17 @@ router.get('/accounts', adminLoggedIn, function(req, res, next) { .then(data => res.json(data)); }) -router.get('/account', adminLoggedIn, function(req, res, next) { - accounts.getFromID(req.query.account) - .then(data => res.json(data)); +router.get('/account', userLoggedIn, function(req, res, next) { + const userIsAdmin = req.user[2]; + const loggedInAccountID = req.user[0]; + const requestedAccountID = req.query.account; + + if(!userIsAdmin && loggedInAccountID != requestedAccountID) { + res.status(403).send("ACCESS DENIED"); + } else { + accounts.getFromID(req.query.account) + .then(data => res.json(data)); + } }) module.exports = router; \ No newline at end of file diff --git a/routes/manage.js b/routes/manage.js index 0a9c16e..73bdfeb 100644 --- a/routes/manage.js +++ b/routes/manage.js @@ -149,23 +149,46 @@ router.post('/team', adminLoggedIn, function(req, res, next) { else teams.add(name, sport).then(res.redirect("/manage")); }); -router.get('/account', adminLoggedIn, (req, res, next) => { - let title = req.query.account ? 'Manage User' : 'Create User' +router.get('/account', userLoggedIn, (req, res, next) => { + const userIsAdmin = req.user[2]; + const accountID = req.user[0]; + + if(userIsAdmin) { + let title = req.query.account ? 'Manage User' : 'Create User' - res.render('accounts/createuser', { title }); + res.render('accounts/createuser', { title }); + } + else { + let title = 'Manage Account'; + + res.render('accounts/createuser', { title, accountID }); + } }); -router.post('/account', adminLoggedIn, (req, res, next) => { +router.post('/account', userLoggedIn, (req, res, next) => { const email = req.body.email; const password = req.body.password; - const isAdmin = !!req.body.admin; const accountID = req.body.account; const remove = req.body.remove; - if(remove) accounts.remove(accountID).then(res.redirect('/manage')); - if(accountID) accounts.edit(accountID, email, password, isAdmin).then(res.redirect('/manage')); - else accounts.create(req.body.email, req.body.password, !!req.body.admin).then(res.redirect('/manage')); + const loggedInAccountIsAdmin = req.user[2]; + const loggedInAccountID = req.user[0]; + + console.log(accountID); + console.log(loggedInAccountID); + + + if(!loggedInAccountIsAdmin && accountID != loggedInAccountID) { + res.status(403).send("ACCESS DENIED"); + } + else { + const isAdmin = loggedInAccountIsAdmin ? !!req.body.admin : false; + + if(remove) accounts.remove(accountID).then(res.redirect('/manage')); + if(accountID) accounts.edit(accountID, email, password, isAdmin).then(res.redirect('/manage')); + else accounts.create(req.body.email, req.body.password, !!req.body.admin).then(res.redirect('/manage')); + } }); module.exports = router; diff --git a/views/accounts/createuser.pug b/views/accounts/createuser.pug index 36e0acb..94a968b 100644 --- a/views/accounts/createuser.pug +++ b/views/accounts/createuser.pug @@ -8,6 +8,8 @@ block content div#mobile-view h1 #{title} form#submission-form(action='/manage/account', method='POST') + if accountID + input#account-id(type="hidden" name="account" value=accountID) span(class='form-section') label Email span(class='form-section-input') @@ -16,7 +18,7 @@ block content label Password span(class='form-section-input' ) input#password-textbox(type="password" name="password" disabled) - span(class='form-section') + span#admin-checkbox-section(class='form-section') span(class='form-section-checkbox') input#admin-checkbox(type="checkbox" name="admin" disabled) label(for="admin-checkbox") Grant admin privileges From 872397d05ed09b0a641d3aa07f5007bad5133ce1 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Fri, 26 Nov 2021 12:37:09 -0700 Subject: [PATCH 134/169] Add login/logout buttons --- public/scripts/main.js | 14 ++++++++++++++ public/stylesheets/index.css | 5 ----- public/stylesheets/style.css | 5 +++++ routes/index.js | 2 +- routes/manage.js | 18 +++++++++--------- views/accounts/createuser.pug | 2 -- views/accounts/login.pug | 26 ++++++++++++-------------- views/index.pug | 12 ++++++------ views/layout.pug | 13 +++++++++++-- views/manage.pug | 2 -- views/manage/adddivision.pug | 4 +--- views/manage/addgame.pug | 4 +--- views/manage/addseason.pug | 4 +--- views/manage/addsport.pug | 4 +--- views/manage/addteam.pug | 4 +--- views/manage/manage-nonadmin.pug | 4 +--- 16 files changed, 64 insertions(+), 59 deletions(-) create mode 100644 public/scripts/main.js diff --git a/public/scripts/main.js b/public/scripts/main.js new file mode 100644 index 0000000..ced1e07 --- /dev/null +++ b/public/scripts/main.js @@ -0,0 +1,14 @@ +const logInButton = document.getElementById('login-button'); +const logOutButton = document.getElementById('logout-button'); + +if(logInButton) { + logInButton.addEventListener('click', () => { + window.location.href = "/auth/login"; + }); +} + +if(logOutButton) { + logOutButton.addEventListener('click', () => { + window.location.href = "/auth/logout"; + }); +} \ No newline at end of file diff --git a/public/stylesheets/index.css b/public/stylesheets/index.css index 7a60f19..9bf14cb 100644 --- a/public/stylesheets/index.css +++ b/public/stylesheets/index.css @@ -25,8 +25,3 @@ tr { flex-direction: row; } -#actions-div { - display: flex; - flex-direction: column; - margin-left: auto; -} \ No newline at end of file diff --git a/public/stylesheets/style.css b/public/stylesheets/style.css index 06b919e..5e83f29 100644 --- a/public/stylesheets/style.css +++ b/public/stylesheets/style.css @@ -22,4 +22,9 @@ a { .send-to-right { margin-left: auto; +} + +#actions-div { + display: flex; + margin-left: auto; } \ No newline at end of file diff --git a/routes/index.js b/routes/index.js index 3c536d2..47fe5d4 100644 --- a/routes/index.js +++ b/routes/index.js @@ -3,7 +3,7 @@ var router = express.Router(); /* GET home page. */ router.get('/', function(req, res, next) { - res.render('index', { title: 'View Scores' }); + res.render('index', { title: 'View Scores', userLoggedIn: !!req.user }); }); module.exports = router; diff --git a/routes/manage.js b/routes/manage.js index 73bdfeb..b04cf75 100644 --- a/routes/manage.js +++ b/routes/manage.js @@ -30,14 +30,14 @@ function adminLoggedIn(req, res, next) { router.get('/' ,userLoggedIn, function(req, res, next) { - if(req.user[2]) res.render('manage', { title: 'Score Management' }); - else res.render('manage/manage-nonadmin', { title: "My Games" }); + if(req.user[2]) res.render('manage', { title: 'Score Management', userLoggedIn: !!req.user }); + else res.render('manage/manage-nonadmin', { title: "My Games", userLoggedIn: !!req.user }); }); router.get('/game', userLoggedIn, function(req, res, next) { let title = req.query.game ? 'Edit Game' : 'Submit Score' - res.render('manage/addgame', { title }); + res.render('manage/addgame', { title, userLoggedIn: !!req.user }); }); router.post('/game', userLoggedIn, function(req, res, next) { @@ -73,7 +73,7 @@ router.post('/game', userLoggedIn, function(req, res, next) { }); router.get('/season', adminLoggedIn, function(req, res, next) { - res.render('manage/addseason', { title: 'Add Season', currentYear : (new Date()).getFullYear() }); + res.render('manage/addseason', { title: 'Add Season', currentYear : (new Date()).getFullYear(), userLoggedIn: !!req.user }); }); router.post('/season', adminLoggedIn, function(req, res, next) { @@ -87,7 +87,7 @@ router.post('/season', adminLoggedIn, function(req, res, next) { }); router.get('/sport', adminLoggedIn, function(req, res, next) { - res.render('manage/addsport', { title: 'Add Sport' }); + res.render('manage/addsport', { title: 'Add Sport', userLoggedIn: !!req.user }); }); router.post('/sport', adminLoggedIn, function(req, res, next) { @@ -103,7 +103,7 @@ router.post('/sport', adminLoggedIn, function(req, res, next) { router.get('/division', adminLoggedIn, function(req, res, next) { let title = req.query.division ? 'Edit Division' : 'Add Division' - res.render('manage/adddivision', { title }); + res.render('manage/adddivision', { title, userLoggedIn: !!req.user }); }); router.post('/division', adminLoggedIn, function(req, res, next) { @@ -134,7 +134,7 @@ router.post('/division', adminLoggedIn, function(req, res, next) { router.get('/team', adminLoggedIn, function(req, res, next) { let title = req.query.team ? 'Edit Team' : 'Add Team' - res.render('manage/addteam', { title }); + res.render('manage/addteam', { title, userLoggedIn: !!req.user }); }); router.post('/team', adminLoggedIn, function(req, res, next) { @@ -156,12 +156,12 @@ router.get('/account', userLoggedIn, (req, res, next) => { if(userIsAdmin) { let title = req.query.account ? 'Manage User' : 'Create User' - res.render('accounts/createuser', { title }); + res.render('accounts/createuser', { title, userLoggedIn: !!req.user }); } else { let title = 'Manage Account'; - res.render('accounts/createuser', { title, accountID }); + res.render('accounts/createuser', { title, accountID, userLoggedIn: !!req.user }); } }); diff --git a/views/accounts/createuser.pug b/views/accounts/createuser.pug index 94a968b..a87897d 100644 --- a/views/accounts/createuser.pug +++ b/views/accounts/createuser.pug @@ -5,8 +5,6 @@ block stylesheets link(rel='stylesheet', href='/stylesheets/form.css') block content - div#mobile-view - h1 #{title} form#submission-form(action='/manage/account', method='POST') if accountID input#account-id(type="hidden" name="account" value=accountID) diff --git a/views/accounts/login.pug b/views/accounts/login.pug index 13f707e..cbc9044 100644 --- a/views/accounts/login.pug +++ b/views/accounts/login.pug @@ -5,17 +5,15 @@ block stylesheets link(rel='stylesheet', href='/stylesheets/form.css') block content - div#mobile-view - h1 #{title} - form(action='/auth/login', method='POST') - span(class='form-section') - label Email - span(class='form-section-input') - input(type="email", name="email") - span(class='form-section') - label Password - span(class='form-section-input') - input(type="password", name="password") - .error #{message} - span(class='form-section') - button#submit-button(type="submit") Submit \ No newline at end of file + form(action='/auth/login', method='POST') + span(class='form-section') + label Email + span(class='form-section-input') + input(type="email", name="email") + span(class='form-section') + label Password + span(class='form-section-input') + input(type="password", name="password") + .error #{message} + span(class='form-section') + button#submit-button(type="submit") Submit \ No newline at end of file diff --git a/views/index.pug b/views/index.pug index 674d874..3deed78 100644 --- a/views/index.pug +++ b/views/index.pug @@ -4,13 +4,13 @@ block stylesheets link(rel='stylesheet', href='/stylesheets/index.css') link(rel='stylesheet', href='/stylesheets/form.css') +block actions + if userLoggedIn + button#add-score-button Submit score + button#manage-button Manage scores + + block content - div#mobile-view - div#header-div - h1 Score Tracker - div#actions-div - button#add-score-button + - button#manage-button Manage div span(class='form-section') label Year diff --git a/views/layout.pug b/views/layout.pug index e4f85eb..52ad62f 100644 --- a/views/layout.pug +++ b/views/layout.pug @@ -1,10 +1,19 @@ doctype html html head - title= title + title= title + ' - Score Tracker' meta(name='viewport', content='width=device-width, initial-scale=1') link(rel='stylesheet', href='/stylesheets/style.css') block stylesheets body - block content + div#mobile-view + div#actions-div + block actions + if userLoggedIn + button#logout-button Log out + else if userLoggedIn !== undefined + button#login-button Log in + h1 #{title} + block content block scripts + script(src='/scripts/main.js' type="module") diff --git a/views/manage.pug b/views/manage.pug index 454d1ed..63d76ec 100644 --- a/views/manage.pug +++ b/views/manage.pug @@ -5,8 +5,6 @@ block stylesheets link(rel='stylesheet', href='/stylesheets/form.css') block content - div#mobile-view - h1 Management Panel div span(class='form-section') label Category diff --git a/views/manage/adddivision.pug b/views/manage/adddivision.pug index 5db737c..10e7281 100644 --- a/views/manage/adddivision.pug +++ b/views/manage/adddivision.pug @@ -1,12 +1,10 @@ -extends layout +extends ../layout block stylesheets link(rel='stylesheet', href='/stylesheets/submit.css') link(rel='stylesheet', href='/stylesheets/form.css') block content - div#mobile-view - h1 #{title} form#submission-form(action='./division', method='POST') span(class='form-section') label Sport diff --git a/views/manage/addgame.pug b/views/manage/addgame.pug index 7981148..61e7aaa 100644 --- a/views/manage/addgame.pug +++ b/views/manage/addgame.pug @@ -1,12 +1,10 @@ -extends layout +extends ../layout block stylesheets link(rel='stylesheet', href='/stylesheets/submit.css') link(rel='stylesheet', href='/stylesheets/form.css') block content - div#mobile-view - h1 #{title} form#submission-form(action='./game', method='POST') span(class='form-section') label Year diff --git a/views/manage/addseason.pug b/views/manage/addseason.pug index bdb13fd..4050cb2 100644 --- a/views/manage/addseason.pug +++ b/views/manage/addseason.pug @@ -1,12 +1,10 @@ -extends layout +extends ../layout block stylesheets link(rel='stylesheet', href='/stylesheets/submit.css') link(rel='stylesheet', href='/stylesheets/form.css') block content - div#mobile-view - h1 #{title} form(action='./season', method='POST') span(class='form-section') label Ending year diff --git a/views/manage/addsport.pug b/views/manage/addsport.pug index 5251b7e..067b9ac 100644 --- a/views/manage/addsport.pug +++ b/views/manage/addsport.pug @@ -1,12 +1,10 @@ -extends layout +extends ../layout block stylesheets link(rel='stylesheet', href='/stylesheets/submit.css') link(rel='stylesheet', href='/stylesheets/form.css') block content - div#mobile-view - h1#main-header Add Sport form#submission-form(action='./sport', method='POST') span(class='form-section') label Sport name diff --git a/views/manage/addteam.pug b/views/manage/addteam.pug index 3cb6ccb..9f7262c 100644 --- a/views/manage/addteam.pug +++ b/views/manage/addteam.pug @@ -1,12 +1,10 @@ -extends layout +extends ../layout block stylesheets link(rel='stylesheet', href='/stylesheets/submit.css') link(rel='stylesheet', href='/stylesheets/form.css') block content - div#mobile-view - h1 #{title} form#submission-form(action='./team', method='POST') span(class='form-section') label Sport diff --git a/views/manage/manage-nonadmin.pug b/views/manage/manage-nonadmin.pug index 4f9df3a..b639b1a 100644 --- a/views/manage/manage-nonadmin.pug +++ b/views/manage/manage-nonadmin.pug @@ -1,12 +1,10 @@ -extends layout +extends ../layout block stylesheets link(rel='stylesheet', href='/stylesheets/manage.css') link(rel='stylesheet', href='/stylesheets/form.css') block content - div#mobile-view - h1 #{title} span(class="flat-content") button#add-new-button Add new... button#manage-account-button(class="send-to-right") Manage account... From 3201ffb2a9509c52ee536b6421e17b939e78adc3 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Fri, 26 Nov 2021 12:46:16 -0700 Subject: [PATCH 135/169] Add "Home" button for navigation --- public/scripts/main.js | 7 +++++++ public/stylesheets/index.css | 3 +++ public/stylesheets/style.css | 5 ++++- routes/index.js | 2 +- views/layout.pug | 2 ++ 5 files changed, 17 insertions(+), 2 deletions(-) diff --git a/public/scripts/main.js b/public/scripts/main.js index ced1e07..aac4daf 100644 --- a/public/scripts/main.js +++ b/public/scripts/main.js @@ -1,5 +1,6 @@ const logInButton = document.getElementById('login-button'); const logOutButton = document.getElementById('logout-button'); +const homeButton = document.getElementById('home-button'); if(logInButton) { logInButton.addEventListener('click', () => { @@ -11,4 +12,10 @@ if(logOutButton) { logOutButton.addEventListener('click', () => { window.location.href = "/auth/logout"; }); +} + +if(homeButton) { + homeButton.addEventListener('click', () => { + window.location.href = '/'; + }); } \ No newline at end of file diff --git a/public/stylesheets/index.css b/public/stylesheets/index.css index 9bf14cb..7e66419 100644 --- a/public/stylesheets/index.css +++ b/public/stylesheets/index.css @@ -25,3 +25,6 @@ tr { flex-direction: row; } +#actions-div { + margin-left: auto; +} \ No newline at end of file diff --git a/public/stylesheets/style.css b/public/stylesheets/style.css index 5e83f29..4582e1e 100644 --- a/public/stylesheets/style.css +++ b/public/stylesheets/style.css @@ -26,5 +26,8 @@ a { #actions-div { display: flex; - margin-left: auto; +} + +#home-button { + margin-right: auto; } \ No newline at end of file diff --git a/routes/index.js b/routes/index.js index 47fe5d4..42fa2b0 100644 --- a/routes/index.js +++ b/routes/index.js @@ -3,7 +3,7 @@ var router = express.Router(); /* GET home page. */ router.get('/', function(req, res, next) { - res.render('index', { title: 'View Scores', userLoggedIn: !!req.user }); + res.render('index', { title: 'View Scores', userLoggedIn: !!req.user, hideHomeButton: true }); }); module.exports = router; diff --git a/views/layout.pug b/views/layout.pug index 52ad62f..11fb12b 100644 --- a/views/layout.pug +++ b/views/layout.pug @@ -8,6 +8,8 @@ html body div#mobile-view div#actions-div + if !hideHomeButton + button#home-button Home block actions if userLoggedIn button#logout-button Log out From bb335c7acac32d1b99d6faaa4ef131f7b325fdc9 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Fri, 26 Nov 2021 12:53:54 -0700 Subject: [PATCH 136/169] Fix bug where games don't load if user is not logged in --- public/scripts/index.js | 17 ++++++++++------- routes/data.js | 2 +- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/public/scripts/index.js b/public/scripts/index.js index 843ed4b..0beced6 100644 --- a/public/scripts/index.js +++ b/public/scripts/index.js @@ -177,11 +177,14 @@ genderDropdown.onchange = listDivisions; teamDropdown.onchange = listGames; seasonDropdown.onchange = listGames; +if(addScoreButton) { + addScoreButton.addEventListener('click', () => { + window.location.href = '/manage/game'; + }); +} -addScoreButton.addEventListener('click', () => { - window.location.href = '/manage/game'; -}); - -manageButton.addEventListener('click', () => { - window.location.href = '/manage' -}); \ No newline at end of file +if(manageButton) { + manageButton.addEventListener('click', () => { + window.location.href = '/manage' + }); +} diff --git a/routes/data.js b/routes/data.js index 5981784..c875622 100644 --- a/routes/data.js +++ b/routes/data.js @@ -71,7 +71,7 @@ router.get('/team', function(req, res, next) { }) router.get('/games', function(req, res, next) { - const userID = req.user[0]; + const userID = req.user ? req.user[0] : null; if(req.query.user) games.retrieveByUser(userID).then(data => res.json(data)); else games.retrieve(req.query.team, req.query.division, req.query.season).then(data => res.json(data)); }) From ad4533e71296f1a5caf114849c797f7f7e3d8d1b Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Fri, 26 Nov 2021 12:59:48 -0700 Subject: [PATCH 137/169] Adjust layout for non-admin management panel --- views/index.pug | 2 +- views/manage/manage-nonadmin.pug | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/views/index.pug b/views/index.pug index 3deed78..b504698 100644 --- a/views/index.pug +++ b/views/index.pug @@ -7,7 +7,7 @@ block stylesheets block actions if userLoggedIn button#add-score-button Submit score - button#manage-button Manage scores + button#manage-button Manage... block content diff --git a/views/manage/manage-nonadmin.pug b/views/manage/manage-nonadmin.pug index b639b1a..890c739 100644 --- a/views/manage/manage-nonadmin.pug +++ b/views/manage/manage-nonadmin.pug @@ -4,14 +4,14 @@ block stylesheets link(rel='stylesheet', href='/stylesheets/manage.css') link(rel='stylesheet', href='/stylesheets/form.css') -block content - span(class="flat-content") - button#add-new-button Add new... - button#manage-account-button(class="send-to-right") Manage account... +block actions + button#manage-account-button(class="send-to-right") Manage account +block content div - h2#table-header table#games-list + div + button#add-new-button Add new... block scripts script(src='/scripts/manage/manage-nonadmin.js' type="module") \ No newline at end of file From 6c221832fc183d1bb08f53434a9c860ac365c1e7 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Fri, 26 Nov 2021 13:07:01 -0700 Subject: [PATCH 138/169] Add 0.25em padding for buttons, dropdowns, and inputs --- public/stylesheets/style.css | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/public/stylesheets/style.css b/public/stylesheets/style.css index 4582e1e..2f0a789 100644 --- a/public/stylesheets/style.css +++ b/public/stylesheets/style.css @@ -30,4 +30,16 @@ a { #home-button { margin-right: auto; +} + +button { + padding: 0.25em; +} + +select { + padding: 0.25em; +} + +input { + padding: 0.25em; } \ No newline at end of file From 3e7e9eeb70eabda18c3ffbff746bf422bd54036d Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Fri, 26 Nov 2021 13:16:24 -0700 Subject: [PATCH 139/169] Add margins for buttons --- public/stylesheets/style.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/public/stylesheets/style.css b/public/stylesheets/style.css index 2f0a789..5ba6ad5 100644 --- a/public/stylesheets/style.css +++ b/public/stylesheets/style.css @@ -34,6 +34,7 @@ a { button { padding: 0.25em; + margin: 0em 0.1em; } select { @@ -42,4 +43,8 @@ select { input { padding: 0.25em; +} + +#logout-button { + margin-left: 0.5em; } \ No newline at end of file From e6c4d2347f9464b50b6bd4e65b3c867e64135edd Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Fri, 26 Nov 2021 13:17:44 -0700 Subject: [PATCH 140/169] Move submit score button to top left of page --- public/stylesheets/index.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/stylesheets/index.css b/public/stylesheets/index.css index 7e66419..ffcfa14 100644 --- a/public/stylesheets/index.css +++ b/public/stylesheets/index.css @@ -25,6 +25,6 @@ tr { flex-direction: row; } -#actions-div { - margin-left: auto; +#add-score-button { + margin-right: auto; } \ No newline at end of file From 24a93104e428e41e887b19e0047df848812fc98c Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Fri, 26 Nov 2021 13:19:20 -0700 Subject: [PATCH 141/169] Set redirect to root when game is added --- routes/manage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/routes/manage.js b/routes/manage.js index b04cf75..227c27d 100644 --- a/routes/manage.js +++ b/routes/manage.js @@ -68,7 +68,7 @@ router.post('/game', userLoggedIn, function(req, res, next) { else if(id) games.edit(id, divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score) .then(res.redirect('/manage')); else games.add(divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score, userID) - .then(res.redirect("/manage")); + .then(res.redirect("/")); }); }); From cceda089c01c6680b196ca7e53aad2f798862271 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Fri, 26 Nov 2021 13:20:14 -0700 Subject: [PATCH 142/169] Move login button to top right of page on index --- public/stylesheets/index.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/public/stylesheets/index.css b/public/stylesheets/index.css index ffcfa14..d08e1e8 100644 --- a/public/stylesheets/index.css +++ b/public/stylesheets/index.css @@ -27,4 +27,8 @@ tr { #add-score-button { margin-right: auto; +} + +#login-button { + margin-left: auto; } \ No newline at end of file From b1637d97bc036d94b3e46bdb7a4210fba273c158 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Fri, 26 Nov 2021 13:40:16 -0700 Subject: [PATCH 143/169] Add data validity check for adding game --- public/scripts/manage/game.js | 84 ++++++++++++++++++++++------------- 1 file changed, 52 insertions(+), 32 deletions(-) diff --git a/public/scripts/manage/game.js b/public/scripts/manage/game.js index 8338d4c..ce80b97 100644 --- a/public/scripts/manage/game.js +++ b/public/scripts/manage/game.js @@ -28,33 +28,25 @@ async function initializeForm() { Form.addHiddenValue('game', gameID, submissionForm); Form.populateSeasons(seasonDropdown, game.seasonID); - Data.getDivision(game.divisionID) - .then(data => { - Form.populateSports(sportDropdown, data.sportID) - .then(() => { - Form.populateGenders(genderDropdown, sportDropdown.value, data.gender.name) - .then(() => { - Form.populateDivisions(divisionDropdown, sportDropdown.value, genderDropdown.value, game.divisionID); - }); - Form.populateTeams(team1Dropdown, sportDropdown.value, game.team1ID); - Form.populateTeams(team2Dropdown, sportDropdown.value, game.team2ID); - }); - }); + const data = await Data.getDivision(game.divisionID) + await Form.populateSports(sportDropdown, data.sportID) + await Form.populateGenders(genderDropdown, sportDropdown.value, data.gender.name) + await Form.populateDivisions(divisionDropdown, sportDropdown.value, genderDropdown.value, game.divisionID); + await Form.populateTeams(team1Dropdown, sportDropdown.value, game.team1ID); + await Form.populateTeams(team2Dropdown, sportDropdown.value, game.team2ID); + dateInput.value = game.date; team1ScoreTextbox.value = game.team1Score; team2ScoreTextbox.value = game.team2Score; } else { - Form.populateSeasons(seasonDropdown); - Form.populateSports(sportDropdown) - .then(() => { - Form.populateGenders(genderDropdown, sportDropdown.value) - .then(() => { - Form.populateDivisions(divisionDropdown, sportDropdown.value, genderDropdown.value); - }); - Form.populateTeams(team1Dropdown, sportDropdown.value); - Form.populateTeams(team2Dropdown, sportDropdown.value); - }); + await Form.populateSeasons(seasonDropdown); + await Form.populateSports(sportDropdown) + await Form.populateGenders(genderDropdown, sportDropdown.value) + await Form.populateDivisions(divisionDropdown, sportDropdown.value, genderDropdown.value); + await Form.populateTeams(team1Dropdown, sportDropdown.value); + await Form.populateTeams(team2Dropdown, sportDropdown.value); + dateInput.value = (new Date()).toISOString().slice(0,10); } seasonDropdown.disabled = false; @@ -66,21 +58,49 @@ async function initializeForm() { team2Dropdown.disabled = false; team1ScoreTextbox.disabled = false; team2ScoreTextbox.disabled = false; - submitButton.disabled = false; - sportDropdown.onchange = () => { - Form.populateGenders(genderDropdown, sportDropdown.value) - .then(() => { - Form.populateDivisions(divisionDropdown, sportDropdown.value, genderDropdown.value); - }); - Form.populateTeams(team1Dropdown, sportDropdown.value); - Form.populateTeams(team2Dropdown, sportDropdown.value); + sportDropdown.onchange = async () => { + await Form.populateGenders(genderDropdown, sportDropdown.value) + await Form.populateDivisions(divisionDropdown, sportDropdown.value, genderDropdown.value); + await Form.populateTeams(team1Dropdown, sportDropdown.value); + await Form.populateTeams(team2Dropdown, sportDropdown.value); + checkDataValidity(); }; - genderDropdown.onchange = () => { - Form.populateDivisions(divisionDropdown, sportDropdown.value, genderDropdown.value); + genderDropdown.onchange = async () => { + await Form.populateDivisions(divisionDropdown, sportDropdown.value, genderDropdown.value); + checkDataValidity(); }; + + dateInput.addEventListener('keyup', checkDataValidity); + team1Dropdown.onchange = checkDataValidity; + team1ScoreTextbox.addEventListener('keyup', checkDataValidity); + team2Dropdown.onchange = checkDataValidity; + team2ScoreTextbox.addEventListener('keyup', checkDataValidity); + + checkDataValidity(); } initializeForm(); +async function checkDataValidity() { + let dataIsValid = true; + + const seasonHasContent = seasonDropdown.options.length; + const sportHasContent = sportDropdown.options.length; + const genderHasContent = genderDropdown.options.length; + const divisionHasContent = divisionDropdown.options.length; + const team1HasContent = team1Dropdown.options.length; + const team2HasContent = team2Dropdown.options.length; + + if(!seasonHasContent || !sportHasContent || !genderHasContent || !divisionHasContent || !team1HasContent || !team2HasContent) dataIsValid = false; + + if(team1Dropdown.selectedIndex == team2Dropdown.selectedIndex) dataIsValid = false; + + if(team1ScoreTextbox.value == "" || team2ScoreTextbox.value == "") dataIsValid = false; + + if(dateInput.value == "") dataIsValid = false; + + submitButton.disabled = !dataIsValid; +} + Form.addRemoveFunction(deleteButton, submissionForm, "game"); From 64a6ba545b123d46d43731ac1eba5fb02b9cda60 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Fri, 26 Nov 2021 13:44:01 -0700 Subject: [PATCH 144/169] Adjust error page layout --- public/stylesheets/error.css | 3 +++ views/error.pug | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 public/stylesheets/error.css diff --git a/public/stylesheets/error.css b/public/stylesheets/error.css new file mode 100644 index 0000000..f1ac617 --- /dev/null +++ b/public/stylesheets/error.css @@ -0,0 +1,3 @@ +#mobile-view { + max-width: 100%; +} \ No newline at end of file diff --git a/views/error.pug b/views/error.pug index 51ec12c..43a7357 100644 --- a/views/error.pug +++ b/views/error.pug @@ -1,6 +1,9 @@ extends layout +block stylesheets + link(rel='stylesheet', href='/stylesheets/error.css') + block content h1= message h2= error.status - pre #{error.stack} + pre #{error.stack} \ No newline at end of file From 2fc17c0c4253f7b3baced28ed6ed7a491719aa48 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Fri, 26 Nov 2021 13:52:57 -0700 Subject: [PATCH 145/169] Add labels for gender and division for index page --- public/stylesheets/form.css | 4 ++++ views/index.pug | 19 +++++++++++++------ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/public/stylesheets/form.css b/public/stylesheets/form.css index 389028c..9859f8c 100644 --- a/public/stylesheets/form.css +++ b/public/stylesheets/form.css @@ -44,4 +44,8 @@ form { #admin-checkbox { width: auto; + } + + #sport-gender-division { + flex-direction: row; } \ No newline at end of file diff --git a/views/index.pug b/views/index.pug index b504698..eec3efa 100644 --- a/views/index.pug +++ b/views/index.pug @@ -16,12 +16,19 @@ block content label Year span(class='form-section-input') select#year-dropdown(name="year" class="form-main-dropdown") - span(class='form-section') - label Sport - span(class='form-section-input') - select#sport-dropdown(name="sport" class="form-main-dropdown") - select#gender-dropdown(name="gender") - select#division-dropdown(name="division") + span#sport-gender-division(class='form-section') + span + label Sport + span(class='form-section-input') + select#sport-dropdown(name="sport" class="form-main-dropdown") + span + label Gender + span(class='form-section-input') + select#gender-dropdown(name="gender") + span + label Division + span(class='form-section-input') + select#division-dropdown(name="division") span(class='form-section') label Team span(class='form-section-input') From 150a186aeab45fe582888f3da129bef2f003811f Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Fri, 26 Nov 2021 13:59:17 -0700 Subject: [PATCH 146/169] Add labels to game submit page --- public/stylesheets/form.css | 2 +- views/index.pug | 2 +- views/manage/addgame.pug | 47 ++++++++++++++++++++++++------------- 3 files changed, 33 insertions(+), 18 deletions(-) diff --git a/public/stylesheets/form.css b/public/stylesheets/form.css index 9859f8c..43fee2e 100644 --- a/public/stylesheets/form.css +++ b/public/stylesheets/form.css @@ -46,6 +46,6 @@ form { width: auto; } - #sport-gender-division { + .flat-form-section { flex-direction: row; } \ No newline at end of file diff --git a/views/index.pug b/views/index.pug index eec3efa..a1b7d2d 100644 --- a/views/index.pug +++ b/views/index.pug @@ -16,7 +16,7 @@ block content label Year span(class='form-section-input') select#year-dropdown(name="year" class="form-main-dropdown") - span#sport-gender-division(class='form-section') + span(class='form-section flat-form-section') span label Sport span(class='form-section-input') diff --git a/views/manage/addgame.pug b/views/manage/addgame.pug index 61e7aaa..61e8f61 100644 --- a/views/manage/addgame.pug +++ b/views/manage/addgame.pug @@ -10,26 +10,41 @@ block content label Year span(class='form-section-input') select#year-dropdown(name="year" class="form-main-dropdown" disabled) - span(class='form-section') - label Sport - span(class='form-section-input') - select#sport-dropdown(name="sport" class="form-main-dropdown" disabled) - select#gender-dropdown(name="gender" disabled) - select#division-dropdown(name="division" disabled) + span(class='form-section flat-form-section') + span + label Sport + span(class='form-section-input') + select#sport-dropdown(name="sport" class="form-main-dropdown") + span + label Gender + span(class='form-section-input') + select#gender-dropdown(name="gender") + span + label Division + span(class='form-section-input') + select#division-dropdown(name="division") span(class='form-section') label Date of match span(class='form-section-input') input#date-input(type="date", name="date" value=date disabled) - span(class='form-section') - label Your team - span(class='form-section-input') - select#team1-dropdown(name="team1" class="form-main-dropdown" disabled) - input#team1-score-textbox(class="form-score-input", type="number", name="team1-score", value="0" disabled) - span(class='form-section') - label Opponent - span(class='form-section-input') - select#team2-dropdown(name="team2" class="form-main-dropdown" disabled) - input#team2-score-textbox(class="form-score-input", type="number", name="team2-score", value="0" disabled) + span(class='form-section flat-form-section') + span + label Your team + span(class='form-section-input') + select#team1-dropdown(name="team1" class="form-main-dropdown" disabled) + span(class='form-score-input') + label Score + span(class='form-section-input') + input#team1-score-textbox(type="number", name="team1-score", value="0" disabled) + span(class='form-section flat-form-section') + span + label Opponent + span(class='form-section-input') + select#team2-dropdown(name="team2" class="form-main-dropdown" disabled) + span(class='form-score-input') + label Score + span(class='form-section-input') + input#team2-score-textbox(type="number", name="team2-score", value="0" disabled) span(class='form-section') button#submit-button(type="submit" disabled) Submit span(class='form-section') From 644c8fdec33271c44065717167aa2dc53c894b12 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Fri, 26 Nov 2021 14:05:06 -0700 Subject: [PATCH 147/169] Add check validity function for add season page --- public/scripts/manage/season.js | 14 ++++++++++++++ views/manage/addseason.pug | 4 ++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/public/scripts/manage/season.js b/public/scripts/manage/season.js index e69de29..82d0204 100644 --- a/public/scripts/manage/season.js +++ b/public/scripts/manage/season.js @@ -0,0 +1,14 @@ +const seasonTextbox = document.getElementById('season-textbox'); +const submitButton = document.getElementById('submit-button'); + +async function checkDataValidity() { + let dataIsValid = true; + + if(seasonTextbox.value == "") dataIsValid = false; + + submitButton.disabled = !dataIsValid; +} +checkDataValidity(); + +seasonTextbox.addEventListener('keyup', checkDataValidity); +seasonTextbox.addEventListener('change', checkDataValidity); diff --git a/views/manage/addseason.pug b/views/manage/addseason.pug index 4050cb2..27783b5 100644 --- a/views/manage/addseason.pug +++ b/views/manage/addseason.pug @@ -9,9 +9,9 @@ block content span(class='form-section') label Ending year span(class='form-section-input') - input(type="number", name="year", value=currentYear) + input#season-textbox(type="number", name="year", value=currentYear) span(class='form-section') - button#submit-button(type="submit") Submit + button#submit-button(type="submit" disabled) Submit block scripts script(src='/scripts/manage/season.js' type="module") \ No newline at end of file From 000905257ea63374c3bffc52dc285f732910f99f Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Fri, 26 Nov 2021 14:09:00 -0700 Subject: [PATCH 148/169] Improve division page validity check --- public/scripts/manage/division.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/public/scripts/manage/division.js b/public/scripts/manage/division.js index fb623c5..a542a65 100644 --- a/public/scripts/manage/division.js +++ b/public/scripts/manage/division.js @@ -48,6 +48,11 @@ async function checkDataValidity() { if(!nameTextbox.value) dataIsValid = false; + const sportHasContent = sportDropdown.options.length; + const genderHasContent = genderDropdown.options.length; + + if(!sportHasContent || !genderHasContent) dataIsValid = false; + if(dataIsValid) submitButton.disabled = false; else submitButton.disabled = true; From 459b1a4a1ab2828083af3ac9fdfff5ca50acc0cf Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Fri, 26 Nov 2021 14:10:09 -0700 Subject: [PATCH 149/169] Improve add team page validity check --- public/scripts/manage/team.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/public/scripts/manage/team.js b/public/scripts/manage/team.js index 10cd386..4bc643e 100644 --- a/public/scripts/manage/team.js +++ b/public/scripts/manage/team.js @@ -39,9 +39,10 @@ async function checkDataValidity() { if(!nameTextbox.value) dataIsValid = false; + const sportHasContent = sportDropdown.options.length; + if(!sportHasContent) dataIsValid = false; - if(dataIsValid) submitButton.disabled = false; - else submitButton.disabled = true; + submitButton.disabled = !dataIsValid; } Form.addRemoveFunction(deleteButton, submissionForm, "team"); \ No newline at end of file From f2085ec0f2d72b359417a2e09ddd9123e41299c6 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Fri, 26 Nov 2021 14:19:53 -0700 Subject: [PATCH 150/169] Add noscript message --- public/stylesheets/style.css | 7 +++++++ views/layout.pug | 2 ++ 2 files changed, 9 insertions(+) diff --git a/public/stylesheets/style.css b/public/stylesheets/style.css index 5ba6ad5..c4252bd 100644 --- a/public/stylesheets/style.css +++ b/public/stylesheets/style.css @@ -47,4 +47,11 @@ input { #logout-button { margin-left: 0.5em; +} + +#noscript-message { + background-color: lightcoral; + padding: 1em; + margin-bottom: 3em; + border-radius: .25em; } \ No newline at end of file diff --git a/views/layout.pug b/views/layout.pug index 11fb12b..80d0238 100644 --- a/views/layout.pug +++ b/views/layout.pug @@ -7,6 +7,8 @@ html block stylesheets body div#mobile-view + noscript + span#noscript-message Please enable JavaScript to run this app. div#actions-div if !hideHomeButton button#home-button Home From 238a22e59797d87248f610858c695e6c22545cc4 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Fri, 26 Nov 2021 14:37:26 -0700 Subject: [PATCH 151/169] Add "NOT NULL" constraints to database columns --- database/init_database.sql | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/database/init_database.sql b/database/init_database.sql index c7de265..404addf 100644 --- a/database/init_database.sql +++ b/database/init_database.sql @@ -55,8 +55,8 @@ CREATE TABLE IF NOT EXISTS scores.sports( CREATE TABLE IF NOT EXISTS scores.divisions( division_id BIGINT GENERATED ALWAYS AS IDENTITY, division_name TEXT NOT NULL, - gender VARCHAR(1) CHECK (gender IN ( 'F', 'M' ) ), - sport_id BIGINT, + gender VARCHAR(1) NOT NULL CHECK (gender IN ( 'F', 'M' ) ), + sport_id BIGINT NOT NULL, currently_active BOOLEAN DEFAULT TRUE, PRIMARY KEY(division_id), CONSTRAINT fk_sport @@ -67,7 +67,7 @@ CREATE TABLE IF NOT EXISTS scores.divisions( CREATE TABLE IF NOT EXISTS scores.teams( team_id BIGINT GENERATED ALWAYS AS IDENTITY, team_name TEXT NOT NULL, - sport_id BIGINT, + sport_id BIGINT NOT NULL, currently_active BOOLEAN DEFAULT TRUE, PRIMARY KEY(team_id), CONSTRAINT fk_sport @@ -83,14 +83,14 @@ CREATE TABLE IF NOT EXISTS scores.seasons( CREATE TABLE IF NOT EXISTS scores.games( game_id BIGINT GENERATED ALWAYS AS IDENTITY, - division_id BIGINT, - season_id BIGINT, - game_date DATE, - team1_id BIGINT, - team2_id BIGINT, - team1_score INTEGER, - team2_score INTEGER, - submitter_id BIGINT, + division_id BIGINT NOT NULL, + season_id BIGINT NOT NULL, + game_date DATE NOT NULL, + team1_id BIGINT NOT NULL, + team2_id BIGINT NOT NULL, + team1_score INTEGER NOT NULL, + team2_score INTEGER NOT NULL, + submitter_id BIGINT NOT NULL, updated_timestamp TIMESTAMP WITH TIME ZONE DEFAULT now(), PRIMARY KEY(game_id), CONSTRAINT fk_division From d2d6bbc5143e2c9b85ce97a35d72465b0b81fdb1 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Fri, 26 Nov 2021 14:49:08 -0700 Subject: [PATCH 152/169] Fix bug where accounts.js tries to create admin account before database is initialized --- database/accounts/accounts.js | 2 +- database/database.js | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/database/accounts/accounts.js b/database/accounts/accounts.js index 84c7a35..d33d11f 100644 --- a/database/accounts/accounts.js +++ b/database/accounts/accounts.js @@ -27,7 +27,7 @@ async function checkForAdminAccount() { console.log("Created temp admin account 'admin@example.com' with password 'admin'."); } } -checkForAdminAccount(); +database.initializationStatus.then(() => checkForAdminAccount()); passport.use(new localStrategy({ diff --git a/database/database.js b/database/database.js index 349e2bc..7390426 100644 --- a/database/database.js +++ b/database/database.js @@ -36,10 +36,11 @@ async function checkForDatabaseInitialization() { await Initialize(); } } -checkForDatabaseInitialization(); +const initializationStatus = checkForDatabaseInitialization(); -exports.executeQuery = executeQuery; \ No newline at end of file +exports.executeQuery = executeQuery; +exports.initializationStatus = initializationStatus; \ No newline at end of file From 5c783880a7d2c60fd7d03aa6629c7bb9a54ad531 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Fri, 26 Nov 2021 15:41:36 -0700 Subject: [PATCH 153/169] Improve error handling for account management --- routes/manage.js | 27 ++++++++++++++++----------- views/accounts/createuser.pug | 1 + 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/routes/manage.js b/routes/manage.js index 227c27d..c995742 100644 --- a/routes/manage.js +++ b/routes/manage.js @@ -156,16 +156,16 @@ router.get('/account', userLoggedIn, (req, res, next) => { if(userIsAdmin) { let title = req.query.account ? 'Manage User' : 'Create User' - res.render('accounts/createuser', { title, userLoggedIn: !!req.user }); + res.render('accounts/createuser', { title, userLoggedIn: !!req.user, message: req.flash('error') }); } else { let title = 'Manage Account'; - res.render('accounts/createuser', { title, accountID, userLoggedIn: !!req.user }); + res.render('accounts/createuser', { title, accountID, userLoggedIn: !!req.user, message: req.flash('error') }); } }); -router.post('/account', userLoggedIn, (req, res, next) => { +router.post('/account', userLoggedIn, async function(req, res, next) { const email = req.body.email; const password = req.body.password; @@ -175,19 +175,24 @@ router.post('/account', userLoggedIn, (req, res, next) => { const loggedInAccountIsAdmin = req.user[2]; const loggedInAccountID = req.user[0]; - console.log(accountID); - console.log(loggedInAccountID); - - if(!loggedInAccountIsAdmin && accountID != loggedInAccountID) { res.status(403).send("ACCESS DENIED"); } else { - const isAdmin = loggedInAccountIsAdmin ? !!req.body.admin : false; + try { + const isAdmin = loggedInAccountIsAdmin ? !!req.body.admin : false; - if(remove) accounts.remove(accountID).then(res.redirect('/manage')); - if(accountID) accounts.edit(accountID, email, password, isAdmin).then(res.redirect('/manage')); - else accounts.create(req.body.email, req.body.password, !!req.body.admin).then(res.redirect('/manage')); + if(remove) await accounts.remove(accountID); + else if(accountID) await accounts.edit(accountID, email, password, isAdmin); + else await accounts.create(req.body.email, req.body.password, !!req.body.admin); + + res.redirect('/manage'); + } + catch (err) { + console.error("ERROR: " + err.message); + req.flash("error", "An error has occurred."); + res.redirect('/manage/account'); + } } }); diff --git a/views/accounts/createuser.pug b/views/accounts/createuser.pug index a87897d..dfc1b13 100644 --- a/views/accounts/createuser.pug +++ b/views/accounts/createuser.pug @@ -20,6 +20,7 @@ block content span(class='form-section-checkbox') input#admin-checkbox(type="checkbox" name="admin" disabled) label(for="admin-checkbox") Grant admin privileges + .error #{message} span(class='form-section') button#submit-button(type="submit" disabled) Submit span(class='form-section') From dd0a9c4316053c640057ea877b56b1069812cade Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Fri, 26 Nov 2021 15:44:23 -0700 Subject: [PATCH 154/169] Remove unnecessary files --- public/scripts/submit.js | 113 --------------------------------------- routes/admin.js | 24 --------- test/test.js | 10 ---- views/manage/layout.pug | 10 ---- 4 files changed, 157 deletions(-) delete mode 100644 public/scripts/submit.js delete mode 100644 routes/admin.js delete mode 100644 test/test.js delete mode 100644 views/manage/layout.pug diff --git a/public/scripts/submit.js b/public/scripts/submit.js deleted file mode 100644 index 8c3cf81..0000000 --- a/public/scripts/submit.js +++ /dev/null @@ -1,113 +0,0 @@ -import * as Data from "./data.js"; - - -const sportDropdown = document.getElementById('sport-dropdown'); -const seasonDropdown = document.getElementById('year-dropdown'); -const genderDropdown = document.getElementById('gender-dropdown'); -const divisionDropdown = document.getElementById('division-dropdown'); -const team1Dropdown = document.getElementById('team1-dropdown'); -const team2Dropdown = document.getElementById('team2-dropdown'); - - - - - -async function listSeasons() { - seasonDropdown.innerHTML = ""; - - const seasonsList = await Data.getSeasons(); - - seasonsList.forEach(season => { - const option = document.createElement('option'); - option.text = (season.year - 1) + "-" + season.year; - option.value = season.id; - seasonDropdown.appendChild(option); - }); -} -listSeasons(); - -async function listSports() { - sportDropdown.innerHTML = ""; - - const sportsList = await Data.getSports(); - - sportsList.forEach(sport => { - const option = document.createElement('option'); - option.text = sport.name; - option.value = sport.id; - sportDropdown.appendChild(option); - }); - - listGenders(); - listTeams(); -} -listSports(); - -async function listGenders() { - genderDropdown.innerHTML = ""; - - const selectedSportID = sportDropdown.value; - const gendersList = await Data.getGenders(selectedSportID); - - if(selectedSportID) { - gendersList.forEach(gender => { - const option = document.createElement('option'); - option.text = (gender.name == "female") ? "Female" : (gender.name == "male") ? "Male" : ""; - option.value = gender.name; - genderDropdown.appendChild(option); - }); - } - - listDivisions(); -} - -async function listDivisions() { - divisionDropdown.innerHTML = ""; - - const selectedSportID = sportDropdown.value; - const selectedGender = genderDropdown.value; - - if(selectedGender) { - const divisionsList = await Data.getDivisions(selectedSportID, selectedGender); - - divisionsList.forEach(division => { - const option = document.createElement('option'); - option.text = division.name; - option.value = division.id; - divisionDropdown.appendChild(option); - }); - } -} - -async function listTeams() { - team1Dropdown.innerHTML = ""; - team2Dropdown.innerHTML = ""; - - const selectedSportID = sportDropdown.value; - - if(selectedSportID) { - const teamsList = await Data.getTeams(selectedSportID); - - teamsList.forEach(team => { - const optionT1 = document.createElement('option'); - optionT1.text = team.name; - optionT1.value = team.id; - team1Dropdown.appendChild(optionT1); - - const optionT2 = document.createElement('option'); - optionT2.text = team.name; - optionT2.value = team.id; - team2Dropdown.appendChild(optionT2); - }); - } -} - - - - - -sportDropdown.onchange = (() => { - listGenders(); - listTeams(); -}); -genderDropdown.onchange = listDivisions; \ No newline at end of file diff --git a/routes/admin.js b/routes/admin.js deleted file mode 100644 index 9e9cfc9..0000000 --- a/routes/admin.js +++ /dev/null @@ -1,24 +0,0 @@ -var express = require('express'); -var router = express.Router(); -const passport = require('passport'); -const app = require('../app'); -const accounts = require('./../database/accounts/accounts'); - - -function adminLoggedIn(req, res, next) { - if (req.user && req.user[2]) { - next(); - } - else { - req.flash('error', 'An admin account is required to access this page.'); - res.redirect('/auth/login'); - } - } - -router.get('/', adminLoggedIn, (req, res, next) => { - res.render -}); - - - -module.exports = router; \ No newline at end of file diff --git a/test/test.js b/test/test.js deleted file mode 100644 index 4f2ac60..0000000 --- a/test/test.js +++ /dev/null @@ -1,10 +0,0 @@ -const request = require('supertest'); -const app = require('../app'); - -describe('App', function() { - it('has the default page', function(done) { - request(app) - .get('/') - .expect(/Welcome to Express/, done); - }); -}); diff --git a/views/manage/layout.pug b/views/manage/layout.pug deleted file mode 100644 index e4f85eb..0000000 --- a/views/manage/layout.pug +++ /dev/null @@ -1,10 +0,0 @@ -doctype html -html - head - title= title - meta(name='viewport', content='width=device-width, initial-scale=1') - link(rel='stylesheet', href='/stylesheets/style.css') - block stylesheets - body - block content - block scripts From fb414ab9d8c424cb20eb387e36987aad41d43686 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Fri, 26 Nov 2021 15:50:43 -0700 Subject: [PATCH 155/169] Add duplicate route functions to separate file --- routes/auth.js | 12 ---------- routes/checkLoginStatus.js | 21 +++++++++++++++++ routes/data.js | 24 ++++---------------- routes/manage.js | 46 +++++++++++++------------------------- 4 files changed, 40 insertions(+), 63 deletions(-) create mode 100644 routes/checkLoginStatus.js diff --git a/routes/auth.js b/routes/auth.js index 25159ab..1e1940b 100644 --- a/routes/auth.js +++ b/routes/auth.js @@ -1,20 +1,8 @@ var express = require('express'); var router = express.Router(); const passport = require('passport'); -const accounts = require('./../database/accounts/accounts'); const app = require('../app'); - -function adminLoggedIn(req, res, next) { - if (req.user && req.user[2]) { - next(); - } - else { - req.flash('error', 'An admin account is required to access this page.'); - res.redirect('/auth/login'); - } - } - router.get('/login', (req, res, next) => { res.render('accounts/login', { title : "Login", message: req.flash('error') }); }); diff --git a/routes/checkLoginStatus.js b/routes/checkLoginStatus.js new file mode 100644 index 0000000..d9fd970 --- /dev/null +++ b/routes/checkLoginStatus.js @@ -0,0 +1,21 @@ +function adminLoggedIn(req, res, next) { + if (req.user && req.user[2]) { + next(); + } + else { + req.flash('error', 'An admin account is required to access this page.'); + res.redirect('/auth/login'); + } +} + +function userLoggedIn(req, res, next) { + if (req.user) { + next(); + } + else { + res.redirect('/auth/login'); + } +} + +exports.admin = adminLoggedIn; +exports.user = userLoggedIn; \ No newline at end of file diff --git a/routes/data.js b/routes/data.js index c875622..d241947 100644 --- a/routes/data.js +++ b/routes/data.js @@ -1,5 +1,6 @@ var express = require('express'); var router = express.Router(); + var sports = require('../database/scores/sports'); var seasons = require('../database/scores/seasons'); var genders = require('../database/scores/genders'); @@ -8,24 +9,7 @@ var teams = require('../database/scores/teams'); var games = require('../database/scores/games'); var accounts = require('../database/accounts/accounts'); -function adminLoggedIn(req, res, next) { - if (req.user && req.user[2]) { - next(); - } - else { - req.flash('error', 'An admin account is required to access this page.'); - res.redirect('/auth/login'); - } -} - -function userLoggedIn(req, res, next) { - if (req.user) { - next(); - } - else { - res.redirect('/auth/login'); - } -} +var checkLoginStatus = require('./checkLoginStatus'); router.get('/sports', function(req, res, next) { sports.retrieveAll() @@ -81,12 +65,12 @@ router.get('/game', function(req, res, next) { .then(data => res.json(data)); }) -router.get('/accounts', adminLoggedIn, function(req, res, next) { +router.get('/accounts', checkLoginStatus.admin, function(req, res, next) { accounts.retrieveAll() .then(data => res.json(data)); }) -router.get('/account', userLoggedIn, function(req, res, next) { +router.get('/account', checkLoginStatus.user, function(req, res, next) { const userIsAdmin = req.user[2]; const loggedInAccountID = req.user[0]; const requestedAccountID = req.query.account; diff --git a/routes/manage.js b/routes/manage.js index c995742..506a5f1 100644 --- a/routes/manage.js +++ b/routes/manage.js @@ -1,5 +1,6 @@ var express = require('express'); var router = express.Router(); + var genders = require('../database/scores/genders'); var games = require('../database/scores/games'); var seasons = require('../database/scores/seasons'); @@ -9,38 +10,21 @@ var genders = require('../database/scores/genders'); var teams = require('../database/scores/teams'); var accounts = require('../database/accounts/accounts'); -function userLoggedIn(req, res, next) { - if (req.user) { - next(); - } - else { - res.redirect('/auth/login'); - } -} - -function adminLoggedIn(req, res, next) { - if (req.user && req.user[2]) { - next(); - } - else { - req.flash('error', 'An admin account is required to access this page.'); - res.redirect('/auth/login'); - } -} +var checkLoginStatus = require('./checkLoginStatus'); -router.get('/' ,userLoggedIn, function(req, res, next) { +router.get('/' ,checkLoginStatus.user, function(req, res, next) { if(req.user[2]) res.render('manage', { title: 'Score Management', userLoggedIn: !!req.user }); else res.render('manage/manage-nonadmin', { title: "My Games", userLoggedIn: !!req.user }); }); -router.get('/game', userLoggedIn, function(req, res, next) { +router.get('/game', checkLoginStatus.user, function(req, res, next) { let title = req.query.game ? 'Edit Game' : 'Submit Score' res.render('manage/addgame', { title, userLoggedIn: !!req.user }); }); -router.post('/game', userLoggedIn, function(req, res, next) { +router.post('/game', checkLoginStatus.user, function(req, res, next) { const seasonID = req.body['year']; const sportID = req.body['sport']; const gender = (req.body['gender'] == "female") ? genders.FEMALE : genders.MALE; @@ -72,11 +56,11 @@ router.post('/game', userLoggedIn, function(req, res, next) { }); }); -router.get('/season', adminLoggedIn, function(req, res, next) { +router.get('/season', checkLoginStatus.admin, function(req, res, next) { res.render('manage/addseason', { title: 'Add Season', currentYear : (new Date()).getFullYear(), userLoggedIn: !!req.user }); }); -router.post('/season', adminLoggedIn, function(req, res, next) { +router.post('/season', checkLoginStatus.admin, function(req, res, next) { const year = req.body['year']; const seasonID = req.body['season']; @@ -86,11 +70,11 @@ router.post('/season', adminLoggedIn, function(req, res, next) { else seasons.add(year).then(res.redirect("/manage")); }); -router.get('/sport', adminLoggedIn, function(req, res, next) { +router.get('/sport', checkLoginStatus.admin, function(req, res, next) { res.render('manage/addsport', { title: 'Add Sport', userLoggedIn: !!req.user }); }); -router.post('/sport', adminLoggedIn, function(req, res, next) { +router.post('/sport', checkLoginStatus.admin, function(req, res, next) { const name = req.body['name']; const id = req.body['sport']; const remove = req.body['remove']; @@ -100,13 +84,13 @@ router.post('/sport', adminLoggedIn, function(req, res, next) { else sports.add(name).then(res.redirect('/manage')); }); -router.get('/division', adminLoggedIn, function(req, res, next) { +router.get('/division', checkLoginStatus.admin, function(req, res, next) { let title = req.query.division ? 'Edit Division' : 'Add Division' res.render('manage/adddivision', { title, userLoggedIn: !!req.user }); }); -router.post('/division', adminLoggedIn, function(req, res, next) { +router.post('/division', checkLoginStatus.admin, function(req, res, next) { const name = req.body['name']; const sport = req.body['sport']; const genderName = req.body['gender']; @@ -131,13 +115,13 @@ router.post('/division', adminLoggedIn, function(req, res, next) { } }); -router.get('/team', adminLoggedIn, function(req, res, next) { +router.get('/team', checkLoginStatus.admin, function(req, res, next) { let title = req.query.team ? 'Edit Team' : 'Add Team' res.render('manage/addteam', { title, userLoggedIn: !!req.user }); }); -router.post('/team', adminLoggedIn, function(req, res, next) { +router.post('/team', checkLoginStatus.admin, function(req, res, next) { const name = req.body['name']; const sport = req.body['sport']; @@ -149,7 +133,7 @@ router.post('/team', adminLoggedIn, function(req, res, next) { else teams.add(name, sport).then(res.redirect("/manage")); }); -router.get('/account', userLoggedIn, (req, res, next) => { +router.get('/account', checkLoginStatus.user, (req, res, next) => { const userIsAdmin = req.user[2]; const accountID = req.user[0]; @@ -165,7 +149,7 @@ router.get('/account', userLoggedIn, (req, res, next) => { } }); -router.post('/account', userLoggedIn, async function(req, res, next) { +router.post('/account', checkLoginStatus.user, async function(req, res, next) { const email = req.body.email; const password = req.body.password; From 2ecb26ca3cae27885bd12135ae7a2e36ff25edab Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Fri, 26 Nov 2021 15:55:29 -0700 Subject: [PATCH 156/169] Remove admin router reference --- app.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/app.js b/app.js index a379166..9082389 100644 --- a/app.js +++ b/app.js @@ -15,7 +15,6 @@ var usersRouter = require('./routes/users'); var dataRouter = require('./routes/data'); var manageRouter = require('./routes/manage'); var authRouter = require('./routes/auth'); -var adminRouter = require('./routes/admin'); var app = express(); @@ -54,7 +53,6 @@ app.use('/users', usersRouter); app.use('/data', dataRouter); app.use('/manage', manageRouter); app.use('/auth', authRouter); -app.use('/admin', adminRouter); // catch 404 and forward to error handler From 4f3581b1e5ea96c1a291102e2268020f9b12df38 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Fri, 26 Nov 2021 16:16:01 -0700 Subject: [PATCH 157/169] Fix bug where genders by sport do not report correctly --- database/scores/genders.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/database/scores/genders.js b/database/scores/genders.js index b59bdb0..17e3ff2 100644 --- a/database/scores/genders.js +++ b/database/scores/genders.js @@ -15,8 +15,8 @@ const FEMALE = new Gender("female"); async function retrieveBySport(sportID) { - const query = `SELECT gender - from scores.divisions + const query = `SELECT DISTINCT(gender) + FROM scores.divisions WHERE sport_id = $1;`; const table = await database.executeQuery(query, [sportID]); From 6705f06e183949af03ca0f247b9d516eaf90b2c6 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Fri, 26 Nov 2021 16:16:11 -0700 Subject: [PATCH 158/169] Add better error handling for data.js route --- routes/data.js | 167 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 121 insertions(+), 46 deletions(-) diff --git a/routes/data.js b/routes/data.js index d241947..dfe6b8d 100644 --- a/routes/data.js +++ b/routes/data.js @@ -11,75 +11,150 @@ var accounts = require('../database/accounts/accounts'); var checkLoginStatus = require('./checkLoginStatus'); -router.get('/sports', function(req, res, next) { - sports.retrieveAll() - .then(data => res.json(data)); +router.get('/sports', async function(req, res, next) { + try { + const data = await sports.retrieveAll(); + res.json(data); + } catch(err) { + console.error("ERROR: " + err.message); + res.status(500).send("An error has occurred"); + } }); -router.get('/sport', function(req, res, next) { - sports.getFromID(req.query.sport) - .then(data => res.json(data)); +router.get('/sport', async function(req, res, next) { + try { + const sportID = req.query.sport; + const data = await sports.getFromID(sportID); + res.json(data); + } catch(err) { + console.error("ERROR: " + err.message); + res.status(500).send("An error has occurred"); + } }); -router.get('/seasons', function(req, res, next) { - seasons.retrieveAll() - .then(data => res.json(data)); +router.get('/seasons', async function(req, res, next) { + try { + const data = await seasons.retrieveAll(); + res.json(data); + } catch(err) { + console.error("ERROR: " + err.message); + res.status(500).send("An error has occurred"); + } }) -router.get('/genders', function(req, res, next) { - genders.retrieveBySport(req.query.sport) - .then(data => res.json(data)); +router.get('/genders', async function(req, res, next) { + try { + const sportID = req.query.sport; + const data = await genders.retrieveBySport(sportID); + res.json(data); + } catch(err) { + console.error("ERROR: " + err.message); + res.status(500).send("An error has occurred"); + } }) -router.get('/divisions', function(req, res, next) { - let gender; - if(req.query.gender) gender = (req.query.gender == 'female' ? genders.FEMALE : genders.MALE); +router.get('/divisions', async function(req, res, next) { + try{ + let gender; + if(req.query.gender) gender = (req.query.gender == 'female' ? genders.FEMALE : genders.MALE); - divisions.retrieve(req.query.sport, gender) - .then(data => res.json(data)); + const sportID = req.query.sport; + const data = await divisions.retrieve(sportID, gender); + res.json(data); + } catch(err) { + console.error("ERROR: " + err.message); + res.status(500).send("An error has occurred"); + } }) -router.get('/division', function(req, res, next) { - divisions.getFromID(req.query.division) - .then(data => res.json(data)); +router.get('/division', async function(req, res, next) { + try { + const divisionID = req.query.division; + const data = await divisions.getFromID(divisionID); + res.json(data); + } catch(err) { + console.error("ERROR: " + err.message); + res.status(500).send("An error has occurred"); + } }) -router.get('/teams', function(req, res, next) { - teams.retrieve(req.query.sport) - .then(data => res.json(data)); +router.get('/teams', async function(req, res, next) { + try { + const sportID = req.query.sport; + const data = await teams.retrieve(sportID); + res.json(data); + } catch(err) { + console.error("ERROR: " + err.message); + res.status(500).send("An error has occurred"); + } }) -router.get('/team', function(req, res, next) { - teams.getFromID(req.query.team) - .then(data => res.json(data)); +router.get('/team', async function(req, res, next) { + try { + const teamID = req.query.team; + const data = await teams.getFromID(teamID); + res.json(data); + } catch(err) { + console.error("ERROR: " + err.message); + res.status(500).send("An error has occurred"); + } }) -router.get('/games', function(req, res, next) { - const userID = req.user ? req.user[0] : null; - if(req.query.user) games.retrieveByUser(userID).then(data => res.json(data)); - else games.retrieve(req.query.team, req.query.division, req.query.season).then(data => res.json(data)); +router.get('/games', async function(req, res, next) { + try { + const userID = req.user ? req.user[0] : null; + if(req.query.user) { + const data = await games.retrieveByUser(userID); + res.json(data); + } else { + const seasonID = req.query.season; + const divisionID = req.query.division; + const teamID = req.query.team; + const data = await games.retrieve(teamID, divisionID, seasonID); + res.json(data); + } + } catch(err) { + console.error("ERROR: " + err.message); + res.status(500).send("An error has occurred"); + } }) -router.get('/game', function(req, res, next) { - games.getFromID(req.query.game) - .then(data => res.json(data)); +router.get('/game', async function(req, res, next) { + try { + const gameID = req.query.game; + const data = await games.getFromID(gameID); + res.json(data); + } catch(err) { + console.error("ERROR: " + err.message); + res.status(500).send("An error has occurred"); + } }) -router.get('/accounts', checkLoginStatus.admin, function(req, res, next) { - accounts.retrieveAll() - .then(data => res.json(data)); +router.get('/accounts', checkLoginStatus.admin, async function(req, res, next) { + try { + const data = await accounts.retrieveAll(); + res.json(data); + } catch(err) { + console.error("ERROR: " + err.message); + res.status(500).send("An error has occurred"); + } }) -router.get('/account', checkLoginStatus.user, function(req, res, next) { - const userIsAdmin = req.user[2]; - const loggedInAccountID = req.user[0]; - const requestedAccountID = req.query.account; - - if(!userIsAdmin && loggedInAccountID != requestedAccountID) { - res.status(403).send("ACCESS DENIED"); - } else { - accounts.getFromID(req.query.account) - .then(data => res.json(data)); +router.get('/account', checkLoginStatus.user, async function(req, res, next) { + try{ + const userIsAdmin = req.user[2]; + const loggedInAccountID = req.user[0]; + const requestedAccountID = req.query.account; + + if(!userIsAdmin && loggedInAccountID != requestedAccountID) { + res.status(403).send("ACCESS DENIED"); + } else { + const data = await accounts.getFromID(req.query.account); + res.json(data); + } + } catch(err) { + console.error("ERROR: " + err.message); + res.status(500).send("An error has occurred"); } }) From 292c4a703943b25b368fa02ac492c814f7e5e37e Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Fri, 26 Nov 2021 16:17:54 -0700 Subject: [PATCH 159/169] Remove pregenerated users route --- app.js | 2 -- routes/users.js | 9 --------- 2 files changed, 11 deletions(-) delete mode 100644 routes/users.js diff --git a/app.js b/app.js index 9082389..4065b14 100644 --- a/app.js +++ b/app.js @@ -11,7 +11,6 @@ var flash = require('connect-flash'); var indexRouter = require('./routes/index'); -var usersRouter = require('./routes/users'); var dataRouter = require('./routes/data'); var manageRouter = require('./routes/manage'); var authRouter = require('./routes/auth'); @@ -49,7 +48,6 @@ app.use(cookieParser()); app.use(express.static(path.join(__dirname, 'public'))); app.use('/', indexRouter); -app.use('/users', usersRouter); app.use('/data', dataRouter); app.use('/manage', manageRouter); app.use('/auth', authRouter); diff --git a/routes/users.js b/routes/users.js deleted file mode 100644 index 623e430..0000000 --- a/routes/users.js +++ /dev/null @@ -1,9 +0,0 @@ -var express = require('express'); -var router = express.Router(); - -/* GET users listing. */ -router.get('/', function(req, res, next) { - res.send('respond with a resource'); -}); - -module.exports = router; From f9ac3f49cd72caca20c02376a0f3891155ee9464 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Fri, 26 Nov 2021 16:29:03 -0700 Subject: [PATCH 160/169] Improve error management for games --- routes/manage.js | 71 +++++++++++++++++++++++----------------- views/manage/addgame.pug | 1 + 2 files changed, 42 insertions(+), 30 deletions(-) diff --git a/routes/manage.js b/routes/manage.js index 506a5f1..44abc09 100644 --- a/routes/manage.js +++ b/routes/manage.js @@ -21,39 +21,50 @@ router.get('/' ,checkLoginStatus.user, function(req, res, next) { router.get('/game', checkLoginStatus.user, function(req, res, next) { let title = req.query.game ? 'Edit Game' : 'Submit Score' - res.render('manage/addgame', { title, userLoggedIn: !!req.user }); + res.render('manage/addgame', { title, userLoggedIn: !!req.user, message: req.flash('error') }); }); -router.post('/game', checkLoginStatus.user, function(req, res, next) { - const seasonID = req.body['year']; - const sportID = req.body['sport']; - const gender = (req.body['gender'] == "female") ? genders.FEMALE : genders.MALE; - const divisionID = req.body['division']; - const date = req.body['date']; - const team1ID = req.body['team1']; - const team1Score = req.body['team1-score']; - const team2ID = req.body['team2']; - const team2Score = req.body['team2-score']; - const userID = req.user[0]; +router.post('/game', checkLoginStatus.user, async function(req, res, next) { + try { + const seasonID = req.body['year']; + const sportID = req.body['sport']; + const gender = (req.body['gender'] == "female") ? genders.FEMALE : genders.MALE; + const divisionID = req.body['division']; + const date = req.body['date']; + const team1ID = req.body['team1']; + const team1Score = req.body['team1-score']; + const team2ID = req.body['team2']; + const team2Score = req.body['team2-score']; + const userID = req.user[0]; + + const id = req.body['game']; + const remove = req.body['remove']; + + const loggedInUserID = req.user[0]; + const loggedInUserIsAdmin = req.user[2]; + + const game = id ? await games.getFromID(id) : null; - const id = req.body['game']; - const remove = req.body['remove']; - - const loggedInUserID = req.user[0]; - const loggedInUserIsAdmin = req.user[2]; - - games.getFromID(id) - .then(game => { - if(!loggedInUserIsAdmin && loggedInUserID != game.submitterID) { - res.status(403).send("ACCESS DENIED"); - } - else if(remove) games.remove(id) - .then(res.redirect("/manage")); - else if(id) games.edit(id, divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score) - .then(res.redirect('/manage')); - else games.add(divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score, userID) - .then(res.redirect("/")); - }); + if(!loggedInUserIsAdmin && game && loggedInUserID != game.submitterID) { + res.status(403).send("ACCESS DENIED"); + } + else if(remove) { + await games.remove(id); + res.redirect("/manage"); + } + else if(id) { + await games.edit(id, divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score); + res.redirect('/manage'); + } + else { + await games.add(divisionID, seasonID, date, team1ID, team2ID, team1Score, team2Score, userID); + res.redirect('/'); + } + } catch(err) { + console.error("ERROR: " + err.message); + req.flash("error", "An error has occurred."); + res.redirect('/manage/game'); + } }); router.get('/season', checkLoginStatus.admin, function(req, res, next) { diff --git a/views/manage/addgame.pug b/views/manage/addgame.pug index 61e8f61..3a44834 100644 --- a/views/manage/addgame.pug +++ b/views/manage/addgame.pug @@ -45,6 +45,7 @@ block content label Score span(class='form-section-input') input#team2-score-textbox(type="number", name="team2-score", value="0" disabled) + .error #{message} span(class='form-section') button#submit-button(type="submit" disabled) Submit span(class='form-section') From 51783c5dec58b9f689ef33fdc365292c06a73298 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Fri, 26 Nov 2021 16:34:26 -0700 Subject: [PATCH 161/169] Add better error management for seasons --- routes/manage.js | 22 +++++++++++++++------- views/manage/addseason.pug | 1 + 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/routes/manage.js b/routes/manage.js index 44abc09..d343952 100644 --- a/routes/manage.js +++ b/routes/manage.js @@ -68,17 +68,25 @@ router.post('/game', checkLoginStatus.user, async function(req, res, next) { }); router.get('/season', checkLoginStatus.admin, function(req, res, next) { - res.render('manage/addseason', { title: 'Add Season', currentYear : (new Date()).getFullYear(), userLoggedIn: !!req.user }); + res.render('manage/addseason', { title: 'Add Season', currentYear : (new Date()).getFullYear(), userLoggedIn: !!req.user, message: req.flash('error') }); }); -router.post('/season', checkLoginStatus.admin, function(req, res, next) { - const year = req.body['year']; +router.post('/season', checkLoginStatus.admin, async function(req, res, next) { + try { + const year = req.body['year']; - const seasonID = req.body['season']; - const remove = req.body['remove']; + const seasonID = req.body['season']; + const remove = req.body['remove']; - if(remove) seasons.remove(seasonID).then(res.redirect('/manage')); - else seasons.add(year).then(res.redirect("/manage")); + if(remove) await seasons.remove(seasonID); + else await seasons.add(year); + + res.redirect('/manage'); + } catch(err) { + console.error("ERROR: " + err.message); + req.flash("error", "An error has occurred."); + res.redirect('/manage/season'); + } }); router.get('/sport', checkLoginStatus.admin, function(req, res, next) { diff --git a/views/manage/addseason.pug b/views/manage/addseason.pug index 27783b5..ba43698 100644 --- a/views/manage/addseason.pug +++ b/views/manage/addseason.pug @@ -10,6 +10,7 @@ block content label Ending year span(class='form-section-input') input#season-textbox(type="number", name="year", value=currentYear) + .error #{message} span(class='form-section') button#submit-button(type="submit" disabled) Submit From 3f6251f5bbbf3b1e5d3e4f80e29c5ca7c0c18cb5 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Fri, 26 Nov 2021 16:38:46 -0700 Subject: [PATCH 162/169] Improve error handling for sports --- routes/manage.js | 24 ++++++++++++++++-------- views/manage/addsport.pug | 1 + 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/routes/manage.js b/routes/manage.js index d343952..4558cfd 100644 --- a/routes/manage.js +++ b/routes/manage.js @@ -90,17 +90,25 @@ router.post('/season', checkLoginStatus.admin, async function(req, res, next) { }); router.get('/sport', checkLoginStatus.admin, function(req, res, next) { - res.render('manage/addsport', { title: 'Add Sport', userLoggedIn: !!req.user }); + res.render('manage/addsport', { title: 'Add Sport', userLoggedIn: !!req.user, message: req.flash('error') }); }); -router.post('/sport', checkLoginStatus.admin, function(req, res, next) { - const name = req.body['name']; - const id = req.body['sport']; - const remove = req.body['remove']; +router.post('/sport', checkLoginStatus.admin, async function(req, res, next) { + try { + const name = req.body['name']; + const id = req.body['sport']; + const remove = req.body['remove']; - if(remove) sports.remove(id).then(res.redirect('/manage')); - else if(id) sports.rename(id, name).then(res.redirect('/manage')); - else sports.add(name).then(res.redirect('/manage')); + if(remove) await sports.remove(id); + else if(id) await sports.rename(id, name); + else await sports.add(name); + + res.redirect('/manage'); + } catch(err) { + console.error("ERROR: " + err.message); + req.flash("error", "An error has occurred."); + res.redirect('/manage/sport'); + } }); router.get('/division', checkLoginStatus.admin, function(req, res, next) { diff --git a/views/manage/addsport.pug b/views/manage/addsport.pug index 067b9ac..b4caa50 100644 --- a/views/manage/addsport.pug +++ b/views/manage/addsport.pug @@ -10,6 +10,7 @@ block content label Sport name span(class='form-section-input') input#name-textbox(type="text" name="name" disabled) + .error #{message} span(class='form-section') button#submit-button(type="submit" disabled) Submit span(class='form-section') From ad361adab1e9605656cd12876386498b2cb6781e Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Fri, 26 Nov 2021 16:46:46 -0700 Subject: [PATCH 163/169] Improve error handling for divisions --- routes/manage.js | 45 +++++++++++++++++++----------------- views/manage/adddivision.pug | 1 + 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/routes/manage.js b/routes/manage.js index 4558cfd..9b5fe6e 100644 --- a/routes/manage.js +++ b/routes/manage.js @@ -114,31 +114,34 @@ router.post('/sport', checkLoginStatus.admin, async function(req, res, next) { router.get('/division', checkLoginStatus.admin, function(req, res, next) { let title = req.query.division ? 'Edit Division' : 'Add Division' - res.render('manage/adddivision', { title, userLoggedIn: !!req.user }); + res.render('manage/adddivision', { title, userLoggedIn: !!req.user, message: req.flash('error') }); }); -router.post('/division', checkLoginStatus.admin, function(req, res, next) { - const name = req.body['name']; - const sport = req.body['sport']; - const genderName = req.body['gender']; - - const id = req.body['division']; - const remove = req.body['remove']; - - - if(remove) divisions.remove(id).then(res.redirect('/manage')); - else if(id) divisions.rename(id, name).then(res.redirect('/manage')); - else { - if(genderName == "both") { - divisions.add(name, genders.FEMALE, sport) - .then(divisions.add(name, genders.MALE, sport) - .then(res.redirect("/manage"))); - } +router.post('/division', checkLoginStatus.admin, async function(req, res, next) { + try { + const name = req.body['name']; + const sport = req.body['sport']; + const genderName = req.body['gender']; + + const id = req.body['division']; + const remove = req.body['remove']; + + if(remove) await divisions.remove(id); + else if(id) await divisions.rename(id, name); else { - const gender = (genderName == "female") ? genders.FEMALE : genders.MALE; - divisions.add(name, gender, sport) - .then(res.redirect("/manage")); + if(genderName == 'both') { + await divisions.add(name, genders.FEMALE, sport); + await divisions.add(name, genders.MALE, sport); + } else { + const gender = (genderName == "female") ? genders.FEMALE : genders.MALE; + await divisions.add(name, gender, sport); + } } + res.redirect('/manage'); + } catch(err) { + console.error("ERROR: " + err.message); + req.flash("error", "An error has occurred."); + res.redirect('/manage/division'); } }); diff --git a/views/manage/adddivision.pug b/views/manage/adddivision.pug index 10e7281..1a17aaf 100644 --- a/views/manage/adddivision.pug +++ b/views/manage/adddivision.pug @@ -21,6 +21,7 @@ block content label Division name span(class='form-section-input') input#name-textbox(type="text", name="name" disabled) + .error #{message} span(class='form-section') button#submit-button(type="submit" disabled) Submit span(class='form-section') From 63b6ef2c2dd8493a426b16af19351a573644a7a3 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Fri, 26 Nov 2021 16:59:51 -0700 Subject: [PATCH 164/169] Improve redirection when error occurs --- public/scripts/manage/sport.js | 3 --- routes/manage.js | 37 ++++++++++++++++++---------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/public/scripts/manage/sport.js b/public/scripts/manage/sport.js index c30fbce..20ad2cb 100644 --- a/public/scripts/manage/sport.js +++ b/public/scripts/manage/sport.js @@ -1,7 +1,6 @@ import * as Data from "../data.js"; -const mainHeader = document.getElementById('main-header'); const nameTextbox = document.getElementById('name-textbox'); const submitButton = document.getElementById('submit-button'); const deleteButton = document.getElementById('delete-button'); @@ -12,8 +11,6 @@ async function initializeForm() { let params = new URLSearchParams(location.search); let sportID = params.get('sport') if(sportID) { - mainHeader.textContent = "Edit Sport"; - const sportName = await Data.getSportName(sportID); nameTextbox.value = sportName; diff --git a/routes/manage.js b/routes/manage.js index 9b5fe6e..e16f9ff 100644 --- a/routes/manage.js +++ b/routes/manage.js @@ -25,6 +25,9 @@ router.get('/game', checkLoginStatus.user, function(req, res, next) { }); router.post('/game', checkLoginStatus.user, async function(req, res, next) { + const id = req.body['game']; + const remove = req.body['remove']; + try { const seasonID = req.body['year']; const sportID = req.body['sport']; @@ -37,9 +40,6 @@ router.post('/game', checkLoginStatus.user, async function(req, res, next) { const team2Score = req.body['team2-score']; const userID = req.user[0]; - const id = req.body['game']; - const remove = req.body['remove']; - const loggedInUserID = req.user[0]; const loggedInUserIsAdmin = req.user[2]; @@ -63,7 +63,7 @@ router.post('/game', checkLoginStatus.user, async function(req, res, next) { } catch(err) { console.error("ERROR: " + err.message); req.flash("error", "An error has occurred."); - res.redirect('/manage/game'); + res.redirect('/manage/game' + (id ? `?game=${id}` : '')); } }); @@ -72,12 +72,12 @@ router.get('/season', checkLoginStatus.admin, function(req, res, next) { }); router.post('/season', checkLoginStatus.admin, async function(req, res, next) { + const seasonID = req.body['season']; + const remove = req.body['remove']; + try { const year = req.body['year']; - const seasonID = req.body['season']; - const remove = req.body['remove']; - if(remove) await seasons.remove(seasonID); else await seasons.add(year); @@ -85,19 +85,22 @@ router.post('/season', checkLoginStatus.admin, async function(req, res, next) { } catch(err) { console.error("ERROR: " + err.message); req.flash("error", "An error has occurred."); - res.redirect('/manage/season'); + res.redirect('/manage/season' + (seasonID ? `?season=${seasonID}` : '')); } }); router.get('/sport', checkLoginStatus.admin, function(req, res, next) { - res.render('manage/addsport', { title: 'Add Sport', userLoggedIn: !!req.user, message: req.flash('error') }); + let title = req.query.sport ? 'Edit Sport' : 'Add Sport'; + + res.render('manage/addsport', { title, userLoggedIn: !!req.user, message: req.flash('error') }); }); router.post('/sport', checkLoginStatus.admin, async function(req, res, next) { + const id = req.body['sport']; + const remove = req.body['remove']; + try { const name = req.body['name']; - const id = req.body['sport']; - const remove = req.body['remove']; if(remove) await sports.remove(id); else if(id) await sports.rename(id, name); @@ -107,7 +110,7 @@ router.post('/sport', checkLoginStatus.admin, async function(req, res, next) { } catch(err) { console.error("ERROR: " + err.message); req.flash("error", "An error has occurred."); - res.redirect('/manage/sport'); + res.redirect('/manage/sport' + (id ? `?sport=${id}` : '')); } }); @@ -118,14 +121,14 @@ router.get('/division', checkLoginStatus.admin, function(req, res, next) { }); router.post('/division', checkLoginStatus.admin, async function(req, res, next) { + const id = req.body['division']; + const remove = req.body['remove']; + try { const name = req.body['name']; const sport = req.body['sport']; const genderName = req.body['gender']; - - const id = req.body['division']; - const remove = req.body['remove']; - + if(remove) await divisions.remove(id); else if(id) await divisions.rename(id, name); else { @@ -141,7 +144,7 @@ router.post('/division', checkLoginStatus.admin, async function(req, res, next) } catch(err) { console.error("ERROR: " + err.message); req.flash("error", "An error has occurred."); - res.redirect('/manage/division'); + res.redirect('/manage/division' + (id ? `?division=${id}` : '')); } }); From 6c174f3e02334f486ec5318c2af3a417ffc1026f Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Fri, 26 Nov 2021 17:03:09 -0700 Subject: [PATCH 165/169] Improve error handling for teams --- routes/manage.js | 22 ++++++++++++++-------- views/manage/addteam.pug | 1 + 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/routes/manage.js b/routes/manage.js index e16f9ff..5b979fa 100644 --- a/routes/manage.js +++ b/routes/manage.js @@ -151,19 +151,25 @@ router.post('/division', checkLoginStatus.admin, async function(req, res, next) router.get('/team', checkLoginStatus.admin, function(req, res, next) { let title = req.query.team ? 'Edit Team' : 'Add Team' - res.render('manage/addteam', { title, userLoggedIn: !!req.user }); + res.render('manage/addteam', { title, userLoggedIn: !!req.user, message: req.flash('error') }); }); -router.post('/team', checkLoginStatus.admin, function(req, res, next) { - const name = req.body['name']; - const sport = req.body['sport']; - +router.post('/team', checkLoginStatus.admin, async function(req, res, next) { const id = req.body['team']; const remove = req.body['remove']; - if(remove) teams.remove(id).then(res.redirect('/manage')); - else if(id) teams.rename(id, name).then(res.redirect('/manage')); - else teams.add(name, sport).then(res.redirect("/manage")); + try { + const name = req.body['name']; + const sport = req.body['sport']; + + if(remove) teams.remove(id).then(res.redirect('/manage')); + else if(id) teams.rename(id, name).then(res.redirect('/manage')); + else teams.add(name, sport).then(res.redirect("/manage")); + } catch(err) { + console.error("ERROR: " + err.message); + req.flash("error", "An error has occurred."); + res.redirect('/manage/team' + (id ? `?team=${id}` : '')); + } }); router.get('/account', checkLoginStatus.user, (req, res, next) => { diff --git a/views/manage/addteam.pug b/views/manage/addteam.pug index 9f7262c..78746a6 100644 --- a/views/manage/addteam.pug +++ b/views/manage/addteam.pug @@ -14,6 +14,7 @@ block content label Team name span(class='form-section-input') input#name-textbox(type="text", name="name" disabled) + .error #{message} span(class='form-section') button#submit-button(type="submit" disabled) Submit span(class='form-section') From 7132273e9ec1cea34f94026fb7dbde272b34a125 Mon Sep 17 00:00:00 2001 From: sudoer777 <78781902+sudoer777@users.noreply.github.com> Date: Fri, 26 Nov 2021 17:08:13 -0700 Subject: [PATCH 166/169] Improve redirect for failure editing accounts --- routes/manage.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/routes/manage.js b/routes/manage.js index 5b979fa..8432ff8 100644 --- a/routes/manage.js +++ b/routes/manage.js @@ -214,7 +214,9 @@ router.post('/account', checkLoginStatus.user, async function(req, res, next) { catch (err) { console.error("ERROR: " + err.message); req.flash("error", "An error has occurred."); - res.redirect('/manage/account'); + let URL = '/manage/account'; + if(loggedInAccountIsAdmin && accountID) URL += `?account=${accountID}`; + res.redirect(URL); } } }); From 15e7163e394c18949b84f761e6beecd3208fbf9a Mon Sep 17 00:00:00 2001 From: Sudoer777 Date: Sat, 27 Nov 2021 01:06:16 +0000 Subject: [PATCH 167/169] Update .node-version --- .node-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.node-version b/.node-version index 2f76972..58a4133 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -8.11.1 +16.13.0 From a68c0eeb54e883a695943a21de05cb008f851b0f Mon Sep 17 00:00:00 2001 From: Sudoer777 Date: Sat, 27 Nov 2021 01:08:02 +0000 Subject: [PATCH 168/169] Update node version in dockerfile --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 43f0bd8..135b43f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:8.11-alpine +FROM node:16.13.0-alpine3.12 WORKDIR /usr/src/app From b5a41dda8b44e375975ae2a889761bc33adefc8a Mon Sep 17 00:00:00 2001 From: Sudoer777 Date: Sat, 27 Nov 2021 01:09:16 +0000 Subject: [PATCH 169/169] Update package.json --- package.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index c3c687d..7b97c2d 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,9 @@ { - "name": "demo", - "version": "0.0.0", + "name": "score-tracker", + "version": "1.0.0-pre", "private": true, "scripts": { "start": "node ./bin/www", - "test": "mocha" }, "dependencies": { "async": "^3.2.2",