From f6904174386efcb7742b8b6a8aaaf33e36e9f4d5 Mon Sep 17 00:00:00 2001 From: Dan Date: Mon, 13 Nov 2023 11:28:57 +0000 Subject: [PATCH] Ability to add a new Race to a season --- server.js | 167 ++++++++++-------- src/app/app.module.ts | 4 +- src/app/components/login/login.component.ts | 2 +- .../new-race-dialog.component.html | 12 ++ .../new-race-dialog.component.less | 0 .../new-race-dialog.component.spec.ts | 21 +++ .../new-race-dialog.component.ts | 32 ++++ .../season-card/season-card.component.html | 6 +- .../season-details.component.html | 17 +- .../season-details.component.ts | 48 ++++- .../components/seasons/seasons.component.ts | 1 + src/app/models/season.ts | 15 +- src/app/services/api.service.ts | 9 +- 13 files changed, 233 insertions(+), 101 deletions(-) create mode 100644 src/app/components/new-race-dialog/new-race-dialog.component.html create mode 100644 src/app/components/new-race-dialog/new-race-dialog.component.less create mode 100644 src/app/components/new-race-dialog/new-race-dialog.component.spec.ts create mode 100644 src/app/components/new-race-dialog/new-race-dialog.component.ts diff --git a/server.js b/server.js index 1d4f3d9..3327ca0 100644 --- a/server.js +++ b/server.js @@ -21,9 +21,9 @@ class Season extends Document { constructor() { super(); - this.name = String; - this.subtitle = String; - this.seasonStartDate = Date; + this.title = String; + this.subTitle = String; + this.seasonStartDate = String; } } @@ -33,8 +33,8 @@ class Race extends Document { super(); this.weekNumber = Number; - this.raceStartDate = Date; - this.raceEndDate = Date; + this.startDate = Date; + this.endDate = Date; this.mapName = String; this.mapLink = String; this.season = Season; @@ -53,7 +53,7 @@ class RaceResult extends Document { } } -connect('nedb://./nedb-database.db'); +connect('nedb://./nedb-database'); const fs = require('fs'); const app = express(); @@ -70,77 +70,26 @@ app.use(bodyParser.json()); const RSA_PRIVATE_KEY = "Secret_KeY"; const RSA_PUBLIC_KEY = fs.readFileSync('./public.key'); - const users = [ { id: 1, username: 'user1', password: 'password1' }, { id: 2, username: 'user2', password: 'password2' } ]; -const seasons = [ - { id: 1, seasonName: "Season 1", seasonTag: "Post Winter Blues", seasonCardImage: "", seasonHeaderImage: "", seasonStartDate: "", seasonEndDate: "", seasonDesc: "",}, - { id: 2, seasonName: "Season 2", seasonTag: "Post Winter Blues", seasonCardImage: "", seasonHeaderImage: "", seasonStartDate: "", seasonEndDate: "", seasonDesc: "",} -] - -const season_details = { - 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", - } - }, - { - position: 2, - points: 2, - user: { - realName: "Dan Mc", - gamerHandle: "Mini-Quildra", - } - }, - ], - weeks:[{ - id: "1", - map: "bob", - mapImg: "bob.jpg", - entries: [ - { - position: 1, - runTime: 4.0, - user: { - realName: "Dan H", - gamerHandle: "Quildra", - } - } - ] - }] -} - function verifyToken(req, res, next) { const token = req.headers['authorization']; + console.log(token); - if (!token) { - return res.status(403).json({ message: 'Token not provided' }); + if (!token || !token.startsWith('Bearer ')) { + return res.status(401).json({ message: 'Unauthorized' }); } - jwt.verify(token, RSA_PRIVATE_KEY, (err, decoded) => { + const tokenWithoutBearer = token.slice(7); // Remove 'Bearer ' prefix + jwt.verify(tokenWithoutBearer, RSA_PRIVATE_KEY, (err, decoded) => { if (err) { - return res.status(401).json({ message: 'Failed to authenticate token', - error: err}); + console.error(err); + return res.status(401).json({ message: 'Invalid token' }); } - - req.userId = decoded.userId; + req.user = decoded.user; next(); }); } @@ -156,33 +105,99 @@ app.post('/api/login', (req, res) => { } // Generate and return a JWT token - const token = jwt.sign({ userId: user.id, username: user.username }, RSA_PRIVATE_KEY, { expiresIn: '1h' }); + const token = jwt.sign({ userId: user.id }, RSA_PRIVATE_KEY, { expiresIn: '1h', algorithm: 'HS256' }); + console.log(token); res.json({ token }); }); -// Protected route example -app.get('/api/profile', verifyToken, (req, res) => { - const user = users.find(u => u.id === req.userId); - res.json({ username: user.username, userId: user.id }); -}); - app.get('/api/seasons', async (req, res) => { const seasons = await Season.find(); res.json({seasons: seasons}); }); -app.get('/api/seasons/:id', (req, res) => { - res.json({data: season_details}); +function remapRaceInfo(raceRecord) { + modableRaceObject = { + weekNumber: raceRecord.weekNumber, + startDate: raceRecord.startDate, + endDate: raceRecord.endDate, + mapName: raceRecord.mapName, + mapLink: raceRecord.mapLink, + season: raceRecord.season, + results: [] + } + + return modableRaceObject; +} + +app.get('/api/seasons/:id', async (req, res) => { + const { id } = req.params; + try { + let races = [] + let racersInSeason = []; + const seasonDetails = await Season.findOne({_id: id}); + const seasonRaceDetails = await Race.find({season: id}); + for (let i = 0; i < seasonRaceDetails.length; i++) { + let raceRecord = seasonRaceDetails[i]; + let modableRace = remapRaceInfo(raceRecord); + + const fastestTimesMap = new Map(); + const raceResults = await RaceResult.find('racer', { 'race._id': raceRecord._id }); + + raceResults.forEach(result => { + const racerId = result.racer._id; + const timeInSeconds = result.timeInSeconds; + + if (!fastestTimesMap.has(racerId) || timeInSeconds < fastestTimesMap.get(racerId)) { + fastestTimesMap.set(racerId, timeInSeconds); + } + }); + + modableRace.results = Array.from(fastestTimesMap.entries()).map(([racerId, timeInSeconds]) => ({ + racerId, + timeInSeconds + })); + + // This is wrong it will get only the racers the in last race it processes. + racersInSeason = Array.from(new Set(raceResults.map(result => result.racer))); + + races.push(modableRace); + } + + let i = 1; + let standings = [] + racersInSeason.forEach((racer) => { + let entry = {} + entry._id = i; + entry.position = i; + entry.points = 100-i; + entry.user = racer; + standings.push(entry); + }); + + res.json({season: seasonDetails, races: races, standings: standings}); + } + catch( error ) { + console.error(error) + res.status(500).send('Internal Server Error'); + } }); app.post('/api/seasons/add', verifyToken, async (req, res) => { - console.log("New Seaosn Time"); const { name, subtitle, seasonStartDate } = req.body; - const season = await Season.create({ name, subtitle, seasonStartDate }); + const season = await Season.create({ name, subtitle, seasonStartDate }).save(); console.log(season); res.json(season); }); +app.post('/api/race/add', verifyToken, async (req, res) => { + const { seasonId, startDate, endDate, mapName, mapLink, weekNumber } = req.body; + const season = await Season.findOne({_id: seasonId}); + const race = await Race.create({ weekNumber, startDate, endDate, mapName, mapLink, season }) + console.log(race); + race.save(); + res.json(race); +}); + // route for handling requests from the Angular client app.post('/api/upload-replay', upload.single('file'), (req, res) => { let file = req.file; diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 89ae213..30da9c6 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -31,6 +31,7 @@ import { NewSeasonDialogComponent } from './components/new-season-dialog/new-sea import { MatMenuModule } from '@angular/material/menu'; import { FormsModule } from '@angular/forms'; import { MatDialogModule } from '@angular/material/dialog'; +import { NewRaceDialogComponent } from './components/new-race-dialog/new-race-dialog.component'; @NgModule({ declarations: [ @@ -47,7 +48,8 @@ import { MatDialogModule } from '@angular/material/dialog'; LoginComponent, AdminComponent, NewSeasonCardComponent, - NewSeasonDialogComponent + NewSeasonDialogComponent, + NewRaceDialogComponent ], imports: [ BrowserModule, diff --git a/src/app/components/login/login.component.ts b/src/app/components/login/login.component.ts index c6ae592..6246cf9 100644 --- a/src/app/components/login/login.component.ts +++ b/src/app/components/login/login.component.ts @@ -23,7 +23,7 @@ export class LoginComponent { (response) => { if (response.token) { localStorage.setItem('token', response.token); - this.router.navigate(['/profile']); + this.router.navigate(['/']); } }, (error) => console.error(error) diff --git a/src/app/components/new-race-dialog/new-race-dialog.component.html b/src/app/components/new-race-dialog/new-race-dialog.component.html new file mode 100644 index 0000000..71f14ea --- /dev/null +++ b/src/app/components/new-race-dialog/new-race-dialog.component.html @@ -0,0 +1,12 @@ +

New Season

+
+

Map Name:

+

Link:

+

Starting Date:

+

End Date:

+

Week#:

+
+
+ + +
diff --git a/src/app/components/new-race-dialog/new-race-dialog.component.less b/src/app/components/new-race-dialog/new-race-dialog.component.less new file mode 100644 index 0000000..e69de29 diff --git a/src/app/components/new-race-dialog/new-race-dialog.component.spec.ts b/src/app/components/new-race-dialog/new-race-dialog.component.spec.ts new file mode 100644 index 0000000..882081d --- /dev/null +++ b/src/app/components/new-race-dialog/new-race-dialog.component.spec.ts @@ -0,0 +1,21 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { NewRaceDialogComponent } from './new-race-dialog.component'; + +describe('NewRaceDialogComponent', () => { + let component: NewRaceDialogComponent; + let fixture: ComponentFixture; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [NewRaceDialogComponent] + }); + fixture = TestBed.createComponent(NewRaceDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/new-race-dialog/new-race-dialog.component.ts b/src/app/components/new-race-dialog/new-race-dialog.component.ts new file mode 100644 index 0000000..5c39b36 --- /dev/null +++ b/src/app/components/new-race-dialog/new-race-dialog.component.ts @@ -0,0 +1,32 @@ +import { Component, OnInit, Inject } from '@angular/core'; +import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { ApiService } from 'src/app/services/api.service'; + +@Component({ + selector: 'app-new-race-dialog', + templateUrl: './new-race-dialog.component.html', + styleUrls: ['./new-race-dialog.component.less'] +}) +export class NewRaceDialogComponent { + constructor( + public dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: any, + private apiService: ApiService + ) { } + + ngOnInit(): void { + } + + onCancelClick(): void { + this.dialogRef.close(); + } + + onSaveClick(): void { + this.apiService.addRace( + this.data.seasonId, this.data.startDate, this.data.endDate, this.data.mapName, this.data.mapLink, this.data.weekNumber + ).subscribe(data => { + console.log(data); + }); + this.dialogRef.close(this.data); + } +} diff --git a/src/app/components/season-card/season-card.component.html b/src/app/components/season-card/season-card.component.html index 2e4d82d..01dd419 100644 --- a/src/app/components/season-card/season-card.component.html +++ b/src/app/components/season-card/season-card.component.html @@ -1,8 +1,8 @@ - +
- {{season?.seasonName}} - {{season?.seasonTag}} + {{season?.title}} + {{season?.subTitle}}
Photo of a Shiba Inu diff --git a/src/app/components/season-details/season-details.component.html b/src/app/components/season-details/season-details.component.html index 4c37c96..12b71f9 100644 --- a/src/app/components/season-details/season-details.component.html +++ b/src/app/components/season-details/season-details.component.html @@ -1,12 +1,17 @@ -
-

{{seasonInfo.seasonName}}

-

{{seasonInfo.seasonTag}}

+
+

{{seasonInfo.title}}

+

{{seasonInfo.subTitle}}

+
+ +

- - - +
+ + + +
diff --git a/src/app/components/season-details/season-details.component.ts b/src/app/components/season-details/season-details.component.ts index c156bc2..126910d 100644 --- a/src/app/components/season-details/season-details.component.ts +++ b/src/app/components/season-details/season-details.component.ts @@ -1,7 +1,10 @@ import { Component } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; +import { MatDialog } from '@angular/material/dialog'; import { ApiService } from '../../services/api.service'; import { Season, SeasonWeek, SeasonStandingsEntry } from '../../models/season'; +import { AuthService } from 'src/app/services/auth-service'; +import { NewRaceDialogComponent } from '../new-race-dialog/new-race-dialog.component'; @Component({ selector: 'app-season-details', @@ -13,23 +16,58 @@ export class SeasonDetailsComponent { seasonStandings?: SeasonStandingsEntry[]; weeks?: SeasonWeek[]; + newRaceDetails = { + seasonId: '', + mapName: '', + mapImgUrl: '', + startDate: '', + endDate: '', + }; + constructor( private route: ActivatedRoute, private apiService: ApiService, - //private location: Location + private authService: AuthService, + public dialog: MatDialog ) { } + isAuthenticated: boolean = false; + ngOnInit(): void { - const id = Number(this.route.snapshot.paramMap.get('id')); + this.isAuthenticated = this.authService.isAuthenticated(); + const id = String(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; + this.seasonInfo = (data as any).season; + this.weeks = (data as any).races; + this.seasonStandings = (data as any).standings; + console.log(data); + console.log(this.seasonInfo); console.log(this.seasonStandings); console.log(this.weeks); }); } + openNewRaceDialog(): void { + if(this.seasonInfo) + { + this.newRaceDetails.seasonId = this.seasonInfo._id; + } + const dialogRef = this.dialog.open(NewRaceDialogComponent, { + width: '400px', + data: { ...this.newRaceDetails } + }); + + dialogRef.afterClosed().subscribe(result => { + if (result) { + console.log('Follow details saved:', result); + // You can update your data or perform other actions here + } else { + console.log('Dialog canceled'); + } + }); + } + } + diff --git a/src/app/components/seasons/seasons.component.ts b/src/app/components/seasons/seasons.component.ts index 9033630..c5ede3c 100644 --- a/src/app/components/seasons/seasons.component.ts +++ b/src/app/components/seasons/seasons.component.ts @@ -23,6 +23,7 @@ export class SeasonsComponent ngOnInit() { this.apiService.getSeasons().subscribe(data => { this.seasons = (data as any).seasons; + console.log(this.seasons); }); this.isAuthenticated = this.authService.isAuthenticated(); } diff --git a/src/app/models/season.ts b/src/app/models/season.ts index 30a216e..14dcfa7 100644 --- a/src/app/models/season.ts +++ b/src/app/models/season.ts @@ -1,31 +1,32 @@ export interface Season { - id: string, - seasonName: string; - seasonTag: string; - seasonCardImage: string; - seasonHeaderImage: string; + _id: string, + title: string; + subTitle: string; + startDate: string; } export interface BridgeUser { - id: string; + _id: string; realName: string; gameHandle: string; } export interface SeasonStandingsEntry { + _id: string; position: string; points: number; user: BridgeUser; } export interface SeasonWeekEntry { + _id: string; position: string; runTime: string; user: BridgeUser; } export interface SeasonWeek { - id: string; + _id: string; seasonId: string; mapName: string; mapImg: string; diff --git a/src/app/services/api.service.ts b/src/app/services/api.service.ts index aabc54f..f235973 100644 --- a/src/app/services/api.service.ts +++ b/src/app/services/api.service.ts @@ -30,7 +30,7 @@ export class ApiService this.server_route+'/api/seasons'); } - getSeasonDetails(id: number) + getSeasonDetails(id: string) { return this.http.get( this.server_route+'/api/seasons/'+id); @@ -38,8 +38,13 @@ export class ApiService addSeason(title: string, subtitle: string, startDate: Date) { - console.log("hullo"); return this.http.post<{title: string, subtitle: string, startDate: Date}>(this.server_route+'/api/seasons/add', { title, subtitle, startDate }, {headers: { authorization: `Bearer ${localStorage.getItem('token')}` },}); } + + addRace(seasonId: string, startDate: Date, endDate: Date, mapName: string, mapLink: string, weekNumber: Number) + { + return this.http.post<{title: string, subtitle: string, startDate: Date}>(this.server_route+'/api/race/add', { seasonId, startDate, endDate, mapName, mapLink, weekNumber }, + {headers: { authorization: `Bearer ${localStorage.getItem('token')}` },}); + } } \ No newline at end of file