Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 26 additions & 8 deletions backend/Controllers/GamesController.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using IP5.Model;
using IP5.Repositories;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;

Expand All @@ -10,21 +12,37 @@ namespace IP5.Controllers
public class GamesController : ControllerBase
{
private readonly ILogger<GamesController> _logger;
private readonly IGamesRepository _gamesRepository;

public GamesController(ILogger<GamesController> logger)
public GamesController(IGamesRepository gamesRepository, ILogger<GamesController> logger)
{
_logger = logger;
_gamesRepository = gamesRepository;
}

[HttpGet]
public IEnumerable<Game> Get()
public async IAsyncEnumerable<Game> GetAll()
{
return new[] {
new Game {Player1 = "Joel", Player2 = "Rohn", Score1 = 3, Score2 = 6, Ongoing = true},
new Game {Player1 = "Jan", Player2 = "Bram", Score1 = 5, Score2 = 11},
new Game {Player1 = "Andy", Player2 = "Raul", Score1 = 11, Score2 = 9},
new Game {Player1 = "Danny", Player2 = "Tony", Score1 = 11, Score2 = 9},
};
await foreach (var game in _gamesRepository.GetAll())
yield return game;
}

[HttpGet("{code}")]
public Task<Game> Get([FromRoute] string code)
{
return _gamesRepository.Get(code);
}

[HttpPut]
public void Create(Game game)
{
_gamesRepository.Add(game);
}

[HttpDelete("{code}")]
public void Delete(string code)
{
_gamesRepository.Delete(code);
}
}
}
26 changes: 26 additions & 0 deletions backend/Model/DbGame.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;

namespace IP5.Repositories
{
public class DbGame
{
public Guid Id { get; set; }

[Column("player_one_id")]
public Guid PlayerOneId { get; set; }

[Column("player_two_id")]
public Guid PlayerTwoId { get; set; }

[Column("score_one")]
public int ScoreOne { get; set; }

[Column("score_two")]
public int ScoreTwo { get; set; }

[Column("is_active")]
public bool IsActive { get; set; }
}
}
11 changes: 6 additions & 5 deletions backend/ModelView/Game.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ namespace IP5.Model
{
public class Game
{
public string Player1 { get; set; }
public string Player2 { get; set; }
public int Score1 { get; set; }
public int Score2 { get; set; }
public bool Ongoing { get; set; }
public string Code { get; set; }
public string PlayerOneCode { get; set; }
public string PlayerTwoCode { get; set; }
public int ScoreOne { get; set; }
public int ScoreTwo { get; set; }
public bool IsActive { get; set; }
}
}
72 changes: 72 additions & 0 deletions backend/Repositories/GamesRepository.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
using IP5.Extensions;
using IP5.Model;
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace IP5.Repositories
{
public interface IGamesRepository
{
IAsyncEnumerable<Game> GetAll();
Task Add(Game game);
Task Delete(string code);
Task<Game> Get(string code);
}

public class GamesRepository : IGamesRepository
{
private PingPongContext _db;

public GamesRepository(PingPongContext db)
{
_db = db;
}

public Task Add(Game game)
{
_db.Games.Add(new DbGame {
PlayerOneId = game.PlayerOneCode.ToGuid(),
PlayerTwoId = game.PlayerTwoCode.ToGuid()
});

return _db.SaveChangesAsync();
}

public Task Delete(string code)
{
var game = _db.Games.Where(i => i.Id == code.ToGuid());
_db.Remove(game);
return _db.SaveChangesAsync();
}

public Task<Game> Get(string code)
{
return _db.Games.Where(i => i.Id == code.ToGuid())
.Select(i => new Game
{
Code = i.Id.ToBase64(),
IsActive = i.IsActive,
PlayerOneCode = i.PlayerOneId.ToBase64(),
PlayerTwoCode = i.PlayerTwoId.ToBase64(),
ScoreOne = i.ScoreOne,
ScoreTwo = i.ScoreTwo
}).FirstAsync();
}

public IAsyncEnumerable<Game> GetAll()
{
return _db.Games.Select(i => new Game
{
Code = i.Id.ToBase64(),
IsActive = i.IsActive,
PlayerOneCode = i.PlayerOneId.ToBase64(),
PlayerTwoCode = i.PlayerTwoId.ToBase64(),
ScoreOne = i.ScoreOne,
ScoreTwo = i.ScoreTwo
}).AsAsyncEnumerable();
}
}
}
4 changes: 4 additions & 0 deletions backend/Repositories/PingPongContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public class PingPongContext : DbContext
public DbSet<DbPlayer> Players { get; set; }
public DbSet<DbSession> Sessions { get; set; }
public DbSet<DbSessionPlayer> SessionPlayers { get; set; }
public DbSet<DbGame> Games { get; set; }

public PingPongContext(DbContextOptions<PingPongContext> options) : base(options)
{ }
Expand All @@ -22,6 +23,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
modelBuilder.Entity<DbSession>()
.ToTable("session");

modelBuilder.Entity<DbGame>()
.ToTable("game");

modelBuilder.Entity<DbSessionPlayer>().HasOne<DbSession>(x => x.Session).WithMany(x => x.SessionPlayers).HasForeignKey(x => x.SessionId);
}
}
Expand Down
1 change: 1 addition & 0 deletions backend/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public void ConfigureServices(IServiceCollection services)
services.AddTransient<IPlayersRepository, PlayersRepository>();
services.AddTransient<ISessionPlayersRepository, SessionPlayersRepository>();
services.AddTransient<ISessionsRepository, SessionsRepository>();
services.AddTransient<IGamesRepository, GamesRepository>();

services.AddControllers();
}
Expand Down
27 changes: 27 additions & 0 deletions database/game.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
CREATE TABLE [dbo].[game](
[id] [uniqueidentifier] NOT NULL,
[player_one_id] [uniqueidentifier] NOT NULL,
[player_two_id] [uniqueidentifier] NOT NULL,
[score_one] [int] NULL,
[score_two] [int] NULL,
[is_active] [bit] NULL,
CONSTRAINT [PK_game] PRIMARY KEY CLUSTERED
(
[id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

ALTER TABLE [dbo].[game] WITH CHECK ADD CONSTRAINT [FK_game_player] FOREIGN KEY([player_one_id])
REFERENCES [dbo].[player] ([id])
GO

ALTER TABLE [dbo].[game] CHECK CONSTRAINT [FK_game_player]
GO

ALTER TABLE [dbo].[game] WITH CHECK ADD CONSTRAINT [FK_game_player1] FOREIGN KEY([player_two_id])
REFERENCES [dbo].[player] ([id])
GO

ALTER TABLE [dbo].[game] CHECK CONSTRAINT [FK_game_player1]
GO
4 changes: 2 additions & 2 deletions frontend/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import React from 'react';
import {render} from 'react-dom';
import {HashRouter as Router, Route, Switch} from "react-router-dom";
import Games from "./views/games/games";
import PlayersList from "./views/players/players";
import {Shell} from "./views/shell/shell";
import Sessions from "./views/sessions/sessions";
import GamesPage from "./views/games/games";

const App = () => (
<Router>
<Shell>
<Switch>
<Route exact path="/" component={LandingPage}/>
<Route path="/sessions" component={Sessions}/>
<Route path="/games" component={Games}/>
<Route path="/games" component={GamesPage}/>
<Route path="/players" component={PlayersList}/>
</Switch>
</Shell>
Expand Down
40 changes: 40 additions & 0 deletions frontend/src/views/games/create_game.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React, {useCallback, useState} from "react";
import {withRouter} from "react-router";
import {BackLink, DefaultButton} from "../../ui/back_button";
import {Page} from "../shell/shell";
import {BusyOverlay} from "../../ui/busy/busy";
import {gamesApi} from "./games_api";

export const CreateGame = withRouter(({history}) => {
const [form, setForm] = useState<{ name: string, email: string }>({name: null, email: null});
const [busy, setBusy] = useState(false);

const onSubmit = useCallback(async (ev) => {
ev.preventDefault();
setBusy(true);
await gamesApi.create({Email: form.email, Description: form.name});
setBusy(false);
history.replace('/players');
}, [form]);

return (
<Page title="New game">
<nav>
<DefaultButton onClick={onSubmit}>Create</DefaultButton>
<BackLink/>
</nav>

<form className="form">
{busy && <BusyOverlay/>}

<label htmlFor="name">Name:</label>
<input required={true} type="text" id="name" name="name" value={form.name}
onChange={(e) => setForm({...form, name: e.target.value})}/>

<label htmlFor="email">Email:</label>
<input required={true} type="email" id="email" title="Email" value={form.email}
onChange={(e) => setForm({...form, email: e.target.value})}/>
</form>
</Page>
)
});
59 changes: 45 additions & 14 deletions frontend/src/views/games/games.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,60 @@
import React from "react";
import React, {useCallback} from "react";
import {cls} from "../../util/react";
import {Page} from "../shell/shell";
import {gamesApi} from "./games_api";
import {Game, gamesApi} from "./games_api";
import {List} from "../../ui/list/list";
import {Link, Route, Switch, useRouteMatch, withRouter} from "react-router-dom";
import {CreateGame} from "./create_game";

const css = require('./games.scss');

const Games = () => {
const GamesPage = () => {
const {path} = useRouteMatch();

return (
<Switch>
<Route path={`${path}/create`}>
<CreateGame/>
</Route>
{/*<Route path={`${path}/:code`}>*/}
{/* <GameDetails/>*/}
{/*</Route>*/}
<Route>
<GamesList/>
</Route>
</Switch>
);
};

const GamesList = withRouter(({history}) => {
const {url} = useRouteMatch();
const getAll = useCallback(() => gamesApi.getAll(), []);

return (
<Page title="Games">
<List data={gamesApi.getAll}
className={"games"}
itemRender={(i) => <GameItem game={i}/>}/>
<nav>
<Link to={`${url}/create`}>Create game</Link>
</nav>

<div className="games">
<List itemRender={game => <GameItem {...game}/>}
data={getAll}
onItemClick={(i) => history.push(url + '/' + i.code)}/>
</div>
</Page>
)
};
});



const GameItem = ({game}) =>
<div className={cls("game", game.ongoing && 'ongoing')}>
<div className="player a">{game.player1}</div>
<div className="score a">{game.score1}</div>
const GameItem = (props: Game) =>
<div className={cls("game", props.isActive && 'ongoing')}>
<div className="player a">{props.playerOneCode}</div>
<div className="score a">{props.scoreOne}</div>
<div className="vs"/>
<div className="score b">{game.score2}</div>
<div className="player b">{game.player2}</div>
<div className="score b">{props.scoreTwo}</div>
<div className="player b">{props.playerTwoCode}</div>
</div>;


export default Games;
export default GamesPage;
11 changes: 6 additions & 5 deletions frontend/src/views/games/games_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import {API_URL} from "../../config";


export type Game = {
// code: string;
// description: string;
// email: string;
// wins: number;
// losses: number;
code: string,
playerOneCode: string,
playerTwoCode: string,
scoreOne: number,
scoreTwo: number,
isActive: boolean
}

export const gamesApi = {
Expand Down