Skip to content

Commit

Permalink
Fix admin notes and database time nonsense. (#25280)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
PJB3005 authored Feb 20, 2024
1 parent 2907e84 commit 2e6eaa4
Show file tree
Hide file tree
Showing 19 changed files with 501 additions and 326 deletions.
6 changes: 3 additions & 3 deletions Content.Client/Administration/UI/Notes/AdminNotesLine.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ private void Refresh()
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";
RoundLabel.Text = Note.Round == null ? "Unknown round" : "Round " + Note.Round;
AdminLabel.Text = Note.CreatedByName;
Expand All @@ -91,7 +91,7 @@ private void Refresh()
if (Note.ExpiryTime.Value > DateTime.UtcNow)
{
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")));
ExpiresLabel.Modulate = Color.FromHex("#86DC3D");
}
Expand All @@ -104,7 +104,7 @@ private void Refresh()

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;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@ public AdminNotesLinePopup(SharedAdminNote note, string playerName, bool showDel
? Loc.GetString("admin-notes-round-id-unknown")
: Loc.GetString("admin-notes-round-id", ("id", note.Round));
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));
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
? 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);

if (note.NoteType is NoteType.ServerBan or NoteType.RoleBan)
Expand Down
6 changes: 3 additions & 3 deletions Content.Client/Administration/UI/Notes/NoteEdit.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ public NoteEdit(SharedAdminNote? note, string playerName, bool canCreate, bool c
{
PermanentCheckBox.Pressed = false;
UpdatePermanentCheckboxFields();
ExpiryLineEdit.Text = ExpiryTime.Value.ToString("yyyy-MM-dd HH:mm:ss");
ExpiryLineEdit.Text = ExpiryTime.Value.ToLocalTime().ToString("yyyy-MM-dd HH:mm:ss");
}
}

Expand Down Expand Up @@ -173,7 +173,7 @@ private void UpdatePermanentCheckboxFields()
ExpiryLabel.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 _)
Expand Down Expand Up @@ -269,7 +269,7 @@ private bool ParseExpiryTime()
return false;
}

ExpiryTime = result;
ExpiryTime = result.ToUniversalTime();
ExpiryLineEdit.ModulateSelfOverride = null;
return true;
}
Expand Down
31 changes: 3 additions & 28 deletions Content.Server.Database/Model.cs
Original file line number Diff line number Diff line change
Expand Up @@ -874,33 +874,8 @@ public sealed class UploadedResourceLog
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))]
public class AdminNote : IAdminRemarksCommon
public class AdminNote
{
[Required, Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; }

Expand Down Expand Up @@ -934,7 +909,7 @@ public class AdminNote : IAdminRemarksCommon
}

[Index(nameof(PlayerUserId))]
public class AdminWatchlist : IAdminRemarksCommon
public class AdminWatchlist
{
[Required, Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; }

Expand Down Expand Up @@ -965,7 +940,7 @@ public class AdminWatchlist : IAdminRemarksCommon
}

[Index(nameof(PlayerUserId))]
public class AdminMessage : IAdminRemarksCommon
public class AdminMessage
{
[Required, Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; }

Expand Down
5 changes: 0 additions & 5 deletions Content.Server.Database/ModelPostgres.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,6 @@ namespace Content.Server.Database
{
public sealed class PostgresServerDbContext : ServerDbContext
{
static PostgresServerDbContext()
{
AppContext.SetSwitch("Npgsql.EnableLegacyTimestampBehavior", true);
}

public PostgresServerDbContext(DbContextOptions<PostgresServerDbContext> options) : base(options)
{
}
Expand Down
6 changes: 3 additions & 3 deletions Content.Server/Administration/Notes/AdminMessageEui.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public sealed class AdminMessageEui : BaseEui
[Dependency] private readonly IAdminNotesManager _notesMan = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
private readonly float _closeWait;
private AdminMessage? _message;
private AdminMessageRecord? _message;
private DateTime _startTime;

public AdminMessageEui()
Expand All @@ -22,7 +22,7 @@ public AdminMessageEui()
_closeWait = _cfg.GetCVar(CCVars.MessageWaitTime);
}

public void SetMessage(AdminMessage message)
public void SetMessage(AdminMessageRecord message)
{
_message = message;
_startTime = DateTime.UtcNow;
Expand All @@ -37,7 +37,7 @@ public override EuiStateBase GetNewState()
_closeWait,
_message.Message,
_message.CreatedBy?.LastSeenUserName ?? "[System]",
_message.CreatedAt
_message.CreatedAt.UtcDateTime
);
}

Expand Down
28 changes: 14 additions & 14 deletions Content.Server/Administration/Notes/AdminNotesExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System.Diagnostics;
using Content.Server.Database;
using Content.Shared.Administration.Notes;
using Content.Shared.Database;
Expand All @@ -7,7 +6,7 @@ namespace Content.Server.Administration.Notes;

public static class AdminNotesExtensions
{
public static SharedAdminNote ToShared(this IAdminRemarksCommon note)
public static SharedAdminNote ToShared(this IAdminRemarksRecord note)
{
NoteSeverity? severity = null;
var secret = false;
Expand All @@ -18,26 +17,26 @@ public static SharedAdminNote ToShared(this IAdminRemarksCommon note)
bool? seen = null;
switch (note)
{
case AdminNote adminNote:
case AdminNoteRecord adminNote:
type = NoteType.Note;
severity = adminNote.Severity;
secret = adminNote.Secret;
break;
case AdminWatchlist:
case AdminWatchlistRecord:
type = NoteType.Watchlist;
secret = true;
break;
case AdminMessage adminMessage:
case AdminMessageRecord adminMessage:
type = NoteType.Message;
seen = adminMessage.Seen;
break;
case ServerBanNote ban:
case ServerBanNoteRecord ban:
type = NoteType.ServerBan;
severity = ban.Severity;
unbannedTime = ban.UnbanTime;
unbannedByName = ban.UnbanningAdmin?.LastSeenUserName ?? Loc.GetString("system-user");
break;
case ServerRoleBanNote roleBan:
case ServerRoleBanNoteRecord roleBan:
type = NoteType.RoleBan;
severity = roleBan.Severity;
bannedRoles = roleBan.Roles;
Expand All @@ -49,12 +48,13 @@ public static SharedAdminNote ToShared(this IAdminRemarksCommon note)
}

// There may be bans without a user, but why would we ever be converting them to shared notes?
if (note.PlayerUserId is null)
throw new ArgumentNullException(nameof(note.PlayerUserId), "Player user ID cannot be null for a note");
if (note.Player is null)
throw new ArgumentNullException(nameof(note), "Player user ID cannot be null for a note");

return new SharedAdminNote(
note.Id,
note.PlayerUserId.Value,
note.RoundId,
note.Player!.UserId,
note.Round?.Id,
note.Round?.Server.Name,
note.PlaytimeAtNote,
type,
Expand All @@ -63,9 +63,9 @@ public static SharedAdminNote ToShared(this IAdminRemarksCommon note)
secret,
note.CreatedBy?.LastSeenUserName ?? Loc.GetString("system-user"),
note.LastEditedBy?.LastSeenUserName ?? string.Empty,
note.CreatedAt,
note.LastEditedAt,
note.ExpirationTime,
note.CreatedAt.UtcDateTime,
note.LastEditedAt?.UtcDateTime,
note.ExpirationTime?.UtcDateTime,
bannedRoles,
unbannedTime,
unbannedByName,
Expand Down
12 changes: 6 additions & 6 deletions Content.Server/Administration/Notes/AdminNotesManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ public async Task AddAdminRemark(ICommonSession createdBy, Guid player, NoteType

var note = new SharedAdminNote(
noteId,
player,
(NetUserId) player,
roundId,
serverName,
playtime,
Expand Down Expand Up @@ -306,27 +306,27 @@ public async Task ModifyAdminRemark(int noteId, NoteType type, ICommonSession ed
NoteModified?.Invoke(newNote);
}

public async Task<List<IAdminRemarksCommon>> GetAllAdminRemarks(Guid player)
public async Task<List<IAdminRemarksRecord>> GetAllAdminRemarks(Guid 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))
{
return await _db.GetVisibleAdminNotes(player);
}
_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);
}

public async Task<List<AdminMessage>> GetNewMessages(Guid player)
public async Task<List<AdminMessageRecord>> GetNewMessages(Guid player)
{
return await _db.GetMessages(player);
}
Expand Down
8 changes: 4 additions & 4 deletions Content.Server/Administration/Notes/IAdminNotesManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,24 +26,24 @@ public interface IAdminNotesManager
/// </summary>
/// <param name="player">Desired player's <see cref="Guid"/></param>
/// <returns>ALL non-deleted notes, secret or not</returns>
Task<List<IAdminRemarksCommon>> GetAllAdminRemarks(Guid player);
Task<List<IAdminRemarksRecord>> GetAllAdminRemarks(Guid player);
/// <summary>
/// Queries the database and retrieves the notes a player should see
/// </summary>
/// <param name="player">Desired player's <see cref="Guid"/></param>
/// <returns>All player-visible notes</returns>
Task<List<IAdminRemarksCommon>> GetVisibleRemarks(Guid player);
Task<List<IAdminRemarksRecord>> GetVisibleRemarks(Guid player);
/// <summary>
/// Queries the database and retrieves watchlists that may have been placed on the player
/// </summary>
/// <param name="player">Desired player's <see cref="Guid"/></param>
/// <returns>Active watchlists</returns>
Task<List<AdminWatchlist>> GetActiveWatchlists(Guid player);
Task<List<AdminWatchlistRecord>> GetActiveWatchlists(Guid player);
/// <summary>
/// Queries the database and retrieves new messages a player has gotten
/// </summary>
/// <param name="player">Desired player's <see cref="Guid"/></param>
/// <returns>All unread messages</returns>
Task<List<AdminMessage>> GetNewMessages(Guid player);
Task<List<AdminMessageRecord>> GetNewMessages(Guid player);
Task MarkMessageAsSeen(int id);
}
Loading

0 comments on commit 2e6eaa4

Please sign in to comment.