mirror of
https://github.com/Wessel/nhl-setgame.git
synced 2026-06-08 22:28:07 +02:00
chore(backend): Clean up Controllers
This commit is contained in:
@@ -5,73 +5,62 @@ using Microsoft.AspNetCore.Mvc;
|
|||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
using backend.Models;
|
using backend.Models;
|
||||||
|
|
||||||
public class UserLogin
|
namespace backend.Controllers;
|
||||||
{
|
|
||||||
public string Username { get; set; }
|
public class UserLogin {
|
||||||
public string Password { get; set; }
|
public required string Username { get; set; }
|
||||||
}
|
public required string Password { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("api/[controller]")]
|
[Route("api/v1/[controller]")]
|
||||||
public class AuthController : ControllerBase
|
public class AuthController(GameContext context) : ControllerBase {
|
||||||
{
|
private readonly GameContext _context = context;
|
||||||
private readonly GameContext _context;
|
|
||||||
|
|
||||||
public static string CreateMD5(string input)
|
[HttpPost("login")]
|
||||||
{
|
public IActionResult Login([FromBody] UserLogin loginData) {
|
||||||
// Use input string to calculate MD5 hash
|
var salt = Environment.GetEnvironmentVariable("MD5_SALT") ?? "";
|
||||||
using (System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create())
|
var passwordHash = CreateMD5(loginData.Password + salt);
|
||||||
{
|
|
||||||
byte[] inputBytes = System.Text.Encoding.ASCII.GetBytes(input);
|
|
||||||
byte[] hashBytes = md5.ComputeHash(inputBytes);
|
|
||||||
|
|
||||||
return Convert.ToHexString(hashBytes); // .NET 5 +
|
var user = _context
|
||||||
|
.Users
|
||||||
|
.Where(u => u.Username == loginData.Username && u.PasswordHash == passwordHash)
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
// Convert the byte array to hexadecimal string prior to .NET 5
|
if (user != null) {
|
||||||
// StringBuilder sb = new System.Text.StringBuilder();
|
var token = GenerateJwtToken(user.Id.ToString());
|
||||||
// for (int i = 0; i < hashBytes.Length; i++)
|
|
||||||
// {
|
return Ok(new { token });
|
||||||
// sb.Append(hashBytes[i].ToString("X2"));
|
|
||||||
// }
|
|
||||||
// return sb.ToString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public AuthController(GameContext context) {
|
|
||||||
_context = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
[HttpPost("login")]
|
|
||||||
public IActionResult Login([FromBody] UserLogin user) {
|
|
||||||
var salt = Environment.GetEnvironmentVariable("MD5_SALT") ?? "";
|
|
||||||
var passwordHash = CreateMD5(user.Password + salt);
|
|
||||||
var dbUser = _context.Users.Where(u => u.Username == user.Username && u.PasswordHash == passwordHash).FirstOrDefault();
|
|
||||||
if (dbUser != null) {
|
|
||||||
var token = GenerateJwtToken(dbUser.Id.ToString());
|
|
||||||
return Ok(new { token });
|
|
||||||
}
|
|
||||||
|
|
||||||
return Unauthorized();
|
|
||||||
}
|
|
||||||
|
|
||||||
private string GenerateJwtToken(string userId)
|
|
||||||
{
|
|
||||||
var claims = new[]
|
|
||||||
{
|
|
||||||
new Claim(JwtRegisteredClaimNames.Sub, userId),
|
|
||||||
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
|
|
||||||
};
|
|
||||||
|
|
||||||
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Environment.GetEnvironmentVariable("JWT_SECRET")));
|
|
||||||
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
|
|
||||||
|
|
||||||
var token = new JwtSecurityToken(
|
|
||||||
issuer: "wessel.gg",
|
|
||||||
audience: "wessel.gg",
|
|
||||||
claims: claims,
|
|
||||||
expires: DateTime.Now.AddMinutes(30),
|
|
||||||
signingCredentials: creds);
|
|
||||||
|
|
||||||
return new JwtSecurityTokenHandler().WriteToken(token);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Unauthorized();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GenerateJwtToken(string userId) {
|
||||||
|
var secret = Environment.GetEnvironmentVariable("JWT_SECRET") ?? "";
|
||||||
|
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret));
|
||||||
|
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
|
||||||
|
|
||||||
|
var claims = new[] {
|
||||||
|
new Claim(JwtRegisteredClaimNames.Sub, userId),
|
||||||
|
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
|
||||||
|
};
|
||||||
|
|
||||||
|
var token = new JwtSecurityToken(
|
||||||
|
signingCredentials: creds,
|
||||||
|
issuer: Environment.GetEnvironmentVariable("JWT_ISSUER") ?? "",
|
||||||
|
audience: Environment.GetEnvironmentVariable("JWT_AUDIENCE") ?? "",
|
||||||
|
claims: claims,
|
||||||
|
expires: DateTime.Now.AddHours(6)
|
||||||
|
);
|
||||||
|
|
||||||
|
return new JwtSecurityTokenHandler().WriteToken(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string CreateMD5(string input) {
|
||||||
|
byte[] inputBytes = Encoding.ASCII.GetBytes(input);
|
||||||
|
byte[] hashBytes = System.Security.Cryptography.MD5.HashData(inputBytes);
|
||||||
|
|
||||||
|
return Convert.ToHexString(hashBytes);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,178 +1,172 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using backend.Models;
|
using backend.Models;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using System.IdentityModel.Tokens.Jwt;
|
|
||||||
|
|
||||||
namespace backend.Controllers
|
namespace backend.Controllers;
|
||||||
{
|
|
||||||
[Route("/api/v1/[controller]")]
|
|
||||||
[ApiController]
|
|
||||||
public class GamesController : ControllerBase {
|
|
||||||
private readonly GameContext _context;
|
|
||||||
|
|
||||||
public GamesController(GameContext context) {
|
[Route("/api/v1/[controller]")]
|
||||||
_context = context;
|
[ApiController]
|
||||||
|
public class GamesController(GameContext context) : ControllerBase {
|
||||||
|
private readonly GameContext _context = context;
|
||||||
|
|
||||||
|
private long? ParseUserId() {
|
||||||
|
var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||||
|
|
||||||
|
if (long.TryParse(userIdClaim, out var userId)) {
|
||||||
|
return userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET: api/Games
|
return null;
|
||||||
[HttpGet]
|
}
|
||||||
[Authorize]
|
|
||||||
public async Task<ActionResult<IEnumerable<Game>>> GetGames() {
|
|
||||||
var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
|
||||||
|
|
||||||
if (!long.TryParse(userIdClaim, out var userId)) {
|
// GET: api/Games
|
||||||
return Unauthorized();
|
[HttpGet]
|
||||||
}
|
[Authorize]
|
||||||
|
public async Task<ActionResult<IEnumerable<Game>>> GetGames() {
|
||||||
// return await _context.Games.ToListAsync();
|
var user = ParseUserId();
|
||||||
return await _context.Games.Where(g => g.UserId == userId).ToListAsync();
|
if (user == null) {
|
||||||
|
return BadRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET: api/Games/5
|
return await _context.Games
|
||||||
[HttpGet("{id}")]
|
.Where(g => g.UserId == user)
|
||||||
[Authorize]
|
.ToListAsync();
|
||||||
public async Task<ActionResult<Game>> GetGame(long id)
|
}
|
||||||
{
|
|
||||||
var game = await _context.Games.FindAsync(id);
|
|
||||||
|
|
||||||
if (game == null)
|
// GET: api/Games/5
|
||||||
{
|
[HttpGet("{id}")]
|
||||||
return NotFound();
|
[Authorize]
|
||||||
}
|
public async Task<ActionResult<Game>> GetGame(long id) {
|
||||||
|
var user = ParseUserId();
|
||||||
return game;
|
if (user == null) {
|
||||||
|
return BadRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
// PUT: api/Games/5
|
var game = await _context.Games
|
||||||
// To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
|
.Where(g => g.Id == id && g.UserId == user)
|
||||||
[HttpPut("{id}")]
|
.FirstOrDefaultAsync();
|
||||||
[Authorize]
|
|
||||||
public async Task<IActionResult> PutGame(long id, Game game)
|
|
||||||
{
|
|
||||||
if (id != game.Id)
|
|
||||||
{
|
|
||||||
return BadRequest();
|
|
||||||
}
|
|
||||||
|
|
||||||
_context.Entry(game).State = EntityState.Modified;
|
if (game == null || game.UserId != user) {
|
||||||
|
return NotFound();
|
||||||
try {
|
|
||||||
await _context.SaveChangesAsync();
|
|
||||||
} catch (DbUpdateConcurrencyException) {
|
|
||||||
if (!GameExists(id)) {
|
|
||||||
return NotFound();
|
|
||||||
} else {
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return NoContent();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST: api/Games
|
return game;
|
||||||
// To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
|
}
|
||||||
[HttpPost]
|
|
||||||
[Authorize]
|
|
||||||
public async Task<ActionResult<Game>> PostGame() {
|
|
||||||
|
|
||||||
var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
// POST: api/Games
|
||||||
|
// To protect from overposting attacks, see https://go.microsoft.com/fwlink/?linkid=2123754
|
||||||
if (!long.TryParse(userIdClaim, out var userId)) {
|
[HttpPost]
|
||||||
return Unauthorized();
|
[Authorize]
|
||||||
}
|
public async Task<ActionResult<Game>> PostGame() {
|
||||||
|
var user = ParseUserId();
|
||||||
var newGame = new Game {
|
if (user == null) {
|
||||||
Deck = (
|
return BadRequest();
|
||||||
from shape in Enum.GetValues(typeof(CardShape)).Cast<CardShape>()
|
|
||||||
from color in Enum.GetValues(typeof(CardColor)).Cast<CardColor>()
|
|
||||||
from count in Enum.GetValues(typeof(CardCount)).Cast<CardCount>()
|
|
||||||
from shade in Enum.GetValues(typeof(CardShade)).Cast<CardShade>()
|
|
||||||
select new Card {
|
|
||||||
Shape = shape,
|
|
||||||
Color = color,
|
|
||||||
Count = count,
|
|
||||||
Shade = shade
|
|
||||||
}.ToUshort()).ToArray(),
|
|
||||||
UserId = userId
|
|
||||||
};
|
|
||||||
|
|
||||||
newGame.ShuffleDeck();
|
|
||||||
newGame.DealHand();
|
|
||||||
|
|
||||||
_context.Games.Add(newGame);
|
|
||||||
await _context.SaveChangesAsync();
|
|
||||||
|
|
||||||
return CreatedAtAction(nameof(GetGame), new { id = newGame.Id }, newGame);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DELETE: api/Games/5
|
var newGame = new Game {
|
||||||
[HttpDelete("{id}")]
|
UserId = (long)user,
|
||||||
[Authorize]
|
Deck = [..
|
||||||
public async Task<IActionResult> DeleteGame(long id) {
|
from shape in Enum.GetValues<CardShape>().Cast<CardShape>()
|
||||||
var game = await _context.Games.FindAsync(id);
|
from color in Enum.GetValues<CardColor>().Cast<CardColor>()
|
||||||
if (game == null)
|
from count in Enum.GetValues<CardCount>().Cast<CardCount>()
|
||||||
{
|
from shade in Enum.GetValues<CardShade>().Cast<CardShade>()
|
||||||
return NotFound();
|
select new Card {
|
||||||
}
|
Shape = shape,
|
||||||
|
Color = color,
|
||||||
|
Count = count,
|
||||||
|
Shade = shade
|
||||||
|
}.ToUshort()
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
_context.Games.Remove(game);
|
newGame.ShuffleDeck();
|
||||||
await _context.SaveChangesAsync();
|
newGame.DealHand();
|
||||||
|
|
||||||
return NoContent();
|
_context.Games.Add(newGame);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
return CreatedAtAction(nameof(GetGame), new { id = newGame.Id }, newGame);
|
||||||
|
}
|
||||||
|
|
||||||
|
// DELETE: api/Games/5
|
||||||
|
[HttpDelete("{id}")]
|
||||||
|
[Authorize]
|
||||||
|
public async Task<IActionResult> DeleteGame(long id) {
|
||||||
|
var user = ParseUserId();
|
||||||
|
if (user == null) {
|
||||||
|
return BadRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool GameExists(long id) {
|
var game = await _context.Games
|
||||||
return _context.Games.Any(e => e.Id == id);
|
.Where(g => g.Id == id && g.UserId == user)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
|
if (game == null) {
|
||||||
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
_context.Games.Remove(game);
|
||||||
[Route("[action]/{id}")]
|
await _context.SaveChangesAsync();
|
||||||
[Authorize]
|
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
[Route("[action]/{id}")]
|
||||||
|
[Authorize]
|
||||||
public async Task<ActionResult<SetCheckResult>> CheckSet(
|
public async Task<ActionResult<SetCheckResult>> CheckSet(
|
||||||
long id,
|
long id,
|
||||||
[FromBody] ushort[] cardIndices
|
[FromBody] ushort[] cardIndices
|
||||||
) {
|
) {
|
||||||
var game = await _context.Games.FindAsync(id);
|
var user = ParseUserId();
|
||||||
if (game == null) {
|
if (user == null) {
|
||||||
return NotFound();
|
return BadRequest();
|
||||||
}
|
|
||||||
|
|
||||||
var res = game.IsSet(cardIndices);
|
|
||||||
|
|
||||||
if (res == null) {
|
|
||||||
return BadRequest();
|
|
||||||
}
|
|
||||||
|
|
||||||
await _context.SaveChangesAsync();
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
var game = await _context.Games
|
||||||
[Route("[action]/{id}")]
|
.Where(g => g.Id == id && g.UserId == user)
|
||||||
[Authorize]
|
.FirstOrDefaultAsync();
|
||||||
public async Task<ActionResult<List<int[]>>> SetsInHand(long id) {
|
|
||||||
var game = await _context.Games.FindAsync(id);
|
|
||||||
if (game == null) {
|
|
||||||
return NotFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
var res = game.GetIndicesOfSet();
|
if (game == null) {
|
||||||
|
return NotFound();
|
||||||
if (res == null) {
|
|
||||||
return BadRequest();
|
|
||||||
}
|
|
||||||
|
|
||||||
Console.WriteLine($"Found {res.Count} sets in hand");
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var res = game.IsSet(cardIndices);
|
||||||
|
|
||||||
|
if (res == null) {
|
||||||
|
return BadRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet]
|
||||||
|
[Route("[action]/{id}")]
|
||||||
|
[Authorize]
|
||||||
|
public async Task<ActionResult<List<int[]>>> SetsInHand(long id) {
|
||||||
|
var user = ParseUserId();
|
||||||
|
if (user == null) {
|
||||||
|
return BadRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
var game = await _context.Games
|
||||||
|
.Where(g => g.Id == id && g.UserId == user)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
|
||||||
|
if (game == null) {
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
var res = game.GetIndicesOfSet();
|
||||||
|
|
||||||
|
if (res == null) {
|
||||||
|
return BadRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -12,8 +12,8 @@ using backend.Models;
|
|||||||
namespace backend.Migrations
|
namespace backend.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(GameContext))]
|
[DbContext(typeof(GameContext))]
|
||||||
[Migration("20250325111947_initial")]
|
[Migration("20250325145201_InitialDatabase")]
|
||||||
partial class initial
|
partial class InitialDatabase
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||||
@@ -102,13 +102,11 @@ namespace backend.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("backend.Models.Game", b =>
|
modelBuilder.Entity("backend.Models.Game", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("backend.Models.User", "User")
|
b.HasOne("backend.Models.User", null)
|
||||||
.WithMany("Games")
|
.WithMany("Games")
|
||||||
.HasForeignKey("UserId")
|
.HasForeignKey("UserId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
b.Navigation("User");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("backend.Models.User", b =>
|
modelBuilder.Entity("backend.Models.User", b =>
|
||||||
@@ -7,7 +7,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
|||||||
namespace backend.Migrations
|
namespace backend.Migrations
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public partial class initial : Migration
|
public partial class InitialDatabase : Migration
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
@@ -99,13 +99,11 @@ namespace backend.Migrations
|
|||||||
|
|
||||||
modelBuilder.Entity("backend.Models.Game", b =>
|
modelBuilder.Entity("backend.Models.Game", b =>
|
||||||
{
|
{
|
||||||
b.HasOne("backend.Models.User", "User")
|
b.HasOne("backend.Models.User", null)
|
||||||
.WithMany("Games")
|
.WithMany("Games")
|
||||||
.HasForeignKey("UserId")
|
.HasForeignKey("UserId")
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
b.Navigation("User");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("backend.Models.User", b =>
|
modelBuilder.Entity("backend.Models.User", b =>
|
||||||
|
|||||||
@@ -18,8 +18,6 @@ public class Game {
|
|||||||
public ushort[]? Deck { get; set; }
|
public ushort[]? Deck { get; set; }
|
||||||
public ushort[] Found { get; set; } = Array.Empty<ushort>();
|
public ushort[] Found { get; set; } = Array.Empty<ushort>();
|
||||||
|
|
||||||
public User User { get; set; } = null!;
|
|
||||||
|
|
||||||
public void ShuffleDeck() {
|
public void ShuffleDeck() {
|
||||||
if (Deck == null) return;
|
if (Deck == null) return;
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ interface LoginResponse {
|
|||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class AuthService {
|
export class AuthService {
|
||||||
private API_URL = 'http://localhost:5224/api/Auth';
|
private API_URL = 'http://localhost:5224/api/v1/Auth';
|
||||||
private tokenKey = 'auth_token';
|
private tokenKey = 'auth_token';
|
||||||
private isAuthenticatedSubject = new BehaviorSubject<boolean>(this.hasToken());
|
private isAuthenticatedSubject = new BehaviorSubject<boolean>(this.hasToken());
|
||||||
public redirectUrl: string | null = null;
|
public redirectUrl: string | null = null;
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { Card, toCard } from '../../app/models/card';
|
import { Card, toCard } from '../../app/models/card';
|
||||||
// todo: Rewrite to use angular http client instead of axios, supports always sending tokens
|
import { lastValueFrom } from 'rxjs';
|
||||||
import axios from 'axios';
|
import { AuthService } from '../auth/auth.service';
|
||||||
|
|
||||||
axios.defaults.headers.common['Authorization'] = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsImp0aSI6ImRmNThhYTU3LWZkNzItNGIzYS05OTNmLTY4NjAyNGMzYjdlNSIsImV4cCI6MTc0MjgxODEzOCwiaXNzIjoid2Vzc2VsLmdnIiwiYXVkIjoid2Vzc2VsLmdnIn0.hDf8qcxXeSFQhmgnMzBrH3ZJJMplwZ-1RQwNxeZo5ok';
|
|
||||||
|
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
|
|
||||||
export class GameService {
|
export class GameService {
|
||||||
|
private API_URL = 'http://localhost:5224/api/v1/Games';
|
||||||
|
|
||||||
public deck: Card[] = [];
|
public deck: Card[] = [];
|
||||||
public hand: Card[] = [];
|
public hand: Card[] = [];
|
||||||
public foundSets: Card[][] = [];
|
public foundSets: Card[][] = [];
|
||||||
@@ -20,8 +20,9 @@ export class GameService {
|
|||||||
public finishedAt?: Date;
|
public finishedAt?: Date;
|
||||||
public selectedCards: Card[] = [];
|
public selectedCards: Card[] = [];
|
||||||
|
|
||||||
constructor() {
|
constructor(
|
||||||
}
|
private http: HttpClient,
|
||||||
|
) {}
|
||||||
|
|
||||||
public initGame(gameId?: string): Promise<string> {
|
public initGame(gameId?: string): Promise<string> {
|
||||||
this.deck = [];
|
this.deck = [];
|
||||||
@@ -35,58 +36,113 @@ export class GameService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async initializeExistingGame(gameId: string): Promise<string> {
|
public async initializeExistingGame(gameId: string): Promise<string> {
|
||||||
const req = await axios.get('http://localhost:5224/api/v1/Games/' + gameId);
|
try {
|
||||||
req.data.deck.forEach((card: number) => {
|
const response = await lastValueFrom(this.http.get<any>(`${this.API_URL}/${gameId}`));
|
||||||
this.deck.push(toCard(card));
|
|
||||||
|
|
||||||
});
|
response.deck.forEach((card: number) => {
|
||||||
req.data.hand.forEach((card: number) => {
|
this.deck.push(toCard(card));
|
||||||
|
});
|
||||||
|
|
||||||
|
response.hand.forEach((card: number) => {
|
||||||
this.hand.push(toCard(card));
|
this.hand.push(toCard(card));
|
||||||
});
|
});
|
||||||
this.startDate = new Date(req.data.startedAt);
|
|
||||||
this.fails = req.data.fails;
|
this.startDate = new Date(response.startedAt);
|
||||||
this.hints = req.data.hints;
|
this.fails = response.fails;
|
||||||
this.finishedAt = req.data.finishedAt;
|
this.hints = response.hints;
|
||||||
|
this.finishedAt = response.finishedAt ? new Date(response.finishedAt) : undefined;
|
||||||
|
|
||||||
this.foundSets = [];
|
this.foundSets = [];
|
||||||
for (let i = 0; i < req.data.found.length; i += 3) {
|
for (let i = 0; i < response.found.length; i += 3) {
|
||||||
this.foundSets.push(req.data.found.slice(i, i + 3).map((card: number) => toCard(card)));
|
this.foundSets.push(response.found.slice(i, i + 3).map((card: number) => toCard(card)));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.gameId = req.data.id;
|
this.gameId = response.id;
|
||||||
await this.updateSets();
|
this.updateSets();
|
||||||
|
return response.id.toString();
|
||||||
return req.data.id;
|
} catch (error) {
|
||||||
|
console.error('Error initializing existing game', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async initializeDeck(): Promise<string> {
|
private async initializeDeck(): Promise<string> {
|
||||||
const res = await axios.post('http://localhost:5224/api/v1/Games', {});
|
try {
|
||||||
res.data.deck.forEach((card: number) => {
|
const response = await lastValueFrom(this.http.post<any>(this.API_URL, {}));
|
||||||
this.deck.push(toCard(card));
|
|
||||||
});
|
|
||||||
|
|
||||||
res.data.hand.forEach((card: number) => {
|
response.deck.forEach((card: number) => {
|
||||||
|
this.deck.push(toCard(card));
|
||||||
|
});
|
||||||
|
|
||||||
|
response.hand.forEach((card: number) => {
|
||||||
this.hand.push(toCard(card));
|
this.hand.push(toCard(card));
|
||||||
});
|
});
|
||||||
|
|
||||||
this.startDate = new Date(res.data.startedAt);
|
this.startDate = new Date(response.startedAt);
|
||||||
this.fails = res.data.fails;
|
this.fails = response.fails;
|
||||||
this.hints = res.data.hints;
|
this.hints = response.hints;
|
||||||
this.gameId = res.data.id;
|
this.gameId = response.id;
|
||||||
this.finishedAt = res.data.finishedAt;
|
this.finishedAt = response.finishedAt ? new Date(response.finishedAt) : undefined;
|
||||||
|
|
||||||
this.foundSets = [];
|
this.foundSets = [];
|
||||||
for (let i = 0; i < res.data.newState.found.length; i += 3) {
|
for (let i = 0; i < response.found.length; i += 3) {
|
||||||
this.foundSets.push(res.data.newState.found.slice(i, i + 3).map((card: number) => toCard(card)));
|
this.foundSets.push(response.found.slice(i, i + 3).map((card: number) => toCard(card)));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.updateSets();
|
||||||
|
return response.id.toString();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error initializing deck', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async updateSets(): Promise<Card[][]> {
|
||||||
|
try {
|
||||||
|
const response = await lastValueFrom(
|
||||||
|
this.http.get<number[][]>(`${this.API_URL}/SetsInHand/${this.gameId}`)
|
||||||
|
);
|
||||||
|
|
||||||
|
const cards = response.map((set: number[]) =>
|
||||||
|
set.map((card: number) => this.hand[card])
|
||||||
|
);
|
||||||
|
|
||||||
|
this.possibleSets = cards;
|
||||||
|
return cards;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error updating sets', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async checkSet(cards: number[]): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const response = await lastValueFrom(
|
||||||
|
this.http.post<any>(`${this.API_URL}/CheckSet/${this.gameId}`, cards)
|
||||||
|
);
|
||||||
|
|
||||||
|
this.hand = response.newState.hand.map((card: number) => toCard(card));
|
||||||
|
this.deck = response.newState.deck.map((card: number) => toCard(card));
|
||||||
|
this.fails = response.newState.fails;
|
||||||
|
this.hints = response.newState.hints;
|
||||||
|
this.finishedAt = response.newState.finishedAt ? new Date(response.newState.finishedAt) : undefined;
|
||||||
|
|
||||||
|
this.foundSets = [];
|
||||||
|
for (let i = 0; i < response.newState.found.length; i += 3) {
|
||||||
|
this.foundSets.push(response.newState.found.slice(i, i + 3).map((card: number) => toCard(card)));
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.updateSets();
|
await this.updateSets();
|
||||||
|
return response.isSet;
|
||||||
return res.data.id;
|
} catch (error) {
|
||||||
|
console.error('Error checking set', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public selectCard(card: Card): void {
|
public selectCard(card: Card): void {
|
||||||
const cardIndex = this.hand.indexOf(card);
|
const cardIndex = this.hand.indexOf(card);
|
||||||
if (cardIndex === -1) return; // Card not found on the board
|
if (cardIndex === -1) return; // Card not found in hand
|
||||||
|
|
||||||
if (this.selectedCards.includes(card)) {
|
if (this.selectedCards.includes(card)) {
|
||||||
this.selectedCards = this.selectedCards.filter(c => c !== card);
|
this.selectedCards = this.selectedCards.filter(c => c !== card);
|
||||||
@@ -95,58 +151,29 @@ export class GameService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.selectedCards.length === 3) {
|
if (this.selectedCards.length === 3) {
|
||||||
const [card1, card2, card3] = this.selectedCards as [Card, Card, Card]; // ✅ Explicitly cast to a tuple
|
const indices = this.selectedCards.map(c => this.hand.indexOf(c));
|
||||||
if (this.isSet([this.hand.indexOf(card1), this.hand.indexOf(card2), this.hand.indexOf(card3)])) {
|
this.checkSet(indices).then(isSet => {
|
||||||
this.replaceSet();
|
this.selectedCards = [];
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async updateSets(): Promise<Card[][]> {
|
public async deleteGame(): Promise<void> {
|
||||||
const req = await axios.get(`http://localhost:5224/api/v1/Games/SetsInHand/${this.gameId}`);
|
try {
|
||||||
const cards = req.data
|
await lastValueFrom(this.http.delete<void>(`${this.API_URL}/${this.gameId}`));
|
||||||
.map((set: number[]) =>
|
} catch (error) {
|
||||||
set.map((card: number) => this.hand[card])
|
console.error('Error deleting game', error);
|
||||||
);
|
throw error;
|
||||||
|
}
|
||||||
this.possibleSets = cards;
|
|
||||||
|
|
||||||
return cards;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private isSet(cards: number[]): boolean {
|
public async showHint(): Promise<void> {
|
||||||
this.selectedCards = [];
|
try {
|
||||||
|
await lastValueFrom(this.http.post<void>(`${this.API_URL}/Hint/${this.gameId}`, {}));
|
||||||
axios.post(`http://localhost:5224/api/v1/Games/CheckSet/${this.gameId}`, cards).then((response) => {
|
this.hints++;
|
||||||
this.hand = response.data.newState.hand.map((card: number) => toCard(card));
|
} catch (error) {
|
||||||
this.deck = response.data.newState.deck.map((card: number) => toCard(card));
|
console.error('Error showing hint', error);
|
||||||
this.fails = response.data.newState.fails;
|
throw error;
|
||||||
this.hints = response.data.newState.hints;
|
}
|
||||||
this.finishedAt = response.data.newState.finishedAt;
|
|
||||||
this.foundSets = [];
|
|
||||||
for (let i = 0; i < response.data.newState.found.length; i += 3) {
|
|
||||||
this.foundSets.push(response.data.newState.found.slice(i, i + 3).map((card: number) => toCard(card)));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateSets();
|
|
||||||
|
|
||||||
return response.data.isSet;
|
|
||||||
});
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private replaceSet(): void {
|
|
||||||
this.hand.find((c, i) => {
|
|
||||||
if (this.selectedCards.includes(c)) {
|
|
||||||
this.hand[i] = this.deck.splice(0, 1)[0];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.selectedCards = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
public async deleteGame() {
|
|
||||||
await axios.delete('http://localhost:5224/api/v1/Games/' + this.gameId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,43 +1,40 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import axios, { AxiosError } from 'axios';
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { lastValueFrom } from 'rxjs';
|
||||||
// axios.defaults.headers.common['Authorization'] = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsImp0aSI6ImRmNThhYTU3LWZkNzItNGIzYS05OTNmLTY4NjAyNGMzYjdlNSIsImV4cCI6MTc0MjgxODEzOCwiaXNzIjoid2Vzc2VsLmdnIiwiYXVkIjoid2Vzc2VsLmdnIn0.hDf8qcxXeSFQhmgnMzBrH3ZJJMplwZ-1RQwNxeZo5ok';
|
import { AuthService } from '../auth/auth.service';
|
||||||
axios.interceptors.request.use(
|
|
||||||
(config) => {
|
|
||||||
const token = localStorage.getItem('token');
|
|
||||||
if (token) {
|
|
||||||
config.headers.Authorization = `Bearer ${token}`;
|
|
||||||
}
|
|
||||||
return config;
|
|
||||||
},
|
|
||||||
(error) => {
|
|
||||||
return Promise.reject(error);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class UserDataService {
|
export class UserDataService {
|
||||||
|
private GAMES_API_URL = 'http://localhost:5224/api/v1/Games';
|
||||||
|
|
||||||
constructor() {
|
constructor(
|
||||||
// this.login({ username: 'admin', password: 'password' });
|
private http: HttpClient,
|
||||||
|
private authService: AuthService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public async login(credentials: { username: string, password: string }): Promise<void> {
|
||||||
|
try {
|
||||||
|
await lastValueFrom(this.authService.login(credentials.username, credentials.password));
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Login error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async login(credentials: any) {
|
public async getGames(): Promise<any[]> {
|
||||||
const response = await axios.post('http://localhost:5224/api/Auth/login', credentials);
|
try {
|
||||||
localStorage.setItem('token', response.data.token);
|
const data = await lastValueFrom(this.http.get<any[]>(this.GAMES_API_URL));
|
||||||
}
|
|
||||||
|
|
||||||
public async getGames(): Promise<any> {
|
const sortedGames = data
|
||||||
const req = await axios.get('http://localhost:5224/api/v1/Games', {
|
.sort((a: any, b: any) => new Date(a.startedAt).getTime() - new Date(b.startedAt).getTime())
|
||||||
headers: {
|
.reverse();
|
||||||
'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZG1pbiIsImp0aSI6ImRmNThhYTU3LWZkNzItNGIzYS05OTNmLTY4NjAyNGMzYjdlNSIsImV4cCI6MTc0MjgxODEzOCwiaXNzIjoid2Vzc2VsLmdnIiwiYXVkIjoid2Vzc2VsLmdnIn0.hDf8qcxXeSFQhmgnMzBrH3ZJJMplwZ-1RQwNxeZo5ok'
|
|
||||||
}
|
return sortedGames;
|
||||||
});
|
} catch (error) {
|
||||||
const sortedGames = req.data
|
console.error('Error getting games:', error);
|
||||||
.sort((a: any, b: any) => new Date(a.startedAt).getTime() - new Date(b.startedAt).getTime())
|
throw error;
|
||||||
.reverse();
|
}
|
||||||
return sortedGames;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user