Initial import into Gitea
This commit is contained in:
64
Registration/Data/IRegistrationDataService.cs
Normal file
64
Registration/Data/IRegistrationDataService.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
namespace Registration.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Abstraction over registration data.
|
||||
/// MockDataService for development, SqlDataService when DB is connected.
|
||||
/// </summary>
|
||||
public interface IRegistrationDataService
|
||||
{
|
||||
Task<RegistrationListResult> GetPendingAsync(CancellationToken ct = default);
|
||||
Task<Applicant?> GetByIdAsync(string registrationId, CancellationToken ct = default);
|
||||
Task<RegistrationResult> RegisterAsync(RegisterRequest request, CancellationToken ct = default);
|
||||
Task<RegistrationResult> RejectAsync(string registrationId, string? reason, string? rejectedBy, CancellationToken ct = default);
|
||||
Task<RegistrationResult> CompleteAsync(string registrationId, string? platformClientId, CancellationToken ct = default);
|
||||
}
|
||||
|
||||
// ── Models ──
|
||||
|
||||
public sealed class Applicant
|
||||
{
|
||||
public string RegistrationId { get; set; } = "";
|
||||
public string BusinessName { get; set; } = "";
|
||||
public string? WebsiteUrl { get; set; }
|
||||
public string? BusinessCategory { get; set; }
|
||||
public string? BusinessDescription { get; set; }
|
||||
public string? ContactName { get; set; }
|
||||
public string? ContactEmail { get; set; }
|
||||
public string? ContactPhone { get; set; }
|
||||
public string? EntraSubjectId { get; set; }
|
||||
public string? ClientCategory { get; set; } // General | Franchisee | Franchisor
|
||||
public string Status { get; set; } = "Pending"; // Pending, Approved, Rejected
|
||||
public bool PaymentVerified { get; set; }
|
||||
public DateTime RegisteredUtc { get; set; }
|
||||
public DateTime? ReviewedUtc { get; set; }
|
||||
public string? ReviewedBy { get; set; }
|
||||
public string? RejectionReason { get; set; }
|
||||
public string? PlatformClientId { get; set; } // Set after approval
|
||||
}
|
||||
|
||||
public sealed class RegisterRequest
|
||||
{
|
||||
public string? BusinessName { get; set; }
|
||||
public string? WebsiteUrl { get; set; }
|
||||
public string? BusinessCategory { get; set; }
|
||||
public string? BusinessDescription { get; set; }
|
||||
public string? ContactName { get; set; }
|
||||
public string? ContactEmail { get; set; }
|
||||
public string? ContactPhone { get; set; }
|
||||
public string? EntraSubjectId { get; set; }
|
||||
public string? ClientCategory { get; set; } // General | Franchisee | Franchisor
|
||||
}
|
||||
|
||||
public sealed class RegistrationListResult
|
||||
{
|
||||
public bool Ok { get; set; }
|
||||
public List<Applicant> Applicants { get; set; } = new();
|
||||
public int TotalCount { get; set; }
|
||||
}
|
||||
|
||||
public sealed class RegistrationResult
|
||||
{
|
||||
public bool Ok { get; set; }
|
||||
public string? Error { get; set; }
|
||||
public string? RegistrationId { get; set; }
|
||||
}
|
||||
74
Registration/Data/SqlDataService.cs
Normal file
74
Registration/Data/SqlDataService.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
using System.Text.Json;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Registration.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Real data service backed by dbRegistration.
|
||||
/// Calls dbo.spRegistration with the standard @action/@rqst/@resp pattern.
|
||||
///
|
||||
/// Activate by swapping DI registration in Program.cs:
|
||||
/// services.AddSingleton<IRegistrationDataService, SqlDataService>();
|
||||
/// </summary>
|
||||
public class SqlDataService : IRegistrationDataService
|
||||
{
|
||||
private readonly SqlService _sql;
|
||||
private readonly ILogger<SqlDataService> _log;
|
||||
|
||||
private const string Proc = "dbo.spRegistration";
|
||||
|
||||
public SqlDataService(SqlService sql, ILogger<SqlDataService> log)
|
||||
{
|
||||
_sql = sql;
|
||||
_log = log;
|
||||
}
|
||||
|
||||
public async Task<RegistrationListResult> GetPendingAsync(CancellationToken ct)
|
||||
{
|
||||
var resp = await _sql.ExecProcAsync(Proc, "pending", "{}", ct: ct);
|
||||
return JsonSerializer.Deserialize<RegistrationListResult>(resp, JsonOpts)
|
||||
?? new() { Ok = false };
|
||||
}
|
||||
|
||||
public async Task<Applicant?> GetByIdAsync(string registrationId, CancellationToken ct)
|
||||
{
|
||||
var rqst = JsonSerializer.Serialize(new { registrationId });
|
||||
var resp = await _sql.ExecProcAsync(Proc, "get", rqst, ct: ct);
|
||||
|
||||
using var doc = JsonDocument.Parse(resp);
|
||||
if (doc.RootElement.TryGetProperty("applicant", out var app))
|
||||
return JsonSerializer.Deserialize<Applicant>(app.GetRawText(), JsonOpts);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<RegistrationResult> RegisterAsync(RegisterRequest request, CancellationToken ct)
|
||||
{
|
||||
var rqst = JsonSerializer.Serialize(request, JsonOpts);
|
||||
var resp = await _sql.ExecProcAsync(Proc, "register", rqst, ct: ct);
|
||||
return JsonSerializer.Deserialize<RegistrationResult>(resp, JsonOpts)
|
||||
?? new() { Ok = false, Error = "Deserialization failed" };
|
||||
}
|
||||
|
||||
public async Task<RegistrationResult> RejectAsync(string registrationId, string? reason, string? rejectedBy, CancellationToken ct)
|
||||
{
|
||||
var rqst = JsonSerializer.Serialize(new { registrationId, reason, rejectedBy });
|
||||
var resp = await _sql.ExecProcAsync(Proc, "reject", rqst, ct: ct);
|
||||
return JsonSerializer.Deserialize<RegistrationResult>(resp, JsonOpts)
|
||||
?? new() { Ok = false, Error = "Deserialization failed" };
|
||||
}
|
||||
|
||||
public async Task<RegistrationResult> CompleteAsync(string registrationId, string? platformClientId, CancellationToken ct)
|
||||
{
|
||||
var rqst = JsonSerializer.Serialize(new { registrationId, platformClientId });
|
||||
var resp = await _sql.ExecProcAsync(Proc, "complete", rqst, ct: ct);
|
||||
return JsonSerializer.Deserialize<RegistrationResult>(resp, JsonOpts)
|
||||
?? new() { Ok = false, Error = "Deserialization failed" };
|
||||
}
|
||||
|
||||
private static readonly JsonSerializerOptions JsonOpts = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
PropertyNameCaseInsensitive = true
|
||||
};
|
||||
}
|
||||
82
Registration/Data/SqlService.cs
Normal file
82
Registration/Data/SqlService.cs
Normal file
@@ -0,0 +1,82 @@
|
||||
using System.Data;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Registration.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Stored procedure executor — same @action/@rqst/@resp OUTPUT pattern
|
||||
/// used by Gateway and Management.
|
||||
///
|
||||
/// Uncomment registration in Program.cs when dbRegistration is ready.
|
||||
/// Connection string: ConnectionStrings__Sql (env var) or ConnectionStrings:Sql (appsettings).
|
||||
/// </summary>
|
||||
public class SqlService
|
||||
{
|
||||
private readonly IConfiguration _config;
|
||||
private readonly ILogger<SqlService> _logger;
|
||||
|
||||
public SqlService(IConfiguration config, ILogger<SqlService> logger)
|
||||
{
|
||||
_config = config;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
private string GetConnectionString()
|
||||
{
|
||||
var cs = _config.GetConnectionString("Sql");
|
||||
if (string.IsNullOrWhiteSpace(cs))
|
||||
throw new InvalidOperationException("Missing ConnectionStrings:Sql");
|
||||
return cs;
|
||||
}
|
||||
|
||||
public async Task<string> ExecProcAsync(
|
||||
string procName,
|
||||
string action,
|
||||
string rqstJson,
|
||||
int commandTimeoutSeconds = 30,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(procName))
|
||||
throw new ArgumentException("procName is required.", nameof(procName));
|
||||
if (string.IsNullOrWhiteSpace(rqstJson))
|
||||
rqstJson = "{}";
|
||||
|
||||
var sw = Stopwatch.StartNew();
|
||||
|
||||
try
|
||||
{
|
||||
await using var conn = new SqlConnection(GetConnectionString());
|
||||
await conn.OpenAsync(ct);
|
||||
|
||||
await using var cmd = new SqlCommand(procName, conn)
|
||||
{
|
||||
CommandType = CommandType.StoredProcedure,
|
||||
CommandTimeout = commandTimeoutSeconds
|
||||
};
|
||||
|
||||
cmd.Parameters.Add(new SqlParameter("@action", SqlDbType.VarChar, 50) { Value = action });
|
||||
cmd.Parameters.Add(new SqlParameter("@rqst", SqlDbType.NVarChar, -1) { Value = rqstJson });
|
||||
|
||||
var pResp = new SqlParameter("@resp", SqlDbType.NVarChar, -1) { Direction = ParameterDirection.Output };
|
||||
cmd.Parameters.Add(pResp);
|
||||
|
||||
await cmd.ExecuteNonQueryAsync(ct);
|
||||
|
||||
var resp = pResp.Value as string ?? "";
|
||||
|
||||
sw.Stop();
|
||||
_logger.LogInformation("SQL ok: {Proc}.{Action} ms={Ms}", procName, action, sw.ElapsedMilliseconds);
|
||||
|
||||
return resp;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
sw.Stop();
|
||||
_logger.LogError(ex, "SQL error: {Proc}.{Action} ms={Ms}", procName, action, sw.ElapsedMilliseconds);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user