130 lines
4.9 KiB
C#
130 lines
4.9 KiB
C#
namespace NeonSprawl.Server.Game.Factions;
|
|
|
|
using NeonSprawl.Server.Game.Rewards;
|
|
|
|
/// <summary>PostgreSQL-backed append-only reputation delta audit log (NEO-135).</summary>
|
|
public sealed class PostgresReputationDeltaStore(Npgsql.NpgsqlDataSource dataSource) : IReputationDeltaStore
|
|
{
|
|
/// <inheritdoc />
|
|
public bool TryAppend(ReputationDeltaRow row)
|
|
{
|
|
if (!IsValidRow(row))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var normalized = row with
|
|
{
|
|
Id = row.Id.Trim(),
|
|
PlayerId = FactionStandingIds.NormalizePlayerId(row.PlayerId),
|
|
FactionId = FactionStandingIds.NormalizeFactionId(row.FactionId),
|
|
SourceKind = row.SourceKind.Trim(),
|
|
SourceId = row.SourceId.Trim(),
|
|
};
|
|
|
|
PostgresReputationDeltaBootstrap.EnsureSchema(dataSource);
|
|
using var conn = dataSource.OpenConnection();
|
|
using var cmd = new Npgsql.NpgsqlCommand(
|
|
"""
|
|
INSERT INTO reputation_delta_audit (
|
|
id, player_id, faction_id, delta_amount, resulting_standing, source_kind, source_id, recorded_at)
|
|
VALUES (@id, @pid, @fid, @delta, @result, @kind, @source_id, @recorded)
|
|
ON CONFLICT (id) DO NOTHING;
|
|
""",
|
|
conn);
|
|
cmd.Parameters.AddWithValue("id", normalized.Id);
|
|
cmd.Parameters.AddWithValue("pid", normalized.PlayerId);
|
|
cmd.Parameters.AddWithValue("fid", normalized.FactionId);
|
|
cmd.Parameters.AddWithValue("delta", normalized.DeltaAmount);
|
|
cmd.Parameters.AddWithValue("result", normalized.ResultingStanding);
|
|
cmd.Parameters.AddWithValue("kind", normalized.SourceKind);
|
|
cmd.Parameters.AddWithValue("source_id", normalized.SourceId);
|
|
cmd.Parameters.AddWithValue("recorded", normalized.RecordedAt);
|
|
return cmd.ExecuteNonQuery() == 1;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public IReadOnlyList<ReputationDeltaRow> GetDeltasForPlayer(string playerId, int? limit = null)
|
|
{
|
|
var player = FactionStandingIds.NormalizePlayerId(playerId);
|
|
if (player.Length == 0)
|
|
{
|
|
return [];
|
|
}
|
|
|
|
PostgresReputationDeltaBootstrap.EnsureSchema(dataSource);
|
|
using var conn = dataSource.OpenConnection();
|
|
var sql = limit is > 0
|
|
? """
|
|
SELECT id, player_id, faction_id, delta_amount, resulting_standing, source_kind, source_id, recorded_at
|
|
FROM reputation_delta_audit
|
|
WHERE player_id = @pid
|
|
ORDER BY recorded_at ASC, id ASC
|
|
LIMIT @lim;
|
|
"""
|
|
: """
|
|
SELECT id, player_id, faction_id, delta_amount, resulting_standing, source_kind, source_id, recorded_at
|
|
FROM reputation_delta_audit
|
|
WHERE player_id = @pid
|
|
ORDER BY recorded_at ASC, id ASC;
|
|
""";
|
|
using var cmd = new Npgsql.NpgsqlCommand(sql, conn);
|
|
cmd.Parameters.AddWithValue("pid", player);
|
|
if (limit is > 0)
|
|
{
|
|
cmd.Parameters.AddWithValue("lim", limit.Value);
|
|
}
|
|
|
|
var rows = new List<ReputationDeltaRow>();
|
|
using var reader = cmd.ExecuteReader();
|
|
while (reader.Read())
|
|
{
|
|
rows.Add(new ReputationDeltaRow(
|
|
reader.GetString(0),
|
|
reader.GetString(1),
|
|
reader.GetString(2),
|
|
reader.GetInt32(3),
|
|
reader.GetInt32(4),
|
|
reader.GetString(5),
|
|
reader.GetString(6),
|
|
reader.GetFieldValue<DateTimeOffset>(7)));
|
|
}
|
|
|
|
return rows;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public bool TryClearQuestCompletionAudit(string playerId, string questId)
|
|
{
|
|
var player = FactionStandingIds.NormalizePlayerId(playerId);
|
|
var sourceId = RewardDeliveryIds.NormalizeQuestId(questId);
|
|
if (player.Length == 0 || sourceId.Length == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
PostgresReputationDeltaBootstrap.EnsureSchema(dataSource);
|
|
using var conn = dataSource.OpenConnection();
|
|
using var cmd = new Npgsql.NpgsqlCommand(
|
|
"""
|
|
DELETE FROM reputation_delta_audit
|
|
WHERE player_id = @pid
|
|
AND source_kind = @kind
|
|
AND source_id = @source_id;
|
|
""",
|
|
conn);
|
|
cmd.Parameters.AddWithValue("pid", player);
|
|
cmd.Parameters.AddWithValue("kind", ReputationDeltaSourceKinds.QuestCompletion);
|
|
cmd.Parameters.AddWithValue("source_id", sourceId);
|
|
cmd.ExecuteNonQuery();
|
|
return true;
|
|
}
|
|
|
|
private static bool IsValidRow(ReputationDeltaRow row) =>
|
|
row.Id.Trim().Length > 0 &&
|
|
FactionStandingIds.NormalizePlayerId(row.PlayerId).Length > 0 &&
|
|
FactionStandingIds.NormalizeFactionId(row.FactionId).Length > 0 &&
|
|
row.SourceKind.Trim().Length > 0 &&
|
|
row.SourceId.Trim().Length > 0;
|
|
}
|