diff --git a/backend/.env b/backend/.env new file mode 100644 index 0000000..13ecaa1 --- /dev/null +++ b/backend/.env @@ -0,0 +1,7 @@ +# Environment variables declared in this file are automatically made available to Prisma. +# See the documentation for more detail: https://pris.ly/d/prisma-schema#using-environment-variables + +# Prisma supports the native connection string format for PostgreSQL, MySQL and SQLite. +# See the documentation for all the connection string options: https://pris.ly/d/connection-strings + +DATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public" \ No newline at end of file diff --git a/backend/package-lock.json b/backend/package-lock.json index 0847323..82be76d 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -1,6 +1,6 @@ { - "name": "backend", - "version": "0.0.1", + "name": "ts-onlinetime-ranks-backend", + "version": "0.0.2", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1179,15 +1179,31 @@ } }, "@nestjs/common": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-7.1.1.tgz", - "integrity": "sha512-ZcFbHw2LEkTG9lR84ysNutHnrZPiRYjdjHM7nDCz0zXra98Cu4mKw0wOdoMmtd6WFtpVaSd9JVMXYWvHleFzmw==", + "version": "7.6.5", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-7.6.5.tgz", + "integrity": "sha512-WvBJd71ktaCRm9KTURVqn1YMyUzsOIkvezjP7WEpP9DVqQUOFVvn6/osJGZky/qL+zE4P7NBNyoXM94bpYvMwQ==", "requires": { - "axios": "0.19.2", - "cli-color": "2.0.0", - "iterare": "1.2.0", - "tslib": "2.0.0", - "uuid": "8.0.0" + "axios": "0.21.1", + "iterare": "1.2.1", + "tslib": "2.0.3", + "uuid": "8.3.2" + }, + "dependencies": { + "iterare": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/iterare/-/iterare-1.2.1.tgz", + "integrity": "sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q==" + }, + "tslib": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", + "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==" + }, + "uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" + } } }, "@nestjs/core": { @@ -1351,6 +1367,41 @@ "node-fetch": "^2.3.0" } }, + "@prisma/bar": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@prisma/bar/-/bar-0.0.1.tgz", + "integrity": "sha512-FVLhwVkbfhXlBhroWfIXMLi+3Jh9IEzYp+9z+MUUiw3ZsbcoAil7CN9/QIjHc4/TcCRyRfuSmT7qCnn4O+TjJw==", + "dev": true + }, + "@prisma/cli": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@prisma/cli/-/cli-2.14.0.tgz", + "integrity": "sha512-zKYiOGlc/Y0VVRQew0jMR2sGSFnKnNGlgY4ToB5K1JMv7jCg51w+FJe2FwTOF4uWv+0Yp2KC1b0TAP8KUbNJ3A==", + "dev": true, + "requires": { + "@prisma/bar": "^0.0.1", + "@prisma/engines": "2.14.0-28.5d491261d382a2a5ffdc71de17072b0e409f1cc1" + } + }, + "@prisma/client": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-2.14.0.tgz", + "integrity": "sha512-0PiiZ2maM9OP/G5mynzftvD4DFrtibeyvZf5U7Be360mtSvgLHQhZ/CHbChb0BVeWeyJuTmAqqni98LcdVgttw==", + "requires": { + "@prisma/engines-version": "2.14.0-28.5d491261d382a2a5ffdc71de17072b0e409f1cc1" + } + }, + "@prisma/engines": { + "version": "2.14.0-28.5d491261d382a2a5ffdc71de17072b0e409f1cc1", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-2.14.0-28.5d491261d382a2a5ffdc71de17072b0e409f1cc1.tgz", + "integrity": "sha512-t5/Mq0VtL7khYQtbap8myr7RwyIvmruCFxsph6oTi8QZaklmuxSp7FAC2DWBz8r/KoZFngMpmliKfDrWN7uwzQ==", + "dev": true + }, + "@prisma/engines-version": { + "version": "2.14.0-28.5d491261d382a2a5ffdc71de17072b0e409f1cc1", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-2.14.0-28.5d491261d382a2a5ffdc71de17072b0e409f1cc1.tgz", + "integrity": "sha512-Oz3yNV//9UTJ+ral5tF6Ryb/MLfOE3+2F3/bH4waTbNRDd/zXKoS3jTI3ViU8yRQ1AsOQa9/pV/kXSlxgtAh8Q==" + }, "@schematics/schematics": { "version": "0.901.7", "resolved": "https://registry.npmjs.org/@schematics/schematics/-/schematics-0.901.7.tgz", @@ -2038,11 +2089,6 @@ "type-fest": "^0.11.0" } }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -2256,11 +2302,11 @@ "dev": true }, "axios": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", - "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", + "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", "requires": { - "follow-redirects": "1.5.10" + "follow-redirects": "^1.10.0" } }, "babel-jest": { @@ -2921,19 +2967,6 @@ } } }, - "cli-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-2.0.0.tgz", - "integrity": "sha512-a0VZ8LeraW0jTuCkuAGMNufareGHhyZU9z8OGsW0gXd1hZGi1SRuNRXdbGkraBBKnhyUhyebFWnRbp+dIn0f0A==", - "requires": { - "ansi-regex": "^2.1.1", - "d": "^1.0.1", - "es5-ext": "^0.10.51", - "es6-iterator": "^2.0.3", - "memoizee": "^0.4.14", - "timers-ext": "^0.1.7" - } - }, "cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -3372,15 +3405,6 @@ "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=", "dev": true }, - "d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "requires": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" - } - }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -3405,6 +3429,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, "requires": { "ms": "2.0.0" } @@ -3658,9 +3683,9 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "elliptic": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz", - "integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==", + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", + "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", "dev": true, "requires": { "bn.js": "^4.4.0", @@ -3778,46 +3803,6 @@ "is-symbol": "^1.0.2" } }, - "es5-ext": { - "version": "0.10.53", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", - "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", - "requires": { - "es6-iterator": "~2.0.3", - "es6-symbol": "~3.1.3", - "next-tick": "~1.0.0" - } - }, - "es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", - "requires": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, - "es6-symbol": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", - "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", - "requires": { - "d": "^1.0.1", - "ext": "^1.1.2" - } - }, - "es6-weak-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", - "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", - "requires": { - "d": "1", - "es5-ext": "^0.10.46", - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.1" - } - }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -4201,15 +4186,6 @@ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" }, - "event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", - "requires": { - "d": "1", - "es5-ext": "~0.10.14" - } - }, "events": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/events/-/events-3.1.0.tgz", @@ -4390,21 +4366,6 @@ } } }, - "ext": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", - "integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==", - "requires": { - "type": "^2.0.0" - }, - "dependencies": { - "type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/type/-/type-2.0.0.tgz", - "integrity": "sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow==" - } - } - }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -4706,12 +4667,9 @@ } }, "follow-redirects": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", - "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", - "requires": { - "debug": "=3.1.0" - } + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.1.tgz", + "integrity": "sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg==" }, "for-in": { "version": "1.0.2", @@ -5544,11 +5502,6 @@ "isobject": "^3.0.1" } }, - "is-promise": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz", - "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==" - }, "is-regex": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", @@ -7445,14 +7398,6 @@ "yallist": "^3.0.2" } }, - "lru-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", - "integrity": "sha1-Jzi9nw089PhEkMVzbEhpmsYyzaM=", - "requires": { - "es5-ext": "~0.10.2" - } - }, "macos-release": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/macos-release/-/macos-release-2.3.0.tgz", @@ -7524,21 +7469,6 @@ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" }, - "memoizee": { - "version": "0.4.14", - "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.14.tgz", - "integrity": "sha512-/SWFvWegAIYAO4NQMpcX+gcra0yEZu4OntmUdrBaWrJncxOqAziGFlHxc7yjKVK2uu3lpPW27P27wkR82wA8mg==", - "requires": { - "d": "1", - "es5-ext": "^0.10.45", - "es6-weak-map": "^2.0.2", - "event-emitter": "^0.3.5", - "is-promise": "^2.1", - "lru-queue": "0.1", - "next-tick": "1", - "timers-ext": "^0.1.5" - } - }, "memory-fs": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", @@ -7916,11 +7846,6 @@ "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", "dev": true }, - "next-tick": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", - "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" - }, "nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", @@ -7937,9 +7862,9 @@ } }, "node-fetch": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", - "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" }, "node-int64": { "version": "0.4.0", @@ -9300,12 +9225,6 @@ } } }, - "serialize-javascript": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz", - "integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==", - "dev": true - }, "serve-static": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", @@ -10180,22 +10099,31 @@ } }, "terser-webpack-plugin": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz", - "integrity": "sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA==", + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz", + "integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==", "dev": true, "requires": { "cacache": "^12.0.2", "find-cache-dir": "^2.1.0", "is-wsl": "^1.1.0", "schema-utils": "^1.0.0", - "serialize-javascript": "^2.1.2", + "serialize-javascript": "^4.0.0", "source-map": "^0.6.1", "terser": "^4.1.2", "webpack-sources": "^1.4.0", "worker-farm": "^1.7.0" }, "dependencies": { + "serialize-javascript": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -10289,15 +10217,6 @@ "setimmediate": "^1.0.4" } }, - "timers-ext": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", - "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==", - "requires": { - "es5-ext": "~0.10.46", - "next-tick": "1" - } - }, "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -10563,11 +10482,6 @@ "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", "dev": true }, - "type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" - }, "type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", diff --git a/backend/package.json b/backend/package.json index b7df95b..e2fcaf7 100644 --- a/backend/package.json +++ b/backend/package.json @@ -20,10 +20,11 @@ "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { - "@nestjs/common": "^7.0.0", + "@nestjs/common": "^7.6.5", "@nestjs/core": "^7.0.0", "@nestjs/platform-express": "^7.0.0", - "node-fetch": "^2.6.0", + "@prisma/client": "^2.14.0", + "node-fetch": "^2.6.1", "reflect-metadata": "^0.1.13", "rimraf": "^3.0.2", "rxjs": "^6.5.4", @@ -33,6 +34,7 @@ "@nestjs/cli": "^7.0.0", "@nestjs/schematics": "^7.0.0", "@nestjs/testing": "^7.0.0", + "@prisma/cli": "^2.14.0", "@types/express": "^4.17.3", "@types/jest": "25.1.4", "@types/node": "^13.9.1", diff --git a/backend/prisma/.env b/backend/prisma/.env new file mode 100644 index 0000000..bb50dd3 --- /dev/null +++ b/backend/prisma/.env @@ -0,0 +1 @@ +DATABASE_URL="mysql://root:lGmKgXmydCT4u2sXHk7IeM@humenius.me:13307/sinusbot?schema=sinusbot" \ No newline at end of file diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma new file mode 100644 index 0000000..c6775cb --- /dev/null +++ b/backend/prisma/schema.prisma @@ -0,0 +1,48 @@ +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "mysql" + url = "mysql://root:lGmKgXmydCT4u2sXHk7IeM@humenius.me:13307/sinusbot?schema=sinusbot" +} + +model ranks { + entry_id Int @id @default(autoincrement()) + season_id Int + rank_id Int @unique + rank_name String + seasons seasons @relation(fields: [season_id], references: [season_id]) + timetracker timetracker[] + + @@unique([season_id, rank_id], name: "ranks_season_id_rank_id_uindex") +} + +model seasons { + season_id Int @id @default(autoincrement()) + start_date DateTime @default(now()) + end_date DateTime? + ranks ranks[] + timetracker timetracker[] +} + +model timetracker { + entry_id Int @id @default(autoincrement()) + user_uid String + season_id Int + rank_id Int? + time Int + ranks ranks? @relation(fields: [rank_id], references: [rank_id]) + seasons seasons @relation(fields: [season_id], references: [season_id]) + user user @relation(fields: [user_uid], references: [uid]) + + @@unique([user_uid, season_id], name: "timetracker_user_uid_season_id_uindex") + @@index([rank_id], name: "timetracker_ranks_rank_id_fk") + @@index([season_id], name: "timetracker_seasons_season_id_fk") +} + +model user { + uid String @id + name String? + timetracker timetracker[] +} diff --git a/backend/src/api/predicate.ts b/backend/src/api/predicate.ts new file mode 100644 index 0000000..fd63b0b --- /dev/null +++ b/backend/src/api/predicate.ts @@ -0,0 +1,3 @@ +export interface Predicate { + process(input: T): O; +} \ No newline at end of file diff --git a/backend/src/api/sort.ts b/backend/src/api/sort.ts new file mode 100644 index 0000000..6da67b6 --- /dev/null +++ b/backend/src/api/sort.ts @@ -0,0 +1,13 @@ +export class Sort { + public static descending = (a: number, b: number) => { + if (a < b) { + return 1; + } + + if (a > b) { + return -1; + } + + return 0; + } +} \ No newline at end of file diff --git a/backend/src/api/timeutil.ts b/backend/src/api/timeutil.ts new file mode 100644 index 0000000..934f84d --- /dev/null +++ b/backend/src/api/timeutil.ts @@ -0,0 +1,11 @@ +export class TimeUtil { + + public static humanize = (seconds: number) => { + const d = Math.floor(seconds / (3600 * 24)); + const h = Math.floor(seconds % (3600 * 24) / 3600); + const m = Math.floor(seconds % 3600 / 60); + const s = Math.floor(seconds % 60); + + return `${d}d ${h}h ${m}m ${s}s`; + } +} \ No newline at end of file diff --git a/backend/src/app.controller.ts b/backend/src/app.controller.ts index 06d5b78..a168a4f 100644 --- a/backend/src/app.controller.ts +++ b/backend/src/app.controller.ts @@ -1,5 +1,7 @@ -import { Controller, Get } from '@nestjs/common'; +import { Controller, Get, HttpException, HttpStatus } from '@nestjs/common'; import { AppService } from './app.service'; +import { DatabaseService } from './database/database.service'; +import logger from './logger/Logger'; import { TableEntry } from "./models/TableEntry"; import { SinusBotService } from "./services/sinusbot.service"; @@ -7,7 +9,8 @@ import { SinusBotService } from "./services/sinusbot.service"; export class AppController { constructor( private readonly appService: AppService, - private readonly sinusBotService: SinusBotService + private readonly sinusBotService: SinusBotService, + private readonly databaseService: DatabaseService ) {} @Get() @@ -15,8 +18,21 @@ export class AppController { return this.appService.getHello(); } - @Get('/stats') - async getStats(): Promise { + @Get('/stats-old') + async getStatsOld(): Promise { return await this.sinusBotService.fetchStats(); } + + @Get('/stats') + async getStats(): Promise { + return this.databaseService.fetchStats() + .then(value => { return value; }) + .catch(err => { + logger.error(`Error occured when fetching stats.`, err); + throw new HttpException( + 'Error when fetching stats. Contact administrator or try again later!', + HttpStatus.INTERNAL_SERVER_ERROR + ); + }); + } } diff --git a/backend/src/app.module.ts b/backend/src/app.module.ts index b52d41f..4248d26 100644 --- a/backend/src/app.module.ts +++ b/backend/src/app.module.ts @@ -2,10 +2,11 @@ import { Module } from '@nestjs/common'; import { AppController } from './app.controller'; import { AppService } from './app.service'; import { SinusBotService } from "./services/sinusbot.service"; +import { DatabaseService } from './database/database.service'; @Module({ imports: [], controllers: [AppController], - providers: [AppService, SinusBotService], + providers: [AppService, SinusBotService, DatabaseService], }) export class AppModule {} diff --git a/backend/src/database/database.service.spec.ts b/backend/src/database/database.service.spec.ts new file mode 100644 index 0000000..b806f31 --- /dev/null +++ b/backend/src/database/database.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { DatabaseService } from './database.service'; + +describe('DatabaseService', () => { + let service: DatabaseService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [DatabaseService], + }).compile(); + + service = module.get(DatabaseService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/backend/src/database/database.service.ts b/backend/src/database/database.service.ts new file mode 100644 index 0000000..ded8efd --- /dev/null +++ b/backend/src/database/database.service.ts @@ -0,0 +1,35 @@ +import { Injectable } from '@nestjs/common'; +import { PrismaClient } from '@prisma/client'; +import logger from 'src/logger/Logger'; +import { TableEntry } from 'src/models/TableEntry'; +import { TimeTrackerPredicate } from './timetracking.predicate'; + +@Injectable() +export class DatabaseService { + // private host = process.env.MYSQL_HOST + // private port = process.env.MYSQL_PORT + // private credentials = { + // username: process.env.MYSQL_USERNAME, + // password: process.env.MYSQL_PASSWORD + // } + // private database = process.env.MYSQL_DATABASE + + constructor( + private readonly prismaClient: PrismaClient, + private readonly timetrackerPredicate: TimeTrackerPredicate + ) {} + + public fetchStats(): Promise { + return new Promise((resolve, reject) => { + this.prismaClient.timetracker.findMany({ + include: { + user: true, + ranks: true + } + }) + .then(this.timetrackerPredicate.process) + .then(result => resolve(result)) + .catch(err => reject(err)); + }); + } +} diff --git a/backend/src/database/timetracking.predicate.ts b/backend/src/database/timetracking.predicate.ts new file mode 100644 index 0000000..2890b44 --- /dev/null +++ b/backend/src/database/timetracking.predicate.ts @@ -0,0 +1,23 @@ +import { ranks, seasons, timetracker, user } from "@prisma/client"; +import { Predicate } from "src/api/predicate"; +import { Sort } from "src/api/sort"; +import { TimeUtil } from "src/api/timeutil"; +import { TableEntry } from "src/models/TableEntry"; + +type UserStats = timetracker & { user: user, ranks: ranks; }; + +export class TimeTrackerPredicate implements Predicate { + + process(input: UserStats[]): TableEntry[] { + return input.filter((userStats: UserStats) => userStats.time != null) + .map((userStats: UserStats) => { + return { + name: userStats.user.name, + rawTime: userStats.time, + onlineTime: TimeUtil.humanize(userStats.time), + rank: userStats.ranks.rank_name + } + }) + .sort((lhs, rhs) => Sort.descending(lhs.rawTime, rhs.rawTime)); + } +} \ No newline at end of file diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 4223f90..9b102e3 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -65,6 +65,7 @@ export default class App extends React.Component { {placement} {entry.name} + {entry.rank} {entry.onlineTime} ) @@ -92,6 +93,7 @@ export default class App extends React.Component { Placement Name + Rank Online time diff --git a/frontend/src/models/TableEntry.ts b/frontend/src/models/TableEntry.ts index 2bb7ac2..8fe8e34 100644 --- a/frontend/src/models/TableEntry.ts +++ b/frontend/src/models/TableEntry.ts @@ -1,4 +1,5 @@ export default interface TableEntry { name: string; + rank: string; onlineTime: string; }