Browse Source

WIP - super basic layout, some lame test data

old-project-state
Dan 2 years ago
parent
commit
2bbc8fe65a
  1. 6
      backend/server.ts
  2. 78
      backend/trackmania-replays/gbx-header.js
  3. 187
      package-lock.json
  4. 4
      package.json
  5. 15
      private.key
  6. 6
      public.key
  7. 166
      server.js
  8. 8
      src/app/app-routing.module.ts
  9. 8
      src/app/app.component.html
  10. 4
      src/app/app.component.less
  11. 4
      src/app/app.component.ts
  12. 38
      src/app/app.module.ts
  13. 18
      src/app/components/login/login.component.html
  14. 0
      src/app/components/login/login.component.less
  15. 21
      src/app/components/login/login.component.spec.ts
  16. 38
      src/app/components/login/login.component.ts
  17. 0
      src/app/components/replay-upload/replay-upload.component.html
  18. 0
      src/app/components/replay-upload/replay-upload.component.less
  19. 0
      src/app/components/replay-upload/replay-upload.component.spec.ts
  20. 2
      src/app/components/replay-upload/replay-upload.component.ts
  21. 15
      src/app/components/season-card/season-card.component.html
  22. 8
      src/app/components/season-card/season-card.component.less
  23. 21
      src/app/components/season-card/season-card.component.spec.ts
  24. 11
      src/app/components/season-card/season-card.component.ts
  25. 16
      src/app/components/season-details/season-details.component.html
  26. 0
      src/app/components/season-details/season-details.component.less
  27. 21
      src/app/components/season-details/season-details.component.spec.ts
  28. 35
      src/app/components/season-details/season-details.component.ts
  29. 1
      src/app/components/season-list/season-list.component.html
  30. 0
      src/app/components/season-list/season-list.component.less
  31. 21
      src/app/components/season-list/season-list.component.spec.ts
  32. 10
      src/app/components/season-list/season-list.component.ts
  33. 29
      src/app/components/season-standings-table/season-standings-table.component.html
  34. 0
      src/app/components/season-standings-table/season-standings-table.component.less
  35. 21
      src/app/components/season-standings-table/season-standings-table.component.spec.ts
  36. 18
      src/app/components/season-standings-table/season-standings-table.component.ts
  37. 5
      src/app/components/seasons/seasons.component.html
  38. 0
      src/app/components/seasons/seasons.component.less
  39. 21
      src/app/components/seasons/seasons.component.spec.ts
  40. 25
      src/app/components/seasons/seasons.component.ts
  41. 13
      src/app/components/top-bar/top-bar.component.html
  42. 3
      src/app/components/top-bar/top-bar.component.less
  43. 21
      src/app/components/top-bar/top-bar.component.spec.ts
  44. 11
      src/app/components/top-bar/top-bar.component.ts
  45. 1
      src/app/components/upload-replay-dialog/upload-replay-dialog.component.html
  46. 0
      src/app/components/upload-replay-dialog/upload-replay-dialog.component.less
  47. 21
      src/app/components/upload-replay-dialog/upload-replay-dialog.component.spec.ts
  48. 10
      src/app/components/upload-replay-dialog/upload-replay-dialog.component.ts
  49. 29
      src/app/components/weekly-standings-table/weekly-standings-table.component.html
  50. 0
      src/app/components/weekly-standings-table/weekly-standings-table.component.less
  51. 21
      src/app/components/weekly-standings-table/weekly-standings-table.component.spec.ts
  52. 13
      src/app/components/weekly-standings-table/weekly-standings-table.component.ts
  53. 25
      src/app/interceptors/auth-interceptor.ts
  54. 36
      src/app/models/season.ts
  55. 25
      src/app/services/api.service.ts
  56. 47
      src/app/services/auth-service.ts

6
backend/server.ts

@ -0,0 +1,6 @@
import {Request, Response} from "express";
import * as express from 'express';
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
import * as jwt from 'jsonwebtoken';
import * as fs from "fs";

78
backend/trackmania-replays/gbx-header.js

@ -0,0 +1,78 @@
class gbxHeader {
has_magic = true
version = -1
format = -1
compressionofRefTable = -1
compressionOfrefBofy = -1
compressionTextFlag = ''
id = -1
userData = []
numNodes = 0
is_vaild = false
parse(buff)
{
let header_magic = buff.readString(3);
if (header_magic != 'GBX')
{
console.log("Header Magic Mismatch: " + header_magic);
return
}
this.has_magic = true
this.version = buff.readUInt16();
console.log(this.version)
if (this.version < 5 || this.version >7 )
{
console.log("Unsupported version")
return;
}
this.format = buff.readInt8();
if (this.format != 66)
{
console.log("Unsupported format")
return;
}
this.compressionofRefTable = buff.readInt8();
if (this.compressionofRefTable != 67 && this.compressionofRefTable != 85)
{
console.log("Unsupported compression format, Ref")
return;
}
this.compressionofRefBody = buff.readInt8();
if (this.compressionofRefBody != 67 && this.compressionofRefBody != 85)
{
console.log("Unsupported compression format, Body")
return;
}
this.compressionTextFlag = '';
if (this.version >= 4)
{
this.compressionTextFlag = buff.readString(1);
}
this.id = buff.readUInt32();
console.log(id)
if (this.version >=6)
{
this.userData = buff.readBytes();
}
this.numNodes = buff.readUInt32();
this.is_vaild = true
}
}
module.exports = gbxHeader;

187
package-lock.json

@ -18,8 +18,12 @@
"@angular/platform-browser": "^16.2.0",
"@angular/platform-browser-dynamic": "^16.2.0",
"@angular/router": "^16.2.0",
"cookie-parser": "^1.4.6",
"express": "^4.18.2",
"express-jwt": "^8.4.1",
"moment": "^2.29.4",
"multer": "^1.4.5-lts.1",
"node-jsonwebtoken": "^0.0.1",
"nodemon": "^3.0.1",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
@ -4312,6 +4316,14 @@
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
"dev": true
},
"node_modules/@types/jsonwebtoken": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.5.tgz",
"integrity": "sha512-VRLSGzik+Unrup6BsouBeHsf4d1hOEgYWTm/7Nmw1sXoN1+tRly/Gy/po3yeahnP4jfnQWWAhQAqcNfH7ngOkA==",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/mime": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
@ -4322,7 +4334,6 @@
"version": "20.9.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.0.tgz",
"integrity": "sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==",
"dev": true,
"dependencies": {
"undici-types": "~5.26.4"
}
@ -5290,6 +5301,11 @@
"ieee754": "^1.1.13"
}
},
"node_modules/buffer-equal-constant-time": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA=="
},
"node_modules/buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
@ -5843,6 +5859,26 @@
"node": ">= 0.6"
}
},
"node_modules/cookie-parser": {
"version": "1.4.6",
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz",
"integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==",
"dependencies": {
"cookie": "0.4.1",
"cookie-signature": "1.0.6"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/cookie-parser/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/cookie-signature": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
@ -6441,6 +6477,14 @@
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
"dev": true
},
"node_modules/ecdsa-sig-formatter": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
"dependencies": {
"safe-buffer": "^5.0.1"
}
},
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
@ -6890,6 +6934,45 @@
"node": ">= 0.10.0"
}
},
"node_modules/express-jwt": {
"version": "8.4.1",
"resolved": "https://registry.npmjs.org/express-jwt/-/express-jwt-8.4.1.tgz",
"integrity": "sha512-IZoZiDv2yZJAb3QrbaSATVtTCYT11OcqgFGoTN4iKVyN6NBkBkhtVIixww5fmakF0Upt5HfOxJuS6ZmJVeOtTQ==",
"dependencies": {
"@types/jsonwebtoken": "^9",
"express-unless": "^2.1.3",
"jsonwebtoken": "^9.0.0"
},
"engines": {
"node": ">= 8.0.0"
}
},
"node_modules/express-jwt/node_modules/jsonwebtoken": {
"version": "9.0.2",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz",
"integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==",
"dependencies": {
"jws": "^3.2.2",
"lodash.includes": "^4.3.0",
"lodash.isboolean": "^3.0.3",
"lodash.isinteger": "^4.0.4",
"lodash.isnumber": "^3.0.3",
"lodash.isplainobject": "^4.0.6",
"lodash.isstring": "^4.0.1",
"lodash.once": "^4.0.0",
"ms": "^2.1.1",
"semver": "^7.5.4"
},
"engines": {
"node": ">=12",
"npm": ">=6"
}
},
"node_modules/express-unless": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/express-unless/-/express-unless-2.1.3.tgz",
"integrity": "sha512-wj4tLMyCVYuIIKHGt0FhCtIViBcwzWejX0EjNxveAa6dG+0XBCQhMbx+PnkLkFCxLC69qoFrxds4pIyL88inaQ=="
},
"node_modules/express/node_modules/array-flatten": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
@ -8595,6 +8678,54 @@
"node >= 0.2.0"
]
},
"node_modules/jsonwebtoken": {
"version": "8.5.1",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz",
"integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==",
"dependencies": {
"jws": "^3.2.2",
"lodash.includes": "^4.3.0",
"lodash.isboolean": "^3.0.3",
"lodash.isinteger": "^4.0.4",
"lodash.isnumber": "^3.0.3",
"lodash.isplainobject": "^4.0.6",
"lodash.isstring": "^4.0.1",
"lodash.once": "^4.0.0",
"ms": "^2.1.1",
"semver": "^5.6.0"
},
"engines": {
"node": ">=4",
"npm": ">=1.4.28"
}
},
"node_modules/jsonwebtoken/node_modules/semver": {
"version": "5.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
"bin": {
"semver": "bin/semver"
}
},
"node_modules/jwa": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz",
"integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==",
"dependencies": {
"buffer-equal-constant-time": "1.0.1",
"ecdsa-sig-formatter": "1.0.11",
"safe-buffer": "^5.0.1"
}
},
"node_modules/jws": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
"integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==",
"dependencies": {
"jwa": "^1.4.1",
"safe-buffer": "^5.0.1"
}
},
"node_modules/karma": {
"version": "6.4.2",
"resolved": "https://registry.npmjs.org/karma/-/karma-6.4.2.tgz",
@ -8939,6 +9070,41 @@
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
"dev": true
},
"node_modules/lodash.includes": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
"integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w=="
},
"node_modules/lodash.isboolean": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
"integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg=="
},
"node_modules/lodash.isinteger": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
"integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA=="
},
"node_modules/lodash.isnumber": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
"integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw=="
},
"node_modules/lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="
},
"node_modules/lodash.isstring": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
"integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw=="
},
"node_modules/lodash.once": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
"integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="
},
"node_modules/log-symbols": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
@ -9675,6 +9841,14 @@
"mkdirp": "bin/cmd.js"
}
},
"node_modules/moment": {
"version": "2.29.4",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
"engines": {
"node": "*"
}
},
"node_modules/mrmime": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz",
@ -9881,6 +10055,14 @@
"node": ">= 8"
}
},
"node_modules/node-jsonwebtoken": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/node-jsonwebtoken/-/node-jsonwebtoken-0.0.1.tgz",
"integrity": "sha512-pkwVycVzovuH9hHmUaO+rAmO4d143d1yvdq+29y/LXWURutp7yt4VU/a1bxmEKc9HyLGwsw81bIEG5hWQAGKxg==",
"dependencies": {
"jsonwebtoken": "^8.5.1"
}
},
"node_modules/node-releases": {
"version": "2.0.13",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz",
@ -12933,8 +13115,7 @@
"node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"dev": true
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
},
"node_modules/unicode-canonical-property-names-ecmascript": {
"version": "2.0.0",

4
package.json

@ -21,8 +21,12 @@
"@angular/platform-browser": "^16.2.0",
"@angular/platform-browser-dynamic": "^16.2.0",
"@angular/router": "^16.2.0",
"cookie-parser": "^1.4.6",
"express": "^4.18.2",
"express-jwt": "^8.4.1",
"moment": "^2.29.4",
"multer": "^1.4.5-lts.1",
"node-jsonwebtoken": "^0.0.1",
"nodemon": "^3.0.1",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",

15
private.key

@ -0,0 +1,15 @@
-----BEGIN RSA PRIVATE KEY-----
MIICWgIBAAKBgGB4qg68oSTGrUmTt2sq7TLMBtxlGYeNZCv1Zhjlhtwp9wGjbWVA
J7pa16o0Pcydo18ApFWCIOnXDyvPwYLoyc/oDA2fVnjdz2MeHNkjD1uIToig8AcP
UaBM8R5QnByRXPyaud8VfVybcCjXJoMzH2dKWsm/O/PmtqV1/dIJyC+ZAgMBAAEC
gYAhDLWV3uGF69qp/kU0HbytTmB7WNdqLPJIbQXRObD99BJ/KTHtIhF6Mmz4DnWt
h8PUZC/oa3BDLD4yUDaHVqDsf4jxOCOeNBuCh24ZCiYXq9gkzX5YFwz/6Leoiccr
WRbfjvaz/Mgi9PfkTijgnbExsEHRhrb/IvIdcKbXQlTqQQJBAL2mgRzvCCQ2oWuI
qgrSEk2CyYJhmOKHbfDazbLvO0b3qtnof9a/T7C28+/8oXfRW2ZJ8eahHkA/WOiw
271Cdx0CQQCCONgR+EQT1hHjpXbVeoRz8gEv8WHvndTjdTYxOMs2k48TV+IZ+wgp
OFkvExdt11W235yA5NX+M8tP0To9+KWtAkBMrf/SNQtzqOsHUZB/I4Tm9hSHtPJd
1SgslCtLR9MN1KGtzYWyzFNqPe7Pf7PBgFKWPxuhhk925qYKH0gZc8A5AkBOMkg9
cjGfH7saUi/rvWhwH3BrE63Vr5c5BxeFsy8EFNOjr/BL3Zxm9DlJtVMqWFZWPCzE
kaWWwg9iXKd2syr5AkBi3Uwd4MMG6AC8nn9mlXVizleN303TNFBc6yGqiPjEJNdL
UcLxu/86PnH8gRA0Iy4b/HPJAKXqHQbtOQTROvWC
-----END RSA PRIVATE KEY-----

6
public.key

@ -0,0 +1,6 @@
-----BEGIN PUBLIC KEY-----
MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgGB4qg68oSTGrUmTt2sq7TLMBtxl
GYeNZCv1Zhjlhtwp9wGjbWVAJ7pa16o0Pcydo18ApFWCIOnXDyvPwYLoyc/oDA2f
Vnjdz2MeHNkjD1uIToig8AcPUaBM8R5QnByRXPyaud8VfVybcCjXJoMzH2dKWsm/
O/PmtqV1/dIJyC+ZAgMBAAE=
-----END PUBLIC KEY-----

166
server.js

@ -1,9 +1,15 @@
//import {Request, Response} from "express";
const express = require('express');
const fs = require('fs');
const app = express();
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
const jwt = require('jsonwebtoken');
const expressJwt = require('express-jwt');
const AdvancableBuffer = require('./backend/utilities/AdvancableBuffer.js');
const gbxHeader = require('./backend/trackmania-replays/gbx-header.js');
// handling CORS
app.use((req, res, next) => {
@ -14,88 +20,118 @@ app.use((req, res, next) => {
next();
});
const upload = require('./backend/routes/upload-replay');
app.use(bodyParser.json());
app.route('/api/login').post(loginRoute);
function readStringAndAdvance(buffer, start, numBytes)
{
let pos = start
let string = buffer.subarray(pos, pos+=numBytes);
return {str : string.toString(), pos : pos};
}
const RSA_PRIVATE_KEY = fs.readFileSync('./private.key');
const RSA_PUBLIC_KEY = fs.readFileSync('./public.key');
function readUInt16AndAdvance(buffer, start)
{
let pos = start;
let t = buffer.subarray(pos, pos+=1);
let value = t.readInt8(0)
return {value: value, pos: pos};
}
function readInt8AndAdvance(buffer, start)
{
let pos = start;
let t = buffer.subarray(pos, pos+=2);
let value = t.readUInt16LE(0)
return {value: value, pos: pos};
}
const checkIfAuthenticated = expressJwt({
secret: RSA_PUBLIC_KEY
});
// route for handling requests from the Angular client
app.post('/api/upload-replay', upload.single('file'), (req, res) => {
let file = req.file;
console.log("File uploaded: ", req.file);
fs.readFile(file.path, function(err, buffer)
{
buff = new AdvancableBuffer(buffer);
console.log(buffer)
let header_magic = buff.readString(3);
console.log(header_magic);
/*export */function loginRoute(req, res) {
let version = buff.readUInt16();
console.log(version)
const email = req.body.email
const password = req.body.password;
if (version < 5 || version >7 )
if (validateEmailAndPassword(email, password))
{
console.log("Unsupported version")
return;
const userId = findUserIdForEmail(email);
const jwtBearerToken = jwt.sign({}, RSA_PRIVATE_KEY, {
algorithm: 'RS256',
expiresIn: 120,
subject: userId
});
res.status(200).json({
idToken: jwtBearerToken,
expiresIn: 120
});
}
let format = buff.readInt8();
if (format != 66)
{
console.log("Unsupported format")
return;
else {
// send status 401 Unauthorized
res.sendStatus(401);
}
}
let compressionofRefTable = buff.readInt8();
//app.route('/api/session/create').get(checkIfAuthenticated, )
if (compressionofRefTable != 67 && compressionofRefTable != 85)
{
console.log("Unsupported compression format, Ref")
return;
}
const upload = require('./backend/routes/upload-replay');
let compressionofRefBody = buff.readInt8();
if (compressionofRefBody != 67 && compressionofRefBody != 85)
{
console.log("Unsupported compression format, Body")
return;
}
app.get('/api/seasons', (req, res) => {
seasons = [{
id: 1,
seasonName: "Season 1",
seasonTag: "Post Winter Blues",
seasonCardImage: "",
seasonHeaderImage: "",
seasonStartDate: "",
seasonSendDate: "",
seasonId: "",
seasonDesc: "",
}]
res.json({seasons: seasons});
});
let unknownByte = '';
if (version >= 4)
app.get('/api/seasons/:id', (req, res) => {
data = {
details: {
id: 1,
seasonName: "Season 1",
seasonTag: "Post Winter Blues",
seasonCardImage: "",
seasonHeaderImage: "",
seasonStartDate: "",
seasonSendDate: "",
seasonId: "",
seasonDesc: "",
},
standings: [{
position: 1,
points: 4,
user: {
realName: "Dan H",
gamerHandle: "Quildra",
}
},
{
unknownByte = buff.readString(1);
position: 2,
points: 2,
user: {
realName: "Dan Mc",
gamerHandle: "Mini-Quildra",
}
let id = buff.readUInt32();
console.log(id)
if (version >=6)
},
],
weeks:[{
id: "1",
map: "bob",
mapImg: "bob.jpg",
entries: [
{
userData = buff.readBytes()
position: 1,
runTime: 4.0,
user: {
realName: "Dan H",
gamerHandle: "Quildra",
}
}
]
}]
}
res.json({data: data});
});
let numNodes = buff.readUInt32();
// route for handling requests from the Angular client
app.post('/api/upload-replay', upload.single('file'), (req, res) => {
let file = req.file;
console.log("File uploaded: ", req.file);
fs.readFile(file.path, function(err, buffer)
{
buff = new AdvancableBuffer(buffer);
header = new gbxHeader().parse(buffer);
})
});

8
src/app/app-routing.module.ts

@ -1,7 +1,13 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { SeasonsComponent } from './components/seasons/seasons.component';
import { SeasonDetailsComponent } from './components/season-details/season-details.component';
const routes: Routes = [];
const routes: Routes = [
{ path: '', redirectTo: '/seasons', pathMatch: 'full' },
{ path: 'seasons', component: SeasonsComponent },
{ path: 'seasons/:id', component: SeasonDetailsComponent },
];
@NgModule({
imports: [RouterModule.forRoot(routes)],

8
src/app/app.component.html

@ -1,3 +1,5 @@
<h1>{{title}}</h1>
<app-replay-upload></app-replay-upload>
<router-outlet></router-outlet>
<app-top-bar></app-top-bar>
<div class="main">
<router-outlet></router-outlet>
</div>

4
src/app/app.component.less

@ -0,0 +1,4 @@
.main {
margin: 1rem auto;
width: 80%;
}

4
src/app/app.component.ts

@ -1,5 +1,5 @@
import { Component } from '@angular/core';
import { ApiService } from './api.service';
import { ApiService } from './services/api.service';
@Component({
selector: 'app-root',
@ -10,6 +10,4 @@ export class AppComponent {
title = 'Bridge';
message: any;
constructor(private apiService: ApiService) { };
ngOnInit() {
}
}

38
src/app/app.module.ts

@ -4,16 +4,41 @@ import { HttpClientModule } from "@angular/common/http";
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { ReplayUploadComponent } from './replay-upload/replay-upload.component';
import { ReplayUploadComponent } from './components/replay-upload/replay-upload.component';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MatIconModule } from '@angular/material/icon';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { MatButtonModule } from '@angular/material/button'
import { MatButtonModule } from '@angular/material/button';
import { MatToolbarModule } from '@angular/material/toolbar';
import { TopBarComponent } from './components/top-bar/top-bar.component';
import { WeeklyStandingsTableComponent } from './components/weekly-standings-table/weekly-standings-table.component';
import { SeasonStandingsTableComponent } from './components/season-standings-table/season-standings-table.component';
import { UploadReplayDialogComponent } from './components/upload-replay-dialog/upload-replay-dialog.component';
import { SeasonListComponent } from './components/season-list/season-list.component';
import { SeasonCardComponent } from './components/season-card/season-card.component';
import { MatCardModule } from '@angular/material/card';
import { SeasonsComponent } from './components/seasons/seasons.component';
import { SeasonDetailsComponent } from './components/season-details/season-details.component';
import { MatTableModule } from '@angular/material/table';
import { MatDividerModule } from '@angular/material/divider';
import { MatTabsModule } from '@angular/material/tabs';
import { LoginComponent } from './components/login/login.component';
import { ReactiveFormsModule } from '@angular/forms';
import { ObserversModule } from '@angular/cdk/observers';
@NgModule({
declarations: [
AppComponent,
ReplayUploadComponent
ReplayUploadComponent,
TopBarComponent,
WeeklyStandingsTableComponent,
SeasonStandingsTableComponent,
UploadReplayDialogComponent,
SeasonListComponent,
SeasonCardComponent,
SeasonsComponent,
SeasonDetailsComponent,
LoginComponent
],
imports: [
BrowserModule,
@ -22,6 +47,13 @@ import { MatButtonModule } from '@angular/material/button'
MatIconModule,
MatSlideToggleModule,
MatButtonModule,
MatToolbarModule,
MatCardModule,
MatTableModule,
MatDividerModule,
MatTabsModule,
ReactiveFormsModule,
ObserversModule,
HttpClientModule
],
providers: [],

18
src/app/components/login/login.component.html

@ -0,0 +1,18 @@
<form [formGroup]="form">
<fieldset>
<legend>Login</legend>
<div class="form-field">
<label>Email:</label>
<input name="email" formControlName="email">
</div>
<div class="form-field">
<label>Password:</label>
<input name="password" formControlName="password"
type="password">
</div>
</fieldset>
<div class="form-buttons">
<button class="button button-primary"
(click)="login()">Login</button>
</div>
</form>

0
src/app/components/login/login.component.less

21
src/app/components/login/login.component.spec.ts

@ -0,0 +1,21 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { LoginComponent } from './login.component';
describe('LoginComponent', () => {
let component: LoginComponent;
let fixture: ComponentFixture<LoginComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [LoginComponent]
});
fixture = TestBed.createComponent(LoginComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

38
src/app/components/login/login.component.ts

@ -0,0 +1,38 @@
import { Component } from '@angular/core';
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { AuthService } from '../../services/auth-service';
@Component({
selector: 'app-login',
templateUrl: './login.component.html',
styleUrls: ['./login.component.less']
})
export class LoginComponent {
form:FormGroup;
constructor(private fb:FormBuilder,
private authService: AuthService,
private router: Router)
{
this.form = this.fb.group({
email: ['',Validators.required],
password: ['',Validators.required]
});
}
login() {
const val = this.form.value;
if (val.email && val.password) {
this.authService.login(val.email, val.password)
.subscribe(
() => {
console.log("User is logged in");
this.router.navigateByUrl('/');
}
);
}
}
}

0
src/app/replay-upload/replay-upload.component.html → src/app/components/replay-upload/replay-upload.component.html

0
src/app/replay-upload/replay-upload.component.less → src/app/components/replay-upload/replay-upload.component.less

0
src/app/replay-upload/replay-upload.component.spec.ts → src/app/components/replay-upload/replay-upload.component.spec.ts

2
src/app/replay-upload/replay-upload.component.ts → src/app/components/replay-upload/replay-upload.component.ts

@ -1,6 +1,6 @@
import { HttpClient } from '@angular/common/http';
import { Component } from '@angular/core';
import { ApiService } from '../api.service';
import { ApiService } from '../../services/api.service';
@Component({
selector: 'app-replay-upload',

15
src/app/components/season-card/season-card.component.html

@ -0,0 +1,15 @@
<mat-card class="example-card" routerLink="/seasons/{{season?.id}}">
<mat-card-header>
<div mat-card-avatar class="example-header-image"></div>
<mat-card-title>{{season?.seasonName}}</mat-card-title>
<mat-card-subtitle>{{season?.seasonTag}}</mat-card-subtitle>
</mat-card-header>
<img mat-card-image src="https://material.angular.io/assets/img/examples/shiba2.jpg" alt="Photo of a Shiba Inu">
<mat-card-content>
<p>
The Shiba Inu is the smallest of the six original and distinct spitz breeds of dog from Japan.
A small, agile dog that copes very well with mountainous terrain, the Shiba Inu was originally
bred for hunting.
</p>
</mat-card-content>
</mat-card>

8
src/app/components/season-card/season-card.component.less

@ -0,0 +1,8 @@
.example-card {
max-width: 400px;
}
.example-header-image {
background-image: url('https://material.angular.io/assets/img/examples/shiba1.jpg');
background-size: cover;
}

21
src/app/components/season-card/season-card.component.spec.ts

@ -0,0 +1,21 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SeasonCardComponent } from './season-card.component';
describe('SeasonCardComponent', () => {
let component: SeasonCardComponent;
let fixture: ComponentFixture<SeasonCardComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [SeasonCardComponent]
});
fixture = TestBed.createComponent(SeasonCardComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

11
src/app/components/season-card/season-card.component.ts

@ -0,0 +1,11 @@
import { Component, Input } from '@angular/core';
import { Season } from '../../models/season';
@Component({
selector: 'app-season-card',
templateUrl: './season-card.component.html',
styleUrls: ['./season-card.component.less']
})
export class SeasonCardComponent {
@Input() season?: Season;
}

16
src/app/components/season-details/season-details.component.html

@ -0,0 +1,16 @@
<div *ngIf="seasonInfo && seasonStandings">
<h1>{{seasonInfo.seasonName}}</h1>
<h3>{{seasonInfo.seasonTag}}</h3>
<mat-divider></mat-divider>
<br/>
<mat-tab-group>
<mat-tab label="Standings">
<app-season-standings-table [dataSource]="seasonStandings"></app-season-standings-table>
</mat-tab>
<div *ngFor="let week of weeks">
<mat-tab label="Second">
<app-weekly-standings-table [dataSource]="week.entries"></app-weekly-standings-table>
</mat-tab>
</div>
</mat-tab-group>
</div>

0
src/app/components/season-details/season-details.component.less

21
src/app/components/season-details/season-details.component.spec.ts

@ -0,0 +1,21 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SeasonDetailsComponent } from './season-details.component';
describe('SeasonDetailsComponent', () => {
let component: SeasonDetailsComponent;
let fixture: ComponentFixture<SeasonDetailsComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [SeasonDetailsComponent]
});
fixture = TestBed.createComponent(SeasonDetailsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

35
src/app/components/season-details/season-details.component.ts

@ -0,0 +1,35 @@
import { Component } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { ApiService } from '../../services/api.service';
import { Season, SeasonWeek, SeasonStandingsEntry } from '../../models/season';
@Component({
selector: 'app-season-details',
templateUrl: './season-details.component.html',
styleUrls: ['./season-details.component.less']
})
export class SeasonDetailsComponent {
seasonInfo?: Season;
seasonStandings?: SeasonStandingsEntry[];
weeks?: SeasonWeek[];
constructor(
private route: ActivatedRoute,
private apiService: ApiService,
//private location: Location
)
{
}
ngOnInit(): void {
const id = Number(this.route.snapshot.paramMap.get('id'));
this.apiService.getSeasonDetails(id).subscribe(data => {
this.seasonInfo = (data as any).data.details;
this.weeks = (data as any).data.weeks;
this.seasonStandings = (data as any).data.standings;
console.log(this.seasonStandings);
console.log(this.weeks);
});
}
}

1
src/app/components/season-list/season-list.component.html

@ -0,0 +1 @@
<p>season-list works!</p>

0
src/app/components/season-list/season-list.component.less

21
src/app/components/season-list/season-list.component.spec.ts

@ -0,0 +1,21 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SeasonListComponent } from './season-list.component';
describe('SeasonListComponent', () => {
let component: SeasonListComponent;
let fixture: ComponentFixture<SeasonListComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [SeasonListComponent]
});
fixture = TestBed.createComponent(SeasonListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

10
src/app/components/season-list/season-list.component.ts

@ -0,0 +1,10 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-season-list',
templateUrl: './season-list.component.html',
styleUrls: ['./season-list.component.less']
})
export class SeasonListComponent {
}

29
src/app/components/season-standings-table/season-standings-table.component.html

@ -0,0 +1,29 @@
<div *ngIf="dataSource">
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
<ng-container matColumnDef="position">
<th mat-header-cell *matHeaderCellDef> No. </th>
<td mat-cell *matCellDef="let element"> {{element.position}} </td>
</ng-container>
<!-- Name Column -->
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef> Name </th>
<td mat-cell *matCellDef="let element"> {{element.user.realName}} </td>
</ng-container>
<!-- Weight Column -->
<ng-container matColumnDef="points">
<th mat-header-cell *matHeaderCellDef> runTime </th>
<td mat-cell *matCellDef="let element"> {{element.points}} </td>
</ng-container>
<!-- Symbol Column -->
<ng-container matColumnDef="gameHandle">
<th mat-header-cell *matHeaderCellDef> gamerTag </th>
<td mat-cell *matCellDef="let element"> {{element.user.gamerHandle}} </td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
</div>

0
src/app/components/season-standings-table/season-standings-table.component.less

21
src/app/components/season-standings-table/season-standings-table.component.spec.ts

@ -0,0 +1,21 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SeasonStandingsTableComponent } from './season-standings-table.component';
describe('SeasonStandingsTableComponent', () => {
let component: SeasonStandingsTableComponent;
let fixture: ComponentFixture<SeasonStandingsTableComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [SeasonStandingsTableComponent]
});
fixture = TestBed.createComponent(SeasonStandingsTableComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

18
src/app/components/season-standings-table/season-standings-table.component.ts

@ -0,0 +1,18 @@
import { Component, Input } from '@angular/core';
import { SeasonStandingsEntry } from '../../models/season';
@Component({
selector: 'app-season-standings-table',
templateUrl: './season-standings-table.component.html',
styleUrls: ['./season-standings-table.component.less']
})
export class SeasonStandingsTableComponent {
displayedColumns: string[] = ['position', 'name', 'gameHandle', 'points'];
@Input() dataSource?: SeasonStandingsEntry[];
constructor()
{
}
}

5
src/app/components/seasons/seasons.component.html

@ -0,0 +1,5 @@
<div *ngIf="seasons">
<div *ngFor="let season of seasons">
<app-season-card [season]="season"></app-season-card>
</div>
</div>

0
src/app/components/seasons/seasons.component.less

21
src/app/components/seasons/seasons.component.spec.ts

@ -0,0 +1,21 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SeasonsComponent } from './seasons.component';
describe('SeasonsComponent', () => {
let component: SeasonsComponent;
let fixture: ComponentFixture<SeasonsComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [SeasonsComponent]
});
fixture = TestBed.createComponent(SeasonsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

25
src/app/components/seasons/seasons.component.ts

@ -0,0 +1,25 @@
import { Component } from '@angular/core';
import { Season } from '../../models/season';
import { ApiService } from '../../services/api.service';
@Component({
selector: 'app-seasons',
templateUrl: './seasons.component.html',
styleUrls: ['./seasons.component.less']
})
export class SeasonsComponent
{
seasons: Season[];
constructor(private apiService: ApiService)
{
this.seasons = [];
};
ngOnInit() {
this.apiService.getSeasons().subscribe(data => {
this.seasons = (data as any).seasons;
});
}
}

13
src/app/components/top-bar/top-bar.component.html

@ -0,0 +1,13 @@
<mat-toolbar>
<button mat-icon-button class="example-icon" aria-label="Example icon-button with menu icon">
<mat-icon>menu</mat-icon>
</button>
<span>Stainless Trackmanaia Tournament Tracker</span>
<span class="example-spacer"></span>
<button mat-icon-button class="example-icon favorite-icon" aria-label="Example icon-button with heart icon">
<mat-icon>favorite</mat-icon>
</button>
<button mat-icon-button class="example-icon" aria-label="Example icon-button with share icon">
<mat-icon>share</mat-icon>
</button>
</mat-toolbar>

3
src/app/components/top-bar/top-bar.component.less

@ -0,0 +1,3 @@
.example-spacer {
flex: 1 1 auto;
}

21
src/app/components/top-bar/top-bar.component.spec.ts

@ -0,0 +1,21 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { TopBarComponent } from './top-bar.component';
describe('TopBarComponent', () => {
let component: TopBarComponent;
let fixture: ComponentFixture<TopBarComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [TopBarComponent]
});
fixture = TestBed.createComponent(TopBarComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

11
src/app/components/top-bar/top-bar.component.ts

@ -0,0 +1,11 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-top-bar',
templateUrl: './top-bar.component.html',
styleUrls: ['./top-bar.component.less']
})
export class TopBarComponent {
}

1
src/app/components/upload-replay-dialog/upload-replay-dialog.component.html

@ -0,0 +1 @@
<p>upload-replay-dialog works!</p>

0
src/app/components/upload-replay-dialog/upload-replay-dialog.component.less

21
src/app/components/upload-replay-dialog/upload-replay-dialog.component.spec.ts

@ -0,0 +1,21 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { UploadReplayDialogComponent } from './upload-replay-dialog.component';
describe('UploadReplayDialogComponent', () => {
let component: UploadReplayDialogComponent;
let fixture: ComponentFixture<UploadReplayDialogComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [UploadReplayDialogComponent]
});
fixture = TestBed.createComponent(UploadReplayDialogComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

10
src/app/components/upload-replay-dialog/upload-replay-dialog.component.ts

@ -0,0 +1,10 @@
import { Component } from '@angular/core';
@Component({
selector: 'app-upload-replay-dialog',
templateUrl: './upload-replay-dialog.component.html',
styleUrls: ['./upload-replay-dialog.component.less']
})
export class UploadReplayDialogComponent {
}

29
src/app/components/weekly-standings-table/weekly-standings-table.component.html

@ -0,0 +1,29 @@
<div *ngIf="dataSource">
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
<ng-container matColumnDef="position">
<th mat-header-cell *matHeaderCellDef> No. </th>
<td mat-cell *matCellDef="let element"> {{element.position}} </td>
</ng-container>
<!-- Name Column -->
<ng-container matColumnDef="name">
<th mat-header-cell *matHeaderCellDef> Name </th>
<td mat-cell *matCellDef="let element"> {{element.user.realName}} </td>
</ng-container>
<!-- Weight Column -->
<ng-container matColumnDef="runTime">
<th mat-header-cell *matHeaderCellDef> runTime </th>
<td mat-cell *matCellDef="let element"> {{element.runTime}} </td>
</ng-container>
<!-- Symbol Column -->
<ng-container matColumnDef="gameHandle">
<th mat-header-cell *matHeaderCellDef> gamerTag </th>
<td mat-cell *matCellDef="let element"> {{element.user.gamerHandle}} </td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
</div>

0
src/app/components/weekly-standings-table/weekly-standings-table.component.less

21
src/app/components/weekly-standings-table/weekly-standings-table.component.spec.ts

@ -0,0 +1,21 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { WeeklyStandingsTableComponent } from './weekly-standings-table.component';
describe('WeeklyStandingsTableComponent', () => {
let component: WeeklyStandingsTableComponent;
let fixture: ComponentFixture<WeeklyStandingsTableComponent>;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [WeeklyStandingsTableComponent]
});
fixture = TestBed.createComponent(WeeklyStandingsTableComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

13
src/app/components/weekly-standings-table/weekly-standings-table.component.ts

@ -0,0 +1,13 @@
import { Component, Input } from '@angular/core';
import { SeasonWeekEntry } from '../../models/season';
@Component({
selector: 'app-weekly-standings-table',
templateUrl: './weekly-standings-table.component.html',
styleUrls: ['./weekly-standings-table.component.less']
})
export class WeeklyStandingsTableComponent {
displayedColumns: string[] = ['position', 'name', 'gameHandle', 'runTime'];
@Input() dataSource?: SeasonWeekEntry[];
}

25
src/app/interceptors/auth-interceptor.ts

@ -0,0 +1,25 @@
import { Injectable } from '@angular/core';
import { HttpInterceptor, HttpRequest, HttpHandler, HttpEvent } from "@angular/common/http";
import { Observable } from 'rxjs';
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>,
next: HttpHandler): Observable<HttpEvent<any>> {
const idToken = localStorage.getItem("id_token");
if (idToken) {
const cloned = req.clone({
headers: req.headers.set("Authorization",
"Bearer " + idToken)
});
return next.handle(cloned);
}
else {
return next.handle(req);
}
}
}

36
src/app/models/season.ts

@ -0,0 +1,36 @@
export interface Season {
id: string,
seasonName: string;
seasonTag: string;
seasonCardImage: string;
seasonHeaderImage: string;
seasonStartDate: string;
seasonSendDate: string;
seasonId: string;
seasonDesc: string;
}
export interface BridgeUser {
id: string;
realName: string;
gameHandle: string;
}
export interface SeasonStandingsEntry {
position: string;
points: number;
user: BridgeUser;
}
export interface SeasonWeekEntry {
position: string;
runTime: string;
user: BridgeUser;
}
export interface SeasonWeek {
id: string;
mapName: string;
mapImg: string;
entries: SeasonWeekEntry[];
}

25
src/app/api.service.ts → src/app/services/api.service.ts

@ -5,15 +5,34 @@ import { HttpClient } from '@angular/common/http';
@Injectable({
providedIn: 'root'
})
export class ApiService {
export class ApiService
{
server_route = 'http://localhost:3000'
constructor(private http: HttpClient) { }
getMessage() {
getMessage()
{
return this.http.get(
'http://localhost:3000/api/message');
}
postReplayUpload(file: any) {
postReplayUpload(file: any)
{
return this.http.post(
this.server_route+'/api/upload-replay', file);
}
getSeasons()
{
return this.http.get(
this.server_route+'/api/seasons');
}
getSeasonDetails(id: number)
{
return this.http.get(
this.server_route+'/api/seasons/'+id);
}
}

47
src/app/services/auth-service.ts

@ -0,0 +1,47 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { shareReplay, tap } from 'rxjs/operators'
import * as moment from "moment";
@Injectable({
providedIn: 'root'
})
export class AuthService
{
constructor(private http: HttpClient) { }
login(email: string, password: string)
{
return this.http.post('/api/login', {email, password}).pipe(
tap( res => this.setSession ),
shareReplay()
);
}
private setSession(authResult: any) {
const expiresAt = moment().add((authResult as any).expiresIn,'second');
localStorage.setItem('id_token', (authResult as any).idToken);
localStorage.setItem("expires_at", JSON.stringify(expiresAt.valueOf()) );
}
logout() {
localStorage.removeItem("id_token");
localStorage.removeItem("expires_at");
}
public isLoggedIn() {
return moment().isBefore(this.getExpiration());
}
isLoggedOut() {
return !this.isLoggedIn();
}
getExpiration() {
const expiration = localStorage.getItem("expires_at");
const expiresAt = JSON.parse(expiration ? expiration : '');
return moment(expiresAt);
}
}
Loading…
Cancel
Save