From ede74c2461f377bbd4c49341ff5cda54605004dc Mon Sep 17 00:00:00 2001 From: Thomas Date: Sun, 14 Aug 2022 19:13:46 +0100 Subject: [PATCH 1/5] floriferous: Add simple Floriferous Game and scoring API points --- .idea/.gitignore | 5 + .idea/aws.xml | 11 + .idea/codeStyles/Project.xml | 65 +++++ .idea/codeStyles/codeStyleConfig.xml | 5 + .idea/inspectionProfiles/Project_Default.xml | 6 + .idea/modules.xml | 8 + .idea/thomaswilson-sveltekit.iml | 12 + .idea/vcs.xml | 6 + .idea/workspace.xml | 69 ++++++ package.json | 12 +- src/components/games/ApiPasswordForm.svelte | 49 ++++ .../games/FloriferousPlayerForm.svelte | 63 +++++ src/components/games/index.ts | 3 + src/lib/Authenticator.ts | 3 + .../floriferous/FloriferousGameRepository.ts | 8 + .../floriferous-api-controller.spec.ts | 117 +++++++++ .../floriferous/floriferous-api-controller.ts | 88 +++++++ .../floriferous-game-api-port.spec.ts | 57 +++++ .../floriferous/floriferous-game-api-port.ts | 39 +++ src/lib/floriferous/floriferous-game.spec.ts | 46 ++++ src/lib/floriferous/floriferous-game.ts | 53 ++++ .../floriferous/floriferous-player.spec.ts | 18 ++ src/lib/floriferous/floriferous-player.ts | 17 ++ src/lib/floriferous/index.ts | 2 + ...ferous-game-repository.integration.spec.ts | 128 ++++++++++ .../mongodb-floriferous-game-repository.ts | 123 ++++++++++ .../stub-floriferous-game-repository.ts | 30 +++ src/lib/simple-password-authenticator.spec.ts | 26 ++ src/lib/simple-password-authenticator.ts | 13 + src/routes/api/games/floriferous.json.ts | 51 ++++ src/routes/games/floriferous/index.svelte | 167 +++++++++++++ src/styles/thomaswilson.css | 4 + svelte.config.js | 5 +- vite.config.js | 2 +- yarn.lock | 230 +++++++++++++++++- 35 files changed, 1533 insertions(+), 8 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/aws.xml create mode 100644 .idea/codeStyles/Project.xml create mode 100644 .idea/codeStyles/codeStyleConfig.xml create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/thomaswilson-sveltekit.iml create mode 100644 .idea/vcs.xml create mode 100644 .idea/workspace.xml create mode 100644 src/components/games/ApiPasswordForm.svelte create mode 100644 src/components/games/FloriferousPlayerForm.svelte create mode 100644 src/components/games/index.ts create mode 100644 src/lib/Authenticator.ts create mode 100644 src/lib/floriferous/FloriferousGameRepository.ts create mode 100644 src/lib/floriferous/floriferous-api-controller.spec.ts create mode 100644 src/lib/floriferous/floriferous-api-controller.ts create mode 100644 src/lib/floriferous/floriferous-game-api-port.spec.ts create mode 100644 src/lib/floriferous/floriferous-game-api-port.ts create mode 100644 src/lib/floriferous/floriferous-game.spec.ts create mode 100644 src/lib/floriferous/floriferous-game.ts create mode 100644 src/lib/floriferous/floriferous-player.spec.ts create mode 100644 src/lib/floriferous/floriferous-player.ts create mode 100644 src/lib/floriferous/index.ts create mode 100644 src/lib/floriferous/mongodb-floriferous-game-repository.integration.spec.ts create mode 100644 src/lib/floriferous/mongodb-floriferous-game-repository.ts create mode 100644 src/lib/floriferous/stub-floriferous-game-repository.ts create mode 100644 src/lib/simple-password-authenticator.spec.ts create mode 100644 src/lib/simple-password-authenticator.ts create mode 100644 src/routes/api/games/floriferous.json.ts create mode 100644 src/routes/games/floriferous/index.svelte diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..b58b603 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,5 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ diff --git a/.idea/aws.xml b/.idea/aws.xml new file mode 100644 index 0000000..b63b642 --- /dev/null +++ b/.idea/aws.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..ca3cfd8 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..79ee123 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..03d9549 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..af4dbd4 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/thomaswilson-sveltekit.iml b/.idea/thomaswilson-sveltekit.iml new file mode 100644 index 0000000..0c8867d --- /dev/null +++ b/.idea/thomaswilson-sveltekit.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..c063cf8 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1661008443366 + + + + + + + + + \ No newline at end of file diff --git a/package.json b/package.json index 16ac517..6ea32f2 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,8 @@ "check": "svelte-check --tsconfig ./tsconfig.json", "check:watch": "svelte-check --tsconfig ./tsconfig.json --watch", "lint": "prettier --ignore-path .gitignore --check --plugin-search-dir=. . && eslint --ignore-path .gitignore .", - "format": "prettier --ignore-path .gitignore --write --plugin-search-dir=. ." + "format": "prettier --ignore-path .gitignore --write --plugin-search-dir=. .", + "test": "vitest" }, "devDependencies": { "@sveltejs/adapter-auto": "^1.0.0-next.65", @@ -28,12 +29,17 @@ "svelte-preprocess": "^4.10.1", "tslib": "^2.3.1", "typescript": "^4.7.4", - "vite": "^3.0.4" + "vite": "^3.0.4", + "vitest": "^0.21.0" }, "type": "module", "dependencies": { "date-fns": "^2.28.0", "mdsvex": "^0.10.5", - "sanitize-html": "^2.7.0" + "mongodb": "^4.8.1", + "nanoid": "^4.0.0", + "node-fetch": "^3.2.10", + "sanitize-html": "^2.7.0", + "zod": "^3.18.0" } } diff --git a/src/components/games/ApiPasswordForm.svelte b/src/components/games/ApiPasswordForm.svelte new file mode 100644 index 0000000..b163a40 --- /dev/null +++ b/src/components/games/ApiPasswordForm.svelte @@ -0,0 +1,49 @@ + + +
+ {#if apiPassword.length === 0} +

+ To save things to the ledger you need to enter the password. Right now you haven't set one. +

+ {/if} + {#if state === 'view'} + + {:else} +
+ + +
+ {/if} +
diff --git a/src/components/games/FloriferousPlayerForm.svelte b/src/components/games/FloriferousPlayerForm.svelte new file mode 100644 index 0000000..679f861 --- /dev/null +++ b/src/components/games/FloriferousPlayerForm.svelte @@ -0,0 +1,63 @@ + + +
handleFormSubmit()}> +
+ + +
+
+ + +
+
+ + +

"1" for the highest row, "2" for the second highest, etc.

+
+ +
+ + diff --git a/src/components/games/index.ts b/src/components/games/index.ts new file mode 100644 index 0000000..29173c3 --- /dev/null +++ b/src/components/games/index.ts @@ -0,0 +1,3 @@ +import FloriferousPlayerForm from './FloriferousPlayerForm.svelte'; + +export { FloriferousPlayerForm }; diff --git a/src/lib/Authenticator.ts b/src/lib/Authenticator.ts new file mode 100644 index 0000000..12e0add --- /dev/null +++ b/src/lib/Authenticator.ts @@ -0,0 +1,3 @@ +export interface Authenticator { + authenticate(password: string): boolean; +} \ No newline at end of file diff --git a/src/lib/floriferous/FloriferousGameRepository.ts b/src/lib/floriferous/FloriferousGameRepository.ts new file mode 100644 index 0000000..8677716 --- /dev/null +++ b/src/lib/floriferous/FloriferousGameRepository.ts @@ -0,0 +1,8 @@ +import type { FloriferousGame } from './floriferous-game'; + +export interface FloriferousGameRepository { + save(game: FloriferousGame): Promise; + getById(id: string): Promise; + getRecent(count: number): Promise; + +} \ No newline at end of file diff --git a/src/lib/floriferous/floriferous-api-controller.spec.ts b/src/lib/floriferous/floriferous-api-controller.spec.ts new file mode 100644 index 0000000..5283859 --- /dev/null +++ b/src/lib/floriferous/floriferous-api-controller.spec.ts @@ -0,0 +1,117 @@ +import { describe, it, expect } from 'vitest'; +import type { ApiGamesFloriferousPostRequest } from './floriferous-api-controller'; +import { FloriferousApiController } from './floriferous-api-controller'; +import { StubFloriferousGameRepository } from './stub-floriferous-game-repository'; +import { FloriferousGame } from './floriferous-game'; +import { SimplePasswordAuthenticator } from '../simple-password-authenticator'; +import { Headers } from 'node-fetch'; + +const isDate = (value = 'invalid'): boolean => { + return value !== 'invalid' && !isNaN(Date.parse(value)); +}; + +describe('FloriferousApiController', () => { + const stubGameRepository = new StubFloriferousGameRepository(); + const authenticator = new SimplePasswordAuthenticator('expected-password'); + const controller = new FloriferousApiController(stubGameRepository, authenticator); + + it('should validate a request with a proper password', async () => { + // GIVEN + const headers: Headers = new Headers(); + headers.set('x-api-password', 'expected-password'); + + // WHEN + const result = controller.isRequestAuthenticated({ headers: headers }); + + // THEN + expect(result).toBe(true); + }); + + it('should not validate a request with an invalid password', async () => { + // GIVEN + const headers = new Headers(); + headers.set('x-api-password', 'invalid-password'); + + // WHEN + const result = controller.isRequestAuthenticated({ headers }); + + // THEN + expect(result).toBe(false); + }); + + it('should not validate a request without a password', async () => { + // GIVEN + const request = { + headers: new Headers() + }; + + // WHEN + const result = controller.isRequestAuthenticated(request); + + // THEN + expect(result).toBe(false); + }); + + it('should get a list of recent games', async () => { + // GIVEN + const gameOne = new FloriferousGame({ + id: 'game-one', + players: [{ name: 'Alice', score: 10, rowAtEndOfGame: 1 }], + playedTs: new Date('2022-07-01T07:00Z') + }); + const gameTwo = new FloriferousGame({ + id: 'game-two', + players: [], + playedTs: new Date('2022-08-20T13:25Z') + }); + + stubGameRepository.setAllGames([gameOne, gameTwo]); + + // WHEN + const result = await controller.getRecentGames(10); + + // THEN + expect(result).toStrictEqual([ + { + id: 'game-one', + playedTs: '2022-07-01T07:00:00.000Z', + players: [{ name: 'Alice', score: 10, rowAtEndOfGame: 1 }] + }, + { + id: 'game-two', + playedTs: '2022-08-20T13:25:00.000Z', + players: [] + } + ]); + }); + + it('should save a new game', async () => { + // GIVEN + const requestBody: ApiGamesFloriferousPostRequest = { + players: [ + { + name: 'Alice', + rowAtEndOfGame: 1, + score: 10 + } + ] + }; + + // WHEN + const response = await controller.createNewGame(requestBody); + + // THEN + expect(isDate(response.playedTs)).toBe(true); + expect(response).toStrictEqual({ + id: expect.any(String), + playedTs: expect.any(String), + players: [ + { + name: 'Alice', + score: 10, + rowAtEndOfGame: 1 + } + ] + }); + }); +}); diff --git a/src/lib/floriferous/floriferous-api-controller.ts b/src/lib/floriferous/floriferous-api-controller.ts new file mode 100644 index 0000000..cdd5cb6 --- /dev/null +++ b/src/lib/floriferous/floriferous-api-controller.ts @@ -0,0 +1,88 @@ +import { z } from 'zod'; +import type { ZodError } from 'zod'; +import type { FloriferousGameJson } from './floriferous-game-api-port'; +import type { FloriferousGameRepository } from './FloriferousGameRepository'; +import { FloriferousGameApiPort } from './floriferous-game-api-port'; +import { FloriferousGame } from './floriferous-game'; +import { FloriferousPlayer } from './floriferous-player'; +import type { Authenticator } from '../Authenticator'; + +export interface ApiGamesFloriferousPostRequest { + players: { + name: string; + score: number; + rowAtEndOfGame: number; + }[]; +} + +const apiGamesFloriferousPostRequestSchema = z.object({ + players: z + .array( + z.object({ + name: z.string().min(1, { message: 'Player names must be at least 1 character long' }), + score: z + .number({ invalid_type_error: 'Player Score must be a number.' }) + .int({ message: 'Player Score must be a whole number.' }) + .min(0, { message: 'Player score cannot be less than 0.' }), + rowAtEndOfGame: z.number().int().min(0) + }) + ) + .min(1) +}); + +export class FloriferousApiController { + constructor( + private readonly repository: FloriferousGameRepository, + private readonly validator: Authenticator + ) {} + + isRequestAuthenticated(request: any): boolean { + const password = request?.headers?.get('x-api-password') ?? ''; + + if (password === undefined) { + return false; + } + + return this.validator.authenticate(password); + } + + async getRecentGames(count = 10): Promise { + const games = await this.repository.getRecent(count); + return games.map((game) => FloriferousGameApiPort.gameToJson(game)); + } + + async createNewGame(data: ApiGamesFloriferousPostRequest): Promise { + try { + apiGamesFloriferousPostRequestSchema.parse(data); + } catch (e: any) { + if (e.issues !== undefined) { + const zodError: ZodError = e; + throw new Error(zodError.issues[0].message); + } + console.error({ + message: `Caught error validating body data in createNewGame`, + error: e, + data: JSON.stringify(data) + }); + + throw new Error(e?.message ?? e); + } + + const validatedData = data; + + const players: FloriferousPlayer[] = validatedData.players.map( + ({ name, rowAtEndOfGame, score }) => { + return new FloriferousPlayer({ name, rowAtEndOfGame, score }); + } + ); + + const game = new FloriferousGame({ + players, + playedTs: new Date() + }); + + const savedGame = await this.repository.save(game); + + return FloriferousGameApiPort.gameToJson(game); + } +} diff --git a/src/lib/floriferous/floriferous-game-api-port.spec.ts b/src/lib/floriferous/floriferous-game-api-port.spec.ts new file mode 100644 index 0000000..4a5357b --- /dev/null +++ b/src/lib/floriferous/floriferous-game-api-port.spec.ts @@ -0,0 +1,57 @@ +import { FloriferousGame } from './floriferous-game'; +import { FloriferousPlayer } from './floriferous-player'; +import { FloriferousGameApiPort } from './floriferous-game-api-port'; + +import { it, expect } from 'vitest'; + +it('should stringify a FloriferousGame into JSON', () => { + // GIVEN + const game = new FloriferousGame({ + id: 'the-id', + playedTs: new Date('2020-01-01T00:00Z'), + players: [ + new FloriferousPlayer({ name: 'first player', rowAtEndOfGame: 1, score: 2 }), + new FloriferousPlayer({ name: 'second player', rowAtEndOfGame: 3, score: 4 }) + ] + }); + + // WHEN + const gameAsJson = FloriferousGameApiPort.gameToJson(game); + + // THEN + expect(gameAsJson).toStrictEqual({ + id: 'the-id', + playedTs: '2020-01-01T00:00:00.000Z', + players: [ + { name: 'first player', rowAtEndOfGame: 1, score: 2 }, + { name: 'second player', rowAtEndOfGame: 3, score: 4 } + ] + }); +}); + +it('should parse JSON into a floriferous game', () => { + // GIVEN + const gameAsJson = { + id: 'the-id', + playedTs: '2020-01-01T00:00:00.000Z', + players: [ + { name: 'first player', rowAtEndOfGame: 1, score: 2 }, + { name: 'second player', rowAtEndOfGame: 3, score: 4 } + ] + }; + + // WHEN + const game = FloriferousGameApiPort.jsonToGame(gameAsJson); + + // THEN + expect(game).toStrictEqual( + new FloriferousGame({ + id: 'the-id', + playedTs: new Date('2020-01-01T00:00Z'), + players: [ + { name: 'first player', rowAtEndOfGame: 1, score: 2 }, + { name: 'second player', rowAtEndOfGame: 3, score: 4 } + ] + }) + ); +}); diff --git a/src/lib/floriferous/floriferous-game-api-port.ts b/src/lib/floriferous/floriferous-game-api-port.ts new file mode 100644 index 0000000..cabda72 --- /dev/null +++ b/src/lib/floriferous/floriferous-game-api-port.ts @@ -0,0 +1,39 @@ +import { FloriferousGame } from './floriferous-game'; + +export interface FloriferousGameJson { + id: string; + playedTs: string; + players: { + name: string; + score: number; + rowAtEndOfGame: number; + }[]; +} + +export class FloriferousGameApiPort { + static jsonToGame(json: FloriferousGameJson): FloriferousGame { + const players = json.players.map((player) => ({ + name: player.name, + score: player.score, + rowAtEndOfGame: player.rowAtEndOfGame + })); + + return new FloriferousGame({ + id: json.id, + playedTs: new Date(json.playedTs), + players + }); + } + + static gameToJson(game: FloriferousGame): FloriferousGameJson { + return { + id: game.id, + playedTs: game.playedTs.toISOString(), + players: game.players.map((player) => ({ + name: player.name, + score: player.score, + rowAtEndOfGame: player.rowAtEndOfGame + })) + }; + } +} diff --git a/src/lib/floriferous/floriferous-game.spec.ts b/src/lib/floriferous/floriferous-game.spec.ts new file mode 100644 index 0000000..b98aa40 --- /dev/null +++ b/src/lib/floriferous/floriferous-game.spec.ts @@ -0,0 +1,46 @@ +import { FloriferousGame } from './floriferous-game'; +import { describe, it, expect } from 'vitest'; +import { FloriferousPlayer } from './floriferous-player'; + +describe('FloriferousGame', () => { + const alice = new FloriferousPlayer({ + name: 'Alice', + score: 2, + rowAtEndOfGame: 0 + }); + + const bob = new FloriferousPlayer({ + name: 'Bob', + score: 1, + rowAtEndOfGame: 1 + }); + + it('Determines a winner', () => { + const game = new FloriferousGame(); + + // WHEN + game.addPlayer(alice); + game.addPlayer(bob); + + // THEN + expect(game.winner).toBe('Alice'); + }); + + it('Breaks a tie using the player closest to the top of the board', () => { + // GIVEN + const bobWithTwoPoints = new FloriferousPlayer({ + name: 'Bob', + score: 2, + rowAtEndOfGame: 1 + }); + + const game = new FloriferousGame(); + + // WHEN + game.addPlayer(alice); + game.addPlayer(bobWithTwoPoints); + + // THEN + expect(game.winner).toBe('Alice'); + }); +}); diff --git a/src/lib/floriferous/floriferous-game.ts b/src/lib/floriferous/floriferous-game.ts new file mode 100644 index 0000000..1af9fce --- /dev/null +++ b/src/lib/floriferous/floriferous-game.ts @@ -0,0 +1,53 @@ +import type { FloriferousPlayer } from './floriferous-player'; +import { nanoid } from 'nanoid'; + +export interface FloriferousGameParams { + playedTs?: Date; + id?: string; + players?: FloriferousPlayer[]; +} + +export class FloriferousGame { + readonly id: string; + readonly playedTs: Date; + private _players: FloriferousPlayer[] = []; + + constructor({ id = nanoid(), playedTs = new Date(), players = [] }: FloriferousGameParams = {}) { + this.id = id; + this.playedTs = playedTs; + this._players = players; + } + + addPlayer(player: FloriferousPlayer): void { + this._players.push(player); + } + + addPlayers(players: FloriferousPlayer[]): void { + players.forEach((player) => { + this.addPlayer(player); + }); + } + + get players(): FloriferousPlayer[] { + return this._players; + } + + get winner(): string | undefined { + if (this._players.length === 0) { + return undefined; + } + const playersSortedByScore = this._players.sort((a, b) => { + return b.score - a.score; + }); + + if (playersSortedByScore[0].score === playersSortedByScore[1].score) { + const playersSortedByRowAtEndOfGame = this._players.sort((a, b) => { + return a.rowAtEndOfGame - b.rowAtEndOfGame; + }); + + return playersSortedByRowAtEndOfGame[0].name; + } + + return playersSortedByScore[0].name; + } +} diff --git a/src/lib/floriferous/floriferous-player.spec.ts b/src/lib/floriferous/floriferous-player.spec.ts new file mode 100644 index 0000000..a22ca71 --- /dev/null +++ b/src/lib/floriferous/floriferous-player.spec.ts @@ -0,0 +1,18 @@ +import { describe, expect, it } from 'vitest'; +import { FloriferousPlayer } from './floriferous-player'; + +describe('FloriferousPlayer', () => { + it('should construct with properties', () => { + // GIVEN + const player = new FloriferousPlayer({ + name: 'Alice', + score: 2, + rowAtEndOfGame: 0 + }); + + // THEN + expect(player.name).toBe('Alice'); + expect(player.score).toBe(2); + expect(player.rowAtEndOfGame).toBe(0); + }); +}); diff --git a/src/lib/floriferous/floriferous-player.ts b/src/lib/floriferous/floriferous-player.ts new file mode 100644 index 0000000..a324138 --- /dev/null +++ b/src/lib/floriferous/floriferous-player.ts @@ -0,0 +1,17 @@ +export interface FloriferousPlayerParams { + name: string; + score: number; + rowAtEndOfGame: number; +} + +export class FloriferousPlayer { + readonly name: string; + readonly score: number; + readonly rowAtEndOfGame: number; + + constructor(params: FloriferousPlayerParams) { + this.name = params.name; + this.score = params.score; + this.rowAtEndOfGame = params.rowAtEndOfGame; + } +} diff --git a/src/lib/floriferous/index.ts b/src/lib/floriferous/index.ts new file mode 100644 index 0000000..7be618f --- /dev/null +++ b/src/lib/floriferous/index.ts @@ -0,0 +1,2 @@ +export { FloriferousGame } from './floriferous-game'; +export { FloriferousPlayer } from './floriferous-player'; diff --git a/src/lib/floriferous/mongodb-floriferous-game-repository.integration.spec.ts b/src/lib/floriferous/mongodb-floriferous-game-repository.integration.spec.ts new file mode 100644 index 0000000..a9b1f5d --- /dev/null +++ b/src/lib/floriferous/mongodb-floriferous-game-repository.integration.spec.ts @@ -0,0 +1,128 @@ +import { describe, expect, it, beforeEach, afterAll } from 'vitest'; + +import { FloriferousGame, type FloriferousGameParams } from './floriferous-game'; +import { FloriferousPlayer, type FloriferousPlayerParams } from './floriferous-player'; +import { customAlphabet } from 'nanoid'; +import { MongodbFloriferousGameRepository } from './mongodb-floriferous-game-repository'; +import { MongoClient } from 'mongodb'; + +describe('MongoDB FloriferousGame Repository', () => { + const mongoDbUrl = import.meta.env.VITE_MONGO_URL; + const mongoDbName = import.meta.env.VITE_MONGO_DB_NAME; + const janitor = new MongodbJanitor(mongoDbUrl, mongoDbName); + + let playerOne: FloriferousPlayer; + let playerTwo: FloriferousPlayer; + let game: FloriferousGame; + + beforeEach(async () => { + playerOne = floriferousPlayerFactory({ name: 'Player 1' }); + playerTwo = floriferousPlayerFactory({ name: 'Player 2' }); + game = floriferousGameFactory(); + game.addPlayer(playerOne); + game.addPlayer(playerTwo); + }); + + afterAll(async () => { + await janitor.deleteAllDocumentsInConnection('floriferous-games'); + }); + + const repository = new MongodbFloriferousGameRepository(mongoDbUrl, mongoDbName); + + it('should save', async () => { + // WHEN + const savedGame = await repository.save(game); + + // THEN + expect(savedGame).toBeDefined(); + }); + + it('should get by ID when it exists', async () => { + // GIVEN + const savedGame = await repository.save(game); + + // WHEN + const foundGame = await repository.getById(game.id); + + // THEN + expect(foundGame).toStrictEqual(game); + }); + + it('should return null when fetching by ID for a game which did not happen', async () => { + // WHEN + const foundGame = await repository.getById('any-random-id'); + + // THEN + expect(foundGame).toBeNull(); + }); + + it('should get the last 10 games, sorted by playedTs', async () => { + // GIVEN + await janitor.deleteAllDocumentsInConnection('floriferous-games'); + const gameOne = floriferousGameFactory({ + playedTs: new Date('2022-08-07T19:00Z') + }); + + const gameTwo = floriferousGameFactory({ + playedTs: new Date('2022-08-07T06:00Z') + }); + + await repository.save(gameOne); + await repository.save(gameTwo); + + // WHEN + const recentGames = await repository.getRecent(10); + + // THEN + expect(recentGames).toStrictEqual([gameTwo, gameOne]); + }); + + it('should return an empty array when no games have taken place', async () => { + // GIVEN + await janitor.deleteAllDocumentsInConnection('floriferous-games'); + + // WHEN + const recentGames = await repository.getRecent(5); + + // THEN + expect(recentGames).toStrictEqual([]); + }); +}); + +class MongodbJanitor { + private client: MongoClient; + + constructor(private readonly url: string, private readonly dbName: string) { + this.client = new MongoClient(url); + } + + async deleteAllDocumentsInConnection(collectionName: string): Promise { + console.info(`Deleting all documents in ${collectionName}`); + + const connection = await this.client.connect(); + + const deltedDocuments = await connection + .db(this.dbName) + .collection(collectionName) + .deleteMany({}); + + await connection.close(); + + console.info(`Deleted ${deltedDocuments.deletedCount} documents`); + } +} + +function floriferousPlayerFactory( + overrides: Partial = {} +): FloriferousPlayer { + return new FloriferousPlayer({ + name: `name-${customAlphabet('abcdefghijklmnopqrstuvwxyz', 8)}`, + score: Math.floor(Math.random() * 100), + rowAtEndOfGame: Math.floor(Math.random() * 10), + ...overrides + }); +} + +function floriferousGameFactory(overrides: Partial = {}): FloriferousGame { + return new FloriferousGame(overrides); +} diff --git a/src/lib/floriferous/mongodb-floriferous-game-repository.ts b/src/lib/floriferous/mongodb-floriferous-game-repository.ts new file mode 100644 index 0000000..e3ccf14 --- /dev/null +++ b/src/lib/floriferous/mongodb-floriferous-game-repository.ts @@ -0,0 +1,123 @@ +import { FloriferousGame } from './floriferous-game'; +import { MongoClient } from 'mongodb'; +import { FloriferousPlayer } from './floriferous-player'; +import type { FloriferousGameRepository } from './FloriferousGameRepository'; + +interface FloriferousGameMongoDocument { + id: string; + playedTs: Date; + players: { + name: string; + score: number; + rowAtEndOfGame: number; + }[]; +} + +export class MongodbFloriferousGameRepository implements FloriferousGameRepository { + private client: MongoClient; + private connection: MongoClient | null = null; + private collectionName = 'floriferous-games'; + + constructor(private readonly mongodbUrl: string, private readonly dbName: string) { + this.client = new MongoClient(mongodbUrl); + } + + private async connect(): Promise { + this.connection = await this.client.connect(); + } + + private async disconnect(): Promise { + if (this.connection === null) { + return; + } + + await this.connection.close(); + } + + async save(game: FloriferousGame): Promise { + await this.connect(); + + const data = MongodbFloriferousGameRepository.gameToMongoDocument(game); + + const document = await this.connection + .db(this.dbName) + .collection(this.collectionName) + .insertOne(data); + + await this.disconnect(); + + return game; + } + + async getById(id: string): Promise { + await this.connect(); + + const document = await this.connection + .db(this.dbName) + .collection(this.collectionName) + .findOne({ id }); + + await this.disconnect(); + + if (document === null) { + return null; + } + + return MongodbFloriferousGameRepository.documentToGame(document); + } + + async getRecent(count = 10): Promise { + await this.connect(); + + const documents = await this.connection + .db(this.dbName) + .collection(this.collectionName) + .find({}) + .sort({ playedTs: 1 }) + .limit(count) + .toArray(); + + await this.disconnect(); + + if (!documents) { + return []; + } + + const formatted = documents.map((document) => + MongodbFloriferousGameRepository.documentToGame(document) + ); + + return formatted; + } + + private static documentToGame(document: FloriferousGameMongoDocument): FloriferousGame { + const players: FloriferousPlayer[] = document.players.map( + ({ name, rowAtEndOfGame, score }) => + new FloriferousPlayer({ + name, + rowAtEndOfGame: rowAtEndOfGame, + score + }) + ); + + const game = new FloriferousGame({ + id: document.id, + playedTs: document.playedTs, + players + }); + + return game; + } + + private static gameToMongoDocument(game: FloriferousGame): FloriferousGameMongoDocument { + return { + id: game.id, + playedTs: game.playedTs, + players: game.players.map((player) => ({ + rowAtEndOfGame: player.rowAtEndOfGame, + name: player.name, + score: player.score + })) + }; + } +} diff --git a/src/lib/floriferous/stub-floriferous-game-repository.ts b/src/lib/floriferous/stub-floriferous-game-repository.ts new file mode 100644 index 0000000..a879f76 --- /dev/null +++ b/src/lib/floriferous/stub-floriferous-game-repository.ts @@ -0,0 +1,30 @@ +import type { FloriferousGameRepository } from './FloriferousGameRepository'; +import type { FloriferousGame } from './floriferous-game'; + +export class StubFloriferousGameRepository implements FloriferousGameRepository{ + private games: FloriferousGame[] = []; + + setAllGames(games: FloriferousGame[]): void { + this.games = games; + } + + + getById(id: string): Promise { + throw new Error('Method not implemented.'); + } + + getRecent(count: number): Promise { + return Promise.resolve(this.games.map((game, index) => { + if (index < count -1) { + return game; + } + + return undefined + }).filter((game) => game !== undefined)); + + } + + save(game: FloriferousGame): Promise { + return Promise.resolve(undefined); + } +} \ No newline at end of file diff --git a/src/lib/simple-password-authenticator.spec.ts b/src/lib/simple-password-authenticator.spec.ts new file mode 100644 index 0000000..d3b5a62 --- /dev/null +++ b/src/lib/simple-password-authenticator.spec.ts @@ -0,0 +1,26 @@ +import { SimplePasswordAuthenticator } from './simple-password-authenticator'; + +import { it, expect} from 'vitest' + +it('should do nothing when things are valid', () => { + // GIVEN + const authenticator = new SimplePasswordAuthenticator('expected-password'); + + // WHEN + const result = authenticator.authenticate('expected-password'); + + // + expect(result).toBeTruthy(); +}) + +it('should not authenticate when the password is invalid', () => { +// GIVEN + const authenticator = new SimplePasswordAuthenticator('expected-password'); + + // WHEN + const result = authenticator.authenticate('invalid-password'); + + // THEN + expect(result).toBeFalsy(); + +}) \ No newline at end of file diff --git a/src/lib/simple-password-authenticator.ts b/src/lib/simple-password-authenticator.ts new file mode 100644 index 0000000..0a6337a --- /dev/null +++ b/src/lib/simple-password-authenticator.ts @@ -0,0 +1,13 @@ +import type { Authenticator } from './Authenticator'; + +export class SimplePasswordAuthenticator implements Authenticator{ + constructor(private readonly password: string) { + if (this.password === undefined) { + throw new Error('Password must be defined'); + } + } + + authenticate(password: string): boolean { + return this.password === password; + } +} \ No newline at end of file diff --git a/src/routes/api/games/floriferous.json.ts b/src/routes/api/games/floriferous.json.ts new file mode 100644 index 0000000..cfeceb8 --- /dev/null +++ b/src/routes/api/games/floriferous.json.ts @@ -0,0 +1,51 @@ +import { MONGO_URL, MONGO_DB_NAME, API_PASSWORD } from '$env/static/private'; +import type { RequestHandler, RequestHandlerOutput } from '@sveltejs/kit'; + +import { MongodbFloriferousGameRepository } from '../../../lib/floriferous/mongodb-floriferous-game-repository'; +import { FloriferousApiController } from '../../../lib/floriferous/floriferous-api-controller'; +import { SimplePasswordAuthenticator } from '../../../lib/simple-password-authenticator'; + +export const GET: RequestHandler = async (): Promise => { + const controller = new FloriferousApiController( + new MongodbFloriferousGameRepository(MONGO_URL, MONGO_DB_NAME), + new SimplePasswordAuthenticator(API_PASSWORD) + ); + + const response = await controller.getRecentGames(10); + + return { + status: 200, + body: JSON.stringify(response) + }; +}; + +export const POST: RequestHandler = async ({ request }) => { + const controller = new FloriferousApiController( + new MongodbFloriferousGameRepository(MONGO_URL, MONGO_DB_NAME), + new SimplePasswordAuthenticator(API_PASSWORD) + ); + + const isAuthenticated = controller.isRequestAuthenticated(request); + + if (!isAuthenticated) { + return { + status: 401, + body: 'Unauthorized' + }; + } + + try { + const requestBody = await request.json(); + const response = await controller.createNewGame(requestBody); + + return { + status: 200, + body: JSON.parse(JSON.stringify(response)) + }; + } catch (e) { + return { + status: 500, + body: JSON.stringify(e) + }; + } +}; diff --git a/src/routes/games/floriferous/index.svelte b/src/routes/games/floriferous/index.svelte new file mode 100644 index 0000000..3e471de --- /dev/null +++ b/src/routes/games/floriferous/index.svelte @@ -0,0 +1,167 @@ + + + + +

Floriferous Scoring

+{#if previousGames.length > 0} +
+

Previous Games

+
    + {#each previousGames as game} +
  • + {Intl.DateTimeFormat('en-GB', { + weekday: 'long', + day: 'numeric', + month: 'long', + year: 'numeric', + hour: '2-digit', + minute: '2-digit' + }).format(game.playedTs)}: + {game.winner} Won +
  • + {/each} +
+
+{/if} + +
+

Players

+ + {#if players.length > 0} +
    + {#each players as player} +
  • + {player.name} ({player.score} points, finished on row {player.rowAtEndOfGame}) () +
  • + {/each} +
+ + {#if players.length > 1} + {#if isWinnerVisible} +

And the winner is:{game.winner}

+

Add to Ledger

+ + (apiPassword = event.detail)} /> + + {#if apiPassword.length > 0} +

You can save this game in the Ledger

+ + {/if} + {:else} + + {/if} + {/if} + {:else} +

Add at least one player to get started

+ {/if} + + {#if !isWinnerVisible} +

Add a New Player

+ + {/if} +
+ + diff --git a/src/styles/thomaswilson.css b/src/styles/thomaswilson.css index 88f3e7d..91f016f 100644 --- a/src/styles/thomaswilson.css +++ b/src/styles/thomaswilson.css @@ -32,6 +32,10 @@ --spacing-lg: 1rem; --spacing-xl: 1.5rem; --navbar-height: 75px; + + --font-size-sm: 0.875rem; + --font-size-md: 1.25rem; + --font-size-lg: 1.5rem; } body { diff --git a/svelte.config.js b/svelte.config.js index 958b7ff..a24937b 100644 --- a/svelte.config.js +++ b/svelte.config.js @@ -13,7 +13,10 @@ const config = { })], kit: { - adapter: adapter({ split: false }) + adapter: adapter({ split: false }), + env: { + publicPrefix: 'PUBLIC_' + } } }; diff --git a/vite.config.js b/vite.config.js index 3871b65..2449d19 100644 --- a/vite.config.js +++ b/vite.config.js @@ -2,7 +2,7 @@ import { sveltekit } from '@sveltejs/kit/vite'; /** @type {import('vite').UserConfig} */ const config = { - plugins: [sveltekit()] + plugins: [sveltekit()], }; export default config; \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index b3696b2..895a233 100644 --- a/yarn.lock +++ b/yarn.lock @@ -200,6 +200,18 @@ magic-string "^0.26.2" svelte-hmr "^0.14.12" +"@types/chai-subset@^1.3.3": + version "1.3.3" + resolved "https://registry.yarnpkg.com/@types/chai-subset/-/chai-subset-1.3.3.tgz#97893814e92abd2c534de422cb377e0e0bdaac94" + integrity sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw== + dependencies: + "@types/chai" "*" + +"@types/chai@*", "@types/chai@^4.3.3": + version "4.3.3" + resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.3.3.tgz#3c90752792660c4b562ad73b3fbd68bf3bc7ae07" + integrity sha512-hC7OMnszpxhZPduX+m+nrx+uFoLkWOMiR4oa/AZF3MuSETYTZmFfJAHqZEM8MVlvfG7BEUcgvtwoCTxBp6hm3g== + "@types/json-schema@^7.0.9": version "7.0.11" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" @@ -234,6 +246,19 @@ resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.6.tgz#250a7b16c3b91f672a24552ec64678eeb1d3a08d" integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ== +"@types/webidl-conversions@*": + version "6.1.1" + resolved "https://registry.yarnpkg.com/@types/webidl-conversions/-/webidl-conversions-6.1.1.tgz#e33bc8ea812a01f63f90481c666334844b12a09e" + integrity sha512-XAahCdThVuCFDQLT7R7Pk/vqeObFNL3YqRyFZg+AqAP/W1/w3xHaIxuW7WszQqTbIBOPRcItYJIou3i/mppu3Q== + +"@types/whatwg-url@^8.2.1": + version "8.2.2" + resolved "https://registry.yarnpkg.com/@types/whatwg-url/-/whatwg-url-8.2.2.tgz#749d5b3873e845897ada99be4448041d4cc39e63" + integrity sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA== + dependencies: + "@types/node" "*" + "@types/webidl-conversions" "*" + "@typescript-eslint/eslint-plugin@^5.32.0": version "5.33.1" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.33.1.tgz#c0a480d05211660221eda963cc844732fe9b1714" @@ -435,6 +460,11 @@ array-union@^2.1.0: resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== +assertion-error@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" + integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== + astral-regex@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" @@ -450,6 +480,11 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +base64-js@^1.3.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + binary-extensions@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" @@ -477,16 +512,44 @@ braces@^3.0.2, braces@~3.0.2: dependencies: fill-range "^7.0.1" +bson@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/bson/-/bson-4.7.0.tgz#7874a60091ffc7a45c5dd2973b5cad7cded9718a" + integrity sha512-VrlEE4vuiO1WTpfof4VmaVolCVYkYTgB9iWgYNOrVlnifpME/06fhFRmONgBhClD5pFC1t9ZWqFUQEQAzY43bA== + dependencies: + buffer "^5.6.0" + buffer-crc32@^0.2.5: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== +buffer@^5.6.0: + version "5.7.1" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" + integrity sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ== + dependencies: + base64-js "^1.3.1" + ieee754 "^1.1.13" + callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== +chai@^4.3.6: + version "4.3.6" + resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.6.tgz#ffe4ba2d9fa9d6680cc0b370adae709ec9011e9c" + integrity sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q== + dependencies: + assertion-error "^1.1.0" + check-error "^1.0.2" + deep-eql "^3.0.1" + get-func-name "^2.0.0" + loupe "^2.3.1" + pathval "^1.1.1" + type-detect "^4.0.5" + chalk@^2.0.0: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" @@ -504,6 +567,11 @@ chalk@^4.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +check-error@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" + integrity sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA== + chokidar@^3.4.1: version "3.5.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" @@ -594,6 +662,13 @@ debug@4, debug@^4.0.1, debug@^4.1.1, debug@^4.3.4: dependencies: ms "2.1.2" +deep-eql@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df" + integrity sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw== + dependencies: + type-detect "^4.0.0" + deep-is@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" @@ -609,6 +684,11 @@ delegates@^1.0.0: resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" integrity sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ== +denque@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1" + integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw== + detect-indent@^6.0.0: version "6.1.0" resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.1.0.tgz#592485ebbbf6b3b1ab2be175c8393d04ca0d57e6" @@ -1095,6 +1175,11 @@ gauge@^3.0.0: strip-ansi "^6.0.1" wide-align "^1.1.2" +get-func-name@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" + integrity sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig== + glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" @@ -1188,6 +1273,11 @@ https-proxy-agent@^5.0.0: agent-base "6" debug "4" +ieee754@^1.1.13: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + ignore@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" @@ -1224,6 +1314,11 @@ inherits@2, inherits@^2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +ip@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.0.tgz#4cf4ab182fee2314c75ede1276f8c80b479936da" + integrity sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ== + is-binary-path@~2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" @@ -1311,6 +1406,11 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" +local-pkg@^0.4.2: + version "0.4.2" + resolved "https://registry.yarnpkg.com/local-pkg/-/local-pkg-0.4.2.tgz#13107310b77e74a0e513147a131a2ba288176c2f" + integrity sha512-mlERgSPrbxU3BP4qBqAvvwlgW4MTg78iwJdGGnv7kibKjWcJksrG3t6LB5lXI93wXRDvG4NpUgJFmTG4T6rdrg== + lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" @@ -1321,6 +1421,13 @@ lodash.truncate@^4.4.2: resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193" integrity sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw== +loupe@^2.3.1: + version "2.3.4" + resolved "https://registry.yarnpkg.com/loupe/-/loupe-2.3.4.tgz#7e0b9bffc76f148f9be769cb1321d3dcf3cb25f3" + integrity sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ== + dependencies: + get-func-name "^2.0.0" + lru-cache@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" @@ -1359,6 +1466,11 @@ mdsvex@^0.10.5: prismjs "^1.17.1" vfile-message "^2.0.4" +memory-pager@^1.0.2: + version "1.5.0" + resolved "https://registry.yarnpkg.com/memory-pager/-/memory-pager-1.5.0.tgz#d8751655d22d384682741c972f2c3d6dfa3e66b5" + integrity sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg== + merge2@^1.3.0, merge2@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" @@ -1421,6 +1533,26 @@ mkdirp@^1.0.3: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== +mongodb-connection-string-url@^2.5.3: + version "2.5.3" + resolved "https://registry.yarnpkg.com/mongodb-connection-string-url/-/mongodb-connection-string-url-2.5.3.tgz#c0c572b71570e58be2bd52b33dffd1330cfb6990" + integrity sha512-f+/WsED+xF4B74l3k9V/XkTVj5/fxFH2o5ToKXd8Iyi5UhM+sO9u0Ape17Mvl/GkZaFtM0HQnzAG5OTmhKw+tQ== + dependencies: + "@types/whatwg-url" "^8.2.1" + whatwg-url "^11.0.0" + +mongodb@^4.8.1: + version "4.9.0" + resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-4.9.0.tgz#58618439b721f2d6f7d38bb10a4612e29d7f1c8a" + integrity sha512-tJJEFJz7OQTQPZeVHZJIeSOjMRqc5eSyXTt86vSQENEErpkiG7279tM/GT5AVZ7TgXNh9HQxoa2ZkbrANz5GQw== + dependencies: + bson "^4.7.0" + denque "^2.1.0" + mongodb-connection-string-url "^2.5.3" + socks "^2.7.0" + optionalDependencies: + saslprep "^1.0.3" + mri@^1.1.0: version "1.2.0" resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b" @@ -1441,6 +1573,11 @@ nanoid@^3.3.4: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== +nanoid@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-4.0.0.tgz#6e144dee117609232c3f415c34b0e550e64999a5" + integrity sha512-IgBP8piMxe/gf73RTQx7hmnhwz0aaEXYakvqZyE302IXW3HyVNhdNGC+O2MwMAVhLEnvXlvKtGbtJf6wvHihCg== + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -1458,7 +1595,7 @@ node-fetch@^2.6.7: dependencies: whatwg-url "^5.0.0" -node-fetch@^3.2.4: +node-fetch@^3.2.10, node-fetch@^3.2.4: version "3.2.10" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.2.10.tgz#e8347f94b54ae18b57c9c049ef641cef398a85c8" integrity sha512-MhuzNwdURnZ1Cp4XTazr69K0BTizsBroX7Zx3UgDSVcZYKF/6p0CBe4EUb/hLqmzVhl0UpYfgRljQ4yxE+iCxA== @@ -1550,6 +1687,11 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== +pathval@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d" + integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ== + picocolors@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" @@ -1599,7 +1741,7 @@ progress@^2.0.0: resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== -punycode@^2.1.0: +punycode@^2.1.0, punycode@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== @@ -1733,6 +1875,13 @@ sanitize-html@^2.7.0: parse-srcset "^1.0.2" postcss "^8.3.11" +saslprep@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/saslprep/-/saslprep-1.0.3.tgz#4c02f946b56cf54297e347ba1093e7acac4cf226" + integrity sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag== + dependencies: + sparse-bitfield "^3.0.3" + semver@^6.0.0: version "6.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" @@ -1795,6 +1944,19 @@ slice-ansi@^4.0.0: astral-regex "^2.0.0" is-fullwidth-code-point "^3.0.0" +smart-buffer@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" + integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== + +socks@^2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.7.0.tgz#f9225acdb841e874dca25f870e9130990f3913d0" + integrity sha512-scnOe9y4VuiNUULJN72GrM26BNOjVsfPXI+j+98PkyEfsIXroa5ofyjT+FzGvn/xHs73U2JtoBYAVx9Hl4quSA== + dependencies: + ip "^2.0.0" + smart-buffer "^4.2.0" + sorcery@^0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/sorcery/-/sorcery-0.10.0.tgz#8ae90ad7d7cb05fc59f1ab0c637845d5c15a52b7" @@ -1815,6 +1977,13 @@ sourcemap-codec@^1.3.0, sourcemap-codec@^1.4.8: resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== +sparse-bitfield@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz#ff4ae6e68656056ba4b3e792ab3334d38273ca11" + integrity sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ== + dependencies: + memory-pager "^1.0.2" + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -1946,6 +2115,16 @@ tiny-glob@^0.2.9: globalyzer "0.1.0" globrex "^0.1.2" +tinypool@^0.2.4: + version "0.2.4" + resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-0.2.4.tgz#4d2598c4689d1a2ce267ddf3360a9c6b3925a20c" + integrity sha512-Vs3rhkUH6Qq1t5bqtb816oT+HeJTXfwt2cbPH17sWHIYKTotQIFPk3tf2fgqRrVyMDVOc1EnPgzIxfIulXVzwQ== + +tinyspy@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/tinyspy/-/tinyspy-1.0.2.tgz#6da0b3918bfd56170fb3cd3a2b5ef832ee1dff0d" + integrity sha512-bSGlgwLBYf7PnUsQ6WOc6SJ3pGOcd+d8AA6EUnLDDM0kWEstC1JIlSZA3UNliDXhd9ABoS7hiRBDCu+XP/sf1Q== + to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" @@ -1958,6 +2137,13 @@ totalist@^3.0.0: resolved "https://registry.yarnpkg.com/totalist/-/totalist-3.0.0.tgz#4ef9c58c5f095255cdc3ff2a0a55091c57a3a1bd" integrity sha512-eM+pCBxXO/njtF7vdFsHuqb+ElbxqtI4r5EAvk6grfAFyJ6IvWlSkfZ5T9ozC6xWw3Fj1fGoSmrl0gUs46JVIw== +tr46@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-3.0.0.tgz#555c4e297a950617e8eeddef633c87d4d9d6cbf9" + integrity sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA== + dependencies: + punycode "^2.1.1" + tr46@~0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" @@ -1987,6 +2173,11 @@ type-check@^0.4.0, type-check@~0.4.0: dependencies: prelude-ls "^1.2.1" +type-detect@^4.0.0, type-detect@^4.0.5: + version "4.0.8" + resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" + integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== + type-fest@^0.20.2: version "0.20.2" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" @@ -2034,7 +2225,7 @@ vfile-message@^2.0.4: "@types/unist" "^2.0.0" unist-util-stringify-position "^2.0.0" -vite@^3.0.4: +"vite@^2.9.12 || ^3.0.0-0", vite@^3.0.4: version "3.0.9" resolved "https://registry.yarnpkg.com/vite/-/vite-3.0.9.tgz#45fac22c2a5290a970f23d66c1aef56a04be8a30" integrity sha512-waYABTM+G6DBTCpYAxvevpG50UOlZuynR0ckTK5PawNVt7ebX6X7wNXHaGIO6wYYFXSM7/WcuFuO2QzhBB6aMw== @@ -2046,6 +2237,21 @@ vite@^3.0.4: optionalDependencies: fsevents "~2.3.2" +vitest@^0.21.0: + version "0.21.1" + resolved "https://registry.yarnpkg.com/vitest/-/vitest-0.21.1.tgz#b4f5b901c9a23a3aaec76d3404f3072821d93d00" + integrity sha512-WBIxuFmIDPuK47GO6Lu9eNeRMqHj/FWL3dk73OHH3eyPPWPiu+UB3QHLkLK2PEggCqJW4FaWoWg8R68S7p9+9Q== + dependencies: + "@types/chai" "^4.3.3" + "@types/chai-subset" "^1.3.3" + "@types/node" "*" + chai "^4.3.6" + debug "^4.3.4" + local-pkg "^0.4.2" + tinypool "^0.2.4" + tinyspy "^1.0.0" + vite "^2.9.12 || ^3.0.0-0" + web-streams-polyfill@^3.0.3: version "3.2.1" resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz#71c2718c52b45fd49dbeee88634b3a60ceab42a6" @@ -2056,6 +2262,19 @@ webidl-conversions@^3.0.0: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== +webidl-conversions@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" + integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== + +whatwg-url@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-11.0.0.tgz#0a849eebb5faf2119b901bb76fd795c2848d4018" + integrity sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ== + dependencies: + tr46 "^3.0.0" + webidl-conversions "^7.0.0" + whatwg-url@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" @@ -2100,3 +2319,8 @@ yallist@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== + +zod@^3.18.0: + version "3.18.0" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.18.0.tgz#2eed58b3cafb8d9a67aa2fee69279702f584f3bc" + integrity sha512-gwTm8RfUCe8l9rDwN5r2A17DkAa8Ez4Yl4yXqc5VqeGaXaJahzYYXbTwvhroZi0SNBqTwh/bKm2N0mpCzuw4bA== From 4d40f3df403b7b4b39f951a73652b15f9648452c Mon Sep 17 00:00:00 2001 From: Thomas Date: Sun, 21 Aug 2022 17:46:20 +0100 Subject: [PATCH 2/5] floriferous: update svelte pages to new +page and +server syntax --- src/routes/api/games/floriferous.json.ts | 51 ------------------- .../api/games/floriferous.json/+server.ts | 51 +++++++++++++++++++ .../{index.svelte => +page.svelte} | 22 ++------ src/routes/games/floriferous/+page.ts | 12 +++++ 4 files changed, 66 insertions(+), 70 deletions(-) delete mode 100644 src/routes/api/games/floriferous.json.ts create mode 100644 src/routes/api/games/floriferous.json/+server.ts rename src/routes/games/floriferous/{index.svelte => +page.svelte} (88%) create mode 100644 src/routes/games/floriferous/+page.ts diff --git a/src/routes/api/games/floriferous.json.ts b/src/routes/api/games/floriferous.json.ts deleted file mode 100644 index cfeceb8..0000000 --- a/src/routes/api/games/floriferous.json.ts +++ /dev/null @@ -1,51 +0,0 @@ -import { MONGO_URL, MONGO_DB_NAME, API_PASSWORD } from '$env/static/private'; -import type { RequestHandler, RequestHandlerOutput } from '@sveltejs/kit'; - -import { MongodbFloriferousGameRepository } from '../../../lib/floriferous/mongodb-floriferous-game-repository'; -import { FloriferousApiController } from '../../../lib/floriferous/floriferous-api-controller'; -import { SimplePasswordAuthenticator } from '../../../lib/simple-password-authenticator'; - -export const GET: RequestHandler = async (): Promise => { - const controller = new FloriferousApiController( - new MongodbFloriferousGameRepository(MONGO_URL, MONGO_DB_NAME), - new SimplePasswordAuthenticator(API_PASSWORD) - ); - - const response = await controller.getRecentGames(10); - - return { - status: 200, - body: JSON.stringify(response) - }; -}; - -export const POST: RequestHandler = async ({ request }) => { - const controller = new FloriferousApiController( - new MongodbFloriferousGameRepository(MONGO_URL, MONGO_DB_NAME), - new SimplePasswordAuthenticator(API_PASSWORD) - ); - - const isAuthenticated = controller.isRequestAuthenticated(request); - - if (!isAuthenticated) { - return { - status: 401, - body: 'Unauthorized' - }; - } - - try { - const requestBody = await request.json(); - const response = await controller.createNewGame(requestBody); - - return { - status: 200, - body: JSON.parse(JSON.stringify(response)) - }; - } catch (e) { - return { - status: 500, - body: JSON.stringify(e) - }; - } -}; diff --git a/src/routes/api/games/floriferous.json/+server.ts b/src/routes/api/games/floriferous.json/+server.ts new file mode 100644 index 0000000..e1a534e --- /dev/null +++ b/src/routes/api/games/floriferous.json/+server.ts @@ -0,0 +1,51 @@ +import { MONGO_URL, MONGO_DB_NAME, API_PASSWORD } from '$env/static/private'; +import type { error } from '@sveltejs/kit'; +import type { RequestHandler } from './$types'; + +import { MongodbFloriferousGameRepository } from '$lib/floriferous/mongodb-floriferous-game-repository'; +import { FloriferousApiController } from '$lib/floriferous/floriferous-api-controller'; +import { SimplePasswordAuthenticator } from '$lib/simple-password-authenticator'; +import { + FloriferousGameApiPort, + type FloriferousGameJson +} from '$lib/floriferous/floriferous-game-api-port'; + +export const GET: RequestHandler = async () => { + const controller = new FloriferousApiController( + new MongodbFloriferousGameRepository(MONGO_URL, MONGO_DB_NAME), + new SimplePasswordAuthenticator(API_PASSWORD) + ); + + const response = await controller.getRecentGames(10); + + return new Response(JSON.stringify(response), { status: 200 }); +}; + +export const POST = async ({ request }) => { + const controller = new FloriferousApiController( + new MongodbFloriferousGameRepository(MONGO_URL, MONGO_DB_NAME), + new SimplePasswordAuthenticator(API_PASSWORD) + ); + + const isAuthenticated = controller.isRequestAuthenticated(request); + + if (!isAuthenticated) { + return { + status: 401, + body: 'Unauthorized' + }; + } + + try { + const requestBody = await request.json(); + const response = await controller.createNewGame(requestBody); + + return new Response(JSON.stringify(response), { + status: 200 + }); + } catch (e) { + return new Response(JSON.stringify(e), { + status: 500 + }); + } +}; diff --git a/src/routes/games/floriferous/index.svelte b/src/routes/games/floriferous/+page.svelte similarity index 88% rename from src/routes/games/floriferous/index.svelte rename to src/routes/games/floriferous/+page.svelte index 3e471de..129dd7d 100644 --- a/src/routes/games/floriferous/index.svelte +++ b/src/routes/games/floriferous/+page.svelte @@ -1,20 +1,7 @@ - -