diff --git a/BackendTests/Controllers/PlayerControllerTests.cs b/BackendTests/Controllers/PlayerControllerTests.cs new file mode 100644 index 0000000..8e4cc3b --- /dev/null +++ b/BackendTests/Controllers/PlayerControllerTests.cs @@ -0,0 +1,6 @@ +namespace BackendTests.Controllers; + +public class PlayerControllerTests +{ + // TODO +} diff --git a/DataAccessLayer/DataAccessLayer.csproj b/DataAccessLayer/DataAccessLayer.csproj index b5bfa98..ae25253 100644 --- a/DataAccessLayer/DataAccessLayer.csproj +++ b/DataAccessLayer/DataAccessLayer.csproj @@ -7,8 +7,4 @@ DAL - - - - diff --git a/DataAccessLayer/Database/Models/User.cs b/DataAccessLayer/Database/Models/User.cs new file mode 100644 index 0000000..eeccfca --- /dev/null +++ b/DataAccessLayer/Database/Models/User.cs @@ -0,0 +1,8 @@ +namespace DAL.Database.Models; + +public class User +{ + public required string Username { get; init; } + public required string Password { get; init; } // TODO use hashing and salt + public string? Colour { get; init; } +} diff --git a/DataAccessLayer/Database/Service/UserService.cs b/DataAccessLayer/Database/Service/UserService.cs index 136f2a1..261da69 100644 --- a/DataAccessLayer/Database/Service/UserService.cs +++ b/DataAccessLayer/Database/Service/UserService.cs @@ -1,26 +1,27 @@ -using pacMan.GameStuff; -using pacMan.GameStuff.Items; +using DAL.Database.Models; namespace DAL.Database.Service; public class UserService { - private readonly List _users = new() + private readonly List _users = new() { - new Player + new User { - Username = "admin", - Colour = "red", - PacMan = new Character - { - Colour = "red", - Type = CharacterType.PacMan - } + Username = "Firefox", + Password = "Firefox", + Colour = "red" + }, + new User + { + Username = "Chrome", + Password = "Chrome", + Colour = "blue" } }; - public async Task Login(string username, string password) + public async Task Login(string username, string password) { - return await Task.Run(() => _users.FirstOrDefault(x => x.Username == username && password == "admin")); + return await Task.Run(() => _users.FirstOrDefault(x => x.Username == username && x.Password == password)); } } diff --git a/pac-man-board-game/ClientApp/.env.development b/pac-man-board-game/ClientApp/.env.development index 117b355..f25ad7a 100644 --- a/pac-man-board-game/ClientApp/.env.development +++ b/pac-man-board-game/ClientApp/.env.development @@ -1,5 +1,5 @@ PORT=44435 HTTPS=true -VITE_API_URI=localhost:3000/api/game +VITE_API_URI=localhost:3000/api VITE_API_HTTP=https://$VITE_API_URI -VITE_API_WS=wss://$VITE_API_URI/connect +VITE_API_WS=wss://$VITE_API_URI/game/connect diff --git a/pac-man-board-game/ClientApp/src/AppRoutes.tsx b/pac-man-board-game/ClientApp/src/AppRoutes.tsx index 6dc29a9..a1d7816 100644 --- a/pac-man-board-game/ClientApp/src/AppRoutes.tsx +++ b/pac-man-board-game/ClientApp/src/AppRoutes.tsx @@ -3,6 +3,7 @@ import {Counter} from "./pages/counter"; import Home from "./pages/home"; import Game from "./pages/game"; import LobbyPage from "./pages/lobby"; +import Login from "./pages/login"; const AppRoutes = [ { @@ -20,6 +21,10 @@ const AppRoutes = [ { path: "/lobby", element: , + }, + { + path: "/login", + element: } ]; diff --git a/pac-man-board-game/ClientApp/src/components/input.tsx b/pac-man-board-game/ClientApp/src/components/input.tsx index 0a375f0..00a44af 100644 --- a/pac-man-board-game/ClientApp/src/components/input.tsx +++ b/pac-man-board-game/ClientApp/src/components/input.tsx @@ -7,10 +7,12 @@ const Input: FRComponent = forwardRef(( id, placeholder, required = false, + name, }, ref) => ( diff --git a/pac-man-board-game/ClientApp/src/pages/lobby.tsx b/pac-man-board-game/ClientApp/src/pages/lobby.tsx index 4837e66..a1cba24 100644 --- a/pac-man-board-game/ClientApp/src/pages/lobby.tsx +++ b/pac-man-board-game/ClientApp/src/pages/lobby.tsx @@ -2,11 +2,13 @@ import React, {FC, Suspense} from "react"; import {atom, useAtomValue} from "jotai"; import {Button} from "../components/button"; import {thisPlayerAtom} from "../utils/state"; +import {getData, postData} from "../utils/api"; const fetchAtom = atom(async () => { - const response = await fetch(import.meta.env.VITE_API_HTTP + "/all"); + const response = await getData("/game/all"); return await response.json() as Game[]; }); + // TODO create game button const LobbyPage: FC = () => ( // TODO check if player is defined in storage, if not redirect to login @@ -26,19 +28,13 @@ const GameTable: FC = ({className}) => { console.debug("Joining game " + gameId); - const result = await fetch(import.meta.env.VITE_API_HTTP + "/join/" + gameId, { - method: "POST", - headers: { - "Content-Type": "application/json" - }, - body: JSON.stringify(thisPlayer), - }) + const result = await postData("/game/join/" + gameId, {body: thisPlayer}); if (result.ok) { - console.debug("Joined game " + gameId, result.body); + console.debug("Joined game " + gameId, await result.json()); // TODO redirect to game page } else { - console.error("Failed to join game " + gameId, result.body); + console.error("Failed to join game " + gameId, await result.json()); // TODO show error message } } @@ -47,10 +43,10 @@ const GameTable: FC = ({className}) => { - - + + - + @@ -66,6 +62,12 @@ const GameTable: FC = ({className}) => { )} + { + data?.length === 0 && + + + + }
IdCountIdCount StateJoinJoin
No games found
); diff --git a/pac-man-board-game/ClientApp/src/pages/login.tsx b/pac-man-board-game/ClientApp/src/pages/login.tsx new file mode 100644 index 0000000..3c95f04 --- /dev/null +++ b/pac-man-board-game/ClientApp/src/pages/login.tsx @@ -0,0 +1,54 @@ +import React, {FormEvent} from "react"; +import {Button} from "../components/button"; +import Input from "../components/input"; +import {useSetAtom} from "jotai"; +import {thisPlayerAtom} from "../utils/state"; +import Player from "../game/player"; +import {useNavigate} from "react-router-dom"; +import {postData} from "../utils/api"; + +const Login = () => { + + const setThisPlayer = useSetAtom(thisPlayerAtom); + const navigate = useNavigate(); + + async function handleLogin(e: FormEvent): Promise { + e.preventDefault(); + const fields = e.currentTarget.querySelectorAll("input"); + + let user: User = {username: "", password: ""}; + for (const field of fields) { + user = { + ...user, + [field.name]: field.value, + } + } + + const response = await postData("/player/login", { + body: {username: user.username, password: user.password} as User + }) + + const data = await response.json(); + + if (response.ok) { + console.debug("Login successful: ", data); + setThisPlayer(new Player(data as PlayerProps)); + navigate("/lobby"); + } else { + console.error("Error: ", data); + // TODO display error + } + + } + + return ( // TODO prettify +
+

Login

+ + + +
+ ); +} + +export default Login; diff --git a/pac-man-board-game/ClientApp/src/types/props.d.ts b/pac-man-board-game/ClientApp/src/types/props.d.ts index 6ca2014..eac152e 100644 --- a/pac-man-board-game/ClientApp/src/types/props.d.ts +++ b/pac-man-board-game/ClientApp/src/types/props.d.ts @@ -21,6 +21,7 @@ interface InputProps extends ComponentProps { type?: string, placeholder?: string, required?: boolean, + name?: string, } interface CharacterProps { diff --git a/pac-man-board-game/ClientApp/src/types/types.d.ts b/pac-man-board-game/ClientApp/src/types/types.d.ts index dc92b0c..7223e37 100644 --- a/pac-man-board-game/ClientApp/src/types/types.d.ts +++ b/pac-man-board-game/ClientApp/src/types/types.d.ts @@ -40,3 +40,16 @@ type Game = { readonly count: number, readonly isGameStarted: boolean, } + +type User = { + readonly username: string, + readonly password: string, + readonly colour?: import("../game/colour").Colour +} + +type Api = (path: string, data?: ApiRequest & T) => Promise; + +type ApiRequest = { + headers?: HeadersInit, + body?: any +} diff --git a/pac-man-board-game/ClientApp/src/utils/api.ts b/pac-man-board-game/ClientApp/src/utils/api.ts new file mode 100644 index 0000000..32cfb36 --- /dev/null +++ b/pac-man-board-game/ClientApp/src/utils/api.ts @@ -0,0 +1,17 @@ +export const getData: Api = async (path, {headers} = {}) => { + return await fetch(import.meta.env.VITE_API_HTTP + path, { + method: "GET", + headers: headers + }); +} + +export const postData: Api = async (path, {body, headers} = {}) => { + return await fetch(import.meta.env.VITE_API_HTTP + path, { + method: "POST", + headers: { + "Content-Type": "application/json", + ...headers, + }, + body: JSON.stringify(body), + }) +} diff --git a/pac-man-board-game/Controllers/PlayerController.cs b/pac-man-board-game/Controllers/PlayerController.cs new file mode 100644 index 0000000..fa82298 --- /dev/null +++ b/pac-man-board-game/Controllers/PlayerController.cs @@ -0,0 +1,26 @@ +using DAL.Database.Models; +using DAL.Database.Service; +using Microsoft.AspNetCore.Mvc; +using pacMan.GameStuff.Items; + +namespace pacMan.Controllers; + +[ApiController] +[Route("api/[controller]")] +public class PlayerController : ControllerBase +{ + private readonly UserService _userService; + + public PlayerController(UserService userService) => _userService = userService; + + [HttpPost("login")] + public async Task Login([FromBody] User user) + { + var result = await _userService.Login(user.Username, user.Password); + if (result is null) return Unauthorized("Invalid username or password"); + return Ok((Player)result); + } + + [HttpPost("register")] + public async Task Register([FromBody] User user) => throw new NotSupportedException(); +} diff --git a/pac-man-board-game/GameStuff/Items/Player.cs b/pac-man-board-game/GameStuff/Items/Player.cs index 3d3435a..e3b4236 100644 --- a/pac-man-board-game/GameStuff/Items/Player.cs +++ b/pac-man-board-game/GameStuff/Items/Player.cs @@ -1,3 +1,5 @@ +using DAL.Database.Models; + namespace pacMan.GameStuff.Items; public interface IPlayer @@ -40,4 +42,16 @@ public class Player : IPlayer, IEquatable } public override int GetHashCode() => Username.GetHashCode(); + + public static explicit operator Player(User user) => + new() + { + Username = user.Username, + PacMan = new Character + { + Colour = user.Colour, + Type = CharacterType.PacMan + }, + Colour = user.Colour + }; } diff --git a/pac-man-board-game/Program.cs b/pac-man-board-game/Program.cs index 826631a..07d04a5 100644 --- a/pac-man-board-game/Program.cs +++ b/pac-man-board-game/Program.cs @@ -1,3 +1,4 @@ +using DAL.Database.Service; using pacMan.Interfaces; using pacMan.Services; @@ -9,6 +10,7 @@ builder.Services.AddControllersWithViews(); builder.Services .AddSingleton() .AddSingleton() + .AddScoped() .AddTransient(); var app = builder.Build(); diff --git a/pac-man-board-game/pac-man-board-game.csproj b/pac-man-board-game/pac-man-board-game.csproj index 41bb151..1e37d0b 100644 --- a/pac-man-board-game/pac-man-board-game.csproj +++ b/pac-man-board-game/pac-man-board-game.csproj @@ -54,6 +54,10 @@ + + + +