Created simple login page, and User model
This commit is contained in:
parent
745f292eee
commit
5e21947870
6
BackendTests/Controllers/PlayerControllerTests.cs
Normal file
6
BackendTests/Controllers/PlayerControllerTests.cs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
namespace BackendTests.Controllers;
|
||||||
|
|
||||||
|
public class PlayerControllerTests
|
||||||
|
{
|
||||||
|
// TODO
|
||||||
|
}
|
@ -7,8 +7,4 @@
|
|||||||
<RootNamespace>DAL</RootNamespace>
|
<RootNamespace>DAL</RootNamespace>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\pac-man-board-game\pac-man-board-game.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
8
DataAccessLayer/Database/Models/User.cs
Normal file
8
DataAccessLayer/Database/Models/User.cs
Normal file
@ -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; }
|
||||||
|
}
|
@ -1,26 +1,27 @@
|
|||||||
using pacMan.GameStuff;
|
using DAL.Database.Models;
|
||||||
using pacMan.GameStuff.Items;
|
|
||||||
|
|
||||||
namespace DAL.Database.Service;
|
namespace DAL.Database.Service;
|
||||||
|
|
||||||
public class UserService
|
public class UserService
|
||||||
{
|
{
|
||||||
private readonly List<Player> _users = new()
|
private readonly List<User> _users = new()
|
||||||
{
|
{
|
||||||
new Player
|
new User
|
||||||
{
|
{
|
||||||
Username = "admin",
|
Username = "Firefox",
|
||||||
Colour = "red",
|
Password = "Firefox",
|
||||||
PacMan = new Character
|
Colour = "red"
|
||||||
{
|
},
|
||||||
Colour = "red",
|
new User
|
||||||
Type = CharacterType.PacMan
|
{
|
||||||
}
|
Username = "Chrome",
|
||||||
|
Password = "Chrome",
|
||||||
|
Colour = "blue"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public async Task<IPlayer?> Login(string username, string password)
|
public async Task<User?> 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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
PORT=44435
|
PORT=44435
|
||||||
HTTPS=true
|
HTTPS=true
|
||||||
VITE_API_URI=localhost:3000/api/game
|
VITE_API_URI=localhost:3000/api
|
||||||
VITE_API_HTTP=https://$VITE_API_URI
|
VITE_API_HTTP=https://$VITE_API_URI
|
||||||
VITE_API_WS=wss://$VITE_API_URI/connect
|
VITE_API_WS=wss://$VITE_API_URI/game/connect
|
||||||
|
@ -3,6 +3,7 @@ import {Counter} from "./pages/counter";
|
|||||||
import Home from "./pages/home";
|
import Home from "./pages/home";
|
||||||
import Game from "./pages/game";
|
import Game from "./pages/game";
|
||||||
import LobbyPage from "./pages/lobby";
|
import LobbyPage from "./pages/lobby";
|
||||||
|
import Login from "./pages/login";
|
||||||
|
|
||||||
const AppRoutes = [
|
const AppRoutes = [
|
||||||
{
|
{
|
||||||
@ -20,6 +21,10 @@ const AppRoutes = [
|
|||||||
{
|
{
|
||||||
path: "/lobby",
|
path: "/lobby",
|
||||||
element: <LobbyPage/>,
|
element: <LobbyPage/>,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/login",
|
||||||
|
element: <Login/>
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -7,10 +7,12 @@ const Input: FRComponent<InputProps, HTMLInputElement> = forwardRef((
|
|||||||
id,
|
id,
|
||||||
placeholder,
|
placeholder,
|
||||||
required = false,
|
required = false,
|
||||||
|
name,
|
||||||
}, ref) => (
|
}, ref) => (
|
||||||
<input type={type}
|
<input type={type}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
id={id}
|
id={id}
|
||||||
|
name={name}
|
||||||
className={"border-2 border-gray-300 rounded-md p-1 " + className}
|
className={"border-2 border-gray-300 rounded-md p-1 " + className}
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
required={required}/>
|
required={required}/>
|
||||||
|
@ -2,11 +2,13 @@ import React, {FC, Suspense} from "react";
|
|||||||
import {atom, useAtomValue} from "jotai";
|
import {atom, useAtomValue} from "jotai";
|
||||||
import {Button} from "../components/button";
|
import {Button} from "../components/button";
|
||||||
import {thisPlayerAtom} from "../utils/state";
|
import {thisPlayerAtom} from "../utils/state";
|
||||||
|
import {getData, postData} from "../utils/api";
|
||||||
|
|
||||||
const fetchAtom = atom(async () => {
|
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[];
|
return await response.json() as Game[];
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO create game button
|
// TODO create game button
|
||||||
const LobbyPage: FC = () => ( // TODO check if player is defined in storage, if not redirect to login
|
const LobbyPage: FC = () => ( // TODO check if player is defined in storage, if not redirect to login
|
||||||
<Suspense fallback={"Please wait"}>
|
<Suspense fallback={"Please wait"}>
|
||||||
@ -26,19 +28,13 @@ const GameTable: FC<ComponentProps> = ({className}) => {
|
|||||||
|
|
||||||
console.debug("Joining game " + gameId);
|
console.debug("Joining game " + gameId);
|
||||||
|
|
||||||
const result = await fetch(import.meta.env.VITE_API_HTTP + "/join/" + gameId, {
|
const result = await postData("/game/join/" + gameId, {body: thisPlayer});
|
||||||
method: "POST",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json"
|
|
||||||
},
|
|
||||||
body: JSON.stringify(thisPlayer),
|
|
||||||
})
|
|
||||||
|
|
||||||
if (result.ok) {
|
if (result.ok) {
|
||||||
console.debug("Joined game " + gameId, result.body);
|
console.debug("Joined game " + gameId, await result.json());
|
||||||
// TODO redirect to game page
|
// TODO redirect to game page
|
||||||
} else {
|
} else {
|
||||||
console.error("Failed to join game " + gameId, result.body);
|
console.error("Failed to join game " + gameId, await result.json());
|
||||||
// TODO show error message
|
// TODO show error message
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -47,10 +43,10 @@ const GameTable: FC<ComponentProps> = ({className}) => {
|
|||||||
<table className={`rounded overflow-hidden ${className}`}>
|
<table className={`rounded overflow-hidden ${className}`}>
|
||||||
<thead className={"bg-gray-500 text-white"}>
|
<thead className={"bg-gray-500 text-white"}>
|
||||||
<tr className={"my-5"}>
|
<tr className={"my-5"}>
|
||||||
<th>Id</th>
|
<th className={"p-2"}>Id</th>
|
||||||
<th>Count</th>
|
<th className={"p-2"}>Count</th>
|
||||||
<th className={"p-2"}>State</th>
|
<th className={"p-2"}>State</th>
|
||||||
<th>Join</th>
|
<th className={"p-2"}>Join</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@ -66,6 +62,12 @@ const GameTable: FC<ComponentProps> = ({className}) => {
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
)}
|
)}
|
||||||
|
{
|
||||||
|
data?.length === 0 &&
|
||||||
|
<tr>
|
||||||
|
<td colSpan={4} className={"text-center"}>No games found</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
);
|
);
|
||||||
|
54
pac-man-board-game/ClientApp/src/pages/login.tsx
Normal file
54
pac-man-board-game/ClientApp/src/pages/login.tsx
Normal file
@ -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<HTMLFormElement>): Promise<void> {
|
||||||
|
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
|
||||||
|
<form onSubmit={handleLogin}>
|
||||||
|
<h1>Login</h1>
|
||||||
|
<Input name={"username"} placeholder={"Username"}/>
|
||||||
|
<Input name={"password"} type={"password"} placeholder={"Password"}/>
|
||||||
|
<Button type={"submit"}>Login</Button>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Login;
|
@ -21,6 +21,7 @@ interface InputProps extends ComponentProps {
|
|||||||
type?: string,
|
type?: string,
|
||||||
placeholder?: string,
|
placeholder?: string,
|
||||||
required?: boolean,
|
required?: boolean,
|
||||||
|
name?: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CharacterProps {
|
interface CharacterProps {
|
||||||
|
@ -40,3 +40,16 @@ type Game = {
|
|||||||
readonly count: number,
|
readonly count: number,
|
||||||
readonly isGameStarted: boolean,
|
readonly isGameStarted: boolean,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type User = {
|
||||||
|
readonly username: string,
|
||||||
|
readonly password: string,
|
||||||
|
readonly colour?: import("../game/colour").Colour
|
||||||
|
}
|
||||||
|
|
||||||
|
type Api<T = ApiRequest> = (path: string, data?: ApiRequest & T) => Promise<Response>;
|
||||||
|
|
||||||
|
type ApiRequest = {
|
||||||
|
headers?: HeadersInit,
|
||||||
|
body?: any
|
||||||
|
}
|
||||||
|
17
pac-man-board-game/ClientApp/src/utils/api.ts
Normal file
17
pac-man-board-game/ClientApp/src/utils/api.ts
Normal file
@ -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),
|
||||||
|
})
|
||||||
|
}
|
26
pac-man-board-game/Controllers/PlayerController.cs
Normal file
26
pac-man-board-game/Controllers/PlayerController.cs
Normal file
@ -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<IActionResult> 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<IActionResult> Register([FromBody] User user) => throw new NotSupportedException();
|
||||||
|
}
|
@ -1,3 +1,5 @@
|
|||||||
|
using DAL.Database.Models;
|
||||||
|
|
||||||
namespace pacMan.GameStuff.Items;
|
namespace pacMan.GameStuff.Items;
|
||||||
|
|
||||||
public interface IPlayer
|
public interface IPlayer
|
||||||
@ -40,4 +42,16 @@ public class Player : IPlayer, IEquatable<Player>
|
|||||||
}
|
}
|
||||||
|
|
||||||
public override int GetHashCode() => Username.GetHashCode();
|
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
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
using DAL.Database.Service;
|
||||||
using pacMan.Interfaces;
|
using pacMan.Interfaces;
|
||||||
using pacMan.Services;
|
using pacMan.Services;
|
||||||
|
|
||||||
@ -9,6 +10,7 @@ builder.Services.AddControllersWithViews();
|
|||||||
builder.Services
|
builder.Services
|
||||||
.AddSingleton<IWebSocketService, WebSocketService>()
|
.AddSingleton<IWebSocketService, WebSocketService>()
|
||||||
.AddSingleton<GameService>()
|
.AddSingleton<GameService>()
|
||||||
|
.AddScoped<UserService>()
|
||||||
.AddTransient<IActionService, ActionService>();
|
.AddTransient<IActionService, ActionService>();
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
@ -54,6 +54,10 @@
|
|||||||
<Folder Include="ClientApp\tests\utils\" />
|
<Folder Include="ClientApp\tests\utils\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\DataAccessLayer\DataAccessLayer.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<Target Name="DebugEnsureNodeEnv" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') ">
|
<Target Name="DebugEnsureNodeEnv" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') ">
|
||||||
<!-- Ensure Node.js is installed -->
|
<!-- Ensure Node.js is installed -->
|
||||||
<Exec Command="node --version" ContinueOnError="true">
|
<Exec Command="node --version" ContinueOnError="true">
|
||||||
|
Loading…
x
Reference in New Issue
Block a user