Fix admin notes and database time nonsense. (#25280)

God bloody christ. There's like three layers of shit here.

So firstly, apparently we were still using Npgsql.EnableLegacyTimestampBehavior. This means that time values (which are stored UTC in the database) were converted to local time when read out. This meant they were passed around as kind Local to clients (instead of UTC in the case of SQLite). That's easy enough to fix just turn off the flag and fix the couple spots we're passing a local DateTime ez.

Oh but it turns out there's a DIFFERENT problem with SQLite: See SQLite we definitely store the DateTimes as UTC, but when Microsoft.Data.Sqlite reads them it reads them as Kind Unspecified instead of Utc.

Why are these so bad? Because the admin notes system passes DateTime instances from EF Core straight to the rest of the game code. And that means it's a PAIN IN THE ASS to run the necessary conversions to fix the DateTime instances. GOD DAMNIT now I have to make a whole new set of "Record" entities so we avoid leaking the EF Core model entities. WAAAAAAA.

Fixes #19897
This commit is contained in:
Pieter-Jan Briers
2024-02-20 10:13:31 +01:00
committed by GitHub
parent 2907e84b6f
commit 2e6eaa45c5
19 changed files with 501 additions and 326 deletions

View File

@@ -68,7 +68,7 @@ public sealed partial class AdminNotesLine : BoxContainer
SeverityRect.Texture = _sprites.Frame0(new SpriteSpecifier.Texture(new ResPath(iconPath))); SeverityRect.Texture = _sprites.Frame0(new SpriteSpecifier.Texture(new ResPath(iconPath)));
} }
TimeLabel.Text = Note.CreatedAt.ToString("yyyy-MM-dd HH:mm:ss"); TimeLabel.Text = Note.CreatedAt.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss");
ServerLabel.Text = Note.ServerName ?? "Unknown"; ServerLabel.Text = Note.ServerName ?? "Unknown";
RoundLabel.Text = Note.Round == null ? "Unknown round" : "Round " + Note.Round; RoundLabel.Text = Note.Round == null ? "Unknown round" : "Round " + Note.Round;
AdminLabel.Text = Note.CreatedByName; AdminLabel.Text = Note.CreatedByName;
@@ -91,7 +91,7 @@ public sealed partial class AdminNotesLine : BoxContainer
if (Note.ExpiryTime.Value > DateTime.UtcNow) if (Note.ExpiryTime.Value > DateTime.UtcNow)
{ {
ExpiresLabel.Text = Loc.GetString("admin-note-editor-expiry-label-params", ExpiresLabel.Text = Loc.GetString("admin-note-editor-expiry-label-params",
("date", Note.ExpiryTime.Value.ToString("yyyy-MM-dd HH:mm:ss")), ("date", Note.ExpiryTime.Value.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss")),
("expiresIn", (Note.ExpiryTime.Value - DateTime.UtcNow).ToString("d'd 'hh':'mm"))); ("expiresIn", (Note.ExpiryTime.Value - DateTime.UtcNow).ToString("d'd 'hh':'mm")));
ExpiresLabel.Modulate = Color.FromHex("#86DC3D"); ExpiresLabel.Modulate = Color.FromHex("#86DC3D");
} }
@@ -104,7 +104,7 @@ public sealed partial class AdminNotesLine : BoxContainer
if (Note.LastEditedAt > Note.CreatedAt) if (Note.LastEditedAt > Note.CreatedAt)
{ {
EditedLabel.Text = Loc.GetString("admin-notes-edited", ("author", Note.EditedByName), ("date", Note.LastEditedAt)); EditedLabel.Text = Loc.GetString("admin-notes-edited", ("author", Note.EditedByName), ("date", Note.LastEditedAt.Value.ToLocalTime()));
EditedLabel.Visible = true; EditedLabel.Visible = true;
} }

View File

@@ -36,12 +36,12 @@ public sealed partial class AdminNotesLinePopup : Popup
? Loc.GetString("admin-notes-round-id-unknown") ? Loc.GetString("admin-notes-round-id-unknown")
: Loc.GetString("admin-notes-round-id", ("id", note.Round)); : Loc.GetString("admin-notes-round-id", ("id", note.Round));
CreatedByLabel.Text = Loc.GetString("admin-notes-created-by", ("author", note.CreatedByName)); CreatedByLabel.Text = Loc.GetString("admin-notes-created-by", ("author", note.CreatedByName));
CreatedAtLabel.Text = Loc.GetString("admin-notes-created-at", ("date", note.CreatedAt.ToString("yyyy-MM-dd HH:mm:ss"))); CreatedAtLabel.Text = Loc.GetString("admin-notes-created-at", ("date", note.CreatedAt.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss")));
EditedByLabel.Text = Loc.GetString("admin-notes-last-edited-by", ("author", note.EditedByName)); EditedByLabel.Text = Loc.GetString("admin-notes-last-edited-by", ("author", note.EditedByName));
EditedAtLabel.Text = Loc.GetString("admin-notes-last-edited-at", ("date", note.LastEditedAt?.ToString("yyyy-MM-dd HH:mm:ss") ?? Loc.GetString("admin-notes-edited-never"))); EditedAtLabel.Text = Loc.GetString("admin-notes-last-edited-at", ("date", note.LastEditedAt?.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss") ?? Loc.GetString("admin-notes-edited-never")));
ExpiryTimeLabel.Text = note.ExpiryTime == null ExpiryTimeLabel.Text = note.ExpiryTime == null
? Loc.GetString("admin-notes-expires-never") ? Loc.GetString("admin-notes-expires-never")
: Loc.GetString("admin-notes-expires", ("expires", note.ExpiryTime.Value.ToString("yyyy-MM-dd HH:mm:ss"))); : Loc.GetString("admin-notes-expires", ("expires", note.ExpiryTime.Value.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss")));
NoteTextEdit.InsertAtCursor(note.Message); NoteTextEdit.InsertAtCursor(note.Message);
if (note.NoteType is NoteType.ServerBan or NoteType.RoleBan) if (note.NoteType is NoteType.ServerBan or NoteType.RoleBan)

View File

@@ -81,7 +81,7 @@ public sealed partial class NoteEdit : FancyWindow
{ {
PermanentCheckBox.Pressed = false; PermanentCheckBox.Pressed = false;
UpdatePermanentCheckboxFields(); UpdatePermanentCheckboxFields();
ExpiryLineEdit.Text = ExpiryTime.Value.ToString("yyyy-MM-dd HH:mm:ss"); ExpiryLineEdit.Text = ExpiryTime.Value.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss");
} }
} }
@@ -173,7 +173,7 @@ public sealed partial class NoteEdit : FancyWindow
ExpiryLabel.Visible = !PermanentCheckBox.Pressed; ExpiryLabel.Visible = !PermanentCheckBox.Pressed;
ExpiryLineEdit.Visible = !PermanentCheckBox.Pressed; ExpiryLineEdit.Visible = !PermanentCheckBox.Pressed;
ExpiryLineEdit.Text = !PermanentCheckBox.Pressed ? DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss") : string.Empty; ExpiryLineEdit.Text = !PermanentCheckBox.Pressed ? DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") : string.Empty;
} }
private void OnSecretPressed(BaseButton.ButtonEventArgs _) private void OnSecretPressed(BaseButton.ButtonEventArgs _)
@@ -269,7 +269,7 @@ public sealed partial class NoteEdit : FancyWindow
return false; return false;
} }
ExpiryTime = result; ExpiryTime = result.ToUniversalTime();
ExpiryLineEdit.ModulateSelfOverride = null; ExpiryLineEdit.ModulateSelfOverride = null;
return true; return true;
} }

View File

@@ -874,33 +874,8 @@ namespace Content.Server.Database
public byte[] Data { get; set; } = default!; public byte[] Data { get; set; } = default!;
} }
public interface IAdminRemarksCommon
{
public int Id { get; }
public int? RoundId { get; }
public Round? Round { get; }
public Guid? PlayerUserId { get; }
public Player? Player { get; }
public TimeSpan PlaytimeAtNote { get; }
public string Message { get; }
public Player? CreatedBy { get; }
public DateTime CreatedAt { get; }
public Player? LastEditedBy { get; }
public DateTime? LastEditedAt { get; }
public DateTime? ExpirationTime { get; }
public bool Deleted { get; }
}
[Index(nameof(PlayerUserId))] [Index(nameof(PlayerUserId))]
public class AdminNote : IAdminRemarksCommon public class AdminNote
{ {
[Required, Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; } [Required, Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; }
@@ -934,7 +909,7 @@ namespace Content.Server.Database
} }
[Index(nameof(PlayerUserId))] [Index(nameof(PlayerUserId))]
public class AdminWatchlist : IAdminRemarksCommon public class AdminWatchlist
{ {
[Required, Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; } [Required, Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; }
@@ -965,7 +940,7 @@ namespace Content.Server.Database
} }
[Index(nameof(PlayerUserId))] [Index(nameof(PlayerUserId))]
public class AdminMessage : IAdminRemarksCommon public class AdminMessage
{ {
[Required, Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; } [Required, Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; }

View File

@@ -10,11 +10,6 @@ namespace Content.Server.Database
{ {
public sealed class PostgresServerDbContext : ServerDbContext public sealed class PostgresServerDbContext : ServerDbContext
{ {
static PostgresServerDbContext()
{
AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
}
public PostgresServerDbContext(DbContextOptions<PostgresServerDbContext> options) : base(options) public PostgresServerDbContext(DbContextOptions<PostgresServerDbContext> options) : base(options)
{ {
} }

View File

@@ -13,7 +13,7 @@ public sealed class AdminMessageEui : BaseEui
[Dependency] private readonly IAdminNotesManager _notesMan = default!; [Dependency] private readonly IAdminNotesManager _notesMan = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!; [Dependency] private readonly IConfigurationManager _cfg = default!;
private readonly float _closeWait; private readonly float _closeWait;
private AdminMessage? _message; private AdminMessageRecord? _message;
private DateTime _startTime; private DateTime _startTime;
public AdminMessageEui() public AdminMessageEui()
@@ -22,7 +22,7 @@ public sealed class AdminMessageEui : BaseEui
_closeWait = _cfg.GetCVar(CCVars.MessageWaitTime); _closeWait = _cfg.GetCVar(CCVars.MessageWaitTime);
} }
public void SetMessage(AdminMessage message) public void SetMessage(AdminMessageRecord message)
{ {
_message = message; _message = message;
_startTime = DateTime.UtcNow; _startTime = DateTime.UtcNow;
@@ -37,7 +37,7 @@ public sealed class AdminMessageEui : BaseEui
_closeWait, _closeWait,
_message.Message, _message.Message,
_message.CreatedBy?.LastSeenUserName ?? "[System]", _message.CreatedBy?.LastSeenUserName ?? "[System]",
_message.CreatedAt _message.CreatedAt.UtcDateTime
); );
} }

View File

@@ -1,4 +1,3 @@
using System.Diagnostics;
using Content.Server.Database; using Content.Server.Database;
using Content.Shared.Administration.Notes; using Content.Shared.Administration.Notes;
using Content.Shared.Database; using Content.Shared.Database;
@@ -7,7 +6,7 @@ namespace Content.Server.Administration.Notes;
public static class AdminNotesExtensions public static class AdminNotesExtensions
{ {
public static SharedAdminNote ToShared(this IAdminRemarksCommon note) public static SharedAdminNote ToShared(this IAdminRemarksRecord note)
{ {
NoteSeverity? severity = null; NoteSeverity? severity = null;
var secret = false; var secret = false;
@@ -18,26 +17,26 @@ public static class AdminNotesExtensions
bool? seen = null; bool? seen = null;
switch (note) switch (note)
{ {
case AdminNote adminNote: case AdminNoteRecord adminNote:
type = NoteType.Note; type = NoteType.Note;
severity = adminNote.Severity; severity = adminNote.Severity;
secret = adminNote.Secret; secret = adminNote.Secret;
break; break;
case AdminWatchlist: case AdminWatchlistRecord:
type = NoteType.Watchlist; type = NoteType.Watchlist;
secret = true; secret = true;
break; break;
case AdminMessage adminMessage: case AdminMessageRecord adminMessage:
type = NoteType.Message; type = NoteType.Message;
seen = adminMessage.Seen; seen = adminMessage.Seen;
break; break;
case ServerBanNote ban: case ServerBanNoteRecord ban:
type = NoteType.ServerBan; type = NoteType.ServerBan;
severity = ban.Severity; severity = ban.Severity;
unbannedTime = ban.UnbanTime; unbannedTime = ban.UnbanTime;
unbannedByName = ban.UnbanningAdmin?.LastSeenUserName ?? Loc.GetString("system-user"); unbannedByName = ban.UnbanningAdmin?.LastSeenUserName ?? Loc.GetString("system-user");
break; break;
case ServerRoleBanNote roleBan: case ServerRoleBanNoteRecord roleBan:
type = NoteType.RoleBan; type = NoteType.RoleBan;
severity = roleBan.Severity; severity = roleBan.Severity;
bannedRoles = roleBan.Roles; bannedRoles = roleBan.Roles;
@@ -49,12 +48,13 @@ public static class AdminNotesExtensions
} }
// There may be bans without a user, but why would we ever be converting them to shared notes? // There may be bans without a user, but why would we ever be converting them to shared notes?
if (note.PlayerUserId is null) if (note.Player is null)
throw new ArgumentNullException(nameof(note.PlayerUserId), "Player user ID cannot be null for a note"); throw new ArgumentNullException(nameof(note), "Player user ID cannot be null for a note");
return new SharedAdminNote( return new SharedAdminNote(
note.Id, note.Id,
note.PlayerUserId.Value, note.Player!.UserId,
note.RoundId, note.Round?.Id,
note.Round?.Server.Name, note.Round?.Server.Name,
note.PlaytimeAtNote, note.PlaytimeAtNote,
type, type,
@@ -63,9 +63,9 @@ public static class AdminNotesExtensions
secret, secret,
note.CreatedBy?.LastSeenUserName ?? Loc.GetString("system-user"), note.CreatedBy?.LastSeenUserName ?? Loc.GetString("system-user"),
note.LastEditedBy?.LastSeenUserName ?? string.Empty, note.LastEditedBy?.LastSeenUserName ?? string.Empty,
note.CreatedAt, note.CreatedAt.UtcDateTime,
note.LastEditedAt, note.LastEditedAt?.UtcDateTime,
note.ExpirationTime, note.ExpirationTime?.UtcDateTime,
bannedRoles, bannedRoles,
unbannedTime, unbannedTime,
unbannedByName, unbannedByName,

View File

@@ -144,7 +144,7 @@ public sealed class AdminNotesManager : IAdminNotesManager, IPostInjectInit
var note = new SharedAdminNote( var note = new SharedAdminNote(
noteId, noteId,
player, (NetUserId) player,
roundId, roundId,
serverName, serverName,
playtime, playtime,
@@ -306,27 +306,27 @@ public sealed class AdminNotesManager : IAdminNotesManager, IPostInjectInit
NoteModified?.Invoke(newNote); NoteModified?.Invoke(newNote);
} }
public async Task<List<IAdminRemarksCommon>> GetAllAdminRemarks(Guid player) public async Task<List<IAdminRemarksRecord>> GetAllAdminRemarks(Guid player)
{ {
return await _db.GetAllAdminRemarks(player); return await _db.GetAllAdminRemarks(player);
} }
public async Task<List<IAdminRemarksCommon>> GetVisibleRemarks(Guid player) public async Task<List<IAdminRemarksRecord>> GetVisibleRemarks(Guid player)
{ {
if (_config.GetCVar(CCVars.SeeOwnNotes)) if (_config.GetCVar(CCVars.SeeOwnNotes))
{ {
return await _db.GetVisibleAdminNotes(player); return await _db.GetVisibleAdminNotes(player);
} }
_sawmill.Warning($"Someone tried to call GetVisibleNotes for {player} when see_own_notes was false"); _sawmill.Warning($"Someone tried to call GetVisibleNotes for {player} when see_own_notes was false");
return new List<IAdminRemarksCommon>(); return new List<IAdminRemarksRecord>();
} }
public async Task<List<AdminWatchlist>> GetActiveWatchlists(Guid player) public async Task<List<AdminWatchlistRecord>> GetActiveWatchlists(Guid player)
{ {
return await _db.GetActiveWatchlists(player); return await _db.GetActiveWatchlists(player);
} }
public async Task<List<AdminMessage>> GetNewMessages(Guid player) public async Task<List<AdminMessageRecord>> GetNewMessages(Guid player)
{ {
return await _db.GetMessages(player); return await _db.GetMessages(player);
} }

View File

@@ -26,24 +26,24 @@ public interface IAdminNotesManager
/// </summary> /// </summary>
/// <param name="player">Desired player's <see cref="Guid"/></param> /// <param name="player">Desired player's <see cref="Guid"/></param>
/// <returns>ALL non-deleted notes, secret or not</returns> /// <returns>ALL non-deleted notes, secret or not</returns>
Task<List<IAdminRemarksCommon>> GetAllAdminRemarks(Guid player); Task<List<IAdminRemarksRecord>> GetAllAdminRemarks(Guid player);
/// <summary> /// <summary>
/// Queries the database and retrieves the notes a player should see /// Queries the database and retrieves the notes a player should see
/// </summary> /// </summary>
/// <param name="player">Desired player's <see cref="Guid"/></param> /// <param name="player">Desired player's <see cref="Guid"/></param>
/// <returns>All player-visible notes</returns> /// <returns>All player-visible notes</returns>
Task<List<IAdminRemarksCommon>> GetVisibleRemarks(Guid player); Task<List<IAdminRemarksRecord>> GetVisibleRemarks(Guid player);
/// <summary> /// <summary>
/// Queries the database and retrieves watchlists that may have been placed on the player /// Queries the database and retrieves watchlists that may have been placed on the player
/// </summary> /// </summary>
/// <param name="player">Desired player's <see cref="Guid"/></param> /// <param name="player">Desired player's <see cref="Guid"/></param>
/// <returns>Active watchlists</returns> /// <returns>Active watchlists</returns>
Task<List<AdminWatchlist>> GetActiveWatchlists(Guid player); Task<List<AdminWatchlistRecord>> GetActiveWatchlists(Guid player);
/// <summary> /// <summary>
/// Queries the database and retrieves new messages a player has gotten /// Queries the database and retrieves new messages a player has gotten
/// </summary> /// </summary>
/// <param name="player">Desired player's <see cref="Guid"/></param> /// <param name="player">Desired player's <see cref="Guid"/></param>
/// <returns>All unread messages</returns> /// <returns>All unread messages</returns>
Task<List<AdminMessage>> GetNewMessages(Guid player); Task<List<AdminMessageRecord>> GetNewMessages(Guid player);
Task MarkMessageAsSeen(int id); Task MarkMessageAsSeen(int id);
} }

View File

@@ -0,0 +1,127 @@
using System.Collections.Immutable;
using System.Net;
using Content.Shared.Database;
using Robust.Shared.Network;
namespace Content.Server.Database;
// This file contains copies of records returned from the database.
// We can't return the raw EF Core entities as they are often unsuited.
// (e.g. datetime handling of Microsoft.Data.Sqlite)
public interface IAdminRemarksRecord
{
public int Id { get; }
public RoundRecord? Round { get; }
public PlayerRecord? Player { get; }
public TimeSpan PlaytimeAtNote { get; }
public string Message { get; }
public PlayerRecord? CreatedBy { get; }
public DateTimeOffset CreatedAt { get; }
public PlayerRecord? LastEditedBy { get; }
public DateTimeOffset? LastEditedAt { get; }
public DateTimeOffset? ExpirationTime { get; }
public bool Deleted { get; }
}
public sealed record ServerRoleBanNoteRecord(
int Id,
RoundRecord? Round,
PlayerRecord? Player,
TimeSpan PlaytimeAtNote,
string Message,
NoteSeverity Severity,
PlayerRecord? CreatedBy,
DateTimeOffset CreatedAt,
PlayerRecord? LastEditedBy,
DateTimeOffset? LastEditedAt,
DateTimeOffset? ExpirationTime,
bool Deleted,
string[] Roles,
PlayerRecord? UnbanningAdmin,
DateTime? UnbanTime) : IAdminRemarksRecord;
public sealed record ServerBanNoteRecord(
int Id,
RoundRecord? Round,
PlayerRecord? Player,
TimeSpan PlaytimeAtNote,
string Message,
NoteSeverity Severity,
PlayerRecord? CreatedBy,
DateTimeOffset CreatedAt,
PlayerRecord? LastEditedBy,
DateTimeOffset? LastEditedAt,
DateTimeOffset? ExpirationTime,
bool Deleted,
PlayerRecord? UnbanningAdmin,
DateTime? UnbanTime) : IAdminRemarksRecord;
public sealed record AdminNoteRecord(
int Id,
RoundRecord? Round,
PlayerRecord? Player,
TimeSpan PlaytimeAtNote,
string Message,
NoteSeverity Severity,
PlayerRecord? CreatedBy,
DateTimeOffset CreatedAt,
PlayerRecord? LastEditedBy,
DateTimeOffset? LastEditedAt,
DateTimeOffset? ExpirationTime,
bool Deleted,
PlayerRecord? DeletedBy,
DateTimeOffset? DeletedAt,
bool Secret) : IAdminRemarksRecord;
public sealed record AdminWatchlistRecord(
int Id,
RoundRecord? Round,
PlayerRecord? Player,
TimeSpan PlaytimeAtNote,
string Message,
PlayerRecord? CreatedBy,
DateTimeOffset CreatedAt,
PlayerRecord? LastEditedBy,
DateTimeOffset? LastEditedAt,
DateTimeOffset? ExpirationTime,
bool Deleted,
PlayerRecord? DeletedBy,
DateTimeOffset? DeletedAt) : IAdminRemarksRecord;
public sealed record AdminMessageRecord(
int Id,
RoundRecord? Round,
PlayerRecord? Player,
TimeSpan PlaytimeAtNote,
string Message,
PlayerRecord? CreatedBy,
DateTimeOffset CreatedAt,
PlayerRecord? LastEditedBy,
DateTimeOffset? LastEditedAt,
DateTimeOffset? ExpirationTime,
bool Deleted,
PlayerRecord? DeletedBy,
DateTimeOffset? DeletedAt,
bool Seen) : IAdminRemarksRecord;
public sealed record PlayerRecord(
NetUserId UserId,
DateTimeOffset FirstSeenTime,
string LastSeenUserName,
DateTimeOffset LastSeenTime,
IPAddress LastSeenAddress,
ImmutableArray<byte>? HWId);
public sealed record RoundRecord(int Id, DateTimeOffset StartDate, ServerRecord Server);
public sealed record ServerRecord(int Id, string Name);

View File

@@ -1,32 +0,0 @@
using System.Collections.Immutable;
using System.Net;
using Robust.Shared.Network;
namespace Content.Server.Database
{
public sealed class PlayerRecord
{
public NetUserId UserId { get; }
public ImmutableArray<byte>? HWId { get; }
public DateTimeOffset FirstSeenTime { get; }
public string LastSeenUserName { get; }
public DateTimeOffset LastSeenTime { get; }
public IPAddress LastSeenAddress { get; }
public PlayerRecord(
NetUserId userId,
DateTimeOffset firstSeenTime,
string lastSeenUserName,
DateTimeOffset lastSeenTime,
IPAddress lastSeenAddress,
ImmutableArray<byte>? hwId)
{
UserId = userId;
FirstSeenTime = firstSeenTime;
LastSeenUserName = lastSeenUserName;
LastSeenTime = lastSeenTime;
LastSeenAddress = lastSeenAddress;
HWId = hwId;
}
}
}

View File

@@ -1,14 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Content.Shared.Database;
namespace Content.Server.Database
{
public record ServerBanNote(int Id, int? RoundId, Round? Round, Guid? PlayerUserId, Player? Player,
TimeSpan PlaytimeAtNote, string Message, NoteSeverity Severity, Player? CreatedBy, DateTime CreatedAt,
Player? LastEditedBy, DateTime? LastEditedAt, DateTime? ExpirationTime, bool Deleted, Player? UnbanningAdmin,
DateTime? UnbanTime) : IAdminRemarksCommon;
}

View File

@@ -1,4 +1,5 @@
using System.Collections.Immutable; using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
@@ -356,7 +357,7 @@ namespace Content.Server.Database
public abstract Task AddServerBanAsync(ServerBanDef serverBan); public abstract Task AddServerBanAsync(ServerBanDef serverBan);
public abstract Task AddServerUnbanAsync(ServerUnbanDef serverUnban); public abstract Task AddServerUnbanAsync(ServerUnbanDef serverUnban);
public async Task EditServerBan(int id, string reason, NoteSeverity severity, DateTime? expiration, Guid editedBy, DateTime editedAt) public async Task EditServerBan(int id, string reason, NoteSeverity severity, DateTimeOffset? expiration, Guid editedBy, DateTimeOffset editedAt)
{ {
await using var db = await GetDb(); await using var db = await GetDb();
@@ -365,9 +366,9 @@ namespace Content.Server.Database
return; return;
ban.Severity = severity; ban.Severity = severity;
ban.Reason = reason; ban.Reason = reason;
ban.ExpirationTime = expiration; ban.ExpirationTime = expiration?.UtcDateTime;
ban.LastEditedById = editedBy; ban.LastEditedById = editedBy;
ban.LastEditedAt = editedAt; ban.LastEditedAt = editedAt.UtcDateTime;
await db.DbContext.SaveChangesAsync(); await db.DbContext.SaveChangesAsync();
} }
@@ -448,7 +449,7 @@ namespace Content.Server.Database
public abstract Task<ServerRoleBanDef> AddServerRoleBanAsync(ServerRoleBanDef serverRoleBan); public abstract Task<ServerRoleBanDef> AddServerRoleBanAsync(ServerRoleBanDef serverRoleBan);
public abstract Task AddServerRoleUnbanAsync(ServerRoleUnbanDef serverRoleUnban); public abstract Task AddServerRoleUnbanAsync(ServerRoleUnbanDef serverRoleUnban);
public async Task EditServerRoleBan(int id, string reason, NoteSeverity severity, DateTime? expiration, Guid editedBy, DateTime editedAt) public async Task EditServerRoleBan(int id, string reason, NoteSeverity severity, DateTimeOffset? expiration, Guid editedBy, DateTimeOffset editedAt)
{ {
await using var db = await GetDb(); await using var db = await GetDb();
@@ -457,9 +458,9 @@ namespace Content.Server.Database
return; return;
ban.Severity = severity; ban.Severity = severity;
ban.Reason = reason; ban.Reason = reason;
ban.ExpirationTime = expiration; ban.ExpirationTime = expiration?.UtcDateTime;
ban.LastEditedById = editedBy; ban.LastEditedById = editedBy;
ban.LastEditedAt = editedAt; ban.LastEditedAt = editedAt.UtcDateTime;
await db.DbContext.SaveChangesAsync(); await db.DbContext.SaveChangesAsync();
} }
#endregion #endregion
@@ -571,7 +572,21 @@ namespace Content.Server.Database
return record == null ? null : MakePlayerRecord(record); return record == null ? null : MakePlayerRecord(record);
} }
protected abstract PlayerRecord MakePlayerRecord(Player player); [return: NotNullIfNotNull(nameof(player))]
protected PlayerRecord? MakePlayerRecord(Player? player)
{
if (player == null)
return null;
return new PlayerRecord(
new NetUserId(player.UserId),
new DateTimeOffset(NormalizeDatabaseTime(player.FirstSeenTime)),
player.LastSeenUserName,
new DateTimeOffset(NormalizeDatabaseTime(player.LastSeenTime)),
player.LastSeenAddress,
player.LastSeenHWId?.ToImmutableArray());
}
#endregion #endregion
#region Connection Logs #region Connection Logs
@@ -733,6 +748,18 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
await db.DbContext.SaveChangesAsync(); await db.DbContext.SaveChangesAsync();
} }
[return: NotNullIfNotNull(nameof(round))]
protected RoundRecord? MakeRoundRecord(Round? round)
{
if (round == null)
return null;
return new RoundRecord(
round.Id,
NormalizeDatabaseTime(round.StartDate),
MakeServerRecord(round.Server));
}
public async Task UpdateAdminRankAsync(AdminRank rank, CancellationToken cancel) public async Task UpdateAdminRankAsync(AdminRank rank, CancellationToken cancel)
{ {
await using var db = await GetDb(); await using var db = await GetDb();
@@ -772,6 +799,15 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
return (server, false); return (server, false);
} }
[return: NotNullIfNotNull(nameof(server))]
protected ServerRecord? MakeServerRecord(Server? server)
{
if (server == null)
return null;
return new ServerRecord(server.Id, server.Name);
}
public async Task AddAdminLogs(List<AdminLog> logs) public async Task AddAdminLogs(List<AdminLog> logs)
{ {
DebugTools.Assert(logs.All(x => x.RoundId > 0), "Adding logs with invalid round ids."); DebugTools.Assert(logs.All(x => x.RoundId > 0), "Adding logs with invalid round ids.");
@@ -943,17 +979,17 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
await db.DbContext.SaveChangesAsync(); await db.DbContext.SaveChangesAsync();
} }
public async Task<DateTime?> GetLastReadRules(NetUserId player) public async Task<DateTimeOffset?> GetLastReadRules(NetUserId player)
{ {
await using var db = await GetDb(); await using var db = await GetDb();
return await db.DbContext.Player return NormalizeDatabaseTime(await db.DbContext.Player
.Where(dbPlayer => dbPlayer.UserId == player) .Where(dbPlayer => dbPlayer.UserId == player)
.Select(dbPlayer => dbPlayer.LastReadRules) .Select(dbPlayer => dbPlayer.LastReadRules)
.SingleOrDefaultAsync(); .SingleOrDefaultAsync());
} }
public async Task SetLastReadRules(NetUserId player, DateTime date) public async Task SetLastReadRules(NetUserId player, DateTimeOffset date)
{ {
await using var db = await GetDb(); await using var db = await GetDb();
@@ -963,7 +999,7 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
return; return;
} }
dbPlayer.LastReadRules = date; dbPlayer.LastReadRules = date.UtcDateTime;
await db.DbContext.SaveChangesAsync(); await db.DbContext.SaveChangesAsync();
} }
@@ -971,11 +1007,11 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
#region Uploaded Resources Logs #region Uploaded Resources Logs
public async Task AddUploadedResourceLogAsync(NetUserId user, DateTime date, string path, byte[] data) public async Task AddUploadedResourceLogAsync(NetUserId user, DateTimeOffset date, string path, byte[] data)
{ {
await using var db = await GetDb(); await using var db = await GetDb();
db.DbContext.UploadedResourceLog.Add(new UploadedResourceLog() { UserId = user, Date = date, Path = path, Data = data }); db.DbContext.UploadedResourceLog.Add(new UploadedResourceLog() { UserId = user, Date = date.UtcDateTime, Path = path, Data = data });
await db.DbContext.SaveChangesAsync(); await db.DbContext.SaveChangesAsync();
} }
@@ -983,7 +1019,7 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
{ {
await using var db = await GetDb(); await using var db = await GetDb();
var date = DateTime.Now.Subtract(TimeSpan.FromDays(days)); var date = DateTime.UtcNow.Subtract(TimeSpan.FromDays(days));
await foreach (var log in db.DbContext.UploadedResourceLog await foreach (var log in db.DbContext.UploadedResourceLog
.Where(l => date > l.Date) .Where(l => date > l.Date)
@@ -1023,10 +1059,10 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
return message.Id; return message.Id;
} }
public async Task<AdminNote?> GetAdminNote(int id) public async Task<AdminNoteRecord?> GetAdminNote(int id)
{ {
await using var db = await GetDb(); await using var db = await GetDb();
return await db.DbContext.AdminNotes var entity = await db.DbContext.AdminNotes
.Where(note => note.Id == id) .Where(note => note.Id == id)
.Include(note => note.Round) .Include(note => note.Round)
.ThenInclude(r => r!.Server) .ThenInclude(r => r!.Server)
@@ -1035,12 +1071,34 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
.Include(note => note.DeletedBy) .Include(note => note.DeletedBy)
.Include(note => note.Player) .Include(note => note.Player)
.SingleOrDefaultAsync(); .SingleOrDefaultAsync();
return entity == null ? null : MakeAdminNoteRecord(entity);
} }
public async Task<AdminWatchlist?> GetAdminWatchlist(int id) private AdminNoteRecord MakeAdminNoteRecord(AdminNote entity)
{
return new AdminNoteRecord(
entity.Id,
MakeRoundRecord(entity.Round),
MakePlayerRecord(entity.Player),
entity.PlaytimeAtNote,
entity.Message,
entity.Severity,
MakePlayerRecord(entity.CreatedBy),
NormalizeDatabaseTime(entity.CreatedAt),
MakePlayerRecord(entity.LastEditedBy),
NormalizeDatabaseTime(entity.LastEditedAt),
NormalizeDatabaseTime(entity.ExpirationTime),
entity.Deleted,
MakePlayerRecord(entity.DeletedBy),
NormalizeDatabaseTime(entity.DeletedAt),
entity.Secret);
}
public async Task<AdminWatchlistRecord?> GetAdminWatchlist(int id)
{ {
await using var db = await GetDb(); await using var db = await GetDb();
return await db.DbContext.AdminWatchlists var entity = await db.DbContext.AdminWatchlists
.Where(note => note.Id == id) .Where(note => note.Id == id)
.Include(note => note.Round) .Include(note => note.Round)
.ThenInclude(r => r!.Server) .ThenInclude(r => r!.Server)
@@ -1049,12 +1107,14 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
.Include(note => note.DeletedBy) .Include(note => note.DeletedBy)
.Include(note => note.Player) .Include(note => note.Player)
.SingleOrDefaultAsync(); .SingleOrDefaultAsync();
return entity == null ? null : MakeAdminWatchlistRecord(entity);
} }
public async Task<AdminMessage?> GetAdminMessage(int id) public async Task<AdminMessageRecord?> GetAdminMessage(int id)
{ {
await using var db = await GetDb(); await using var db = await GetDb();
return await db.DbContext.AdminMessages var entity = await db.DbContext.AdminMessages
.Where(note => note.Id == id) .Where(note => note.Id == id)
.Include(note => note.Round) .Include(note => note.Round)
.ThenInclude(r => r!.Server) .ThenInclude(r => r!.Server)
@@ -1063,9 +1123,30 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
.Include(note => note.DeletedBy) .Include(note => note.DeletedBy)
.Include(note => note.Player) .Include(note => note.Player)
.SingleOrDefaultAsync(); .SingleOrDefaultAsync();
return entity == null ? null : MakeAdminMessageRecord(entity);
} }
public async Task<ServerBanNote?> GetServerBanAsNoteAsync(int id) private AdminMessageRecord MakeAdminMessageRecord(AdminMessage entity)
{
return new AdminMessageRecord(
entity.Id,
MakeRoundRecord(entity.Round),
MakePlayerRecord(entity.Player),
entity.PlaytimeAtNote,
entity.Message,
MakePlayerRecord(entity.CreatedBy),
NormalizeDatabaseTime(entity.CreatedAt),
MakePlayerRecord(entity.LastEditedBy),
NormalizeDatabaseTime(entity.LastEditedAt),
NormalizeDatabaseTime(entity.ExpirationTime),
entity.Deleted,
MakePlayerRecord(entity.DeletedBy),
NormalizeDatabaseTime(entity.DeletedAt),
entity.Seen);
}
public async Task<ServerBanNoteRecord?> GetServerBanAsNoteAsync(int id)
{ {
await using var db = await GetDb(); await using var db = await GetDb();
@@ -1082,22 +1163,37 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
return null; return null;
var player = await db.DbContext.Player.SingleOrDefaultAsync(p => p.UserId == ban.PlayerUserId); var player = await db.DbContext.Player.SingleOrDefaultAsync(p => p.UserId == ban.PlayerUserId);
return new ServerBanNote(ban.Id, ban.RoundId, ban.Round, ban.PlayerUserId, player, return new ServerBanNoteRecord(
ban.PlaytimeAtNote, ban.Reason, ban.Severity, ban.CreatedBy, ban.BanTime, ban.Id,
ban.LastEditedBy, ban.LastEditedAt, ban.ExpirationTime, ban.Hidden, MakeRoundRecord(ban.Round),
ban.Unban?.UnbanningAdmin == null MakePlayerRecord(player),
ban.PlaytimeAtNote,
ban.Reason,
ban.Severity,
MakePlayerRecord(ban.CreatedBy),
ban.BanTime,
MakePlayerRecord(ban.LastEditedBy),
ban.LastEditedAt,
ban.ExpirationTime,
ban.Hidden,
MakePlayerRecord(ban.Unban?.UnbanningAdmin == null
? null ? null
: await db.DbContext.Player.SingleOrDefaultAsync(p => : await db.DbContext.Player.SingleOrDefaultAsync(p =>
p.UserId == ban.Unban.UnbanningAdmin.Value), p.UserId == ban.Unban.UnbanningAdmin.Value)),
ban.Unban?.UnbanTime); ban.Unban?.UnbanTime);
} }
public async Task<ServerRoleBanNote?> GetServerRoleBanAsNoteAsync(int id) public async Task<ServerRoleBanNoteRecord?> GetServerRoleBanAsNoteAsync(int id)
{ {
await using var db = await GetDb(); await using var db = await GetDb();
var ban = await db.DbContext.RoleBan var ban = await db.DbContext.RoleBan
.Include(b => b.Unban) .Include(ban => ban.Unban)
.Include(ban => ban.Round)
.ThenInclude(r => r!.Server)
.Include(ban => ban.CreatedBy)
.Include(ban => ban.LastEditedBy)
.Include(ban => ban.Unban)
.SingleOrDefaultAsync(b => b.Id == id); .SingleOrDefaultAsync(b => b.Id == id);
if (ban is null) if (ban is null)
@@ -1108,36 +1204,48 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
ban.Unban is null ban.Unban is null
? null ? null
: await db.DbContext.Player.SingleOrDefaultAsync(b => b.UserId == ban.Unban.UnbanningAdmin); : await db.DbContext.Player.SingleOrDefaultAsync(b => b.UserId == ban.Unban.UnbanningAdmin);
return new ServerRoleBanNote(ban.Id, ban.RoundId, ban.Round, ban.PlayerUserId,
player, ban.PlaytimeAtNote, ban.Reason, ban.Severity, ban.CreatedBy, return new ServerRoleBanNoteRecord(
ban.BanTime, ban.LastEditedBy, ban.LastEditedAt, ban.ExpirationTime, ban.Id,
ban.Hidden, new [] { ban.RoleId.Replace(BanManager.JobPrefix, null) }, MakeRoundRecord(ban.Round),
unbanningAdmin, ban.Unban?.UnbanTime); MakePlayerRecord(player),
ban.PlaytimeAtNote,
ban.Reason,
ban.Severity,
MakePlayerRecord(ban.CreatedBy),
ban.BanTime,
MakePlayerRecord(ban.LastEditedBy),
ban.LastEditedAt,
ban.ExpirationTime,
ban.Hidden,
new [] { ban.RoleId.Replace(BanManager.JobPrefix, null) },
MakePlayerRecord(unbanningAdmin),
ban.Unban?.UnbanTime);
} }
public async Task<List<IAdminRemarksCommon>> GetAllAdminRemarks(Guid player) public async Task<List<IAdminRemarksRecord>> GetAllAdminRemarks(Guid player)
{ {
await using var db = await GetDb(); await using var db = await GetDb();
List<IAdminRemarksCommon> notes = new(); List<IAdminRemarksRecord> notes = new();
notes.AddRange( notes.AddRange(
await (from note in db.DbContext.AdminNotes (await (from note in db.DbContext.AdminNotes
where note.PlayerUserId == player && where note.PlayerUserId == player &&
!note.Deleted && !note.Deleted &&
(note.ExpirationTime == null || DateTime.UtcNow < note.ExpirationTime) (note.ExpirationTime == null || DateTime.UtcNow < note.ExpirationTime)
select note) select note)
.Include(note => note.Round) .Include(note => note.Round)
.ThenInclude(r => r!.Server) .ThenInclude(r => r!.Server)
.Include(note => note.CreatedBy) .Include(note => note.CreatedBy)
.Include(note => note.LastEditedBy) .Include(note => note.LastEditedBy)
.Include(note => note.Player) .Include(note => note.Player)
.ToListAsync()); .ToListAsync()).Select(MakeAdminNoteRecord));
notes.AddRange(await GetActiveWatchlistsImpl(db, player)); notes.AddRange(await GetActiveWatchlistsImpl(db, player));
notes.AddRange(await GetMessagesImpl(db, player)); notes.AddRange(await GetMessagesImpl(db, player));
notes.AddRange(await GetServerBansAsNotesForUser(db, player)); notes.AddRange(await GetServerBansAsNotesForUser(db, player));
notes.AddRange(await GetGroupedServerRoleBansAsNotesForUser(db, player)); notes.AddRange(await GetGroupedServerRoleBansAsNotesForUser(db, player));
return notes; return notes;
} }
public async Task EditAdminNote(int id, string message, NoteSeverity severity, bool secret, Guid editedBy, DateTime editedAt, DateTime? expiryTime) public async Task EditAdminNote(int id, string message, NoteSeverity severity, bool secret, Guid editedBy, DateTimeOffset editedAt, DateTimeOffset? expiryTime)
{ {
await using var db = await GetDb(); await using var db = await GetDb();
@@ -1146,39 +1254,39 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
note.Severity = severity; note.Severity = severity;
note.Secret = secret; note.Secret = secret;
note.LastEditedById = editedBy; note.LastEditedById = editedBy;
note.LastEditedAt = editedAt; note.LastEditedAt = editedAt.UtcDateTime;
note.ExpirationTime = expiryTime; note.ExpirationTime = expiryTime?.UtcDateTime;
await db.DbContext.SaveChangesAsync(); await db.DbContext.SaveChangesAsync();
} }
public async Task EditAdminWatchlist(int id, string message, Guid editedBy, DateTime editedAt, DateTime? expiryTime) public async Task EditAdminWatchlist(int id, string message, Guid editedBy, DateTimeOffset editedAt, DateTimeOffset? expiryTime)
{ {
await using var db = await GetDb(); await using var db = await GetDb();
var note = await db.DbContext.AdminWatchlists.Where(note => note.Id == id).SingleAsync(); var note = await db.DbContext.AdminWatchlists.Where(note => note.Id == id).SingleAsync();
note.Message = message; note.Message = message;
note.LastEditedById = editedBy; note.LastEditedById = editedBy;
note.LastEditedAt = editedAt; note.LastEditedAt = editedAt.UtcDateTime;
note.ExpirationTime = expiryTime; note.ExpirationTime = expiryTime?.UtcDateTime;
await db.DbContext.SaveChangesAsync(); await db.DbContext.SaveChangesAsync();
} }
public async Task EditAdminMessage(int id, string message, Guid editedBy, DateTime editedAt, DateTime? expiryTime) public async Task EditAdminMessage(int id, string message, Guid editedBy, DateTimeOffset editedAt, DateTimeOffset? expiryTime)
{ {
await using var db = await GetDb(); await using var db = await GetDb();
var note = await db.DbContext.AdminMessages.Where(note => note.Id == id).SingleAsync(); var note = await db.DbContext.AdminMessages.Where(note => note.Id == id).SingleAsync();
note.Message = message; note.Message = message;
note.LastEditedById = editedBy; note.LastEditedById = editedBy;
note.LastEditedAt = editedAt; note.LastEditedAt = editedAt.UtcDateTime;
note.ExpirationTime = expiryTime; note.ExpirationTime = expiryTime?.UtcDateTime;
await db.DbContext.SaveChangesAsync(); await db.DbContext.SaveChangesAsync();
} }
public async Task DeleteAdminNote(int id, Guid deletedBy, DateTime deletedAt) public async Task DeleteAdminNote(int id, Guid deletedBy, DateTimeOffset deletedAt)
{ {
await using var db = await GetDb(); await using var db = await GetDb();
@@ -1186,12 +1294,12 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
note.Deleted = true; note.Deleted = true;
note.DeletedById = deletedBy; note.DeletedById = deletedBy;
note.DeletedAt = deletedAt; note.DeletedAt = deletedAt.UtcDateTime;
await db.DbContext.SaveChangesAsync(); await db.DbContext.SaveChangesAsync();
} }
public async Task DeleteAdminWatchlist(int id, Guid deletedBy, DateTime deletedAt) public async Task DeleteAdminWatchlist(int id, Guid deletedBy, DateTimeOffset deletedAt)
{ {
await using var db = await GetDb(); await using var db = await GetDb();
@@ -1199,12 +1307,12 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
watchlist.Deleted = true; watchlist.Deleted = true;
watchlist.DeletedById = deletedBy; watchlist.DeletedById = deletedBy;
watchlist.DeletedAt = deletedAt; watchlist.DeletedAt = deletedAt.UtcDateTime;
await db.DbContext.SaveChangesAsync(); await db.DbContext.SaveChangesAsync();
} }
public async Task DeleteAdminMessage(int id, Guid deletedBy, DateTime deletedAt) public async Task DeleteAdminMessage(int id, Guid deletedBy, DateTimeOffset deletedAt)
{ {
await using var db = await GetDb(); await using var db = await GetDb();
@@ -1212,12 +1320,12 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
message.Deleted = true; message.Deleted = true;
message.DeletedById = deletedBy; message.DeletedById = deletedBy;
message.DeletedAt = deletedAt; message.DeletedAt = deletedAt.UtcDateTime;
await db.DbContext.SaveChangesAsync(); await db.DbContext.SaveChangesAsync();
} }
public async Task HideServerBanFromNotes(int id, Guid deletedBy, DateTime deletedAt) public async Task HideServerBanFromNotes(int id, Guid deletedBy, DateTimeOffset deletedAt)
{ {
await using var db = await GetDb(); await using var db = await GetDb();
@@ -1225,12 +1333,12 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
ban.Hidden = true; ban.Hidden = true;
ban.LastEditedById = deletedBy; ban.LastEditedById = deletedBy;
ban.LastEditedAt = deletedAt; ban.LastEditedAt = deletedAt.UtcDateTime;
await db.DbContext.SaveChangesAsync(); await db.DbContext.SaveChangesAsync();
} }
public async Task HideServerRoleBanFromNotes(int id, Guid deletedBy, DateTime deletedAt) public async Task HideServerRoleBanFromNotes(int id, Guid deletedBy, DateTimeOffset deletedAt)
{ {
await using var db = await GetDb(); await using var db = await GetDb();
@@ -1238,40 +1346,40 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
roleBan.Hidden = true; roleBan.Hidden = true;
roleBan.LastEditedById = deletedBy; roleBan.LastEditedById = deletedBy;
roleBan.LastEditedAt = deletedAt; roleBan.LastEditedAt = deletedAt.UtcDateTime;
await db.DbContext.SaveChangesAsync(); await db.DbContext.SaveChangesAsync();
} }
public async Task<List<IAdminRemarksCommon>> GetVisibleAdminRemarks(Guid player) public async Task<List<IAdminRemarksRecord>> GetVisibleAdminRemarks(Guid player)
{ {
await using var db = await GetDb(); await using var db = await GetDb();
List<IAdminRemarksCommon> notesCol = new(); List<IAdminRemarksRecord> notesCol = new();
notesCol.AddRange( notesCol.AddRange(
await (from note in db.DbContext.AdminNotes (await (from note in db.DbContext.AdminNotes
where note.PlayerUserId == player && where note.PlayerUserId == player &&
!note.Secret && !note.Secret &&
!note.Deleted && !note.Deleted &&
(note.ExpirationTime == null || DateTime.UtcNow < note.ExpirationTime) (note.ExpirationTime == null || DateTime.UtcNow < note.ExpirationTime)
select note) select note)
.Include(note => note.Round) .Include(note => note.Round)
.ThenInclude(r => r!.Server) .ThenInclude(r => r!.Server)
.Include(note => note.CreatedBy) .Include(note => note.CreatedBy)
.Include(note => note.Player) .Include(note => note.Player)
.ToListAsync()); .ToListAsync()).Select(MakeAdminNoteRecord));
notesCol.AddRange(await GetMessagesImpl(db, player)); notesCol.AddRange(await GetMessagesImpl(db, player));
return notesCol; return notesCol;
} }
public async Task<List<AdminWatchlist>> GetActiveWatchlists(Guid player) public async Task<List<AdminWatchlistRecord>> GetActiveWatchlists(Guid player)
{ {
await using var db = await GetDb(); await using var db = await GetDb();
return await GetActiveWatchlistsImpl(db, player); return await GetActiveWatchlistsImpl(db, player);
} }
protected async Task<List<AdminWatchlist>> GetActiveWatchlistsImpl(DbGuard db, Guid player) protected async Task<List<AdminWatchlistRecord>> GetActiveWatchlistsImpl(DbGuard db, Guid player)
{ {
return await (from watchlist in db.DbContext.AdminWatchlists var entities = await (from watchlist in db.DbContext.AdminWatchlists
where watchlist.PlayerUserId == player && where watchlist.PlayerUserId == player &&
!watchlist.Deleted && !watchlist.Deleted &&
(watchlist.ExpirationTime == null || DateTime.UtcNow < watchlist.ExpirationTime) (watchlist.ExpirationTime == null || DateTime.UtcNow < watchlist.ExpirationTime)
@@ -1282,27 +1390,34 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
.Include(note => note.LastEditedBy) .Include(note => note.LastEditedBy)
.Include(note => note.Player) .Include(note => note.Player)
.ToListAsync(); .ToListAsync();
return entities.Select(MakeAdminWatchlistRecord).ToList();
} }
public async Task<List<AdminMessage>> GetMessages(Guid player) private AdminWatchlistRecord MakeAdminWatchlistRecord(AdminWatchlist entity)
{
return new AdminWatchlistRecord(entity.Id, MakeRoundRecord(entity.Round), MakePlayerRecord(entity.Player), entity.PlaytimeAtNote, entity.Message, MakePlayerRecord(entity.CreatedBy), NormalizeDatabaseTime(entity.CreatedAt), MakePlayerRecord(entity.LastEditedBy), NormalizeDatabaseTime(entity.LastEditedAt), NormalizeDatabaseTime(entity.ExpirationTime), entity.Deleted, MakePlayerRecord(entity.DeletedBy), NormalizeDatabaseTime(entity.DeletedAt));
}
public async Task<List<AdminMessageRecord>> GetMessages(Guid player)
{ {
await using var db = await GetDb(); await using var db = await GetDb();
return await GetMessagesImpl(db, player); return await GetMessagesImpl(db, player);
} }
protected async Task<List<AdminMessage>> GetMessagesImpl(DbGuard db, Guid player) protected async Task<List<AdminMessageRecord>> GetMessagesImpl(DbGuard db, Guid player)
{ {
return await (from message in db.DbContext.AdminMessages var entities = await (from message in db.DbContext.AdminMessages
where message.PlayerUserId == player && where message.PlayerUserId == player && !message.Deleted &&
!message.Deleted && (message.ExpirationTime == null || DateTime.UtcNow < message.ExpirationTime)
(message.ExpirationTime == null || DateTime.UtcNow < message.ExpirationTime) select message).Include(note => note.Round)
select message) .ThenInclude(r => r!.Server)
.Include(note => note.Round) .Include(note => note.CreatedBy)
.ThenInclude(r => r!.Server) .Include(note => note.LastEditedBy)
.Include(note => note.CreatedBy) .Include(note => note.Player)
.Include(note => note.LastEditedBy) .ToListAsync();
.Include(note => note.Player)
.ToListAsync(); return entities.Select(MakeAdminMessageRecord).ToList();
} }
public async Task MarkMessageAsSeen(int id) public async Task MarkMessageAsSeen(int id)
@@ -1314,7 +1429,7 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
} }
// These two are here because they get converted into notes later // These two are here because they get converted into notes later
protected async Task<List<ServerBanNote>> GetServerBansAsNotesForUser(DbGuard db, Guid user) protected async Task<List<ServerBanNoteRecord>> GetServerBansAsNotesForUser(DbGuard db, Guid user)
{ {
// You can't group queries, as player will not always exist. When it doesn't, the // You can't group queries, as player will not always exist. When it doesn't, the
// whole query returns nothing // whole query returns nothing
@@ -1329,17 +1444,27 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
.Include(ban => ban.Unban) .Include(ban => ban.Unban)
.ToArrayAsync(); .ToArrayAsync();
var banNotes = new List<ServerBanNote>(); var banNotes = new List<ServerBanNoteRecord>();
foreach (var ban in bans) foreach (var ban in bans)
{ {
var banNote = new ServerBanNote(ban.Id, ban.RoundId, ban.Round, ban.PlayerUserId, player, var banNote = new ServerBanNoteRecord(
ban.PlaytimeAtNote, ban.Reason, ban.Severity, ban.CreatedBy, ban.BanTime, ban.Id,
ban.LastEditedBy, ban.LastEditedAt, ban.ExpirationTime, ban.Hidden, MakeRoundRecord(ban.Round),
ban.Unban?.UnbanningAdmin == null MakePlayerRecord(player),
ban.PlaytimeAtNote,
ban.Reason,
ban.Severity,
MakePlayerRecord(ban.CreatedBy),
NormalizeDatabaseTime(ban.BanTime),
MakePlayerRecord(ban.LastEditedBy),
NormalizeDatabaseTime(ban.LastEditedAt),
NormalizeDatabaseTime(ban.ExpirationTime),
ban.Hidden,
MakePlayerRecord(ban.Unban?.UnbanningAdmin == null
? null ? null
: await db.DbContext.Player.SingleOrDefaultAsync( : await db.DbContext.Player.SingleOrDefaultAsync(
p => p.UserId == ban.Unban.UnbanningAdmin.Value), p => p.UserId == ban.Unban.UnbanningAdmin.Value)),
ban.Unban?.UnbanTime); NormalizeDatabaseTime(ban.Unban?.UnbanTime));
banNotes.Add(banNote); banNotes.Add(banNote);
} }
@@ -1347,7 +1472,7 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
return banNotes; return banNotes;
} }
protected async Task<List<ServerRoleBanNote>> GetGroupedServerRoleBansAsNotesForUser(DbGuard db, Guid user) protected async Task<List<ServerRoleBanNoteRecord>> GetGroupedServerRoleBansAsNotesForUser(DbGuard db, Guid user)
{ {
// Server side query // Server side query
var bansQuery = await db.DbContext.RoleBan var bansQuery = await db.DbContext.RoleBan
@@ -1366,7 +1491,7 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
.Select(banGroup => banGroup) .Select(banGroup => banGroup)
.ToArray(); .ToArray();
List<ServerRoleBanNote> bans = new(); List<ServerRoleBanNoteRecord> bans = new();
var player = await db.DbContext.Player.SingleOrDefaultAsync(p => p.UserId == user); var player = await db.DbContext.Player.SingleOrDefaultAsync(p => p.UserId == user);
foreach (var banGroup in bansEnumerable) foreach (var banGroup in bansEnumerable)
{ {
@@ -1376,11 +1501,22 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
if (firstBan.Unban?.UnbanningAdmin is not null) if (firstBan.Unban?.UnbanningAdmin is not null)
unbanningAdmin = await db.DbContext.Player.SingleOrDefaultAsync(p => p.UserId == firstBan.Unban.UnbanningAdmin.Value); unbanningAdmin = await db.DbContext.Player.SingleOrDefaultAsync(p => p.UserId == firstBan.Unban.UnbanningAdmin.Value);
bans.Add(new ServerRoleBanNote(firstBan.Id, firstBan.RoundId, firstBan.Round, firstBan.PlayerUserId, bans.Add(new ServerRoleBanNoteRecord(
player, firstBan.PlaytimeAtNote, firstBan.Reason, firstBan.Severity, firstBan.CreatedBy, firstBan.Id,
firstBan.BanTime, firstBan.LastEditedBy, firstBan.LastEditedAt, firstBan.ExpirationTime, MakeRoundRecord(firstBan.Round),
firstBan.Hidden, banGroup.Select(ban => ban.RoleId.Replace(BanManager.JobPrefix, null)).ToArray(), MakePlayerRecord(player),
unbanningAdmin, firstBan.Unban?.UnbanTime)); firstBan.PlaytimeAtNote,
firstBan.Reason,
firstBan.Severity,
MakePlayerRecord(firstBan.CreatedBy),
NormalizeDatabaseTime(firstBan.BanTime),
MakePlayerRecord(firstBan.LastEditedBy),
NormalizeDatabaseTime(firstBan.LastEditedAt),
NormalizeDatabaseTime(firstBan.ExpirationTime),
firstBan.Hidden,
banGroup.Select(ban => ban.RoleId.Replace(BanManager.JobPrefix, null)).ToArray(),
MakePlayerRecord(unbanningAdmin),
NormalizeDatabaseTime(firstBan.Unban?.UnbanTime)));
} }
return bans; return bans;
@@ -1388,6 +1524,16 @@ INSERT INTO player_round (players_id, rounds_id) VALUES ({players[player]}, {id}
#endregion #endregion
// SQLite returns DateTime as Kind=Unspecified, Npgsql actually knows for sure it's Kind=Utc.
// Normalize DateTimes here so they're always Utc. Thanks.
protected abstract DateTime NormalizeDatabaseTime(DateTime time);
[return: NotNullIfNotNull(nameof(time))]
protected DateTime? NormalizeDatabaseTime(DateTime? time)
{
return time != null ? NormalizeDatabaseTime(time.Value) : time;
}
protected abstract Task<DbGuard> GetDb([CallerMemberName] string? name = null); protected abstract Task<DbGuard> GetDb([CallerMemberName] string? name = null);
protected void LogDbOp(string? name) protected void LogDbOp(string? name)

View File

@@ -92,9 +92,9 @@ namespace Content.Server.Database
int id, int id,
string reason, string reason,
NoteSeverity severity, NoteSeverity severity,
DateTime? expiration, DateTimeOffset? expiration,
Guid editedBy, Guid editedBy,
DateTime editedAt); DateTimeOffset editedAt);
/// <summary> /// <summary>
/// Update ban exemption information for a player. /// Update ban exemption information for a player.
@@ -146,9 +146,9 @@ namespace Content.Server.Database
int id, int id,
string reason, string reason,
NoteSeverity severity, NoteSeverity severity,
DateTime? expiration, DateTimeOffset? expiration,
Guid editedBy, Guid editedBy,
DateTime editedAt); DateTimeOffset editedAt);
#endregion #endregion
#region Playtime #region Playtime
@@ -239,7 +239,7 @@ namespace Content.Server.Database
#region Uploaded Resources Logs #region Uploaded Resources Logs
Task AddUploadedResourceLogAsync(NetUserId user, DateTime date, string path, byte[] data); Task AddUploadedResourceLogAsync(NetUserId user, DateTimeOffset date, string path, byte[] data);
Task PurgeUploadedResourceLogAsync(int days); Task PurgeUploadedResourceLogAsync(int days);
@@ -247,33 +247,33 @@ namespace Content.Server.Database
#region Rules #region Rules
Task<DateTime?> GetLastReadRules(NetUserId player); Task<DateTimeOffset?> GetLastReadRules(NetUserId player);
Task SetLastReadRules(NetUserId player, DateTime time); Task SetLastReadRules(NetUserId player, DateTimeOffset time);
#endregion #endregion
#region Admin Notes #region Admin Notes
Task<int> AddAdminNote(int? roundId, Guid player, TimeSpan playtimeAtNote, string message, NoteSeverity severity, bool secret, Guid createdBy, DateTime createdAt, DateTime? expiryTime); Task<int> AddAdminNote(int? roundId, Guid player, TimeSpan playtimeAtNote, string message, NoteSeverity severity, bool secret, Guid createdBy, DateTimeOffset createdAt, DateTimeOffset? expiryTime);
Task<int> AddAdminWatchlist(int? roundId, Guid player, TimeSpan playtimeAtNote, string message, Guid createdBy, DateTime createdAt, DateTime? expiryTime); Task<int> AddAdminWatchlist(int? roundId, Guid player, TimeSpan playtimeAtNote, string message, Guid createdBy, DateTimeOffset createdAt, DateTimeOffset? expiryTime);
Task<int> AddAdminMessage(int? roundId, Guid player, TimeSpan playtimeAtNote, string message, Guid createdBy, DateTime createdAt, DateTime? expiryTime); Task<int> AddAdminMessage(int? roundId, Guid player, TimeSpan playtimeAtNote, string message, Guid createdBy, DateTimeOffset createdAt, DateTimeOffset? expiryTime);
Task<AdminNote?> GetAdminNote(int id); Task<AdminNoteRecord?> GetAdminNote(int id);
Task<AdminWatchlist?> GetAdminWatchlist(int id); Task<AdminWatchlistRecord?> GetAdminWatchlist(int id);
Task<AdminMessage?> GetAdminMessage(int id); Task<AdminMessageRecord?> GetAdminMessage(int id);
Task<ServerBanNote?> GetServerBanAsNoteAsync(int id); Task<ServerBanNoteRecord?> GetServerBanAsNoteAsync(int id);
Task<ServerRoleBanNote?> GetServerRoleBanAsNoteAsync(int id); Task<ServerRoleBanNoteRecord?> GetServerRoleBanAsNoteAsync(int id);
Task<List<IAdminRemarksCommon>> GetAllAdminRemarks(Guid player); Task<List<IAdminRemarksRecord>> GetAllAdminRemarks(Guid player);
Task<List<IAdminRemarksCommon>> GetVisibleAdminNotes(Guid player); Task<List<IAdminRemarksRecord>> GetVisibleAdminNotes(Guid player);
Task<List<AdminWatchlist>> GetActiveWatchlists(Guid player); Task<List<AdminWatchlistRecord>> GetActiveWatchlists(Guid player);
Task<List<AdminMessage>> GetMessages(Guid player); Task<List<AdminMessageRecord>> GetMessages(Guid player);
Task EditAdminNote(int id, string message, NoteSeverity severity, bool secret, Guid editedBy, DateTime editedAt, DateTime? expiryTime); Task EditAdminNote(int id, string message, NoteSeverity severity, bool secret, Guid editedBy, DateTimeOffset editedAt, DateTimeOffset? expiryTime);
Task EditAdminWatchlist(int id, string message, Guid editedBy, DateTime editedAt, DateTime? expiryTime); Task EditAdminWatchlist(int id, string message, Guid editedBy, DateTimeOffset editedAt, DateTimeOffset? expiryTime);
Task EditAdminMessage(int id, string message, Guid editedBy, DateTime editedAt, DateTime? expiryTime); Task EditAdminMessage(int id, string message, Guid editedBy, DateTimeOffset editedAt, DateTimeOffset? expiryTime);
Task DeleteAdminNote(int id, Guid deletedBy, DateTime deletedAt); Task DeleteAdminNote(int id, Guid deletedBy, DateTimeOffset deletedAt);
Task DeleteAdminWatchlist(int id, Guid deletedBy, DateTime deletedAt); Task DeleteAdminWatchlist(int id, Guid deletedBy, DateTimeOffset deletedAt);
Task DeleteAdminMessage(int id, Guid deletedBy, DateTime deletedAt); Task DeleteAdminMessage(int id, Guid deletedBy, DateTimeOffset deletedAt);
Task HideServerBanFromNotes(int id, Guid deletedBy, DateTime deletedAt); Task HideServerBanFromNotes(int id, Guid deletedBy, DateTimeOffset deletedAt);
Task HideServerRoleBanFromNotes(int id, Guid deletedBy, DateTime deletedAt); Task HideServerRoleBanFromNotes(int id, Guid deletedBy, DateTimeOffset deletedAt);
Task MarkMessageAsSeen(int id); Task MarkMessageAsSeen(int id);
#endregion #endregion
@@ -423,7 +423,7 @@ namespace Content.Server.Database
return RunDbCommand(() => _db.AddServerUnbanAsync(serverUnban)); return RunDbCommand(() => _db.AddServerUnbanAsync(serverUnban));
} }
public Task EditServerBan(int id, string reason, NoteSeverity severity, DateTime? expiration, Guid editedBy, DateTime editedAt) public Task EditServerBan(int id, string reason, NoteSeverity severity, DateTimeOffset? expiration, Guid editedBy, DateTimeOffset editedAt)
{ {
DbWriteOpsMetric.Inc(); DbWriteOpsMetric.Inc();
return RunDbCommand(() => _db.EditServerBan(id, reason, severity, expiration, editedBy, editedAt)); return RunDbCommand(() => _db.EditServerBan(id, reason, severity, expiration, editedBy, editedAt));
@@ -470,7 +470,7 @@ namespace Content.Server.Database
return RunDbCommand(() => _db.AddServerRoleUnbanAsync(serverRoleUnban)); return RunDbCommand(() => _db.AddServerRoleUnbanAsync(serverRoleUnban));
} }
public Task EditServerRoleBan(int id, string reason, NoteSeverity severity, DateTime? expiration, Guid editedBy, DateTime editedAt) public Task EditServerRoleBan(int id, string reason, NoteSeverity severity, DateTimeOffset? expiration, Guid editedBy, DateTimeOffset editedAt)
{ {
DbWriteOpsMetric.Inc(); DbWriteOpsMetric.Inc();
return RunDbCommand(() => _db.EditServerRoleBan(id, reason, severity, expiration, editedBy, editedAt)); return RunDbCommand(() => _db.EditServerRoleBan(id, reason, severity, expiration, editedBy, editedAt));
@@ -665,7 +665,7 @@ namespace Content.Server.Database
return RunDbCommand(() => _db.RemoveFromWhitelistAsync(player)); return RunDbCommand(() => _db.RemoveFromWhitelistAsync(player));
} }
public Task AddUploadedResourceLogAsync(NetUserId user, DateTime date, string path, byte[] data) public Task AddUploadedResourceLogAsync(NetUserId user, DateTimeOffset date, string path, byte[] data)
{ {
DbWriteOpsMetric.Inc(); DbWriteOpsMetric.Inc();
return RunDbCommand(() => _db.AddUploadedResourceLogAsync(user, date, path, data)); return RunDbCommand(() => _db.AddUploadedResourceLogAsync(user, date, path, data));
@@ -677,19 +677,19 @@ namespace Content.Server.Database
return RunDbCommand(() => _db.PurgeUploadedResourceLogAsync(days)); return RunDbCommand(() => _db.PurgeUploadedResourceLogAsync(days));
} }
public Task<DateTime?> GetLastReadRules(NetUserId player) public Task<DateTimeOffset?> GetLastReadRules(NetUserId player)
{ {
DbReadOpsMetric.Inc(); DbReadOpsMetric.Inc();
return RunDbCommand(() => _db.GetLastReadRules(player)); return RunDbCommand(() => _db.GetLastReadRules(player));
} }
public Task SetLastReadRules(NetUserId player, DateTime time) public Task SetLastReadRules(NetUserId player, DateTimeOffset time)
{ {
DbWriteOpsMetric.Inc(); DbWriteOpsMetric.Inc();
return RunDbCommand(() => _db.SetLastReadRules(player, time)); return RunDbCommand(() => _db.SetLastReadRules(player, time));
} }
public Task<int> AddAdminNote(int? roundId, Guid player, TimeSpan playtimeAtNote, string message, NoteSeverity severity, bool secret, Guid createdBy, DateTime createdAt, DateTime? expiryTime) public Task<int> AddAdminNote(int? roundId, Guid player, TimeSpan playtimeAtNote, string message, NoteSeverity severity, bool secret, Guid createdBy, DateTimeOffset createdAt, DateTimeOffset? expiryTime)
{ {
DbWriteOpsMetric.Inc(); DbWriteOpsMetric.Inc();
var note = new AdminNote var note = new AdminNote
@@ -702,15 +702,15 @@ namespace Content.Server.Database
Message = message, Message = message,
Severity = severity, Severity = severity,
Secret = secret, Secret = secret,
CreatedAt = createdAt, CreatedAt = createdAt.UtcDateTime,
LastEditedAt = createdAt, LastEditedAt = createdAt.UtcDateTime,
ExpirationTime = expiryTime ExpirationTime = expiryTime?.UtcDateTime
}; };
return RunDbCommand(() => _db.AddAdminNote(note)); return RunDbCommand(() => _db.AddAdminNote(note));
} }
public Task<int> AddAdminWatchlist(int? roundId, Guid player, TimeSpan playtimeAtNote, string message, Guid createdBy, DateTime createdAt, DateTime? expiryTime) public Task<int> AddAdminWatchlist(int? roundId, Guid player, TimeSpan playtimeAtNote, string message, Guid createdBy, DateTimeOffset createdAt, DateTimeOffset? expiryTime)
{ {
DbWriteOpsMetric.Inc(); DbWriteOpsMetric.Inc();
var note = new AdminWatchlist var note = new AdminWatchlist
@@ -721,15 +721,15 @@ namespace Content.Server.Database
PlayerUserId = player, PlayerUserId = player,
PlaytimeAtNote = playtimeAtNote, PlaytimeAtNote = playtimeAtNote,
Message = message, Message = message,
CreatedAt = createdAt, CreatedAt = createdAt.UtcDateTime,
LastEditedAt = createdAt, LastEditedAt = createdAt.UtcDateTime,
ExpirationTime = expiryTime ExpirationTime = expiryTime?.UtcDateTime
}; };
return RunDbCommand(() => _db.AddAdminWatchlist(note)); return RunDbCommand(() => _db.AddAdminWatchlist(note));
} }
public Task<int> AddAdminMessage(int? roundId, Guid player, TimeSpan playtimeAtNote, string message, Guid createdBy, DateTime createdAt, DateTime? expiryTime) public Task<int> AddAdminMessage(int? roundId, Guid player, TimeSpan playtimeAtNote, string message, Guid createdBy, DateTimeOffset createdAt, DateTimeOffset? expiryTime)
{ {
DbWriteOpsMetric.Inc(); DbWriteOpsMetric.Inc();
var note = new AdminMessage var note = new AdminMessage
@@ -740,108 +740,108 @@ namespace Content.Server.Database
PlayerUserId = player, PlayerUserId = player,
PlaytimeAtNote = playtimeAtNote, PlaytimeAtNote = playtimeAtNote,
Message = message, Message = message,
CreatedAt = createdAt, CreatedAt = createdAt.UtcDateTime,
LastEditedAt = createdAt, LastEditedAt = createdAt.UtcDateTime,
ExpirationTime = expiryTime ExpirationTime = expiryTime?.UtcDateTime
}; };
return RunDbCommand(() => _db.AddAdminMessage(note)); return RunDbCommand(() => _db.AddAdminMessage(note));
} }
public Task<AdminNote?> GetAdminNote(int id) public Task<AdminNoteRecord?> GetAdminNote(int id)
{ {
DbReadOpsMetric.Inc(); DbReadOpsMetric.Inc();
return RunDbCommand(() => _db.GetAdminNote(id)); return RunDbCommand(() => _db.GetAdminNote(id));
} }
public Task<AdminWatchlist?> GetAdminWatchlist(int id) public Task<AdminWatchlistRecord?> GetAdminWatchlist(int id)
{ {
DbReadOpsMetric.Inc(); DbReadOpsMetric.Inc();
return RunDbCommand(() => _db.GetAdminWatchlist(id)); return RunDbCommand(() => _db.GetAdminWatchlist(id));
} }
public Task<AdminMessage?> GetAdminMessage(int id) public Task<AdminMessageRecord?> GetAdminMessage(int id)
{ {
DbReadOpsMetric.Inc(); DbReadOpsMetric.Inc();
return RunDbCommand(() => _db.GetAdminMessage(id)); return RunDbCommand(() => _db.GetAdminMessage(id));
} }
public Task<ServerBanNote?> GetServerBanAsNoteAsync(int id) public Task<ServerBanNoteRecord?> GetServerBanAsNoteAsync(int id)
{ {
DbReadOpsMetric.Inc(); DbReadOpsMetric.Inc();
return RunDbCommand(() => _db.GetServerBanAsNoteAsync(id)); return RunDbCommand(() => _db.GetServerBanAsNoteAsync(id));
} }
public Task<ServerRoleBanNote?> GetServerRoleBanAsNoteAsync(int id) public Task<ServerRoleBanNoteRecord?> GetServerRoleBanAsNoteAsync(int id)
{ {
DbReadOpsMetric.Inc(); DbReadOpsMetric.Inc();
return RunDbCommand(() => _db.GetServerRoleBanAsNoteAsync(id)); return RunDbCommand(() => _db.GetServerRoleBanAsNoteAsync(id));
} }
public Task<List<IAdminRemarksCommon>> GetAllAdminRemarks(Guid player) public Task<List<IAdminRemarksRecord>> GetAllAdminRemarks(Guid player)
{ {
DbReadOpsMetric.Inc(); DbReadOpsMetric.Inc();
return RunDbCommand(() => _db.GetAllAdminRemarks(player)); return RunDbCommand(() => _db.GetAllAdminRemarks(player));
} }
public Task<List<IAdminRemarksCommon>> GetVisibleAdminNotes(Guid player) public Task<List<IAdminRemarksRecord>> GetVisibleAdminNotes(Guid player)
{ {
DbReadOpsMetric.Inc(); DbReadOpsMetric.Inc();
return RunDbCommand(() => _db.GetVisibleAdminRemarks(player)); return RunDbCommand(() => _db.GetVisibleAdminRemarks(player));
} }
public Task<List<AdminWatchlist>> GetActiveWatchlists(Guid player) public Task<List<AdminWatchlistRecord>> GetActiveWatchlists(Guid player)
{ {
DbReadOpsMetric.Inc(); DbReadOpsMetric.Inc();
return RunDbCommand(() => _db.GetActiveWatchlists(player)); return RunDbCommand(() => _db.GetActiveWatchlists(player));
} }
public Task<List<AdminMessage>> GetMessages(Guid player) public Task<List<AdminMessageRecord>> GetMessages(Guid player)
{ {
DbReadOpsMetric.Inc(); DbReadOpsMetric.Inc();
return RunDbCommand(() => _db.GetMessages(player)); return RunDbCommand(() => _db.GetMessages(player));
} }
public Task EditAdminNote(int id, string message, NoteSeverity severity, bool secret, Guid editedBy, DateTime editedAt, DateTime? expiryTime) public Task EditAdminNote(int id, string message, NoteSeverity severity, bool secret, Guid editedBy, DateTimeOffset editedAt, DateTimeOffset? expiryTime)
{ {
DbWriteOpsMetric.Inc(); DbWriteOpsMetric.Inc();
return RunDbCommand(() => _db.EditAdminNote(id, message, severity, secret, editedBy, editedAt, expiryTime)); return RunDbCommand(() => _db.EditAdminNote(id, message, severity, secret, editedBy, editedAt, expiryTime));
} }
public Task EditAdminWatchlist(int id, string message, Guid editedBy, DateTime editedAt, DateTime? expiryTime) public Task EditAdminWatchlist(int id, string message, Guid editedBy, DateTimeOffset editedAt, DateTimeOffset? expiryTime)
{ {
DbWriteOpsMetric.Inc(); DbWriteOpsMetric.Inc();
return RunDbCommand(() => _db.EditAdminWatchlist(id, message, editedBy, editedAt, expiryTime)); return RunDbCommand(() => _db.EditAdminWatchlist(id, message, editedBy, editedAt, expiryTime));
} }
public Task EditAdminMessage(int id, string message, Guid editedBy, DateTime editedAt, DateTime? expiryTime) public Task EditAdminMessage(int id, string message, Guid editedBy, DateTimeOffset editedAt, DateTimeOffset? expiryTime)
{ {
DbWriteOpsMetric.Inc(); DbWriteOpsMetric.Inc();
return RunDbCommand(() => _db.EditAdminMessage(id, message, editedBy, editedAt, expiryTime)); return RunDbCommand(() => _db.EditAdminMessage(id, message, editedBy, editedAt, expiryTime));
} }
public Task DeleteAdminNote(int id, Guid deletedBy, DateTime deletedAt) public Task DeleteAdminNote(int id, Guid deletedBy, DateTimeOffset deletedAt)
{ {
DbWriteOpsMetric.Inc(); DbWriteOpsMetric.Inc();
return RunDbCommand(() => _db.DeleteAdminNote(id, deletedBy, deletedAt)); return RunDbCommand(() => _db.DeleteAdminNote(id, deletedBy, deletedAt));
} }
public Task DeleteAdminWatchlist(int id, Guid deletedBy, DateTime deletedAt) public Task DeleteAdminWatchlist(int id, Guid deletedBy, DateTimeOffset deletedAt)
{ {
DbWriteOpsMetric.Inc(); DbWriteOpsMetric.Inc();
return RunDbCommand(() => _db.DeleteAdminWatchlist(id, deletedBy, deletedAt)); return RunDbCommand(() => _db.DeleteAdminWatchlist(id, deletedBy, deletedAt));
} }
public Task DeleteAdminMessage(int id, Guid deletedBy, DateTime deletedAt) public Task DeleteAdminMessage(int id, Guid deletedBy, DateTimeOffset deletedAt)
{ {
DbWriteOpsMetric.Inc(); DbWriteOpsMetric.Inc();
return RunDbCommand(() => _db.DeleteAdminMessage(id, deletedBy, deletedAt)); return RunDbCommand(() => _db.DeleteAdminMessage(id, deletedBy, deletedAt));
} }
public Task HideServerBanFromNotes(int id, Guid deletedBy, DateTime deletedAt) public Task HideServerBanFromNotes(int id, Guid deletedBy, DateTimeOffset deletedAt)
{ {
DbWriteOpsMetric.Inc(); DbWriteOpsMetric.Inc();
return RunDbCommand(() => _db.HideServerBanFromNotes(id, deletedBy, deletedAt)); return RunDbCommand(() => _db.HideServerBanFromNotes(id, deletedBy, deletedAt));
} }
public Task HideServerRoleBanFromNotes(int id, Guid deletedBy, DateTime deletedAt) public Task HideServerRoleBanFromNotes(int id, Guid deletedBy, DateTimeOffset deletedAt)
{ {
DbWriteOpsMetric.Inc(); DbWriteOpsMetric.Inc();
return RunDbCommand(() => _db.HideServerRoleBanFromNotes(id, deletedBy, deletedAt)); return RunDbCommand(() => _db.HideServerRoleBanFromNotes(id, deletedBy, deletedAt));

View File

@@ -162,7 +162,7 @@ namespace Content.Server.Database
if (!includeUnbanned) if (!includeUnbanned)
{ {
query = query.Where(p => query = query.Where(p =>
p.Unban == null && (p.ExpirationTime == null || p.ExpirationTime.Value > DateTime.Now)); p.Unban == null && (p.ExpirationTime == null || p.ExpirationTime.Value > DateTime.UtcNow));
} }
if (exemptFlags is { } exempt) if (exemptFlags is { } exempt)
@@ -354,7 +354,7 @@ namespace Content.Server.Database
if (!includeUnbanned) if (!includeUnbanned)
{ {
query = query?.Where(p => query = query?.Where(p =>
p.Unban == null && (p.ExpirationTime == null || p.ExpirationTime.Value > DateTime.Now)); p.Unban == null && (p.ExpirationTime == null || p.ExpirationTime.Value > DateTime.UtcNow));
} }
query = query!.Distinct(); query = query!.Distinct();
@@ -457,17 +457,6 @@ namespace Content.Server.Database
} }
#endregion #endregion
protected override PlayerRecord MakePlayerRecord(Player record)
{
return new PlayerRecord(
new NetUserId(record.UserId),
new DateTimeOffset(record.FirstSeenTime),
record.LastSeenUserName,
new DateTimeOffset(record.LastSeenTime),
record.LastSeenAddress,
record.LastSeenHWId?.ToImmutableArray());
}
public override async Task<int> AddConnectionLogAsync( public override async Task<int> AddConnectionLogAsync(
NetUserId userId, NetUserId userId,
string userName, string userName,
@@ -532,6 +521,12 @@ WHERE to_tsvector('english'::regconfig, a.message) @@ websearch_to_tsquery('engl
return db.AdminLog; return db.AdminLog;
} }
protected override DateTime NormalizeDatabaseTime(DateTime time)
{
DebugTools.Assert(time.Kind == DateTimeKind.Utc);
return time;
}
private async Task<DbGuardImpl> GetDbImpl([CallerMemberName] string? name = null) private async Task<DbGuardImpl> GetDbImpl([CallerMemberName] string? name = null)
{ {
LogDbOp(name); LogDbOp(name);

View File

@@ -12,6 +12,7 @@ using Content.Shared.CCVar;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Robust.Shared.Configuration; using Robust.Shared.Configuration;
using Robust.Shared.Network; using Robust.Shared.Network;
using Robust.Shared.Utility;
namespace Content.Server.Database namespace Content.Server.Database
{ {
@@ -350,17 +351,6 @@ namespace Content.Server.Database
} }
#endregion #endregion
protected override PlayerRecord MakePlayerRecord(Player record)
{
return new PlayerRecord(
new NetUserId(record.UserId),
new DateTimeOffset(record.FirstSeenTime, TimeSpan.Zero),
record.LastSeenUserName,
new DateTimeOffset(record.LastSeenTime, TimeSpan.Zero),
record.LastSeenAddress,
record.LastSeenHWId?.ToImmutableArray());
}
private static ServerBanDef? ConvertBan(ServerBan? ban) private static ServerBanDef? ConvertBan(ServerBan? ban)
{ {
if (ban == null) if (ban == null)
@@ -546,6 +536,12 @@ namespace Content.Server.Database
return await base.AddAdminMessage(message); return await base.AddAdminMessage(message);
} }
protected override DateTime NormalizeDatabaseTime(DateTime time)
{
DebugTools.Assert(time.Kind == DateTimeKind.Unspecified);
return DateTime.SpecifyKind(time, DateTimeKind.Utc);
}
private async Task<DbGuardImpl> GetDbImpl([CallerMemberName] string? name = null) private async Task<DbGuardImpl> GetDbImpl([CallerMemberName] string? name = null)
{ {
LogDbOp(name); LogDbOp(name);

View File

@@ -1,14 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Content.Shared.Database;
namespace Content.Server.Database
{
public record ServerRoleBanNote(int Id, int? RoundId, Round? Round, Guid? PlayerUserId, Player? Player,
TimeSpan PlaytimeAtNote, string Message, NoteSeverity Severity, Player? CreatedBy, DateTime CreatedAt,
Player? LastEditedBy, DateTime? LastEditedAt, DateTime? ExpirationTime, bool Deleted, string[] Roles,
Player? UnbanningAdmin, DateTime? UnbanTime) : IAdminRemarksCommon;
}

View File

@@ -1,4 +1,5 @@
using Content.Shared.Database; using Content.Shared.Database;
using Robust.Shared.Network;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
namespace Content.Shared.Administration.Notes; namespace Content.Shared.Administration.Notes;
@@ -6,7 +7,7 @@ namespace Content.Shared.Administration.Notes;
[Serializable, NetSerializable] [Serializable, NetSerializable]
public sealed record SharedAdminNote( public sealed record SharedAdminNote(
int Id, // Id of note, message, watchlist, ban or role ban. Should be paired with NoteType to uniquely identify a shared admin note. int Id, // Id of note, message, watchlist, ban or role ban. Should be paired with NoteType to uniquely identify a shared admin note.
Guid Player, // Notes player NetUserId Player, // Notes player
int? Round, // Which round was it added in? int? Round, // Which round was it added in?
string? ServerName, // Which server was this added on? string? ServerName, // Which server was this added on?
TimeSpan PlaytimeAtNote, // Playtime at the time of getting the note TimeSpan PlaytimeAtNote, // Playtime at the time of getting the note

View File

@@ -794,7 +794,7 @@ namespace Content.Shared.CCVar
/// Default severity for server bans /// Default severity for server bans
/// </summary> /// </summary>
public static readonly CVarDef<string> ServerBanDefaultSeverity = public static readonly CVarDef<string> ServerBanDefaultSeverity =
CVarDef.Create("admin.server_ban_default_severity", "high", CVar.ARCHIVE | CVar.SERVER); CVarDef.Create("admin.server_ban_default_severity", "High", CVar.ARCHIVE | CVar.SERVER);
/// <summary> /// <summary>
/// Minimum explosion intensity to create an admin alert message. -1 to disable the alert. /// Minimum explosion intensity to create an admin alert message. -1 to disable the alert.