Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
607e475
DockerConfig
Mjsims42 Oct 21, 2025
f149752
Merge branch 'main' of https://github.com/250908-NET/Daunting-Devitos…
Mjsims42 Oct 23, 2025
3cb6557
init
ArielFSoler Oct 23, 2025
e20a14a
muhiddin's branch
ArielFSoler Oct 23, 2025
4548039
Start room minimum players fixed
Oct 23, 2025
10c139c
Merge with main in progress
Oct 23, 2025
267c4d6
Functional lobby, many changes remain unvalidated
Oct 23, 2025
2d0268e
Docker modifications
Mjsims42 Oct 23, 2025
f69dcdd
Merge branch 'main' of https://github.com/250908-NET/Daunting-Devitos…
Mjsims42 Oct 23, 2025
7a37e0b
User flow corrected
Oct 23, 2025
e7c95d8
Program cs 'it just works'
Oct 23, 2025
3017813
FullDeployment
Mjsims42 Oct 23, 2025
402ada4
Reverted some tests to main
Oct 23, 2025
61abcc5
Merge branch 'lobby-page-functionality' of https://github.com/250908-…
Mjsims42 Oct 23, 2025
9b83060
Merge remote-tracking branch 'upstream/83-modifications-for-deploymen…
alt-potato Oct 27, 2025
55083e7
Remove duplicated code in integration test base
alt-potato Oct 27, 2025
cac3fe4
Format and lint frontend
alt-potato Oct 27, 2025
9aefe69
Format backend
alt-potato Oct 27, 2025
89629a6
Fix dockerfile
alt-potato Oct 27, 2025
acaddcb
Re-add removed migration files
alt-potato Oct 27, 2025
dac5491
Disable conflicting config
alt-potato Oct 27, 2025
03f77bd
Merge pull request #1 from alt-potato/frontend/merge-from-deploy
alt-potato Oct 27, 2025
65e1794
Modify backend SSE message format to what frontend expects
alt-potato Oct 27, 2025
3652881
Rename MessageEvent to ChatEvent
alt-potato Oct 28, 2025
14bbe68
Refactor frontend to support new backend response
alt-potato Oct 28, 2025
a8b3d41
Merge pull request #2 from alt-potato/frontend/integrate-with-backend…
alt-potato Oct 28, 2025
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
2 changes: 2 additions & 0 deletions Project.App/Project.Api/.env.development
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
PORT=5131
ASPNETCORE_ENVIRONMENT=Production
7 changes: 6 additions & 1 deletion Project.App/Project.Api/Controllers/AuthController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@ public IActionResult Login([FromQuery] string? returnUrl = null)
Console.WriteLine($"[AUTH] Login called with returnUrl: {returnUrl}");

// Allow frontend URLs (localhost:3000 or your production frontend)
var allowedOrigins = new[] { "http://localhost:3000", "https://localhost:3000" };
var allowedOrigins = new[]
{
"http://localhost:3000",
"https://localhost:3000",
"https://dannyscasino-abhqdhcwabdaccfg.westus3-01.azurewebsites.net/", // production frontend
};
var safe = "/swagger"; // default fallback

if (!string.IsNullOrEmpty(returnUrl))
Expand Down
2 changes: 2 additions & 0 deletions Project.App/Project.Api/Controllers/HandController.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using AutoMapper;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Project.Api.DTOs;
using Project.Api.Models;
Expand All @@ -8,6 +9,7 @@ namespace Project.Api.Controllers
{
[ApiController]
[Route("api/rooms/{roomId}/hands")]
[Authorize] // Require authentication for all hand endpoints
public class HandController : ControllerBase
{
private readonly ILogger<HandController> _logger;
Expand Down
29 changes: 27 additions & 2 deletions Project.App/Project.Api/Controllers/RoomController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Microsoft.AspNetCore.Mvc;
using Project.Api.DTOs;
using Project.Api.Models.Games;
using Project.Api.Repositories.Interface;
using Project.Api.Services.Interface;
using Project.Api.Utilities;
using Project.Api.Utilities.Enums;
Expand All @@ -11,6 +12,7 @@ namespace Project.Api.Controllers;

[ApiController]
[Route("api/[controller]")]
[Authorize] // Require authentication for all room endpoints
public class RoomController(
IRoomService roomService,
IRoomSSEService roomSSEService,
Expand Down Expand Up @@ -207,6 +209,28 @@ [FromBody] LeaveRoomRequest request
return Ok(room);
}

// GET: api/room/{roomId}/players
[HttpGet("{roomId}/players")]
public async Task<ActionResult> GetRoomPlayers(
Guid roomId,
[FromServices] IRoomPlayerRepository roomPlayerRepository
)
{
var players = await roomPlayerRepository.GetByRoomIdAsync(roomId);
var playerDtos = players.Select(p => new
{
id = p.Id,
userId = p.UserId,
userName = p.User?.Name ?? "Unknown",
userEmail = p.User?.Email ?? "",
role = p.Role.ToString(),
status = p.Status.ToString(),
balance = p.Balance,
balanceDelta = p.BalanceDelta,
});
return Ok(playerDtos);
}

#region SSE

private readonly IRoomSSEService _roomSSEService = roomSSEService;
Expand All @@ -216,6 +240,7 @@ [FromBody] LeaveRoomRequest request
/// </summary>
/// <param name="roomId"></param>
/// <returns></returns>
[AllowAnonymous]
[HttpGet("{roomId}/events")]
public async Task GetRoomEvents(Guid roomId)
{
Expand Down Expand Up @@ -244,13 +269,13 @@ public async Task<IActionResult> BroadcastMessage(Guid roomId, [FromBody] Messag
throw new BadRequestException("Message content cannot be empty.");
}

MessageEventData data = new()
ChatEventData data = new()
{
Sender = User.Identity?.Name ?? "Anonymous",
Content = message.Content,
};

await _roomSSEService.BroadcastEventAsync(roomId, RoomEventType.Message, data);
await _roomSSEService.BroadcastEventAsync(roomId, RoomEventType.Chat, data);
return Ok();
}

Expand Down
2 changes: 1 addition & 1 deletion Project.App/Project.Api/Models/Games/RoomEvents.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public interface IRoomEventData { }
/// <summary>
/// Specific DTO for a chat message event
/// </summary>
public class MessageEventData : IRoomEventData
public class ChatEventData : IRoomEventData
{
public string Sender { get; set; } = string.Empty;
public string Content { get; set; } = string.Empty;
Expand Down
70 changes: 35 additions & 35 deletions Project.App/Project.Api/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.Google;
using Microsoft.AspNetCore.Authentication.OAuth;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.EntityFrameworkCore;
using Project.Api.Data;
using Project.Api.Models.Games;
Expand All @@ -22,7 +23,6 @@ public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);

// use adminsetting.json as configuration
builder.Configuration.AddJsonFile(
"adminsetting.json",
optional: true,
Expand Down Expand Up @@ -52,14 +52,39 @@ public static void Main(string[] args)
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

// if (File.Exists(".env.development"))
// {
// DotNetEnv.Env.Load(".env.development");
// }

var app = builder.Build();

if (!builder.Environment.IsEnvironment("Testing"))
app.ApplyMigrations(); // there's no database in testing

var fwd = new ForwardedHeadersOptions
{
ForwardedHeaders = ForwardedHeaders.XForwardedProto | ForwardedHeaders.XForwardedFor,
};
fwd.KnownNetworks.Clear();
fwd.KnownProxies.Clear();
app.UseForwardedHeaders(fwd);

app.UseMiddleware<GlobalExceptionHandler>();

// connection string print
// var port = Environment.GetEnvironmentVariable("PORT") ?? "80";
// var httpsPort = 7069;

// if (!File.Exists(".env.development"))
// {
// app.Urls.Add($"http://*:{port}");
// }
// else
// {
// app.Urls.Add($"http://localhost:{port}");
// app.Urls.Add($"https://localhost:{httpsPort}");
// }

app.MapGet(
"/string",
() =>
Expand All @@ -69,15 +94,14 @@ public static void Main(string[] args)
}
);

// is development envrionment
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
app.UseSwagger();
app.UseSwaggerUI();
}

app.UseCors(ProgramExtensions.CorsPolicy); // Enable CORS with our policy
app.UseCors(ProgramExtensions.CorsPolicy);
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
Expand All @@ -91,9 +115,6 @@ public static class ProgramExtensions
{
public const string CorsPolicy = "FrontendCors";

/// <summary>
/// Applies the configuration for the CORS policy.
/// </summary>
public static IServiceCollection AddCorsPolicy(this IServiceCollection services)
{
services.AddCors(options =>
Expand All @@ -103,19 +124,20 @@ public static IServiceCollection AddCorsPolicy(this IServiceCollection services)
policy =>
{
policy
.WithOrigins("http://localhost:3000", "https://localhost:3000") // Next.js frontend
.WithOrigins(
"http://localhost:3000",
"https://localhost:3000",
"https://dannyscasino-abhqdhcwabdaccfg.westus3-01.azurewebsites.net"
)
.AllowAnyHeader()
.AllowAnyMethod()
.AllowCredentials(); // Required for cookies
.AllowCredentials();
}
);
});
return services;
}

/// <summary>
/// Registers the database.
/// </summary>
public static IServiceCollection AddDatabase(
this IServiceCollection services,
IConfiguration configuration
Expand All @@ -127,9 +149,6 @@ IConfiguration configuration
return services;
}

/// <summary>
/// Registers the services and repositories used by the application.
/// </summary>
public static IServiceCollection AddApplicationServices(this IServiceCollection services)
{
// scoped services
Expand All @@ -138,32 +157,25 @@ public static IServiceCollection AddApplicationServices(this IServiceCollection
services.AddScoped<IRoomService, RoomService>();
services.AddScoped<IUserService, UserService>();

// singleton services
services.AddHttpClient<IDeckApiService, DeckApiService>();
services.AddSingleton<IRoomSSEService, RoomSSEService>();

// scoped repositories
services.AddScoped<IHandRepository, HandRepository>();
services.AddScoped<IRoomPlayerRepository, RoomPlayerRepository>();
services.AddScoped<IRoomRepository, RoomRepository>();
services.AddScoped<IUserRepository, UserRepository>();

// automapper!!!
services.AddAutoMapper(typeof(Program));

return services;
}

/// <summary>
/// Configures and registers the authentication services.
/// </summary>
public static IServiceCollection AddAuth(
this IServiceCollection services,
IConfiguration configuration,
IWebHostEnvironment environment
)
{
// sanity check for auth errors logging
if (!environment.IsEnvironment("Testing"))
{
var gid = configuration["Google:ClientId"];
Expand All @@ -180,24 +192,16 @@ IWebHostEnvironment environment
);
}

// Google OAuth
services
.AddAuthentication(options =>
{
// where the app reads identity from on each request
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
// how the app prompts an unauthenticated user to log in
// you can set DefaultChallengeScheme to Cookies instead but it didnt work before
options.DefaultChallengeScheme = GoogleDefaults.AuthenticationScheme;
})
.AddCookie(cookie =>
{
// cross-site cookie SPA on :3000 to API on :7069
cookie.Cookie.SameSite = SameSiteMode.None;
// browsers require Secure when SameSite=None
cookie.Cookie.SecurePolicy = CookieSecurePolicy.Always;

// for APIs return status codes instead of 302 redirects
cookie.Events = new CookieAuthenticationEvents
{
OnRedirectToLogin = ctx =>
Expand All @@ -219,16 +223,12 @@ IWebHostEnvironment environment
options.CallbackPath = "/auth/google/callback";
options.Scope.Add("email");
options.Scope.Add("profile");

// ensure we actually fetch profile claims from Google UserInfo endpoint
options.SaveTokens = true;
options.UserInformationEndpoint = "https://www.googleapis.com/oauth2/v3/userinfo";
options.ClaimActions.MapJsonKey(ClaimTypes.Email, "email");
options.ClaimActions.MapJsonKey(ClaimTypes.Name, "name");
options.ClaimActions.MapJsonKey("picture", "picture");
options.ClaimActions.MapJsonKey("email_verified", "email_verified");

// Upsert user into DB when Google signs in successfully
options.Events = new OAuthEvents
{
OnCreatingTicket = async ctx =>
Expand All @@ -239,7 +239,7 @@ IWebHostEnvironment environment

// fetch the full user JSON from Google's userinfo endpoint
var request = new HttpRequestMessage(
System.Net.Http.HttpMethod.Get,
HttpMethod.Get,
ctx.Options.UserInformationEndpoint
);
request.Headers.Authorization =
Expand Down
1 change: 1 addition & 0 deletions Project.App/Project.Api/Project.Api.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="DotNetEnv" Version="3.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.Google" Version="9.0.10" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.8" />
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.1" />
Expand Down
24 changes: 0 additions & 24 deletions Project.App/Project.Api/Project.Api.sln

This file was deleted.

Loading