Add migrations, add private streams
This commit is contained in:
parent
cade2df6a5
commit
59b3dc6f68
|
@ -4,3 +4,4 @@ obj/
|
|||
/packages/
|
||||
/data/
|
||||
app.db
|
||||
app.db.bak
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
using RTMPDash.DataModels;
|
||||
|
||||
namespace RTMPDash.Controllers {
|
||||
namespace RTMPDash.Controllers;
|
||||
|
||||
[ApiController, Route("/api/authenticate")]
|
||||
public class RtmpAuthController : ControllerBase {
|
||||
[HttpGet]
|
||||
|
@ -15,7 +16,7 @@ namespace RTMPDash.Controllers {
|
|||
|
||||
var user = db.Users.FirstOrDefault(p => p.StreamKey == Request.Query["name"]);
|
||||
|
||||
Response.Headers.Add("x-rtmp-user", user!.Username);
|
||||
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);
|
||||
|
@ -24,4 +25,3 @@ namespace RTMPDash.Controllers {
|
|||
return "authorized as " + user!.Username;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,7 +5,8 @@ using LinqToDB.Configuration;
|
|||
using LinqToDB.Data;
|
||||
using RTMPDash.DataModels.Tables;
|
||||
|
||||
namespace RTMPDash.DataModels {
|
||||
namespace RTMPDash.DataModels;
|
||||
|
||||
public class AppDb {
|
||||
public class ConnectionStringSettings : IConnectionStringSettings {
|
||||
public string ConnectionString { get; set; }
|
||||
|
@ -21,11 +22,7 @@ namespace RTMPDash.DataModels {
|
|||
public string DefaultDataProvider => "SQLite";
|
||||
|
||||
public IEnumerable<IConnectionStringSettings> ConnectionStrings {
|
||||
get {
|
||||
yield return new ConnectionStringSettings {
|
||||
Name = "db", ProviderName = "SQLite", ConnectionString = @"Data Source=app.db;"
|
||||
};
|
||||
}
|
||||
get { yield return new ConnectionStringSettings { Name = "db", ProviderName = "SQLite", ConnectionString = @"Data Source=app.db;" }; }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -34,6 +31,6 @@ namespace RTMPDash.DataModels {
|
|||
|
||||
public ITable<User> Users => GetTable<User>();
|
||||
public ITable<Invite> Invites => GetTable<Invite>();
|
||||
}
|
||||
public ITable<DbInfo> DbInfo => GetTable<DbInfo>();
|
||||
}
|
||||
}
|
|
@ -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; }
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
using LinqToDB.Mapping;
|
||||
|
||||
namespace RTMPDash.DataModels.Tables {
|
||||
namespace RTMPDash.DataModels.Tables;
|
||||
|
||||
[Table(Name = "Users")]
|
||||
public class User {
|
||||
[Column(Name = "Username"), PrimaryKey] public string Username { get; set; }
|
||||
|
@ -14,5 +15,6 @@ namespace RTMPDash.DataModels.Tables {
|
|||
[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; }
|
||||
}
|
|
@ -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<Migration> _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<DbInfo>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -24,11 +24,29 @@ else {
|
|||
<p>Thanks for using @Program.SiteName. If you have any issues, please contact me on @Html.Raw(Program.ContactInfo)</p>
|
||||
<hr/>
|
||||
<p>Please subscribe to the <a href="@Program.ServiceAnnouncementUrl" target="_blank">Service Announcements Channel</a> to get informed about maintenance and other important things.</p>
|
||||
<hr>
|
||||
<p class="mb-0">When using OBS, the stream key and leading slash before it is to be left out in the RTMP url.</p>
|
||||
<hr/>
|
||||
<p class="mb-0">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.</p>
|
||||
</div>
|
||||
if (StreamUtils.ListLiveUsers().Contains(user.Username) && user.IsPrivate) {
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<h4 class="alert-heading">Warning!</h4>
|
||||
<p class="mb-0">
|
||||
You set your stream to private, but have not restarted your stream since.
|
||||
<br/>
|
||||
While this setting is applied immediately, the old player URL will remain accessible until you stop and restart your stream.
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
else if (StreamUtils.ListLiveUsers().Contains(user.PrivateAccessKey) && !user.IsPrivate) {
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<h4 class="alert-heading">Warning!</h4>
|
||||
<p class="mb-0">
|
||||
You set your stream to public, but have not restarted your stream since.
|
||||
<br/>
|
||||
While this setting is applied immediately, the public player will not work until you restart your stream.
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
<div class="input-group mb-3">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text" style="width:23ch">Profile URL</span>
|
||||
|
@ -42,10 +60,21 @@ else {
|
|||
<div class="input-group-prepend">
|
||||
<span class="input-group-text" style="width:23ch">Player URL</span>
|
||||
</div>
|
||||
<input type="text" class="form-control" id="input-playerurl" value="@Program.PlayerDomain/@user.Username" disabled>
|
||||
@if (user.IsPrivate) {
|
||||
<input type="text" class="form-control" id="input-playerurl" value="@Program.PlayerDomain/@user.PrivateAccessKey" disabled>
|
||||
<div class="input-group-append">
|
||||
<button onclick="ajax_and_reload('private_toggle')" class="btn btn-outline-info" role="button" id="button-toggle-private">Private</button>
|
||||
<button class="btn btn-outline-secondary" role="button" id="button-copy-playerurl" onclick="copyToClipboard(document.getElementById('input-playerurl').value);">Copy</button>
|
||||
</div>
|
||||
}
|
||||
else {
|
||||
<input type="text" class="form-control" id="input-playerurl" value="@Program.PlayerDomain/@user.Username" disabled>
|
||||
<div class="input-group-append">
|
||||
<button onclick="ajax_and_reload('private_toggle')" class="btn btn-outline-success" role="button" id="button-toggle-private">Public</button>
|
||||
<button class="btn btn-outline-secondary" role="button" id="button-copy-playerurl" onclick="copyToClipboard(document.getElementById('input-playerurl').value);">Copy</button>
|
||||
</div>
|
||||
}
|
||||
|
||||
</div>
|
||||
<div class="input-group mb-3">
|
||||
<div class="input-group-prepend">
|
||||
|
@ -61,9 +90,10 @@ else {
|
|||
<div class="input-group-prepend">
|
||||
<span class="input-group-text" style="width:23ch">Stream URL</span>
|
||||
</div>
|
||||
<input type="text" class="form-control" id="input-streamurl" value="@Program.IngressDomain/ingress/@user.StreamKey" disabled>
|
||||
<input type="text" class="form-control" id="input-streamurl" value="@Program.IngressDomain/ingress" disabled>
|
||||
<div class="input-group-append">
|
||||
@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 {
|
|||
}
|
||||
<button class="btn btn-dark" role="button" disabled>@uptime.ToString("c")</button>
|
||||
}
|
||||
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) {
|
||||
<button class="btn btn-success" role="button" style="width:20ch" disabled>Live & restreaming</button>
|
||||
}
|
||||
else {
|
||||
<button class="btn btn-warning" role="button" style="width:22ch" disabled>Live & restream down</button>
|
||||
}
|
||||
}
|
||||
else {
|
||||
<button class="btn btn-dark" role="button" style="width:13ch" disabled>Starting...</button>
|
||||
}
|
||||
}
|
||||
else {
|
||||
<button class="btn btn-success" role="button" style="width:13ch" disabled>Live</button>
|
||||
}
|
||||
<button class="btn btn-dark" role="button" disabled>@uptime.ToString("c")</button>
|
||||
}
|
||||
else {
|
||||
<button class="btn btn-danger" role="button" style="width:13ch" disabled>No data</button>
|
||||
}
|
||||
|
|
|
@ -5,14 +5,13 @@ using Microsoft.AspNetCore.Http;
|
|||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using RTMPDash.DataModels;
|
||||
|
||||
namespace RTMPDash.Pages {
|
||||
namespace RTMPDash.Pages;
|
||||
|
||||
public class DashboardModel : PageModel {
|
||||
public void OnGet() { }
|
||||
|
||||
public void OnPost() {
|
||||
if (!Request.HasFormContentType
|
||||
|| string.IsNullOrWhiteSpace(Request.Form["action"])
|
||||
|| string.IsNullOrWhiteSpace(HttpContext.Session.GetString("authenticatedUser")))
|
||||
if (!Request.HasFormContentType || string.IsNullOrWhiteSpace(Request.Form["action"]) || string.IsNullOrWhiteSpace(HttpContext.Session.GetString("authenticatedUser")))
|
||||
return;
|
||||
|
||||
using var db = new AppDb.DbConn();
|
||||
|
@ -46,9 +45,7 @@ namespace RTMPDash.Pages {
|
|||
|
||||
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))
|
||||
if (newtgts.Contains("localhost") || newtgts.Contains("127.0.0.1") || newtgts.Contains(user.StreamKey))
|
||||
return;
|
||||
|
||||
user!.RestreamTargets = newtgts;
|
||||
|
@ -58,18 +55,14 @@ namespace RTMPDash.Pages {
|
|||
}
|
||||
|
||||
if (Request.Form["action"] == "pronoun_subj_set") {
|
||||
var target = string.IsNullOrWhiteSpace(Request.Form["value"])
|
||||
? "they"
|
||||
: Request.Form["value"].ToString();
|
||||
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();
|
||||
var target = string.IsNullOrWhiteSpace(Request.Form["value"]) ? "their" : Request.Form["value"].ToString();
|
||||
user!.PronounPossessive = target.ToLowerInvariant();
|
||||
db.Update(user);
|
||||
Response.Redirect("/Dashboard");
|
||||
|
@ -79,6 +72,17 @@ namespace RTMPDash.Pages {
|
|||
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;
|
||||
}
|
||||
|
||||
db.Update(user);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
<div class="text-center">
|
||||
<h1 class="display-4">Welcome</h1>
|
||||
@if (liveUsers.Count > 0) {
|
||||
@if (liveUsers.Any()) {
|
||||
<p>The following users are currently live:</p>
|
||||
<div class="btn-group btn-group" role="group">
|
||||
@foreach (var user in liveUsers) {
|
||||
|
|
|
@ -6,7 +6,8 @@ using Microsoft.AspNetCore.Mvc.RazorPages;
|
|||
using RTMPDash.DataModels;
|
||||
using RTMPDash.DataModels.Tables;
|
||||
|
||||
namespace RTMPDash.Pages {
|
||||
namespace RTMPDash.Pages;
|
||||
|
||||
public class RegisterModel : PageModel {
|
||||
public void OnPost() {
|
||||
if (!Request.HasFormContentType
|
||||
|
@ -26,6 +27,12 @@ namespace RTMPDash.Pages {
|
|||
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(),
|
||||
|
@ -42,4 +49,3 @@ namespace RTMPDash.Pages {
|
|||
HttpContext.Session.SetString("authenticatedUser", user.Username);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -5,7 +5,8 @@ using Microsoft.AspNetCore.Hosting;
|
|||
using Microsoft.Extensions.Hosting;
|
||||
using RTMPDash.DataModels;
|
||||
|
||||
namespace RTMPDash {
|
||||
namespace RTMPDash;
|
||||
|
||||
public class Program {
|
||||
public const string SiteName = "chaos.stream";
|
||||
public const string IngressDomain = "rtmp://chaos.stream";
|
||||
|
@ -25,14 +26,13 @@ namespace RTMPDash {
|
|||
public static void Main(string[] args) {
|
||||
DataConnection.DefaultSettings = new AppDb.Settings();
|
||||
ThreadPool.SetMinThreads(100, 100);
|
||||
Migrations.RunMigrations();
|
||||
CreateHostBuilder(args).Build().Run();
|
||||
}
|
||||
|
||||
public static IHostBuilder CreateHostBuilder(string[] args) =>
|
||||
Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); });
|
||||
public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); });
|
||||
}
|
||||
|
||||
public static class TimeExtensions {
|
||||
public static TimeSpan StripMilliseconds(this TimeSpan time) => new(time.Days, time.Hours, time.Minutes, time.Seconds);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue