feat: photo upload at level 3 + report a player (nudity avatar / chat insult)
Photo upload:
- Lower the custom profile-photo gate from level 25 to level 3 (client const +
i18n hint + server gate in ProfileService.Update). The level-25 "Expert" title
is unrelated and unchanged.
Report a player:
- New ReportReason type + service.reportUser(targetId, reason, details?).
- Report entry points: a "گزارش تخلف" button + reason picker (nudity / insult /
other) in the public-profile modal, and a flag button in the chat header
(reports the peer for an insulting chat) with a confirmation toast.
- Mock records reports to localStorage; SignalR POSTs /api/report.
- Server: POST /api/report → ProfileService.ReportUser stores the report in the
write-only ledger (kind="report", ref="{targetId}|{reason}|{details}") so no
schema change is needed (server uses EnsureCreated, not migrations).
- i18n: report.* keys (fa + en).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -54,13 +54,28 @@ public class ProfileService
|
||||
await _db.SaveChangesAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Record a moderation report (inappropriate avatar / insulting chat). Stored
|
||||
/// in the write-only ledger as kind="report" so no schema change is needed;
|
||||
/// Ref encodes "{targetId}|{reason}|{details}".
|
||||
/// </summary>
|
||||
public async Task ReportUser(string reporterUid, string targetId, string? reason, string? details)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(targetId)) return;
|
||||
var safeReason = reason is "nudity" or "insult" or "other" ? reason : "other";
|
||||
var safeDetails = (details ?? "").Replace("\n", " ").Trim();
|
||||
var @ref = $"{targetId}|{safeReason}|{safeDetails}";
|
||||
if (@ref.Length > 480) @ref = @ref[..480];
|
||||
await Ledger(reporterUid, "report", 0, @ref);
|
||||
}
|
||||
|
||||
public async Task<ProfileDto> Update(string uid, JsonElement patch)
|
||||
{
|
||||
var p = await GetOrCreate(uid, null);
|
||||
if (patch.TryGetProperty("displayName", out var dn) && dn.ValueKind == JsonValueKind.String) p.DisplayName = dn.GetString()!;
|
||||
if (patch.TryGetProperty("avatar", out var av) && av.ValueKind == JsonValueKind.String) p.Avatar = av.GetString()!;
|
||||
// Custom photo upload is gated behind level 25.
|
||||
if (p.Level >= 25 && patch.TryGetProperty("avatarImage", out var ai) && ai.ValueKind == JsonValueKind.String) p.AvatarImage = ai.GetString();
|
||||
// Custom photo upload is gated behind level 3.
|
||||
if (p.Level >= 3 && patch.TryGetProperty("avatarImage", out var ai) && ai.ValueKind == JsonValueKind.String) p.AvatarImage = ai.GetString();
|
||||
if (patch.TryGetProperty("title", out var ti) && ti.ValueKind == JsonValueKind.String) p.Title = ti.GetString();
|
||||
if (patch.TryGetProperty("cardFront", out var cf) && cf.ValueKind == JsonValueKind.String) p.CardFront = cf.GetString()!;
|
||||
if (patch.TryGetProperty("cardBack", out var cb) && cb.ValueKind == JsonValueKind.String) p.CardBack = cb.GetString()!;
|
||||
|
||||
@@ -181,6 +181,13 @@ app.MapGet("/api/profile/{id}/public", async (string id, ClaimsPrincipal u, Soci
|
||||
return p != null ? Results.Json(p, JsonOpts.Default) : Results.NotFound();
|
||||
}).RequireAuthorization();
|
||||
|
||||
// Report a player (inappropriate avatar / insulting chat).
|
||||
app.MapPost("/api/report", async (ClaimsPrincipal u, ProfileService svc, ReportReq req) =>
|
||||
{
|
||||
await svc.ReportUser(Uid(u), req.TargetId, req.Reason, req.Details);
|
||||
return Results.Json(new { ok = true }, JsonOpts.Default);
|
||||
}).RequireAuthorization();
|
||||
|
||||
// Discover players (find-friends hub): search by name + suggestions.
|
||||
app.MapGet("/api/players/search", async (string? q, ClaimsPrincipal u, SocialService s) =>
|
||||
Results.Json(await s.SearchPlayers(Uid(u), q ?? ""), JsonOpts.Default)).RequireAuthorization();
|
||||
@@ -308,3 +315,4 @@ record IabVerifyReq(string Store, string ProductId, string Token);
|
||||
record QueryReq(string? Query = null, string? UserId = null);
|
||||
record IdReq(string Id);
|
||||
record SendReq(string PeerId, string Text);
|
||||
record ReportReq(string TargetId, string? Reason, string? Details);
|
||||
|
||||
Reference in New Issue
Block a user