Server names and admin log full-text search (#6327)

Co-authored-by: Julian Giebel <j.giebel@netrocks.info>
Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
This commit is contained in:
Julian Giebel
2022-03-13 18:36:48 +01:00
committed by GitHub
parent bd3fc84488
commit 414d226ec5
13 changed files with 2424 additions and 8 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,80 @@
using Microsoft.EntityFrameworkCore.Migrations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Content.Server.Database.Migrations.Postgres
{
public partial class ServerNameFts : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "server_id",
table: "round",
type: "integer",
nullable: false,
defaultValue: 0);
migrationBuilder.CreateTable(
name: "server",
columns: table => new
{
server_id = table.Column<int>(type: "integer", nullable: false)
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
name = table.Column<string>(type: "text", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_server", x => x.server_id);
});
migrationBuilder.InsertData(
"server",
new[] {"server_id", "name"},
new object[] { 0, "unknown" }
);
migrationBuilder.CreateIndex(
name: "IX_round_server_id",
table: "round",
column: "server_id");
migrationBuilder.CreateIndex(
name: "IX_admin_log_message",
table: "admin_log",
column: "message")
.Annotation("Npgsql:TsVectorConfig", "english");
migrationBuilder.AddForeignKey(
name: "FK_round_server_server_id",
table: "round",
column: "server_id",
principalTable: "server",
principalColumn: "server_id",
onDelete: ReferentialAction.Cascade);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_round_server_server_id",
table: "round");
migrationBuilder.DropTable(
name: "server");
migrationBuilder.DropIndex(
name: "IX_round_server_id",
table: "round");
migrationBuilder.DropIndex(
name: "IX_admin_log_message",
table: "admin_log");
migrationBuilder.DropColumn(
name: "server_id",
table: "round");
}
}
}

View File

@@ -120,6 +120,9 @@ namespace Content.Server.Database.Migrations.Postgres
b.HasKey("Id", "RoundId")
.HasName("PK_admin_log");
b.HasIndex("Message")
.HasAnnotation("Npgsql:TsVectorConfig", "english");
b.HasIndex("RoundId")
.HasDatabaseName("IX_admin_log_round_id");
@@ -559,12 +562,39 @@ namespace Content.Server.Database.Migrations.Postgres
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<int>("ServerId")
.HasColumnType("integer")
.HasColumnName("server_id");
b.HasKey("Id")
.HasName("PK_round");
b.HasIndex("ServerId")
.HasDatabaseName("IX_round_server_id");
b.ToTable("round", (string)null);
});
modelBuilder.Entity("Content.Server.Database.Server", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("server_id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("Name")
.IsRequired()
.HasColumnType("text")
.HasColumnName("name");
b.HasKey("Id")
.HasName("PK_server");
b.ToTable("server", (string)null);
});
modelBuilder.Entity("Content.Server.Database.ServerBan", b =>
{
b.Property<int>("Id")
@@ -908,6 +938,18 @@ namespace Content.Server.Database.Migrations.Postgres
b.Navigation("Preference");
});
modelBuilder.Entity("Content.Server.Database.Round", b =>
{
b.HasOne("Content.Server.Database.Server", "Server")
.WithMany("Rounds")
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("FK_round_server_server_id");
b.Navigation("Server");
});
modelBuilder.Entity("Content.Server.Database.ServerBanHit", b =>
{
b.HasOne("Content.Server.Database.ServerBan", "Ban")
@@ -1016,6 +1058,11 @@ namespace Content.Server.Database.Migrations.Postgres
b.Navigation("AdminLogs");
});
modelBuilder.Entity("Content.Server.Database.Server", b =>
{
b.Navigation("Rounds");
});
modelBuilder.Entity("Content.Server.Database.ServerBan", b =>
{
b.Navigation("BanHits");

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,69 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Content.Server.Database.Migrations.Sqlite
{
public partial class ServerNameFts : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "server_id",
table: "round",
type: "INTEGER",
nullable: false,
defaultValue: 0);
migrationBuilder.CreateTable(
name: "server",
columns: table => new
{
server_id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
name = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_server", x => x.server_id);
});
migrationBuilder.InsertData(
"server",
new[] {"server_id", "name"},
new object[] { 0, "unknown" }
);
migrationBuilder.CreateIndex(
name: "IX_round_server_id",
table: "round",
column: "server_id");
migrationBuilder.AddForeignKey(
name: "FK_round_server_server_id",
table: "round",
column: "server_id",
principalTable: "server",
principalColumn: "server_id",
onDelete: ReferentialAction.Cascade);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_round_server_server_id",
table: "round");
migrationBuilder.DropTable(
name: "server");
migrationBuilder.DropIndex(
name: "IX_round_server_id",
table: "round");
migrationBuilder.DropColumn(
name: "server_id",
table: "round");
}
}
}

View File

@@ -522,12 +522,37 @@ namespace Content.Server.Database.Migrations.Sqlite
.HasColumnType("INTEGER")
.HasColumnName("round_id");
b.Property<int>("ServerId")
.HasColumnType("INTEGER")
.HasColumnName("server_id");
b.HasKey("Id")
.HasName("PK_round");
b.HasIndex("ServerId")
.HasDatabaseName("IX_round_server_id");
b.ToTable("round", (string)null);
});
modelBuilder.Entity("Content.Server.Database.Server", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasColumnName("server_id");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT")
.HasColumnName("name");
b.HasKey("Id")
.HasName("PK_server");
b.ToTable("server", (string)null);
});
modelBuilder.Entity("Content.Server.Database.ServerBan", b =>
{
b.Property<int>("Id")
@@ -857,6 +882,18 @@ namespace Content.Server.Database.Migrations.Sqlite
b.Navigation("Preference");
});
modelBuilder.Entity("Content.Server.Database.Round", b =>
{
b.HasOne("Content.Server.Database.Server", "Server")
.WithMany("Rounds")
.HasForeignKey("ServerId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("FK_round_server_server_id");
b.Navigation("Server");
});
modelBuilder.Entity("Content.Server.Database.ServerBanHit", b =>
{
b.HasOne("Content.Server.Database.ServerBan", "Ban")
@@ -965,6 +1002,11 @@ namespace Content.Server.Database.Migrations.Sqlite
b.Navigation("AdminLogs");
});
modelBuilder.Entity("Content.Server.Database.Server", b =>
{
b.Navigation("Rounds");
});
modelBuilder.Entity("Content.Server.Database.ServerBan", b =>
{
b.Navigation("BanHits");

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Net;
using System.Text.Json;
using Content.Shared.Database;
@@ -22,6 +23,7 @@ namespace Content.Server.Database
public DbSet<Admin> Admin { get; set; } = null!;
public DbSet<AdminRank> AdminRank { get; set; } = null!;
public DbSet<Round> Round { get; set; } = null!;
public DbSet<Server> Server { get; set; } = null!;
public DbSet<AdminLog> AdminLog { get; set; } = null!;
public DbSet<AdminLogPlayer> AdminLogPlayer { get; set; } = null!;
public DbSet<Whitelist> Whitelist { get; set; } = null!;
@@ -138,6 +140,11 @@ namespace Content.Server.Database
modelBuilder.Entity<ConnectionLog>()
.HasIndex(p => p.UserId);
}
public virtual IQueryable<AdminLog> SearchLogs(IQueryable<AdminLog> query, string searchText)
{
return query.Where(log => EF.Functions.Like(log.Message, "%" + searchText + "%"));
}
}
public class Preference
@@ -295,6 +302,20 @@ namespace Content.Server.Database
public List<Player> Players { get; set; } = default!;
public List<AdminLog> AdminLogs { get; set; } = default!;
[ForeignKey("Server")] public int ServerId { get; set; }
public Server Server { get; set; } = default!;
}
public class Server
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; } = default!;
[InverseProperty(nameof(Round.Server))]
public List<Round> Rounds { get; set; } = default!;
}
[Index(nameof(Type))]

View File

@@ -1,8 +1,10 @@
using System;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage;
using NpgsqlTypes;
namespace Content.Server.Database
{
@@ -59,6 +61,10 @@ namespace Content.Server.Database
"NOT inet '::ffff:0.0.0.0/96' >>= address");
// ReSharper restore StringLiteralTypo
modelBuilder.Entity<AdminLog>()
.HasIndex(l => l.Message)
.IsTsVectorExpressionIndex("english");
foreach(var entity in modelBuilder.Model.GetEntityTypes())
{
foreach(var property in entity.GetProperties())
@@ -68,5 +74,10 @@ namespace Content.Server.Database
}
}
}
public override IQueryable<AdminLog> SearchLogs(IQueryable<AdminLog> query, string searchText)
{
return query.Where(log => EF.Functions.ToTsVector("english", log.Message).Matches(searchText));
}
}
}

View File

@@ -500,7 +500,7 @@ namespace Content.Server.Database
await db.DbContext.SaveChangesAsync(cancel);
}
public virtual async Task<int> AddNewRound(params Guid[] playerIds)
public virtual async Task<int> AddNewRound(Server server, params Guid[] playerIds)
{
await using var db = await GetDb();
@@ -510,7 +510,8 @@ namespace Content.Server.Database
var round = new Round
{
Players = players
Players = players,
ServerId = server.Id
};
db.DbContext.Round.Add(round);
@@ -574,6 +575,27 @@ namespace Content.Server.Database
#region Admin Logs
public async Task<Server> AddOrGetServer(string serverName)
{
await using var db = await GetDb();
var server = await db.DbContext.Server.Where(server => server.Name.Equals(serverName)).SingleOrDefaultAsync();
if (server != default)
{
return server;
}
server = new Server
{
Name = serverName
};
db.DbContext.Server.Add(server);
await db.DbContext.SaveChangesAsync();
return server;
}
public virtual async Task AddAdminLogs(List<QueuedLog> logs)
{
await using var db = await GetDb();

View File

@@ -160,7 +160,7 @@ namespace Content.Server.Database
#region Rounds
Task<int> AddNewRound(params Guid[] playerIds);
Task<int> AddNewRound(Server server, params Guid[] playerIds);
Task<Round> GetRound(int id);
Task AddRoundPlayers(int id, params Guid[] playerIds);
@@ -168,6 +168,7 @@ namespace Content.Server.Database
#region Admin Logs
Task<Server> AddOrGetServer(string serverName);
Task AddAdminLogs(List<QueuedLog> logs);
IAsyncEnumerable<string> GetAdminLogMessages(LogFilter? filter = null);
IAsyncEnumerable<SharedAdminLog> GetAdminLogs(LogFilter? filter = null);
@@ -394,9 +395,9 @@ namespace Content.Server.Database
return _db.AddAdminRankAsync(rank, cancel);
}
public Task<int> AddNewRound(params Guid[] playerIds)
public Task<int> AddNewRound(Server server, params Guid[] playerIds)
{
return _db.AddNewRound(playerIds);
return _db.AddNewRound(server, playerIds);
}
public Task<Round> GetRound(int id)
@@ -414,6 +415,11 @@ namespace Content.Server.Database
return _db.UpdateAdminRankAsync(rank, cancel);
}
public Task<Server> AddOrGetServer(string serverName)
{
return _db.AddOrGetServer(serverName);
}
public Task AddAdminLogs(List<QueuedLog> logs)
{
return _db.AddAdminLogs(logs);

View File

@@ -427,7 +427,7 @@ namespace Content.Server.Database
return (admins.Select(p => (p.a, p.LastSeenUserName)).ToArray(), adminRanks)!;
}
public override async Task<int> AddNewRound(params Guid[] playerIds)
public override async Task<int> AddNewRound(Server server, params Guid[] playerIds)
{
await using var db = await GetDb();
@@ -444,7 +444,8 @@ namespace Content.Server.Database
var round = new Round
{
Id = nextId,
Players = players
Players = players,
ServerId = server.Id
};
db.DbContext.Round.Add(round);

View File

@@ -6,6 +6,7 @@ using Content.Server.Maps;
using Content.Server.Mind;
using Content.Server.Players;
using Content.Server.Station;
using Content.Shared.CCVar;
using Content.Shared.Coordinates;
using Content.Shared.GameTicking;
using Content.Shared.Preferences;
@@ -190,10 +191,15 @@ namespace Content.Server.GameTicking
RoundLengthMetric.Set(0);
var playerIds = _playersInLobby.Keys.Select(player => player.UserId.UserId).ToArray();
var serverName = _configurationManager.GetCVar(CCVars.AdminLogsServerName);
// TODO FIXME AAAAAAAAAAAAAAAAAAAH THIS IS BROKEN
// Task.Run as a terrible dirty workaround to avoid synchronization context deadlock from .Result here.
// This whole setup logic should be made asynchronous so we can properly wait on the DB AAAAAAAAAAAAAH
RoundId = Task.Run(async () => await _db.AddNewRound(playerIds)).Result;
RoundId = Task.Run(async () =>
{
var server = await _db.AddOrGetServer(serverName);
return await _db.AddNewRound(server, playerIds);
}).Result;
var startingEvent = new RoundStartingEvent();
RaiseLocalEvent(startingEvent);

View File

@@ -401,6 +401,9 @@ namespace Content.Shared.CCVar
public static readonly CVarDef<int> AdminLogsClientBatchSize =
CVarDef.Create("adminlogs.client_batch_size", 1000, CVar.SERVERONLY);
public static readonly CVarDef<string> AdminLogsServerName =
CVarDef.Create("adminlogs.server_name", "unknown", CVar.SERVERONLY);
/*
* Atmos
*/