Model.cs 46 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Collections.Immutable;
  4. using System.ComponentModel.DataAnnotations;
  5. using System.ComponentModel.DataAnnotations.Schema;
  6. using System.Diagnostics.CodeAnalysis;
  7. using System.Linq;
  8. using System.Net;
  9. using System.Text.Json;
  10. using Content.Shared.Database;
  11. using Microsoft.EntityFrameworkCore;
  12. using NpgsqlTypes;
  13. namespace Content.Server.Database
  14. {
  15. public abstract class ServerDbContext : DbContext
  16. {
  17. protected ServerDbContext(DbContextOptions options) : base(options)
  18. {
  19. }
  20. public DbSet<Preference> Preference { get; set; } = null!;
  21. public DbSet<Profile> Profile { get; set; } = null!;
  22. public DbSet<AssignedUserId> AssignedUserId { get; set; } = null!;
  23. public DbSet<Player> Player { get; set; } = default!;
  24. public DbSet<Admin> Admin { get; set; } = null!;
  25. public DbSet<AdminRank> AdminRank { get; set; } = null!;
  26. public DbSet<Round> Round { get; set; } = null!;
  27. public DbSet<Server> Server { get; set; } = null!;
  28. public DbSet<AdminLog> AdminLog { get; set; } = null!;
  29. public DbSet<AdminLogPlayer> AdminLogPlayer { get; set; } = null!;
  30. public DbSet<Whitelist> Whitelist { get; set; } = null!;
  31. public DbSet<Blacklist> Blacklist { get; set; } = null!;
  32. public DbSet<ServerBan> Ban { get; set; } = default!;
  33. public DbSet<ServerUnban> Unban { get; set; } = default!;
  34. public DbSet<ServerBanExemption> BanExemption { get; set; } = default!;
  35. public DbSet<ConnectionLog> ConnectionLog { get; set; } = default!;
  36. public DbSet<ServerBanHit> ServerBanHit { get; set; } = default!;
  37. public DbSet<ServerRoleBan> RoleBan { get; set; } = default!;
  38. public DbSet<ServerRoleUnban> RoleUnban { get; set; } = default!;
  39. public DbSet<PlayTime> PlayTime { get; set; } = default!;
  40. public DbSet<UploadedResourceLog> UploadedResourceLog { get; set; } = default!;
  41. public DbSet<AdminNote> AdminNotes { get; set; } = null!;
  42. public DbSet<AdminWatchlist> AdminWatchlists { get; set; } = null!;
  43. public DbSet<AdminMessage> AdminMessages { get; set; } = null!;
  44. public DbSet<RoleWhitelist> RoleWhitelists { get; set; } = null!;
  45. public DbSet<BanTemplate> BanTemplate { get; set; } = null!;
  46. public DbSet<IPIntelCache> IPIntelCache { get; set; } = null!;
  47. protected override void OnModelCreating(ModelBuilder modelBuilder)
  48. {
  49. modelBuilder.Entity<Preference>()
  50. .HasIndex(p => p.UserId)
  51. .IsUnique();
  52. modelBuilder.Entity<Profile>()
  53. .HasIndex(p => new {p.Slot, PrefsId = p.PreferenceId})
  54. .IsUnique();
  55. modelBuilder.Entity<Antag>()
  56. .HasIndex(p => new {HumanoidProfileId = p.ProfileId, p.AntagName})
  57. .IsUnique();
  58. modelBuilder.Entity<Trait>()
  59. .HasIndex(p => new {HumanoidProfileId = p.ProfileId, p.TraitName})
  60. .IsUnique();
  61. modelBuilder.Entity<ProfileRoleLoadout>()
  62. .HasOne(e => e.Profile)
  63. .WithMany(e => e.Loadouts)
  64. .HasForeignKey(e => e.ProfileId)
  65. .IsRequired();
  66. modelBuilder.Entity<ProfileLoadoutGroup>()
  67. .HasOne(e => e.ProfileRoleLoadout)
  68. .WithMany(e => e.Groups)
  69. .HasForeignKey(e => e.ProfileRoleLoadoutId)
  70. .IsRequired();
  71. modelBuilder.Entity<ProfileLoadout>()
  72. .HasOne(e => e.ProfileLoadoutGroup)
  73. .WithMany(e => e.Loadouts)
  74. .HasForeignKey(e => e.ProfileLoadoutGroupId)
  75. .IsRequired();
  76. modelBuilder.Entity<Job>()
  77. .HasIndex(j => j.ProfileId);
  78. modelBuilder.Entity<Job>()
  79. .HasIndex(j => j.ProfileId, "IX_job_one_high_priority")
  80. .IsUnique()
  81. .HasFilter("priority = 3");
  82. modelBuilder.Entity<Job>()
  83. .HasIndex(j => new { j.ProfileId, j.JobName })
  84. .IsUnique();
  85. modelBuilder.Entity<AssignedUserId>()
  86. .HasIndex(p => p.UserName)
  87. .IsUnique();
  88. // Can't have two usernames with the same user ID.
  89. modelBuilder.Entity<AssignedUserId>()
  90. .HasIndex(p => p.UserId)
  91. .IsUnique();
  92. modelBuilder.Entity<Admin>()
  93. .HasOne(p => p.AdminRank)
  94. .WithMany(p => p!.Admins)
  95. .OnDelete(DeleteBehavior.SetNull);
  96. modelBuilder.Entity<AdminFlag>()
  97. .HasIndex(f => new {f.Flag, f.AdminId})
  98. .IsUnique();
  99. modelBuilder.Entity<AdminRankFlag>()
  100. .HasIndex(f => new {f.Flag, f.AdminRankId})
  101. .IsUnique();
  102. modelBuilder.Entity<AdminLog>()
  103. .HasKey(log => new {log.RoundId, log.Id});
  104. modelBuilder.Entity<AdminLog>()
  105. .Property(log => log.Id);
  106. modelBuilder.Entity<AdminLog>()
  107. .HasIndex(log => log.Date);
  108. modelBuilder.Entity<PlayTime>()
  109. .HasIndex(v => new { v.PlayerId, Role = v.Tracker })
  110. .IsUnique();
  111. modelBuilder.Entity<AdminLogPlayer>()
  112. .HasOne(player => player.Player)
  113. .WithMany(player => player.AdminLogs)
  114. .HasForeignKey(player => player.PlayerUserId)
  115. .HasPrincipalKey(player => player.UserId);
  116. modelBuilder.Entity<AdminLogPlayer>()
  117. .HasIndex(p => p.PlayerUserId);
  118. modelBuilder.Entity<Round>()
  119. .HasIndex(round => round.StartDate);
  120. modelBuilder.Entity<AdminLogPlayer>()
  121. .HasKey(logPlayer => new {logPlayer.RoundId, logPlayer.LogId, logPlayer.PlayerUserId});
  122. modelBuilder.Entity<ServerBan>()
  123. .HasIndex(p => p.PlayerUserId);
  124. modelBuilder.Entity<ServerBan>()
  125. .HasIndex(p => p.Address);
  126. modelBuilder.Entity<ServerBan>()
  127. .HasIndex(p => p.PlayerUserId);
  128. modelBuilder.Entity<ServerUnban>()
  129. .HasIndex(p => p.BanId)
  130. .IsUnique();
  131. modelBuilder.Entity<ServerBan>().ToTable(t =>
  132. t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"));
  133. // Ban exemption can't have flags 0 since that wouldn't exempt anything.
  134. // The row should be removed if setting to 0.
  135. modelBuilder.Entity<ServerBanExemption>().ToTable(t =>
  136. t.HasCheckConstraint("FlagsNotZero", "flags != 0"));
  137. modelBuilder.Entity<ServerRoleBan>()
  138. .HasIndex(p => p.PlayerUserId);
  139. modelBuilder.Entity<ServerRoleBan>()
  140. .HasIndex(p => p.Address);
  141. modelBuilder.Entity<ServerRoleBan>()
  142. .HasIndex(p => p.PlayerUserId);
  143. modelBuilder.Entity<ServerRoleUnban>()
  144. .HasIndex(p => p.BanId)
  145. .IsUnique();
  146. modelBuilder.Entity<ServerRoleBan>().ToTable(t =>
  147. t.HasCheckConstraint("HaveEitherAddressOrUserIdOrHWId", "address IS NOT NULL OR player_user_id IS NOT NULL OR hwid IS NOT NULL"));
  148. modelBuilder.Entity<Player>()
  149. .HasIndex(p => p.UserId)
  150. .IsUnique();
  151. modelBuilder.Entity<Player>()
  152. .HasIndex(p => p.LastSeenUserName);
  153. modelBuilder.Entity<ConnectionLog>()
  154. .HasIndex(p => p.UserId);
  155. modelBuilder.Entity<ConnectionLog>()
  156. .HasIndex(p => p.Time);
  157. modelBuilder.Entity<ConnectionLog>()
  158. .Property(p => p.ServerId)
  159. .HasDefaultValue(0);
  160. modelBuilder.Entity<ConnectionLog>()
  161. .HasOne(p => p.Server)
  162. .WithMany(p => p.ConnectionLogs)
  163. .OnDelete(DeleteBehavior.SetNull);
  164. // SetNull is necessary for created by/edited by-s here,
  165. // so you can safely delete admins (GDPR right to erasure) while keeping the notes intact
  166. modelBuilder.Entity<AdminNote>()
  167. .HasOne(note => note.Player)
  168. .WithMany(player => player.AdminNotesReceived)
  169. .HasForeignKey(note => note.PlayerUserId)
  170. .HasPrincipalKey(player => player.UserId)
  171. .OnDelete(DeleteBehavior.Cascade);
  172. modelBuilder.Entity<AdminNote>()
  173. .HasOne(version => version.CreatedBy)
  174. .WithMany(author => author.AdminNotesCreated)
  175. .HasForeignKey(note => note.CreatedById)
  176. .HasPrincipalKey(author => author.UserId)
  177. .OnDelete(DeleteBehavior.SetNull);
  178. modelBuilder.Entity<AdminNote>()
  179. .HasOne(version => version.LastEditedBy)
  180. .WithMany(author => author.AdminNotesLastEdited)
  181. .HasForeignKey(note => note.LastEditedById)
  182. .HasPrincipalKey(author => author.UserId)
  183. .OnDelete(DeleteBehavior.SetNull);
  184. modelBuilder.Entity<AdminNote>()
  185. .HasOne(version => version.DeletedBy)
  186. .WithMany(author => author.AdminNotesDeleted)
  187. .HasForeignKey(note => note.DeletedById)
  188. .HasPrincipalKey(author => author.UserId)
  189. .OnDelete(DeleteBehavior.SetNull);
  190. modelBuilder.Entity<AdminWatchlist>()
  191. .HasOne(note => note.Player)
  192. .WithMany(player => player.AdminWatchlistsReceived)
  193. .HasForeignKey(note => note.PlayerUserId)
  194. .HasPrincipalKey(player => player.UserId)
  195. .OnDelete(DeleteBehavior.Cascade);
  196. modelBuilder.Entity<AdminWatchlist>()
  197. .HasOne(version => version.CreatedBy)
  198. .WithMany(author => author.AdminWatchlistsCreated)
  199. .HasForeignKey(note => note.CreatedById)
  200. .HasPrincipalKey(author => author.UserId)
  201. .OnDelete(DeleteBehavior.SetNull);
  202. modelBuilder.Entity<AdminWatchlist>()
  203. .HasOne(version => version.LastEditedBy)
  204. .WithMany(author => author.AdminWatchlistsLastEdited)
  205. .HasForeignKey(note => note.LastEditedById)
  206. .HasPrincipalKey(author => author.UserId)
  207. .OnDelete(DeleteBehavior.SetNull);
  208. modelBuilder.Entity<AdminWatchlist>()
  209. .HasOne(version => version.DeletedBy)
  210. .WithMany(author => author.AdminWatchlistsDeleted)
  211. .HasForeignKey(note => note.DeletedById)
  212. .HasPrincipalKey(author => author.UserId)
  213. .OnDelete(DeleteBehavior.SetNull);
  214. modelBuilder.Entity<AdminMessage>()
  215. .HasOne(note => note.Player)
  216. .WithMany(player => player.AdminMessagesReceived)
  217. .HasForeignKey(note => note.PlayerUserId)
  218. .HasPrincipalKey(player => player.UserId)
  219. .OnDelete(DeleteBehavior.Cascade);
  220. modelBuilder.Entity<AdminMessage>()
  221. .HasOne(version => version.CreatedBy)
  222. .WithMany(author => author.AdminMessagesCreated)
  223. .HasForeignKey(note => note.CreatedById)
  224. .HasPrincipalKey(author => author.UserId)
  225. .OnDelete(DeleteBehavior.SetNull);
  226. modelBuilder.Entity<AdminMessage>()
  227. .HasOne(version => version.LastEditedBy)
  228. .WithMany(author => author.AdminMessagesLastEdited)
  229. .HasForeignKey(note => note.LastEditedById)
  230. .HasPrincipalKey(author => author.UserId)
  231. .OnDelete(DeleteBehavior.SetNull);
  232. modelBuilder.Entity<AdminMessage>()
  233. .HasOne(version => version.DeletedBy)
  234. .WithMany(author => author.AdminMessagesDeleted)
  235. .HasForeignKey(note => note.DeletedById)
  236. .HasPrincipalKey(author => author.UserId)
  237. .OnDelete(DeleteBehavior.SetNull);
  238. // A message cannot be "dismissed" without also being "seen".
  239. modelBuilder.Entity<AdminMessage>().ToTable(t =>
  240. t.HasCheckConstraint("NotDismissedAndSeen",
  241. "NOT dismissed OR seen"));
  242. modelBuilder.Entity<ServerBan>()
  243. .HasOne(ban => ban.CreatedBy)
  244. .WithMany(author => author.AdminServerBansCreated)
  245. .HasForeignKey(ban => ban.BanningAdmin)
  246. .HasPrincipalKey(author => author.UserId)
  247. .OnDelete(DeleteBehavior.SetNull);
  248. modelBuilder.Entity<ServerBan>()
  249. .HasOne(ban => ban.LastEditedBy)
  250. .WithMany(author => author.AdminServerBansLastEdited)
  251. .HasForeignKey(ban => ban.LastEditedById)
  252. .HasPrincipalKey(author => author.UserId)
  253. .OnDelete(DeleteBehavior.SetNull);
  254. modelBuilder.Entity<ServerRoleBan>()
  255. .HasOne(ban => ban.CreatedBy)
  256. .WithMany(author => author.AdminServerRoleBansCreated)
  257. .HasForeignKey(ban => ban.BanningAdmin)
  258. .HasPrincipalKey(author => author.UserId)
  259. .OnDelete(DeleteBehavior.SetNull);
  260. modelBuilder.Entity<ServerRoleBan>()
  261. .HasOne(ban => ban.LastEditedBy)
  262. .WithMany(author => author.AdminServerRoleBansLastEdited)
  263. .HasForeignKey(ban => ban.LastEditedById)
  264. .HasPrincipalKey(author => author.UserId)
  265. .OnDelete(DeleteBehavior.SetNull);
  266. modelBuilder.Entity<RoleWhitelist>()
  267. .HasOne(w => w.Player)
  268. .WithMany(p => p.JobWhitelists)
  269. .HasForeignKey(w => w.PlayerUserId)
  270. .HasPrincipalKey(p => p.UserId)
  271. .OnDelete(DeleteBehavior.Cascade);
  272. // Changes for modern HWID integration
  273. modelBuilder.Entity<Player>()
  274. .OwnsOne(p => p.LastSeenHWId)
  275. .Property(p => p.Hwid)
  276. .HasColumnName("last_seen_hwid");
  277. modelBuilder.Entity<Player>()
  278. .OwnsOne(p => p.LastSeenHWId)
  279. .Property(p => p.Type)
  280. .HasDefaultValue(HwidType.Legacy);
  281. modelBuilder.Entity<ServerBan>()
  282. .OwnsOne(p => p.HWId)
  283. .Property(p => p.Hwid)
  284. .HasColumnName("hwid");
  285. modelBuilder.Entity<ServerBan>()
  286. .OwnsOne(p => p.HWId)
  287. .Property(p => p.Type)
  288. .HasDefaultValue(HwidType.Legacy);
  289. modelBuilder.Entity<ServerRoleBan>()
  290. .OwnsOne(p => p.HWId)
  291. .Property(p => p.Hwid)
  292. .HasColumnName("hwid");
  293. modelBuilder.Entity<ServerRoleBan>()
  294. .OwnsOne(p => p.HWId)
  295. .Property(p => p.Type)
  296. .HasDefaultValue(HwidType.Legacy);
  297. modelBuilder.Entity<ConnectionLog>()
  298. .OwnsOne(p => p.HWId)
  299. .Property(p => p.Hwid)
  300. .HasColumnName("hwid");
  301. modelBuilder.Entity<ConnectionLog>()
  302. .OwnsOne(p => p.HWId)
  303. .Property(p => p.Type)
  304. .HasDefaultValue(HwidType.Legacy);
  305. }
  306. public virtual IQueryable<AdminLog> SearchLogs(IQueryable<AdminLog> query, string searchText)
  307. {
  308. return query.Where(log => EF.Functions.Like(log.Message, "%" + searchText + "%"));
  309. }
  310. public abstract int CountAdminLogs();
  311. }
  312. public class Preference
  313. {
  314. // NOTE: on postgres there SHOULD be an FK ensuring that the selected character slot always exists.
  315. // I had to use a migration to implement it and as a result its creation is a finicky mess.
  316. // Because if I let EFCore know about it it would explode on a circular reference.
  317. // Also it has to be DEFERRABLE INITIALLY DEFERRED so that insertion of new preferences works.
  318. // Also I couldn't figure out how to create it on SQLite.
  319. public int Id { get; set; }
  320. public Guid UserId { get; set; }
  321. public int SelectedCharacterSlot { get; set; }
  322. public string AdminOOCColor { get; set; } = null!;
  323. public List<Profile> Profiles { get; } = new();
  324. }
  325. public class Profile
  326. {
  327. public int Id { get; set; }
  328. public int Slot { get; set; }
  329. [Column("char_name")] public string CharacterName { get; set; } = null!;
  330. public string FlavorText { get; set; } = null!;
  331. public int Age { get; set; }
  332. public string Sex { get; set; } = null!;
  333. public string Gender { get; set; } = null!;
  334. public string Species { get; set; } = null!;
  335. [Column(TypeName = "jsonb")] public JsonDocument? Markings { get; set; } = null!;
  336. public string HairName { get; set; } = null!;
  337. public string HairColor { get; set; } = null!;
  338. public string FacialHairName { get; set; } = null!;
  339. public string FacialHairColor { get; set; } = null!;
  340. public string EyeColor { get; set; } = null!;
  341. public string SkinColor { get; set; } = null!;
  342. public int SpawnPriority { get; set; } = 0;
  343. public List<Job> Jobs { get; } = new();
  344. public List<Antag> Antags { get; } = new();
  345. public List<Trait> Traits { get; } = new();
  346. public List<ProfileRoleLoadout> Loadouts { get; } = new();
  347. [Column("pref_unavailable")] public DbPreferenceUnavailableMode PreferenceUnavailable { get; set; }
  348. public int PreferenceId { get; set; }
  349. public Preference Preference { get; set; } = null!;
  350. }
  351. public class Job
  352. {
  353. public int Id { get; set; }
  354. public Profile Profile { get; set; } = null!;
  355. public int ProfileId { get; set; }
  356. public string JobName { get; set; } = null!;
  357. public DbJobPriority Priority { get; set; }
  358. }
  359. public enum DbJobPriority
  360. {
  361. // These enum values HAVE to match the ones in JobPriority in Content.Shared
  362. Never = 0,
  363. Low = 1,
  364. Medium = 2,
  365. High = 3
  366. }
  367. public class Antag
  368. {
  369. public int Id { get; set; }
  370. public Profile Profile { get; set; } = null!;
  371. public int ProfileId { get; set; }
  372. public string AntagName { get; set; } = null!;
  373. }
  374. public class Trait
  375. {
  376. public int Id { get; set; }
  377. public Profile Profile { get; set; } = null!;
  378. public int ProfileId { get; set; }
  379. public string TraitName { get; set; } = null!;
  380. }
  381. #region Loadouts
  382. /// <summary>
  383. /// Corresponds to a single role's loadout inside the DB.
  384. /// </summary>
  385. public class ProfileRoleLoadout
  386. {
  387. public int Id { get; set; }
  388. public int ProfileId { get; set; }
  389. public Profile Profile { get; set; } = null!;
  390. /// <summary>
  391. /// The corresponding role prototype on the profile.
  392. /// </summary>
  393. public string RoleName { get; set; } = string.Empty;
  394. /// <summary>
  395. /// Custom name of the role loadout if it supports it.
  396. /// </summary>
  397. [MaxLength(256)]
  398. public string? EntityName { get; set; }
  399. /// <summary>
  400. /// Store the saved loadout groups. These may get validated and removed when loaded at runtime.
  401. /// </summary>
  402. public List<ProfileLoadoutGroup> Groups { get; set; } = new();
  403. }
  404. /// <summary>
  405. /// Corresponds to a loadout group prototype with the specified loadouts attached.
  406. /// </summary>
  407. public class ProfileLoadoutGroup
  408. {
  409. public int Id { get; set; }
  410. public int ProfileRoleLoadoutId { get; set; }
  411. /// <summary>
  412. /// The corresponding RoleLoadout that owns this.
  413. /// </summary>
  414. public ProfileRoleLoadout ProfileRoleLoadout { get; set; } = null!;
  415. /// <summary>
  416. /// The corresponding group prototype.
  417. /// </summary>
  418. public string GroupName { get; set; } = string.Empty;
  419. /// <summary>
  420. /// Selected loadout prototype. Null if none is set.
  421. /// May get validated at runtime and updated to to the default.
  422. /// </summary>
  423. public List<ProfileLoadout> Loadouts { get; set; } = new();
  424. }
  425. /// <summary>
  426. /// Corresponds to a selected loadout.
  427. /// </summary>
  428. public class ProfileLoadout
  429. {
  430. public int Id { get; set; }
  431. public int ProfileLoadoutGroupId { get; set; }
  432. public ProfileLoadoutGroup ProfileLoadoutGroup { get; set; } = null!;
  433. /// <summary>
  434. /// Corresponding loadout prototype.
  435. /// </summary>
  436. public string LoadoutName { get; set; } = string.Empty;
  437. /*
  438. * Insert extra data here like custom descriptions or colors or whatever.
  439. */
  440. }
  441. #endregion
  442. public enum DbPreferenceUnavailableMode
  443. {
  444. // These enum values HAVE to match the ones in PreferenceUnavailableMode in Shared.
  445. StayInLobby = 0,
  446. SpawnAsOverflow,
  447. }
  448. public class AssignedUserId
  449. {
  450. public int Id { get; set; }
  451. public string UserName { get; set; } = null!;
  452. public Guid UserId { get; set; }
  453. }
  454. [Table("player")]
  455. public class Player
  456. {
  457. public int Id { get; set; }
  458. // Permanent data
  459. public Guid UserId { get; set; }
  460. public DateTime FirstSeenTime { get; set; }
  461. // Data that gets updated on each join.
  462. public string LastSeenUserName { get; set; } = null!;
  463. public DateTime LastSeenTime { get; set; }
  464. public IPAddress LastSeenAddress { get; set; } = null!;
  465. public TypedHwid? LastSeenHWId { get; set; }
  466. // Data that changes with each round
  467. public List<Round> Rounds { get; set; } = null!;
  468. public List<AdminLogPlayer> AdminLogs { get; set; } = null!;
  469. public DateTime? LastReadRules { get; set; }
  470. public List<AdminNote> AdminNotesReceived { get; set; } = null!;
  471. public List<AdminNote> AdminNotesCreated { get; set; } = null!;
  472. public List<AdminNote> AdminNotesLastEdited { get; set; } = null!;
  473. public List<AdminNote> AdminNotesDeleted { get; set; } = null!;
  474. public List<AdminWatchlist> AdminWatchlistsReceived { get; set; } = null!;
  475. public List<AdminWatchlist> AdminWatchlistsCreated { get; set; } = null!;
  476. public List<AdminWatchlist> AdminWatchlistsLastEdited { get; set; } = null!;
  477. public List<AdminWatchlist> AdminWatchlistsDeleted { get; set; } = null!;
  478. public List<AdminMessage> AdminMessagesReceived { get; set; } = null!;
  479. public List<AdminMessage> AdminMessagesCreated { get; set; } = null!;
  480. public List<AdminMessage> AdminMessagesLastEdited { get; set; } = null!;
  481. public List<AdminMessage> AdminMessagesDeleted { get; set; } = null!;
  482. public List<ServerBan> AdminServerBansCreated { get; set; } = null!;
  483. public List<ServerBan> AdminServerBansLastEdited { get; set; } = null!;
  484. public List<ServerRoleBan> AdminServerRoleBansCreated { get; set; } = null!;
  485. public List<ServerRoleBan> AdminServerRoleBansLastEdited { get; set; } = null!;
  486. public List<RoleWhitelist> JobWhitelists { get; set; } = null!;
  487. }
  488. [Table("whitelist")]
  489. public class Whitelist
  490. {
  491. [Required, Key] public Guid UserId { get; set; }
  492. }
  493. /// <summary>
  494. /// List of users who are on the "blacklist". This is a list that may be used by Whitelist implementations to deny access to certain users.
  495. /// </summary>
  496. [Table("blacklist")]
  497. public class Blacklist
  498. {
  499. [Required, Key] public Guid UserId { get; set; }
  500. }
  501. public class Admin
  502. {
  503. [Key] public Guid UserId { get; set; }
  504. public string? Title { get; set; }
  505. /// <summary>
  506. /// If true, the admin is voluntarily deadminned. They can re-admin at any time.
  507. /// </summary>
  508. public bool Deadminned { get; set; }
  509. /// <summary>
  510. /// If true, the admin is suspended by an admin with <c>PERMISSIONS</c>. They will not have in-game permissions.
  511. /// </summary>
  512. public bool Suspended { get; set; }
  513. public int? AdminRankId { get; set; }
  514. public AdminRank? AdminRank { get; set; }
  515. public List<AdminFlag> Flags { get; set; } = default!;
  516. }
  517. public class AdminFlag
  518. {
  519. public int Id { get; set; }
  520. public string Flag { get; set; } = default!;
  521. public bool Negative { get; set; }
  522. public Guid AdminId { get; set; }
  523. public Admin Admin { get; set; } = default!;
  524. }
  525. public class AdminRank
  526. {
  527. public int Id { get; set; }
  528. public string Name { get; set; } = default!;
  529. public List<Admin> Admins { get; set; } = default!;
  530. public List<AdminRankFlag> Flags { get; set; } = default!;
  531. }
  532. public class AdminRankFlag
  533. {
  534. public int Id { get; set; }
  535. public string Flag { get; set; } = default!;
  536. public int AdminRankId { get; set; }
  537. public AdminRank Rank { get; set; } = default!;
  538. }
  539. public class Round
  540. {
  541. [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
  542. public int Id { get; set; }
  543. public DateTime? StartDate { get; set; }
  544. public List<Player> Players { get; set; } = default!;
  545. public List<AdminLog> AdminLogs { get; set; } = default!;
  546. [ForeignKey("Server")] public int ServerId { get; set; }
  547. public Server Server { get; set; } = default!;
  548. }
  549. public class Server
  550. {
  551. [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
  552. public int Id { get; set; }
  553. public string Name { get; set; } = default!;
  554. [InverseProperty(nameof(Round.Server))]
  555. public List<Round> Rounds { get; set; } = default!;
  556. [InverseProperty(nameof(ConnectionLog.Server))]
  557. public List<ConnectionLog> ConnectionLogs { get; set; } = default!;
  558. }
  559. [Index(nameof(Type))]
  560. public class AdminLog
  561. {
  562. [Key, ForeignKey("Round")] public int RoundId { get; set; }
  563. [Key]
  564. public int Id { get; set; }
  565. public Round Round { get; set; } = default!;
  566. [Required] public LogType Type { get; set; }
  567. [Required] public LogImpact Impact { get; set; }
  568. [Required] public DateTime Date { get; set; }
  569. [Required] public string Message { get; set; } = default!;
  570. [Required, Column(TypeName = "jsonb")] public JsonDocument Json { get; set; } = default!;
  571. public List<AdminLogPlayer> Players { get; set; } = default!;
  572. }
  573. public class AdminLogPlayer
  574. {
  575. [Required, Key] public int RoundId { get; set; }
  576. [Required, Key] public int LogId { get; set; }
  577. [Required, Key, ForeignKey("Player")] public Guid PlayerUserId { get; set; }
  578. public Player Player { get; set; } = default!;
  579. [ForeignKey("RoundId,LogId")] public AdminLog Log { get; set; } = default!;
  580. }
  581. // Used by SS14.Admin
  582. public interface IBanCommon<TUnban> where TUnban : IUnbanCommon
  583. {
  584. int Id { get; set; }
  585. Guid? PlayerUserId { get; set; }
  586. NpgsqlInet? Address { get; set; }
  587. TypedHwid? HWId { get; set; }
  588. DateTime BanTime { get; set; }
  589. DateTime? ExpirationTime { get; set; }
  590. string Reason { get; set; }
  591. NoteSeverity Severity { get; set; }
  592. Guid? BanningAdmin { get; set; }
  593. TUnban? Unban { get; set; }
  594. }
  595. // Used by SS14.Admin
  596. public interface IUnbanCommon
  597. {
  598. int Id { get; set; }
  599. int BanId { get; set; }
  600. Guid? UnbanningAdmin { get; set; }
  601. DateTime UnbanTime { get; set; }
  602. }
  603. /// <summary>
  604. /// Flags for use with <see cref="ServerBanExemption"/>.
  605. /// </summary>
  606. [Flags]
  607. public enum ServerBanExemptFlags
  608. {
  609. // @formatter:off
  610. None = 0,
  611. /// <summary>
  612. /// Ban is a datacenter range, connections usually imply usage of a VPN service.
  613. /// </summary>
  614. Datacenter = 1 << 0,
  615. /// <summary>
  616. /// Ban only matches the IP.
  617. /// </summary>
  618. /// <remarks>
  619. /// Intended use is for users with shared connections. This should not be used as an alternative to <see cref="Datacenter"/>.
  620. /// </remarks>
  621. IP = 1 << 1,
  622. /// <summary>
  623. /// Ban is an IP range that is only applied for first time joins.
  624. /// </summary>
  625. /// <remarks>
  626. /// Intended for use with residential IP ranges that are often used maliciously.
  627. /// </remarks>
  628. BlacklistedRange = 1 << 2,
  629. /// <summary>
  630. /// Represents having all possible exemption flags.
  631. /// </summary>
  632. All = int.MaxValue,
  633. // @formatter:on
  634. }
  635. /// <summary>
  636. /// A ban from playing on the server.
  637. /// If an incoming connection matches any of UserID, IP, or HWID, they will be blocked from joining the server.
  638. /// </summary>
  639. /// <remarks>
  640. /// At least one of UserID, IP, or HWID must be given (otherwise the ban would match nothing).
  641. /// </remarks>
  642. [Table("server_ban"), Index(nameof(PlayerUserId))]
  643. public class ServerBan : IBanCommon<ServerUnban>
  644. {
  645. public int Id { get; set; }
  646. [ForeignKey("Round")]
  647. public int? RoundId { get; set; }
  648. public Round? Round { get; set; }
  649. /// <summary>
  650. /// The user ID of the banned player.
  651. /// </summary>
  652. public Guid? PlayerUserId { get; set; }
  653. [Required] public TimeSpan PlaytimeAtNote { get; set; }
  654. /// <summary>
  655. /// CIDR IP address range of the ban. The whole range can match the ban.
  656. /// </summary>
  657. public NpgsqlInet? Address { get; set; }
  658. /// <summary>
  659. /// Hardware ID of the banned player.
  660. /// </summary>
  661. public TypedHwid? HWId { get; set; }
  662. /// <summary>
  663. /// The time when the ban was applied by an administrator.
  664. /// </summary>
  665. public DateTime BanTime { get; set; }
  666. /// <summary>
  667. /// The time the ban will expire. If null, the ban is permanent and will not expire naturally.
  668. /// </summary>
  669. public DateTime? ExpirationTime { get; set; }
  670. /// <summary>
  671. /// The administrator-stated reason for applying the ban.
  672. /// </summary>
  673. public string Reason { get; set; } = null!;
  674. /// <summary>
  675. /// The severity of the incident
  676. /// </summary>
  677. public NoteSeverity Severity { get; set; }
  678. /// <summary>
  679. /// User ID of the admin that applied the ban.
  680. /// </summary>
  681. [ForeignKey("CreatedBy")]
  682. public Guid? BanningAdmin { get; set; }
  683. public Player? CreatedBy { get; set; }
  684. /// <summary>
  685. /// User ID of the admin that last edited the note
  686. /// </summary>
  687. [ForeignKey("LastEditedBy")]
  688. public Guid? LastEditedById { get; set; }
  689. public Player? LastEditedBy { get; set; }
  690. /// <summary>
  691. /// When the ban was last edited
  692. /// </summary>
  693. public DateTime? LastEditedAt { get; set; }
  694. /// <summary>
  695. /// Optional flags that allow adding exemptions to the ban via <see cref="ServerBanExemption"/>.
  696. /// </summary>
  697. public ServerBanExemptFlags ExemptFlags { get; set; }
  698. /// <summary>
  699. /// If present, an administrator has manually repealed this ban.
  700. /// </summary>
  701. public ServerUnban? Unban { get; set; }
  702. /// <summary>
  703. /// Whether this ban should be automatically deleted from the database when it expires.
  704. /// </summary>
  705. /// <remarks>
  706. /// This isn't done automatically by the game,
  707. /// you will need to set up something like a cron job to clear this from your database,
  708. /// using a command like this:
  709. /// psql -d ss14 -c "DELETE FROM server_ban WHERE auto_delete AND expiration_time &lt; NOW()"
  710. /// </remarks>
  711. public bool AutoDelete { get; set; }
  712. /// <summary>
  713. /// Whether to display this ban in the admin remarks (notes) panel
  714. /// </summary>
  715. public bool Hidden { get; set; }
  716. public List<ServerBanHit> BanHits { get; set; } = null!;
  717. }
  718. /// <summary>
  719. /// An explicit repeal of a <see cref="ServerBan"/> by an administrator.
  720. /// Having an entry for a ban neutralizes it.
  721. /// </summary>
  722. [Table("server_unban")]
  723. public class ServerUnban : IUnbanCommon
  724. {
  725. [Column("unban_id")] public int Id { get; set; }
  726. /// <summary>
  727. /// The ID of ban that is being repealed.
  728. /// </summary>
  729. public int BanId { get; set; }
  730. /// <summary>
  731. /// The ban that is being repealed.
  732. /// </summary>
  733. public ServerBan Ban { get; set; } = null!;
  734. /// <summary>
  735. /// The admin that repealed the ban.
  736. /// </summary>
  737. public Guid? UnbanningAdmin { get; set; }
  738. /// <summary>
  739. /// The time the ban repealed.
  740. /// </summary>
  741. public DateTime UnbanTime { get; set; }
  742. }
  743. /// <summary>
  744. /// An exemption for a specific user to a certain type of <see cref="ServerBan"/>.
  745. /// </summary>
  746. /// <example>
  747. /// Certain players may need to be exempted from VPN bans due to issues with their ISP.
  748. /// We would tag all VPN bans with <see cref="ServerBanExemptFlags.Datacenter"/>,
  749. /// and then add an exemption for these players to this table with the same flag.
  750. /// They will only be exempted from VPN bans, other bans (if they manage to get any) will still apply.
  751. /// </example>
  752. [Table("server_ban_exemption")]
  753. public sealed class ServerBanExemption
  754. {
  755. /// <summary>
  756. /// The UserID of the exempted player.
  757. /// </summary>
  758. [Key]
  759. public Guid UserId { get; set; }
  760. /// <summary>
  761. /// The ban flags to exempt this player from.
  762. /// If any bit overlaps <see cref="ServerBan.ExemptFlags"/>, the ban is ignored.
  763. /// </summary>
  764. public ServerBanExemptFlags Flags { get; set; }
  765. }
  766. [Table("connection_log")]
  767. public class ConnectionLog
  768. {
  769. public int Id { get; set; }
  770. public Guid UserId { get; set; }
  771. public string UserName { get; set; } = null!;
  772. public DateTime Time { get; set; }
  773. public IPAddress Address { get; set; } = null!;
  774. public TypedHwid? HWId { get; set; }
  775. public ConnectionDenyReason? Denied { get; set; }
  776. /// <summary>
  777. /// ID of the <see cref="Server"/> that the connection was attempted to.
  778. /// </summary>
  779. /// <remarks>
  780. /// <para>
  781. /// The default value of this column is set to <c>0</c>, which is the ID of the "<c>unknown</c>" server.
  782. /// This is intended for old entries (that didn't track this) and if the server name isn't configured.
  783. /// </para>
  784. /// </remarks>
  785. public int ServerId { get; set; }
  786. public List<ServerBanHit> BanHits { get; set; } = null!;
  787. public Server Server { get; set; } = null!;
  788. public float Trust { get; set; }
  789. }
  790. public enum ConnectionDenyReason : byte
  791. {
  792. Ban = 0,
  793. Whitelist = 1,
  794. Full = 2,
  795. Panic = 3,
  796. /*
  797. * If baby jail is removed, please reserve this value for as long as can reasonably be done to prevent causing ambiguity in connection denial reasons.
  798. * Reservation by commenting out the value is likely sufficient for this purpose, but may impact projects which depend on SS14 like SS14.Admin.
  799. *
  800. * Edit: It has
  801. */
  802. BabyJail = 4,
  803. /// Results from rejected connections with external API checking tools
  804. IPChecks = 5,
  805. /// Results from rejected connections who are authenticated but have no modern hwid associated with them.
  806. NoHwid = 6
  807. }
  808. public class ServerBanHit
  809. {
  810. public int Id { get; set; }
  811. public int BanId { get; set; }
  812. public int ConnectionId { get; set; }
  813. public ServerBan Ban { get; set; } = null!;
  814. public ConnectionLog Connection { get; set; } = null!;
  815. }
  816. [Table("server_role_ban"), Index(nameof(PlayerUserId))]
  817. public sealed class ServerRoleBan : IBanCommon<ServerRoleUnban>
  818. {
  819. public int Id { get; set; }
  820. public int? RoundId { get; set; }
  821. public Round? Round { get; set; }
  822. public Guid? PlayerUserId { get; set; }
  823. [Required] public TimeSpan PlaytimeAtNote { get; set; }
  824. public NpgsqlInet? Address { get; set; }
  825. public TypedHwid? HWId { get; set; }
  826. public DateTime BanTime { get; set; }
  827. public DateTime? ExpirationTime { get; set; }
  828. public string Reason { get; set; } = null!;
  829. public NoteSeverity Severity { get; set; }
  830. [ForeignKey("CreatedBy")] public Guid? BanningAdmin { get; set; }
  831. public Player? CreatedBy { get; set; }
  832. [ForeignKey("LastEditedBy")] public Guid? LastEditedById { get; set; }
  833. public Player? LastEditedBy { get; set; }
  834. public DateTime? LastEditedAt { get; set; }
  835. public ServerRoleUnban? Unban { get; set; }
  836. public bool Hidden { get; set; }
  837. public string RoleId { get; set; } = null!;
  838. }
  839. [Table("server_role_unban")]
  840. public sealed class ServerRoleUnban : IUnbanCommon
  841. {
  842. [Column("role_unban_id")] public int Id { get; set; }
  843. public int BanId { get; set; }
  844. public ServerRoleBan Ban { get; set; } = null!;
  845. public Guid? UnbanningAdmin { get; set; }
  846. public DateTime UnbanTime { get; set; }
  847. }
  848. [Table("play_time")]
  849. public sealed class PlayTime
  850. {
  851. [Required, Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
  852. public int Id { get; set; }
  853. [Required, ForeignKey("player")]
  854. public Guid PlayerId { get; set; }
  855. public string Tracker { get; set; } = null!;
  856. public TimeSpan TimeSpent { get; set; }
  857. }
  858. [Table("uploaded_resource_log")]
  859. public sealed class UploadedResourceLog
  860. {
  861. [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
  862. public int Id { get; set; }
  863. public DateTime Date { get; set; }
  864. public Guid UserId { get; set; }
  865. public string Path { get; set; } = string.Empty;
  866. public byte[] Data { get; set; } = default!;
  867. }
  868. // Note: this interface isn't used by the game, but it *is* used by SS14.Admin.
  869. // Don't remove! Or face the consequences!
  870. public interface IAdminRemarksCommon
  871. {
  872. public int Id { get; }
  873. public int? RoundId { get; }
  874. public Round? Round { get; }
  875. public Guid? PlayerUserId { get; }
  876. public Player? Player { get; }
  877. public TimeSpan PlaytimeAtNote { get; }
  878. public string Message { get; }
  879. public Player? CreatedBy { get; }
  880. public DateTime CreatedAt { get; }
  881. public Player? LastEditedBy { get; }
  882. public DateTime? LastEditedAt { get; }
  883. public DateTime? ExpirationTime { get; }
  884. public bool Deleted { get; }
  885. }
  886. [Index(nameof(PlayerUserId))]
  887. public class AdminNote : IAdminRemarksCommon
  888. {
  889. [Required, Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; }
  890. [ForeignKey("Round")] public int? RoundId { get; set; }
  891. public Round? Round { get; set; }
  892. [ForeignKey("Player")] public Guid? PlayerUserId { get; set; }
  893. public Player? Player { get; set; }
  894. [Required] public TimeSpan PlaytimeAtNote { get; set; }
  895. [Required, MaxLength(4096)] public string Message { get; set; } = string.Empty;
  896. [Required] public NoteSeverity Severity { get; set; }
  897. [ForeignKey("CreatedBy")] public Guid? CreatedById { get; set; }
  898. public Player? CreatedBy { get; set; }
  899. [Required] public DateTime CreatedAt { get; set; }
  900. [ForeignKey("LastEditedBy")] public Guid? LastEditedById { get; set; }
  901. public Player? LastEditedBy { get; set; }
  902. [Required] public DateTime? LastEditedAt { get; set; }
  903. public DateTime? ExpirationTime { get; set; }
  904. public bool Deleted { get; set; }
  905. [ForeignKey("DeletedBy")] public Guid? DeletedById { get; set; }
  906. public Player? DeletedBy { get; set; }
  907. public DateTime? DeletedAt { get; set; }
  908. public bool Secret { get; set; }
  909. }
  910. [Index(nameof(PlayerUserId))]
  911. public class AdminWatchlist : IAdminRemarksCommon
  912. {
  913. [Required, Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; }
  914. [ForeignKey("Round")] public int? RoundId { get; set; }
  915. public Round? Round { get; set; }
  916. [ForeignKey("Player")] public Guid? PlayerUserId { get; set; }
  917. public Player? Player { get; set; }
  918. [Required] public TimeSpan PlaytimeAtNote { get; set; }
  919. [Required, MaxLength(4096)] public string Message { get; set; } = string.Empty;
  920. [ForeignKey("CreatedBy")] public Guid? CreatedById { get; set; }
  921. public Player? CreatedBy { get; set; }
  922. [Required] public DateTime CreatedAt { get; set; }
  923. [ForeignKey("LastEditedBy")] public Guid? LastEditedById { get; set; }
  924. public Player? LastEditedBy { get; set; }
  925. [Required] public DateTime? LastEditedAt { get; set; }
  926. public DateTime? ExpirationTime { get; set; }
  927. public bool Deleted { get; set; }
  928. [ForeignKey("DeletedBy")] public Guid? DeletedById { get; set; }
  929. public Player? DeletedBy { get; set; }
  930. public DateTime? DeletedAt { get; set; }
  931. }
  932. [Index(nameof(PlayerUserId))]
  933. public class AdminMessage : IAdminRemarksCommon
  934. {
  935. [Required, Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; }
  936. [ForeignKey("Round")] public int? RoundId { get; set; }
  937. public Round? Round { get; set; }
  938. [ForeignKey("Player")]
  939. public Guid? PlayerUserId { get; set; }
  940. public Player? Player { get; set; }
  941. [Required] public TimeSpan PlaytimeAtNote { get; set; }
  942. [Required, MaxLength(4096)] public string Message { get; set; } = string.Empty;
  943. [ForeignKey("CreatedBy")] public Guid? CreatedById { get; set; }
  944. public Player? CreatedBy { get; set; }
  945. [Required] public DateTime CreatedAt { get; set; }
  946. [ForeignKey("LastEditedBy")] public Guid? LastEditedById { get; set; }
  947. public Player? LastEditedBy { get; set; }
  948. public DateTime? LastEditedAt { get; set; }
  949. public DateTime? ExpirationTime { get; set; }
  950. public bool Deleted { get; set; }
  951. [ForeignKey("DeletedBy")] public Guid? DeletedById { get; set; }
  952. public Player? DeletedBy { get; set; }
  953. public DateTime? DeletedAt { get; set; }
  954. /// <summary>
  955. /// Whether the message has been seen at least once by the player.
  956. /// </summary>
  957. public bool Seen { get; set; }
  958. /// <summary>
  959. /// Whether the message has been dismissed permanently by the player.
  960. /// </summary>
  961. public bool Dismissed { get; set; }
  962. }
  963. [PrimaryKey(nameof(PlayerUserId), nameof(RoleId))]
  964. public class RoleWhitelist
  965. {
  966. [Required, ForeignKey("Player")]
  967. public Guid PlayerUserId { get; set; }
  968. public Player Player { get; set; } = default!;
  969. [Required]
  970. public string RoleId { get; set; } = default!;
  971. }
  972. /// <summary>
  973. /// Defines a template that admins can use to quickly fill out ban information.
  974. /// </summary>
  975. /// <remarks>
  976. /// <para>
  977. /// This information is not currently used by the game itself, but it is used by SS14.Admin.
  978. /// </para>
  979. /// </remarks>
  980. public sealed class BanTemplate
  981. {
  982. public int Id { get; set; }
  983. /// <summary>
  984. /// Title of the ban template. This is purely for reference by admins and not copied into the ban.
  985. /// </summary>
  986. public required string Title { get; set; }
  987. /// <summary>
  988. /// How long the ban should last. 0 for permanent.
  989. /// </summary>
  990. public TimeSpan Length { get; set; }
  991. /// <summary>
  992. /// The reason for the ban.
  993. /// </summary>
  994. /// <seealso cref="ServerBan.Reason"/>
  995. public string Reason { get; set; } = "";
  996. /// <summary>
  997. /// Exemptions granted to the ban.
  998. /// </summary>
  999. /// <seealso cref="ServerBan.ExemptFlags"/>
  1000. public ServerBanExemptFlags ExemptFlags { get; set; }
  1001. /// <summary>
  1002. /// Severity of the ban
  1003. /// </summary>
  1004. /// <seealso cref="ServerBan.Severity"/>
  1005. public NoteSeverity Severity { get; set; }
  1006. /// <summary>
  1007. /// Ban will be automatically deleted once expired.
  1008. /// </summary>
  1009. /// <seealso cref="ServerBan.AutoDelete"/>
  1010. public bool AutoDelete { get; set; }
  1011. /// <summary>
  1012. /// Ban is not visible to players in the remarks menu.
  1013. /// </summary>
  1014. /// <seealso cref="ServerBan.Hidden"/>
  1015. public bool Hidden { get; set; }
  1016. }
  1017. /// <summary>
  1018. /// A hardware ID value together with its <see cref="HwidType"/>.
  1019. /// </summary>
  1020. /// <seealso cref="ImmutableTypedHwid"/>
  1021. [Owned]
  1022. public sealed class TypedHwid
  1023. {
  1024. public byte[] Hwid { get; set; } = default!;
  1025. public HwidType Type { get; set; }
  1026. [return: NotNullIfNotNull(nameof(immutable))]
  1027. public static implicit operator TypedHwid?(ImmutableTypedHwid? immutable)
  1028. {
  1029. if (immutable == null)
  1030. return null;
  1031. return new TypedHwid
  1032. {
  1033. Hwid = immutable.Hwid.ToArray(),
  1034. Type = immutable.Type,
  1035. };
  1036. }
  1037. [return: NotNullIfNotNull(nameof(hwid))]
  1038. public static implicit operator ImmutableTypedHwid?(TypedHwid? hwid)
  1039. {
  1040. if (hwid == null)
  1041. return null;
  1042. return new ImmutableTypedHwid(hwid.Hwid.ToImmutableArray(), hwid.Type);
  1043. }
  1044. }
  1045. /// <summary>
  1046. /// Cache for the IPIntel system
  1047. /// </summary>
  1048. public class IPIntelCache
  1049. {
  1050. public int Id { get; set; }
  1051. /// <summary>
  1052. /// The IP address (duh). This is made unique manually for psql cause of ef core bug.
  1053. /// </summary>
  1054. public IPAddress Address { get; set; } = null!;
  1055. /// <summary>
  1056. /// Date this record was added. Used to check if our cache is out of date.
  1057. /// </summary>
  1058. public DateTime Time { get; set; }
  1059. /// <summary>
  1060. /// The score IPIntel returned
  1061. /// </summary>
  1062. public float Score { get; set; }
  1063. }
  1064. }