using HuntingAppApi.Models; using HuntingAppApi.Services; using Microsoft.AspNetCore.Mvc; using Npgsql; using NpgsqlTypes; namespace HuntingAppApi.Controllers; [ApiController] [Route("api/auth")] public sealed class AuthController(NpgsqlDataSource dataSource, ExternalTokenValidator validator, JwtTokenService jwt) : ControllerBase { [HttpPost("external-login")] public async Task> ExternalLogin([FromBody] ExternalLoginRequest request, CancellationToken ct) { var external = await validator.ValidateAsync(request.Provider, request.IdToken, ct); await using var conn = await dataSource.OpenConnectionAsync(ct); await using var tx = await conn.BeginTransactionAsync(ct); Guid? userId = null; string displayName = external.DisplayName; string? email = external.Email; await using (var find = new NpgsqlCommand(""" SELECT u.id, u.display_name, u.email FROM user_identity i JOIN app_user u ON u.id = i.user_id WHERE i.provider = @provider AND i.provider_subject = @subject """, conn, tx)) { find.Parameters.AddWithValue("provider", external.Provider); find.Parameters.AddWithValue("subject", external.Subject); await using var r = await find.ExecuteReaderAsync(ct); if (await r.ReadAsync(ct)) { userId = r.GetGuid(0); displayName = r.GetString(1); email = r.IsDBNull(2) ? null : r.GetString(2); } } if (userId is null) { userId = Guid.NewGuid(); await using var createUser = new NpgsqlCommand(""" INSERT INTO app_user (id, display_name, email, avatar_url) VALUES (@id, @display_name, @email, @avatar_url) """, conn, tx); createUser.Parameters.AddWithValue("id", userId.Value); createUser.Parameters.AddWithValue("display_name", displayName); createUser.Parameters.AddWithValue("email", (object?)email ?? DBNull.Value); createUser.Parameters.AddWithValue("avatar_url", (object?)external.AvatarUrl ?? DBNull.Value); await createUser.ExecuteNonQueryAsync(ct); await using var createIdentity = new NpgsqlCommand(""" INSERT INTO user_identity (user_id, provider, provider_subject, email, email_verified, display_name, avatar_url) VALUES (@user_id, @provider, @subject, @email, @email_verified, @display_name, @avatar_url) """, conn, tx); createIdentity.Parameters.AddWithValue("user_id", userId.Value); createIdentity.Parameters.AddWithValue("provider", external.Provider); createIdentity.Parameters.AddWithValue("subject", external.Subject); createIdentity.Parameters.AddWithValue("email", (object?)email ?? DBNull.Value); createIdentity.Parameters.AddWithValue("email_verified", external.EmailVerified); createIdentity.Parameters.AddWithValue("display_name", displayName); createIdentity.Parameters.AddWithValue("avatar_url", (object?)external.AvatarUrl ?? DBNull.Value); await createIdentity.ExecuteNonQueryAsync(ct); } else { await using var updateLogin = new NpgsqlCommand(""" UPDATE user_identity SET last_login_at = now() WHERE provider = @provider AND provider_subject = @subject """, conn, tx); updateLogin.Parameters.AddWithValue("provider", external.Provider); updateLogin.Parameters.AddWithValue("subject", external.Subject); await updateLogin.ExecuteNonQueryAsync(ct); } await tx.CommitAsync(ct); return Ok(new LoginResponse { UserId = userId.Value, DisplayName = displayName, Email = email, Token = jwt.CreateToken(userId.Value, displayName, email) }); } }