From 59b3dc6f68c37285411bd32ce4d4c2aa895c628f Mon Sep 17 00:00:00 2001 From: Laura Hausmann Date: Fri, 4 Feb 2022 04:48:04 +0100 Subject: [PATCH] Add migrations, add private streams --- .gitignore | 1 + Controllers/RtmpAuthController.cs | 38 +++++----- DataModels/AppDb.cs | 47 ++++++------ DataModels/Tables/DbInfo.cs | 9 +++ DataModels/Tables/User.cs | 32 ++++---- Migrations.cs | 75 +++++++++++++++++++ Pages/Dashboard.cshtml | 67 +++++++++++++++-- Pages/Dashboard.cshtml.cs | 120 +++++++++++++++--------------- Pages/Index.cshtml | 8 +- Pages/Register.cshtml.cs | 68 +++++++++-------- Pages/profile.cshtml | 2 +- Program.cs | 50 ++++++------- 12 files changed, 333 insertions(+), 184 deletions(-) create mode 100644 DataModels/Tables/DbInfo.cs create mode 100644 Migrations.cs diff --git a/.gitignore b/.gitignore index b901252..46d0fe9 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ obj/ /packages/ /data/ app.db +app.db.bak diff --git a/Controllers/RtmpAuthController.cs b/Controllers/RtmpAuthController.cs index b979eb1..55db290 100644 --- a/Controllers/RtmpAuthController.cs +++ b/Controllers/RtmpAuthController.cs @@ -2,26 +2,26 @@ using Microsoft.AspNetCore.Mvc; using RTMPDash.DataModels; -namespace RTMPDash.Controllers { - [ApiController, Route("/api/authenticate")] - public class RtmpAuthController : ControllerBase { - [HttpGet] - public string Get() { - var db = new AppDb.DbConn(); - if (!db.Users.Any(p => p.StreamKey == Request.Query["name"])) { - Response.StatusCode = 403; - return "unauthorized"; - } +namespace RTMPDash.Controllers; - var user = db.Users.FirstOrDefault(p => p.StreamKey == Request.Query["name"]); - - Response.Headers.Add("x-rtmp-user", user!.Username); - - if (user.AllowRestream && !string.IsNullOrWhiteSpace(user.RestreamTargets)) - Response.Headers.Add("x-rtmp-target", user.RestreamTargets); - - Response.StatusCode = 302; - return "authorized as " + user!.Username; +[ApiController, Route("/api/authenticate")] +public class RtmpAuthController : ControllerBase { + [HttpGet] + public string Get() { + var db = new AppDb.DbConn(); + if (!db.Users.Any(p => p.StreamKey == Request.Query["name"])) { + Response.StatusCode = 403; + return "unauthorized"; } + + var user = db.Users.FirstOrDefault(p => p.StreamKey == Request.Query["name"]); + + Response.Headers.Add("x-rtmp-user", user!.IsPrivate ? user!.PrivateAccessKey : user!.Username); + + if (user.AllowRestream && !string.IsNullOrWhiteSpace(user.RestreamTargets)) + Response.Headers.Add("x-rtmp-target", user.RestreamTargets); + + Response.StatusCode = 302; + return "authorized as " + user!.Username; } } \ No newline at end of file diff --git a/DataModels/AppDb.cs b/DataModels/AppDb.cs index 9fe2300..2fe2ce2 100644 --- a/DataModels/AppDb.cs +++ b/DataModels/AppDb.cs @@ -5,35 +5,32 @@ using LinqToDB.Configuration; using LinqToDB.Data; using RTMPDash.DataModels.Tables; -namespace RTMPDash.DataModels { - public class AppDb { - public class ConnectionStringSettings : IConnectionStringSettings { - public string ConnectionString { get; set; } - public string Name { get; set; } - public string ProviderName { get; set; } - public bool IsGlobal => false; - } +namespace RTMPDash.DataModels; - public class Settings : ILinqToDBSettings { - public IEnumerable DataProviders => Enumerable.Empty(); +public class AppDb { + public class ConnectionStringSettings : IConnectionStringSettings { + public string ConnectionString { get; set; } + public string Name { get; set; } + public string ProviderName { get; set; } + public bool IsGlobal => false; + } - public string DefaultConfiguration => "SQLite"; - public string DefaultDataProvider => "SQLite"; + public class Settings : ILinqToDBSettings { + public IEnumerable DataProviders => Enumerable.Empty(); - public IEnumerable ConnectionStrings { - get { - yield return new ConnectionStringSettings { - Name = "db", ProviderName = "SQLite", ConnectionString = @"Data Source=app.db;" - }; - } - } - } + public string DefaultConfiguration => "SQLite"; + public string DefaultDataProvider => "SQLite"; - public class DbConn : DataConnection { - public DbConn() : base("db") { } - - public ITable Users => GetTable(); - public ITable Invites => GetTable(); + public IEnumerable ConnectionStrings { + get { yield return new ConnectionStringSettings { Name = "db", ProviderName = "SQLite", ConnectionString = @"Data Source=app.db;" }; } } } + + public class DbConn : DataConnection { + public DbConn() : base("db") { } + + public ITable Users => GetTable(); + public ITable Invites => GetTable(); + public ITable DbInfo => GetTable(); + } } \ No newline at end of file diff --git a/DataModels/Tables/DbInfo.cs b/DataModels/Tables/DbInfo.cs new file mode 100644 index 0000000..cc06981 --- /dev/null +++ b/DataModels/Tables/DbInfo.cs @@ -0,0 +1,9 @@ +using LinqToDB.Mapping; + +namespace RTMPDash.DataModels.Tables; + +[Table(Name = "DbInfo")] +public class DbInfo { + [Column(Name = "ID"), PrimaryKey, Identity, NotNull] public int Id { get; set; } + [Column(Name = "DbVer"), NotNull] public int DbVer { get; set; } +} \ No newline at end of file diff --git a/DataModels/Tables/User.cs b/DataModels/Tables/User.cs index 873f554..7644938 100644 --- a/DataModels/Tables/User.cs +++ b/DataModels/Tables/User.cs @@ -1,18 +1,20 @@ using LinqToDB.Mapping; -namespace RTMPDash.DataModels.Tables { - [Table(Name = "Users")] - public class User { - [Column(Name = "Username"), PrimaryKey] public string Username { get; set; } - [Column(Name = "Password")] public string Password { get; set; } - [Column(Name = "IsAdmin")] public bool IsAdmin { get; set; } - [Column(Name = "AllowRestream")] public bool AllowRestream { get; set; } - [Column(Name = "StreamKey")] public string StreamKey { get; set; } - [Column(Name = "PronounSubject")] public string PronounSubject { get; set; } - [Column(Name = "PronounPossessive")] public string PronounPossessive { get; set; } - [Column(Name = "ChatUrl")] public string ChatUrl { get; set; } - [Column(Name = "AnnouncementUrl")] public string AnnouncementUrl { get; set; } - [Column(Name = "RestreamTargets")] public string RestreamTargets { get; set; } - [Column(Name = "RestreamUrls")] public string RestreamUrls { get; set; } - } +namespace RTMPDash.DataModels.Tables; + +[Table(Name = "Users")] +public class User { + [Column(Name = "Username"), PrimaryKey] public string Username { get; set; } + [Column(Name = "Password")] public string Password { get; set; } + [Column(Name = "IsAdmin")] public bool IsAdmin { get; set; } + [Column(Name = "AllowRestream")] public bool AllowRestream { get; set; } + [Column(Name = "StreamKey")] public string StreamKey { get; set; } + [Column(Name = "PronounSubject")] public string PronounSubject { get; set; } + [Column(Name = "PronounPossessive")] public string PronounPossessive { get; set; } + [Column(Name = "ChatUrl")] public string ChatUrl { get; set; } + [Column(Name = "AnnouncementUrl")] public string AnnouncementUrl { get; set; } + [Column(Name = "RestreamTargets")] public string RestreamTargets { get; set; } + [Column(Name = "RestreamUrls")] public string RestreamUrls { get; set; } + [Column(Name = "IsPrivate")] public bool IsPrivate { get; set; } + [Column(Name = "PrivateAccessKey")] public string PrivateAccessKey { get; set; } } \ No newline at end of file diff --git a/Migrations.cs b/Migrations.cs new file mode 100644 index 0000000..88bd309 --- /dev/null +++ b/Migrations.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using LinqToDB; +using LinqToDB.Data; +using RTMPDash.DataModels; +using RTMPDash.DataModels.Tables; + +namespace RTMPDash; + +/** + * TODO: Initial migrations should create all tables & an admin user and print a randomly generated password for the admin user + * * + */ +public static class Migrations { + public const int DbVer = 1; + + private static readonly List _migrations = new() { + new Migration(1, "ALTER TABLE Users ADD IsPrivate INTEGER DEFAULT 0 NOT NULL"), new Migration(1, "ALTER TABLE Users ADD PrivateAccessKey TEXT") + }; + + public static void RunMigrations() { + using var db = new AppDb.DbConn(); + + if (db.DataProvider.GetSchemaProvider().GetSchema(db).Tables.All(t => t.TableName != "DbInfo")) { + db.CreateTable(); + db.InsertWithIdentity(new DbInfo { DbVer = 0 }); + } + + var dbinfo = db.DbInfo.First(); + var ccolor = Console.ForegroundColor; + + Console.ForegroundColor = ConsoleColor.Yellow; + Console.WriteLine($"Database version: {db.DbInfo.ToList().First().DbVer}"); + + var migrationsToRun = _migrations.FindAll(p => p.IntroducedWithDbVer > db.DbInfo.First().DbVer); + if (migrationsToRun.Count == 0) { + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine("No migrations to run."); + } + else { + migrationsToRun.ForEach(RunMigration); + dbinfo.DbVer = DbVer; + db.Update(dbinfo); + Console.ForegroundColor = ConsoleColor.DarkGreen; + Console.WriteLine($"Database version is now: {DbVer}"); + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine("Finished running migrations."); + } + + Console.ForegroundColor = ccolor; + } + + private static void RunMigration(Migration mig) => mig.Run(); + + private class Migration { + private readonly string _sql; + public readonly int IntroducedWithDbVer; + + public Migration(int introducedWithDbVer, string sql) { + IntroducedWithDbVer = introducedWithDbVer; + _sql = sql; + } + + public void Run() { + using var db = new AppDb.DbConn(); + + Console.ForegroundColor = ConsoleColor.DarkCyan; + Console.Write("Running migration: "); + Console.ForegroundColor = ConsoleColor.Yellow; + Console.WriteLine(_sql); + db.Execute(_sql); + } + } +} \ No newline at end of file diff --git a/Pages/Dashboard.cshtml b/Pages/Dashboard.cshtml index f169f30..9e881c7 100644 --- a/Pages/Dashboard.cshtml +++ b/Pages/Dashboard.cshtml @@ -24,11 +24,29 @@ else {

Thanks for using @Program.SiteName. If you have any issues, please contact me on @Html.Raw(Program.ContactInfo)


Please subscribe to the Service Announcements Channel to get informed about maintenance and other important things.

-
-

When using OBS, the stream key and leading slash before it is to be left out in the RTMP url.


For low-latancy streams, please set your keyframe interval to 1-2 seconds. Otherwise, automatic or something in the range of 4-8 seconds is fine.

+ if (StreamUtils.ListLiveUsers().Contains(user.Username) && user.IsPrivate) { + + } + else if (StreamUtils.ListLiveUsers().Contains(user.PrivateAccessKey) && !user.IsPrivate) { + + }
Profile URL @@ -42,10 +60,21 @@ else {
Player URL
- -
- -
+ @if (user.IsPrivate) { + +
+ + +
+ } + else { + +
+ + +
+ } +
@@ -61,9 +90,10 @@ else {
Stream URL
- +
- @if (StreamUtils.IsLive(user.Username, stats)) { + @* ReSharper disable once ConvertIfStatementToSwitchStatement *@ + @if (!user.IsPrivate && StreamUtils.IsLive(user.Username, stats)) { var uptime = TimeSpan.FromMilliseconds(StreamUtils.GetClientTime(user.Username, stats)).StripMilliseconds(); if (user.AllowRestream && !string.IsNullOrWhiteSpace(user.RestreamTargets)) { if (StreamUtils.GetClientTime(user.Username, stats) > 5000) { @@ -84,6 +114,27 @@ else { } } + else if (user.IsPrivate && StreamUtils.IsLive(user.PrivateAccessKey, stats)) { + var uptime = TimeSpan.FromMilliseconds(StreamUtils.GetClientTime(user.PrivateAccessKey, stats)).StripMilliseconds(); + if (user.AllowRestream && !string.IsNullOrWhiteSpace(user.RestreamTargets)) { + if (StreamUtils.GetClientTime(user.Username, stats) > 5000) { + var restreams = StreamUtils.CountLiveRestreams(user.PrivateAccessKey, stats); + if (restreams > 0) { + + } + else { + + } + } + else { + + } + } + else { + + } + + } else { } diff --git a/Pages/Dashboard.cshtml.cs b/Pages/Dashboard.cshtml.cs index b546189..3f0490f 100644 --- a/Pages/Dashboard.cshtml.cs +++ b/Pages/Dashboard.cshtml.cs @@ -5,80 +5,84 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.RazorPages; using RTMPDash.DataModels; -namespace RTMPDash.Pages { - public class DashboardModel : PageModel { - public void OnGet() { } +namespace RTMPDash.Pages; - public void OnPost() { - if (!Request.HasFormContentType - || string.IsNullOrWhiteSpace(Request.Form["action"]) - || string.IsNullOrWhiteSpace(HttpContext.Session.GetString("authenticatedUser"))) - return; +public class DashboardModel : PageModel { + public void OnGet() { } - using var db = new AppDb.DbConn(); - var user = db.Users.FirstOrDefault(p => p.Username == HttpContext.Session.GetString("authenticatedUser")); + public void OnPost() { + if (!Request.HasFormContentType || string.IsNullOrWhiteSpace(Request.Form["action"]) || string.IsNullOrWhiteSpace(HttpContext.Session.GetString("authenticatedUser"))) + return; - if (Request.Form["action"] == "password_change") { - var newPass = Request.Form["pass"]; - user!.Password = newPass.ToString().Sha256(); - db.Update(user); - Response.Redirect("/Logout"); - } + using var db = new AppDb.DbConn(); + var user = db.Users.FirstOrDefault(p => p.Username == HttpContext.Session.GetString("authenticatedUser")); - if (Request.Form["action"] == "chaturl_set") { - user!.ChatUrl = Request.Form["value"]; + if (Request.Form["action"] == "password_change") { + var newPass = Request.Form["pass"]; + user!.Password = newPass.ToString().Sha256(); + db.Update(user); + Response.Redirect("/Logout"); + } + + if (Request.Form["action"] == "chaturl_set") { + user!.ChatUrl = Request.Form["value"]; + db.Update(user); + Response.Redirect("/Dashboard"); + } + + if (Request.Form["action"] == "announceurl_set") { + user!.AnnouncementUrl = Request.Form["value"]; + db.Update(user); + Response.Redirect("/Dashboard"); + } + + if (user!.AllowRestream) { + if (Request.Form["action"] == "restream_urls_set") { + user!.RestreamUrls = Request.Form["value"]; db.Update(user); Response.Redirect("/Dashboard"); } - if (Request.Form["action"] == "announceurl_set") { - user!.AnnouncementUrl = Request.Form["value"]; + if (Request.Form["action"] == "restream_targets_set") { + var newtgts = Request.Form["value"].ToString(); + if (newtgts.Contains("localhost") || newtgts.Contains("127.0.0.1") || newtgts.Contains(user.StreamKey)) + return; + + user!.RestreamTargets = newtgts; db.Update(user); Response.Redirect("/Dashboard"); } + } - if (user!.AllowRestream) { - if (Request.Form["action"] == "restream_urls_set") { - user!.RestreamUrls = Request.Form["value"]; - db.Update(user); - Response.Redirect("/Dashboard"); - } + if (Request.Form["action"] == "pronoun_subj_set") { + var target = string.IsNullOrWhiteSpace(Request.Form["value"]) ? "they" : Request.Form["value"].ToString(); + user!.PronounSubject = target.ToLowerInvariant(); + db.Update(user); + Response.Redirect("/Dashboard"); + } - if (Request.Form["action"] == "restream_targets_set") { - var newtgts = Request.Form["value"].ToString(); - if (newtgts.Contains("localhost") - || newtgts.Contains("127.0.0.1") - || newtgts.Contains(user.StreamKey)) - return; + if (Request.Form["action"] == "pronoun_poss_set") { + var target = string.IsNullOrWhiteSpace(Request.Form["value"]) ? "their" : Request.Form["value"].ToString(); + user!.PronounPossessive = target.ToLowerInvariant(); + db.Update(user); + Response.Redirect("/Dashboard"); + } - user!.RestreamTargets = newtgts; - db.Update(user); - Response.Redirect("/Dashboard"); - } + if (Request.Form["action"] == "streamkey_reset") { + user!.StreamKey = Guid.NewGuid().ToString(); + db.Update(user); + } + + if (Request.Form["action"] == "private_toggle") { + if (user.IsPrivate) { + user!.IsPrivate = false; + } + else { + user!.PrivateAccessKey = Guid.NewGuid().ToString(); + user!.IsPrivate = true; } - if (Request.Form["action"] == "pronoun_subj_set") { - var target = string.IsNullOrWhiteSpace(Request.Form["value"]) - ? "they" - : Request.Form["value"].ToString(); - user!.PronounSubject = target.ToLowerInvariant(); - db.Update(user); - Response.Redirect("/Dashboard"); - } - - if (Request.Form["action"] == "pronoun_poss_set") { - var target = string.IsNullOrWhiteSpace(Request.Form["value"]) - ? "their" - : Request.Form["value"].ToString(); - user!.PronounPossessive = target.ToLowerInvariant(); - db.Update(user); - Response.Redirect("/Dashboard"); - } - - if (Request.Form["action"] == "streamkey_reset") { - user!.StreamKey = Guid.NewGuid().ToString(); - db.Update(user); - } + db.Update(user); } } } \ No newline at end of file diff --git a/Pages/Index.cshtml b/Pages/Index.cshtml index db545f7..13a11af 100644 --- a/Pages/Index.cshtml +++ b/Pages/Index.cshtml @@ -1,13 +1,17 @@ @page +@using RTMPDash.DataModels @model IndexModel @{ ViewData["Title"] = "Home"; - var liveUsers = StreamUtils.ListLiveUsers(); + var db = new AppDb.DbConn(); + var allStreams = StreamUtils.ListLiveUsers(); + var allUsers = db.Users.Where(p => !p.IsPrivate).Select(p => p.Username); + var liveUsers = allStreams.Intersect(allUsers); }

Welcome

- @if (liveUsers.Count > 0) { + @if (liveUsers.Any()) {

The following users are currently live:

@foreach (var user in liveUsers) { diff --git a/Pages/Register.cshtml.cs b/Pages/Register.cshtml.cs index 7bf0948..7b3b2c1 100644 --- a/Pages/Register.cshtml.cs +++ b/Pages/Register.cshtml.cs @@ -6,40 +6,46 @@ using Microsoft.AspNetCore.Mvc.RazorPages; using RTMPDash.DataModels; using RTMPDash.DataModels.Tables; -namespace RTMPDash.Pages { - public class RegisterModel : PageModel { - public void OnPost() { - if (!Request.HasFormContentType - || string.IsNullOrWhiteSpace(Request.Form["user"]) - || string.IsNullOrWhiteSpace(Request.Form["pass"]) - || string.IsNullOrWhiteSpace(Request.Form["code"])) - return; +namespace RTMPDash.Pages; - using var db = new AppDb.DbConn(); - if (!db.Invites.Any(p => p.Code == Request.Form["code"])) - return; +public class RegisterModel : PageModel { + public void OnPost() { + if (!Request.HasFormContentType + || string.IsNullOrWhiteSpace(Request.Form["user"]) + || string.IsNullOrWhiteSpace(Request.Form["pass"]) + || string.IsNullOrWhiteSpace(Request.Form["code"])) + return; - var user = db.Users.FirstOrDefault(p => p.Username == Request.Form["user"].ToString()); - if (user != null) { - //user already exists - Response.Redirect("/Register?e=user_exists"); - return; - } + using var db = new AppDb.DbConn(); + if (!db.Invites.Any(p => p.Code == Request.Form["code"])) + return; - user = new User { - Username = Request.Form["user"].ToString(), - Password = Request.Form["pass"].ToString().Sha256(), - StreamKey = Guid.NewGuid().ToString(), - PronounSubject = "they", - PronounPossessive = "their", - AllowRestream = true - }; - - db.Insert(user); - - db.Delete(db.Invites.First(p => p.Code == Request.Form["code"])); - - HttpContext.Session.SetString("authenticatedUser", user.Username); + var user = db.Users.FirstOrDefault(p => p.Username == Request.Form["user"].ToString()); + if (user != null) { + //user already exists + Response.Redirect("/Register?e=user_exists"); + return; } + + if (db.Users.Any(p => p.StreamKey == Request.Form["user"] || p.PrivateAccessKey == Request.Form["user"])) { + //user invalid + Response.Redirect("/Register?e=user_invalid"); + return; + } + + user = new User { + Username = Request.Form["user"].ToString(), + Password = Request.Form["pass"].ToString().Sha256(), + StreamKey = Guid.NewGuid().ToString(), + PronounSubject = "they", + PronounPossessive = "their", + AllowRestream = true + }; + + db.Insert(user); + + db.Delete(db.Invites.First(p => p.Code == Request.Form["code"])); + + HttpContext.Session.SetString("authenticatedUser", user.Username); } } \ No newline at end of file diff --git a/Pages/profile.cshtml b/Pages/profile.cshtml index 34d6cd7..bfabe65 100644 --- a/Pages/profile.cshtml +++ b/Pages/profile.cshtml @@ -10,7 +10,7 @@ } var user = db.Users.First(p => p.Username == Model.User); var stats = StreamUtils.GetStatsObject(); - var live = StreamUtils.IsLive(user.Username, stats); + var live = StreamUtils.IsLive(user.Username, stats) && !user.IsPrivate; Stream stream = null; if (live) { stream = stats.Server.Applications.First(p => p.Name == "ingress").MethodLive.Streams.FirstOrDefault(p => p.Name == user.Username); diff --git a/Program.cs b/Program.cs index a500c76..0b5ed00 100644 --- a/Program.cs +++ b/Program.cs @@ -5,34 +5,34 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Hosting; using RTMPDash.DataModels; -namespace RTMPDash { - public class Program { - public const string SiteName = "chaos.stream"; - public const string IngressDomain = "rtmp://chaos.stream"; - public const string RootDomain = "https://chaos.stream"; - public const string PlayerDomain = "https://live.on.chaos.stream"; - public const string FragmentDomain = "https://cdn.chaos.stream"; - public const string StatsDomain = "https://stats.chaos.stream"; - public const string PrivacyEmail = "chaosstream-privacy@zotan.email"; - public const string CopyrightEmail = "chaosstream-copyright@zotan.email"; - public const string AbuseEmail = "chaosstream-abuse@zotan.email"; - public const string ServiceAnnouncementUrl = "https://t.me/chaosstream"; - public const string ServiceStatusUrl = "https://status.chaos.stream"; +namespace RTMPDash; - public const string ContactInfo = - "Telegram, Threema, or via email."; +public class Program { + public const string SiteName = "chaos.stream"; + public const string IngressDomain = "rtmp://chaos.stream"; + public const string RootDomain = "https://chaos.stream"; + public const string PlayerDomain = "https://live.on.chaos.stream"; + public const string FragmentDomain = "https://cdn.chaos.stream"; + public const string StatsDomain = "https://stats.chaos.stream"; + public const string PrivacyEmail = "chaosstream-privacy@zotan.email"; + public const string CopyrightEmail = "chaosstream-copyright@zotan.email"; + public const string AbuseEmail = "chaosstream-abuse@zotan.email"; + public const string ServiceAnnouncementUrl = "https://t.me/chaosstream"; + public const string ServiceStatusUrl = "https://status.chaos.stream"; - public static void Main(string[] args) { - DataConnection.DefaultSettings = new AppDb.Settings(); - ThreadPool.SetMinThreads(100, 100); - CreateHostBuilder(args).Build().Run(); - } + public const string ContactInfo = + "Telegram, Threema, or via email."; - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); + public static void Main(string[] args) { + DataConnection.DefaultSettings = new AppDb.Settings(); + ThreadPool.SetMinThreads(100, 100); + Migrations.RunMigrations(); + CreateHostBuilder(args).Build().Run(); } - public static class TimeExtensions { - public static TimeSpan StripMilliseconds(this TimeSpan time) => new(time.Days, time.Hours, time.Minutes, time.Seconds); - } + public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); +} + +public static class TimeExtensions { + public static TimeSpan StripMilliseconds(this TimeSpan time) => new(time.Days, time.Hours, time.Minutes, time.Seconds); } \ No newline at end of file