advent-of-code

Entries to advent of code, multiple years
git clone git://git.finwo.net/misc/advent-of-code
Log | Files | Refs

commit 376aab437d3df209b90ff989d3ec73ce1420a964
parent ce42583414a689837101ec26c0f67b415d56cfb2
Author: finwo <finwo@pm.me>
Date:   Sun, 12 Dec 2021 02:36:16 +0100

Showing drawn numbers on web page through rest call now

Diffstat:
Asolutions/day-04/api/src/bingo/bingo.controller.ts | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msolutions/day-04/api/src/bingo/bingo.module.ts | 12++++++++++--
Asolutions/day-04/api/src/bingo/bingo.service.ts | 31+++++++++++++++++++++++++++++++
Msolutions/day-04/api/src/interface/rest/index.ts | 2+-
Msolutions/day-04/web/public/global.less | 3+++
Msolutions/day-04/web/src/main.js | 10+++++++---
Asolutions/day-04/web/src/page/game-list.vue | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Msolutions/day-04/web/src/page/game.vue | 71+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
8 files changed, 232 insertions(+), 10 deletions(-)

diff --git a/solutions/day-04/api/src/bingo/bingo.controller.ts b/solutions/day-04/api/src/bingo/bingo.controller.ts @@ -0,0 +1,63 @@ +import { Service, Inject } from 'typedi'; +import { Request, Response } from 'express'; + +import { Controller, Get } from '../interface/rest'; +import { BingoService } from './bingo.service'; + +@Controller('bingo') +@Service() +export class BingoController { + + constructor( + private bingoService: BingoService + ) {} + + // List game uuids + @Get('games/:uuid/boards') + async getGameBoards({ res, uuid }: { res: Response, uuid: string }) { + const found = await this.bingoService.getGameBoards(uuid); + if (!found) return res.end(JSON.stringify({ + ok : false, + error : 'not-found', + })); + res.end(JSON.stringify({ + ok : true, + data : found.map(board => ({ + uuid : board.uuid, + values : board.values, + })) + }, null, 2)); + } + + // Fetch a single game + @Get('games/:uuid') + async getGame({ res, uuid }: { res: Response, uuid: string }) { + const found = await this.bingoService.get(uuid); + if (!found) return res.end(JSON.stringify({ + ok : false, + error : 'not-found', + })); + res.end(JSON.stringify({ + ok : true, + data : { + uuid : found.uuid, + name : found.name, + drawn : found.drawn, + }, + }, null, 2)); + } + + // List game uuids + @Get('games') + async listGames({ res }: { res: Response }) { + const found = await this.bingoService.all(); + res.end(JSON.stringify({ + ok : true, + data : found.map(game => ({ + uuid : game.uuid, + name : game.name, + })), + })); + } + +} diff --git a/solutions/day-04/api/src/bingo/bingo.module.ts b/solutions/day-04/api/src/bingo/bingo.module.ts @@ -1,13 +1,17 @@ import { Service, Container } from 'typedi'; import { lineByLine } from '@common/line-by-line'; +import { BingoController } from './bingo.controller'; + import { Board } from './model/board'; import { Game } from './model/game'; @Service() export class BingoModule { - constructor() { + constructor( + private bingoController: BingoController + ) { this.initializeDb(); } @@ -24,7 +28,11 @@ export class BingoModule { // Task defined first line as already-drawn numbers if (!game) { - game = new Game({ drawn: line.split(',').map(v => parseInt(v)), board: [] }); + game = new Game({ + name : 'Base game', + drawn : line.split(',').map(v => parseInt(v)), + board : [] + }); db.game.push(game); return; } diff --git a/solutions/day-04/api/src/bingo/bingo.service.ts b/solutions/day-04/api/src/bingo/bingo.service.ts @@ -0,0 +1,31 @@ +import { Service, Inject } from 'typedi'; +import { Board } from './model/board'; +import { Game } from './model/game'; + +@Service() +export class BingoService { + + constructor( + @Inject('db') private db + ) {} + + async all(): Promise<Game[]> { + return this.db.game; + } + + async get(game: string|Partial<Game>): Promise<Game> { + if ('string' === typeof game) return this.db.game.find(g => g.uuid === game); + if ('uuid' in game) return this.db.game.find(g => g.uuid === game.uuid); + throw new Error("Could not fetch game: invalid identifier"); + } + + async getGameBoards(game: string|Partial<Game>): Promise<Board[]> { + const found = await this.get(game); + if (!found) return []; + return found.board.map(board => { + if (board instanceof Board) return board; + return this.db.board.find(g => g.uuid === game); + });; + } + +} diff --git a/solutions/day-04/api/src/interface/rest/index.ts b/solutions/day-04/api/src/interface/rest/index.ts @@ -19,7 +19,7 @@ export function Controller(prefix?: string): ClassDecorator { const path = ['',prefix,route.path].join('/').replace(/\/+/g,'/'); router[route.method](path, async (req: Request, res: Response, next: Function) => { const controller = Container.get(constructor); - await controller[route.name]({ req, res }); + await controller[route.name]({ req, res, ...req.params }); if (!res.writableEnded) next(); }); } diff --git a/solutions/day-04/web/public/global.less b/solutions/day-04/web/public/global.less @@ -8,6 +8,7 @@ * { box-sizing: border-box; + line-height: 1.5em; } html, body { @@ -15,3 +16,5 @@ html, body { margin : 0; padding : 0; } + +h1, h2, h3 { font-weight: 500; } diff --git a/solutions/day-04/web/src/main.js b/solutions/day-04/web/src/main.js @@ -6,15 +6,17 @@ import VueNotificationList from '@dafcoe/vue-notification' import '@dafcoe/vue-notification/dist/vue-notification.css' import PageHome from './page/home.vue'; +import PageGameList from './page/game-list.vue'; import PageGame from './page/game.vue'; import PageNotFound from './page/not-found.vue'; const router = createRouter({ history: createWebHashHistory(), routes: [ - { path: '/' , component: PageHome , name: 'Home' , meta: { nav: true , icon: 'home' } }, - { path: '/game' , component: PageGame , name: 'Game' , meta: { nav: true , icon: 'videogame_asset' } }, - { path: '/:catchAll(.*)', component: PageNotFound, name: 'Not Found', meta: { nav: false, icon: 'error' } }, + { path: '/' , component: PageHome , name: 'Home' , meta: { nav: true , icon: 'home' } }, + { path: '/game/:uuid(.*)', component: PageGame , name: 'Game' , meta: { nav: false, icon: 'videogame_asset' } }, + { path: '/game' , component: PageGameList, name: 'Games' , meta: { nav: true , icon: 'videogame_asset' } }, + { path: '/:catchAll(.*)' , component: PageNotFound, name: 'Not Found', meta: { nav: false, icon: 'error' } }, ] }); @@ -22,3 +24,5 @@ const app = createApp(root); app.use(router); app.use(VueNotificationList); app.mount(document.body); + +console.log(router.getRoutes()); diff --git a/solutions/day-04/web/src/page/game-list.vue b/solutions/day-04/web/src/page/game-list.vue @@ -0,0 +1,50 @@ +<template> + <layout> + <div id="gamelist"> + <div v-for="g in games"> + <router-link :to="'/game/' + g.uuid">{{ g.name }}</router-link> + </div> + </div> + </layout> +</template> + +<style> +#gamelist { + display: flex; + flex-wrap: wrap; + /* margin: -0.5rem; */ + gap: 1rem; +} +#gamelist > * { + /* flex: 1; */ + background: var(--col-primary); + color: #fff; + padding: 1rem; + /* margin: 0.5rem; */ +} +#gamelist a { + background: var(--col-primary); + color: #fff; +} +</style> + +<script lang="ts"> +import { ref, onMounted } from 'vue'; +import Layout from '../layout/default.vue'; + +export default { + components: {Layout}, + setup() { + const games = ref([]); + + onMounted(async () => { + const gameResponse = await (await fetch('http://api.docker/bingo/games', { method: 'GET' })).json(); + if (gameResponse.ok) games.value = gameResponse.data; + }); + + return { + games, + }; + } +} +</script> diff --git a/solutions/day-04/web/src/page/game.vue b/solutions/day-04/web/src/page/game.vue @@ -1,21 +1,84 @@ <template> <layout> - <div id="drawn"> - DRAWN + <div> + <h3>Drawn numbers</h3> + <div id="numberlist"> + <div v-for="n in game.drawn">{{ n }}</div> + </div> </div> - <div id="boards"> - BOARDS + <div> + <h3>Boards</h3> + <div id="boardlist"> + <div v-for="board in boards"> + + </div> + </div> </div> + Single game life </layout> </template> <style> +#numberlist { + display: flex; + flex-wrap: wrap; + gap: 1rem; +} +#numberlist > * { + background: var(--col-primary); + color: #fff; + padding: 1rem; + height: 3.5rem; /* 1.5 line height + 2 edges of padding */ + width: 3.5rem; /* making the block square */ + text-align: center; +} + +#boardlist { + display: flex; + flex-wrap: wrap; + gap: 1rem; +} +#boardlist > * { + background: var(--col-primary); + color: #fff; + padding: 1rem; + font-size: 1rem; + height: 3.5rem; + width: 3.5rem; + text-align: center; +} + </style> <script lang="ts"> +import { ref, onMounted, getCurrentInstance } from 'vue'; import Layout from '../layout/default.vue'; + export default { components: {Layout}, + setup() { + const instance = getCurrentInstance(); + const route = instance.proxy.$root.$route; + const { uuid } = route.params; + + console.log({uuid}); + + const selected = ref([]); + const game = ref({}); + const boards = ref([]); + + onMounted(async () => { + const gameResponse = await (await fetch(`http://api.docker/bingo/games/${uuid}` , { method: 'GET' })).json(); + const boardResponse = await (await fetch(`http://api.docker/bingo/games/${uuid}/boards`, { method: 'GET' })).json(); + if (gameResponse.ok ) game.value = gameResponse.data; + if (boardResponse.ok) boards.value = boardResponse.data; + }); + + return { + game, + boards + }; + } } </script>