TOS
Bugfix stream editor
This commit is contained in:
@ -1,18 +1,20 @@
|
||||
using Newtonsoft.Json;
|
||||
using Nostr.Client.Messages;
|
||||
|
||||
namespace NostrStreamer.ApiModel;
|
||||
|
||||
public class Account
|
||||
{
|
||||
[JsonProperty("event")]
|
||||
public NostrEvent? Event { get; init; }
|
||||
public PatchEvent? Event { get; init; }
|
||||
|
||||
[JsonProperty("endpoints")]
|
||||
public List<AccountEndpoint> Endpoints { get; init; } = new();
|
||||
|
||||
[JsonProperty("balance")]
|
||||
public long Balance { get; init; }
|
||||
|
||||
[JsonProperty("tos")]
|
||||
public AccountTos Tos { get; init; }
|
||||
}
|
||||
|
||||
public class AccountEndpoint
|
||||
@ -42,15 +44,11 @@ public class EndpointCost
|
||||
public string Unit { get; init; } = null!;
|
||||
}
|
||||
|
||||
[Obsolete("Use EndpointCost")]
|
||||
public class AccountQuota
|
||||
public class AccountTos
|
||||
{
|
||||
[JsonProperty("rate")]
|
||||
public double Rate { get; init; }
|
||||
[JsonProperty("accepted")]
|
||||
public bool Accepted { get; init; }
|
||||
|
||||
[JsonProperty("unit")]
|
||||
public string Unit { get; init; } = null!;
|
||||
|
||||
[JsonProperty("remaining")]
|
||||
public long Remaining { get; init; }
|
||||
[JsonProperty("link")]
|
||||
public Uri Link { get; init; } = null!;
|
||||
}
|
9
NostrStreamer/ApiModel/PatchAccount.cs
Normal file
9
NostrStreamer/ApiModel/PatchAccount.cs
Normal file
@ -0,0 +1,9 @@
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace NostrStreamer.ApiModel;
|
||||
|
||||
public class PatchAccount
|
||||
{
|
||||
[JsonProperty("accept_tos")]
|
||||
public bool AcceptTos { get; init; }
|
||||
}
|
@ -38,6 +38,8 @@ public class Config
|
||||
public LndConfig Lnd { get; init; } = null!;
|
||||
|
||||
public S3BlobConfig DvrStore { get; init; } = null!;
|
||||
|
||||
public DateTime TosDate { get; init; }
|
||||
}
|
||||
|
||||
public class LndConfig
|
||||
|
@ -43,16 +43,16 @@ public class NostrController : Controller
|
||||
.AsNoTracking()
|
||||
.ToListAsync();
|
||||
|
||||
var latestEvent = await _db.Streams
|
||||
.AsNoTracking()
|
||||
.Where(a => a.User.PubKey == user.PubKey)
|
||||
.OrderByDescending(a => a.Starts)
|
||||
.Select(a => a.Event)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
var account = new Account
|
||||
{
|
||||
Event = !string.IsNullOrEmpty(latestEvent) ? JsonConvert.DeserializeObject<NostrEvent>(latestEvent, NostrSerializer.Settings) : null,
|
||||
Event = new PatchEvent()
|
||||
{
|
||||
Title = user.Title ?? "",
|
||||
Summary = user.Summary ?? "",
|
||||
Image = user.Image ?? "",
|
||||
ContentWarning = user.ContentWarning,
|
||||
Tags = user.SplitTags()
|
||||
},
|
||||
Endpoints = endpoints.Select(a => new AccountEndpoint
|
||||
{
|
||||
Name = a.Name,
|
||||
@ -65,7 +65,12 @@ public class NostrController : Controller
|
||||
Rate = a.Cost / 1000d
|
||||
}
|
||||
}).ToList(),
|
||||
Balance = (long)Math.Floor(user.Balance / 1000m)
|
||||
Balance = (long)Math.Floor(user.Balance / 1000m),
|
||||
Tos = new()
|
||||
{
|
||||
Accepted = user.TosAccepted >= _config.TosDate,
|
||||
Link = new Uri(_config.ApiHost, "/tos")
|
||||
}
|
||||
};
|
||||
|
||||
return Content(JsonConvert.SerializeObject(account, NostrSerializer.Settings), "application/json");
|
||||
@ -77,8 +82,17 @@ public class NostrController : Controller
|
||||
var pubkey = GetPubKey();
|
||||
if (string.IsNullOrEmpty(pubkey)) return Unauthorized();
|
||||
|
||||
var streamManager = await _streamManagerFactory.ForCurrentStream(pubkey);
|
||||
await streamManager.PatchEvent(req.Title, req.Summary, req.Image, req.Tags, req.ContentWarning);
|
||||
await _userService.UpdateStreamInfo(pubkey, req);
|
||||
try
|
||||
{
|
||||
var streamManager = await _streamManagerFactory.ForCurrentStream(pubkey);
|
||||
await streamManager.UpdateEvent();
|
||||
}
|
||||
catch
|
||||
{
|
||||
//ignore
|
||||
}
|
||||
|
||||
return Accepted();
|
||||
}
|
||||
|
||||
@ -95,6 +109,23 @@ public class NostrController : Controller
|
||||
});
|
||||
}
|
||||
|
||||
[HttpPatch("account")]
|
||||
public async Task<IActionResult> PatchAccount([FromBody] PatchAccount patch)
|
||||
{
|
||||
var user = await GetUser();
|
||||
if (user == default)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
if (patch.AcceptTos)
|
||||
{
|
||||
await _userService.AcceptTos(user.PubKey);
|
||||
}
|
||||
|
||||
return Accepted();
|
||||
}
|
||||
|
||||
private async Task<User?> GetUser()
|
||||
{
|
||||
var pk = GetPubKey();
|
||||
|
@ -14,6 +14,9 @@ public class UserConfiguration : IEntityTypeConfiguration<User>
|
||||
builder.Property(a => a.Balance)
|
||||
.IsRequired();
|
||||
|
||||
builder.Property(a => a.TosAccepted)
|
||||
.IsRequired();
|
||||
|
||||
builder.Property(a => a.Version)
|
||||
.IsRowVersion();
|
||||
|
||||
|
@ -38,6 +38,11 @@ public class User
|
||||
/// Any content warning tag (NIP-36)
|
||||
/// </summary>
|
||||
public string? ContentWarning { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Date when user accepted TOS
|
||||
/// </summary>
|
||||
public DateTime? TosAccepted { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Concurrency token
|
||||
|
@ -20,7 +20,7 @@ public static class Extensions
|
||||
{
|
||||
return NostrPrivateKey.FromBech32(cfg.PrivateKey).DerivePublicKey().Hex;
|
||||
}
|
||||
|
||||
|
||||
public static NostrPrivateKey GetPrivateKey(this Config cfg)
|
||||
{
|
||||
return NostrPrivateKey.FromBech32(cfg.PrivateKey);
|
||||
@ -32,7 +32,7 @@ public static class Extensions
|
||||
.Where(a => a.StartsWith("variant"))
|
||||
.Select(Variant.FromString).ToList();
|
||||
}
|
||||
|
||||
|
||||
public static AmazonS3Client CreateClient(this S3BlobConfig c)
|
||||
{
|
||||
return new AmazonS3Client(new BasicAWSCredentials(c.AccessKey, c.SecretKey),
|
||||
@ -44,6 +44,12 @@ public static class Extensions
|
||||
ForcePathStyle = true
|
||||
});
|
||||
}
|
||||
|
||||
public static string[] SplitTags(this User user)
|
||||
{
|
||||
return !string.IsNullOrEmpty(user.Tags) ?
|
||||
user.Tags.Split(",", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) : Array.Empty<string>();
|
||||
}
|
||||
}
|
||||
|
||||
public class Variant
|
||||
|
314
NostrStreamer/Migrations/20230801110903_TOS.Designer.cs
generated
Normal file
314
NostrStreamer/Migrations/20230801110903_TOS.Designer.cs
generated
Normal file
@ -0,0 +1,314 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using NostrStreamer.Database;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace NostrStreamer.Migrations
|
||||
{
|
||||
[DbContext(typeof(StreamerContext))]
|
||||
[Migration("20230801110903_TOS")]
|
||||
partial class TOS
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "7.0.8")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("NostrStreamer.Database.IngestEndpoint", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("App")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<List<string>>("Capabilities")
|
||||
.IsRequired()
|
||||
.HasColumnType("text[]");
|
||||
|
||||
b.Property<int>("Cost")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Forward")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("App")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Endpoints");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NostrStreamer.Database.Payment", b =>
|
||||
{
|
||||
b.Property<string>("PaymentHash")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<decimal>("Amount")
|
||||
.HasColumnType("numeric(20,0)");
|
||||
|
||||
b.Property<DateTime>("Created")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Invoice")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<bool>("IsPaid")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("Nostr")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("PubKey")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int>("Type")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("PaymentHash");
|
||||
|
||||
b.HasIndex("PubKey");
|
||||
|
||||
b.ToTable("Payments");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NostrStreamer.Database.User", b =>
|
||||
{
|
||||
b.Property<string>("PubKey")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<long>("Balance")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<string>("ContentWarning")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Image")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("StreamKey")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Summary")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Tags")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTime?>("TosAccepted")
|
||||
.IsRequired()
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<uint>("Version")
|
||||
.IsConcurrencyToken()
|
||||
.ValueGeneratedOnAddOrUpdate()
|
||||
.HasColumnType("xid")
|
||||
.HasColumnName("xmin");
|
||||
|
||||
b.HasKey("PubKey");
|
||||
|
||||
b.ToTable("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NostrStreamer.Database.UserStream", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("EdgeIp")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid>("EndpointId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<DateTime?>("Ends")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Event")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("ForwardClientId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("PubKey")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Recording")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTime>("Starts")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<int>("State")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("StreamId")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("EndpointId");
|
||||
|
||||
b.HasIndex("PubKey");
|
||||
|
||||
b.ToTable("Streams");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NostrStreamer.Database.UserStreamGuest", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("PubKey")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Relay")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Role")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Sig")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid>("StreamId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<decimal>("ZapSplit")
|
||||
.HasColumnType("numeric");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("StreamId", "PubKey")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Guests");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NostrStreamer.Database.UserStreamRecording", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<double>("Duration")
|
||||
.HasColumnType("double precision");
|
||||
|
||||
b.Property<DateTime>("Timestamp")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Url")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<Guid>("UserStreamId")
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserStreamId");
|
||||
|
||||
b.ToTable("Recordings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NostrStreamer.Database.Payment", b =>
|
||||
{
|
||||
b.HasOne("NostrStreamer.Database.User", "User")
|
||||
.WithMany("Payments")
|
||||
.HasForeignKey("PubKey")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NostrStreamer.Database.UserStream", b =>
|
||||
{
|
||||
b.HasOne("NostrStreamer.Database.IngestEndpoint", "Endpoint")
|
||||
.WithMany()
|
||||
.HasForeignKey("EndpointId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("NostrStreamer.Database.User", "User")
|
||||
.WithMany("Streams")
|
||||
.HasForeignKey("PubKey")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Endpoint");
|
||||
|
||||
b.Navigation("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NostrStreamer.Database.UserStreamGuest", b =>
|
||||
{
|
||||
b.HasOne("NostrStreamer.Database.UserStream", "Stream")
|
||||
.WithMany("Guests")
|
||||
.HasForeignKey("StreamId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Stream");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NostrStreamer.Database.UserStreamRecording", b =>
|
||||
{
|
||||
b.HasOne("NostrStreamer.Database.UserStream", "Stream")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserStreamId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Stream");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NostrStreamer.Database.User", b =>
|
||||
{
|
||||
b.Navigation("Payments");
|
||||
|
||||
b.Navigation("Streams");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("NostrStreamer.Database.UserStream", b =>
|
||||
{
|
||||
b.Navigation("Guests");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
30
NostrStreamer/Migrations/20230801110903_TOS.cs
Normal file
30
NostrStreamer/Migrations/20230801110903_TOS.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace NostrStreamer.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class TOS : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "TosAccepted",
|
||||
table: "Users",
|
||||
type: "timestamp with time zone",
|
||||
nullable: false,
|
||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "TosAccepted",
|
||||
table: "Users");
|
||||
}
|
||||
}
|
||||
}
|
@ -118,6 +118,10 @@ namespace NostrStreamer.Migrations
|
||||
b.Property<string>("Title")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTime?>("TosAccepted")
|
||||
.IsRequired()
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<uint>("Version")
|
||||
.IsConcurrencyToken()
|
||||
.ValueGeneratedOnAddOrUpdate()
|
||||
@ -282,7 +286,7 @@ namespace NostrStreamer.Migrations
|
||||
modelBuilder.Entity("NostrStreamer.Database.UserStreamRecording", b =>
|
||||
{
|
||||
b.HasOne("NostrStreamer.Database.UserStream", "Stream")
|
||||
.WithMany("Recordings")
|
||||
.WithMany()
|
||||
.HasForeignKey("UserStreamId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
@ -300,8 +304,6 @@ namespace NostrStreamer.Migrations
|
||||
modelBuilder.Entity("NostrStreamer.Database.UserStream", b =>
|
||||
{
|
||||
b.Navigation("Guests");
|
||||
|
||||
b.Navigation("Recordings");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
|
@ -48,8 +48,4 @@
|
||||
<PackageReference Include="Nostr.Client" Version="1.4.2" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="7.0.4" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Services\Abstract\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
63
NostrStreamer/Pages/Tos.cshtml
Normal file
63
NostrStreamer/Pages/Tos.cshtml
Normal file
@ -0,0 +1,63 @@
|
||||
@page
|
||||
@using NostrStreamer
|
||||
@model NostrStreamer.Pages.Tos
|
||||
@inject Config Config
|
||||
@{
|
||||
Layout = null;
|
||||
}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>api.zap.stream Terms of Service</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>api.zap.stream Terms of Service</h1>
|
||||
<p>
|
||||
<strong>Effective Date:</strong> @Config.TosDate.ToString("u")</p>
|
||||
<p>
|
||||
Welcome to zap.stream! By using our streaming service, you agree to the following Terms of Service ("Terms").
|
||||
Please read them carefully.
|
||||
</p>
|
||||
<ol>
|
||||
<li>
|
||||
User Accounts: You are responsible for maintaining the security of your account and any activities that occur
|
||||
under it.
|
||||
</li>
|
||||
<li>
|
||||
User Conduct: You agree not to violate any laws, infringe upon others' rights, or engage in fraudulent or
|
||||
harmful activities while using our service.
|
||||
</li>
|
||||
<li>
|
||||
Prohibited Content: You may not stream or upload copyrighted content unless you have obtained the necessary
|
||||
rights or permissions.
|
||||
</li>
|
||||
<li>
|
||||
Termination: We reserve the right to suspend or terminate your access to the service if you violate these
|
||||
Terms.
|
||||
</li>
|
||||
<li>
|
||||
Disclaimer: The zap.stream service is provided "as is" without any warranties.
|
||||
</li>
|
||||
<li>
|
||||
Limitation of Liability: We shall not be liable for any damages arising from your use of the service.
|
||||
</li>
|
||||
<li>
|
||||
Governing Law: These Terms shall be governed by and construed in accordance with the applicable local laws,
|
||||
regulations, and jurisdiction where the service is accessed and used.
|
||||
</li>
|
||||
<li>
|
||||
Modifications: We may update these Terms from time to time, and your continued use of the service constitutes
|
||||
acceptance of the modified Terms.
|
||||
</li>
|
||||
<li>
|
||||
Entire Agreement: These Terms constitute the entire agreement between you and zap.stream.
|
||||
</li>
|
||||
</ol>
|
||||
<p>If you have any questions or concerns, please contact us at <a href="mailto:info@zap.stream">info@zap.stream</a>.</p>
|
||||
<p>By using zap.stream, you agree to abide by these Terms of Service. Enjoy your streaming experience!</p>
|
||||
</body>
|
||||
|
||||
</html>
|
11
NostrStreamer/Pages/Tos.cshtml.cs
Normal file
11
NostrStreamer/Pages/Tos.cshtml.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace NostrStreamer.Pages;
|
||||
|
||||
public class Tos : PageModel
|
||||
{
|
||||
public void OnGet()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
@ -23,6 +23,7 @@ internal static class Program
|
||||
services.AddCors();
|
||||
services.AddMemoryCache();
|
||||
services.AddHttpClient();
|
||||
services.AddRazorPages();
|
||||
services.AddControllers().AddNewtonsoftJson();
|
||||
services.AddSingleton(config);
|
||||
|
||||
@ -75,6 +76,7 @@ internal static class Program
|
||||
|
||||
app.UseCors(o => o.AllowAnyHeader().AllowAnyMethod().AllowAnyOrigin());
|
||||
app.UseAuthorization();
|
||||
app.MapRazorPages();
|
||||
app.MapControllers();
|
||||
|
||||
await app.RunAsync();
|
||||
|
@ -61,8 +61,7 @@ public class StreamEventBuilder
|
||||
tags.Add(new("recording", new Uri(_config.DataHost, $"recording/{stream.Id}.m3u8").ToString()));
|
||||
}
|
||||
|
||||
foreach (var tag in !string.IsNullOrEmpty(user.Tags) ?
|
||||
user.Tags.Split(",", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries) : Array.Empty<string>())
|
||||
foreach (var tag in user.SplitTags())
|
||||
{
|
||||
tags.Add(new("t", tag));
|
||||
}
|
||||
|
@ -9,6 +9,12 @@ public interface IStreamManager
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
UserStream GetStream();
|
||||
|
||||
/// <summary>
|
||||
/// Test if streaming is allowed for this user, otherwise throw
|
||||
/// </summary>
|
||||
/// <exception cref="Exception">Throws if cant stream</exception>
|
||||
void TestCanStream();
|
||||
|
||||
/// <summary>
|
||||
/// Stream ingress check on srs-edge
|
||||
@ -35,17 +41,6 @@ public interface IStreamManager
|
||||
/// <returns></returns>
|
||||
Task ConsumeQuota(double duration);
|
||||
|
||||
/// <summary>
|
||||
/// Update stream details
|
||||
/// </summary>
|
||||
/// <param name="title"></param>
|
||||
/// <param name="summary"></param>
|
||||
/// <param name="image"></param>
|
||||
/// <param name="tags"></param>
|
||||
/// <param name="contentWarning"></param>
|
||||
/// <returns></returns>
|
||||
Task PatchEvent(string? title, string? summary, string? image, string[]? tags, string? contentWarning);
|
||||
|
||||
/// <summary>
|
||||
/// Update viewer count
|
||||
/// </summary>
|
||||
|
@ -1,4 +1,3 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Newtonsoft.Json;
|
||||
using Nostr.Client.Json;
|
||||
@ -14,15 +13,16 @@ public class NostrStreamManager : IStreamManager
|
||||
private readonly StreamEventBuilder _eventBuilder;
|
||||
private readonly IDvrStore _dvrStore;
|
||||
private readonly ThumbnailService _thumbnailService;
|
||||
private readonly Config _config;
|
||||
|
||||
public NostrStreamManager(ILogger<NostrStreamManager> logger, StreamManagerContext context,
|
||||
StreamEventBuilder eventBuilder, IDvrStore dvrStore, ThumbnailService thumbnailService)
|
||||
public NostrStreamManager(ILogger<NostrStreamManager> logger, StreamManagerContext context, IServiceProvider serviceProvider)
|
||||
{
|
||||
_logger = logger;
|
||||
_context = context;
|
||||
_eventBuilder = eventBuilder;
|
||||
_dvrStore = dvrStore;
|
||||
_thumbnailService = thumbnailService;
|
||||
_eventBuilder = serviceProvider.GetRequiredService<StreamEventBuilder>();
|
||||
_dvrStore = serviceProvider.GetRequiredService<IDvrStore>();
|
||||
_thumbnailService = serviceProvider.GetRequiredService<ThumbnailService>();
|
||||
_config = serviceProvider.GetRequiredService<Config>();
|
||||
}
|
||||
|
||||
public UserStream GetStream()
|
||||
@ -30,13 +30,22 @@ public class NostrStreamManager : IStreamManager
|
||||
return _context.UserStream;
|
||||
}
|
||||
|
||||
public Task<List<string>> OnForward()
|
||||
public void TestCanStream()
|
||||
{
|
||||
if (_context.User.Balance <= 0)
|
||||
{
|
||||
throw new LowBalanceException("User balance empty");
|
||||
}
|
||||
|
||||
if (_context.User.TosAccepted == null || _context.User.TosAccepted < _config.TosDate)
|
||||
{
|
||||
throw new Exception("TOS not accepted");
|
||||
}
|
||||
}
|
||||
|
||||
public Task<List<string>> OnForward()
|
||||
{
|
||||
TestCanStream();
|
||||
return Task.FromResult(new List<string>
|
||||
{
|
||||
$"rtmp://127.0.0.1:1935/{_context.UserStream.Endpoint.App}/{_context.User.StreamKey}?vhost={_context.UserStream.Endpoint.Forward}"
|
||||
@ -46,12 +55,7 @@ public class NostrStreamManager : IStreamManager
|
||||
public async Task StreamStarted()
|
||||
{
|
||||
_logger.LogInformation("Stream started for: {pubkey}", _context.User.PubKey);
|
||||
|
||||
if (_context.User.Balance <= 0)
|
||||
{
|
||||
throw new Exception("User balance empty");
|
||||
}
|
||||
|
||||
TestCanStream();
|
||||
await UpdateStreamState(UserStreamState.Live);
|
||||
|
||||
#pragma warning disable CS4014
|
||||
@ -95,31 +99,6 @@ public class NostrStreamManager : IStreamManager
|
||||
}
|
||||
}
|
||||
|
||||
public async Task PatchEvent(string? title, string? summary, string? image, string[]? tags, string? contentWarning)
|
||||
{
|
||||
var user = _context.User;
|
||||
|
||||
await _context.Db.Users
|
||||
.Where(a => a.PubKey == _context.User.PubKey)
|
||||
.ExecuteUpdateAsync(o => o.SetProperty(v => v.Title, title)
|
||||
.SetProperty(v => v.Summary, summary)
|
||||
.SetProperty(v => v.Image, image)
|
||||
.SetProperty(v => v.Tags, tags != null ? string.Join(",", tags) : null)
|
||||
.SetProperty(v => v.ContentWarning, contentWarning));
|
||||
|
||||
user.Title = title;
|
||||
user.Summary = summary;
|
||||
user.Image = image;
|
||||
user.Tags = tags != null ? string.Join(",", tags) : null;
|
||||
user.ContentWarning = contentWarning;
|
||||
|
||||
var ev = _eventBuilder.CreateStreamEvent(user, _context.UserStream);
|
||||
await _context.Db.Streams.Where(a => a.Id == _context.UserStream.Id)
|
||||
.ExecuteUpdateAsync(o => o.SetProperty(v => v.Event, JsonConvert.SerializeObject(ev, NostrSerializer.Settings)));
|
||||
|
||||
_eventBuilder.BroadcastEvent(ev);
|
||||
}
|
||||
|
||||
public async Task AddGuest(string pubkey, string role, decimal zapSplit)
|
||||
{
|
||||
_context.Db.Guests.Add(new()
|
||||
|
@ -2,7 +2,6 @@ using Microsoft.EntityFrameworkCore;
|
||||
using Newtonsoft.Json;
|
||||
using Nostr.Client.Json;
|
||||
using NostrStreamer.Database;
|
||||
using NostrStreamer.Services.Dvr;
|
||||
|
||||
namespace NostrStreamer.Services.StreamManager;
|
||||
|
||||
@ -12,18 +11,16 @@ public class StreamManagerFactory
|
||||
private readonly ILoggerFactory _loggerFactory;
|
||||
private readonly StreamEventBuilder _eventBuilder;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly IDvrStore _dvrStore;
|
||||
private readonly ThumbnailService _thumbnailService;
|
||||
private readonly Config _config;
|
||||
|
||||
public StreamManagerFactory(StreamerContext db, ILoggerFactory loggerFactory, StreamEventBuilder eventBuilder,
|
||||
IServiceProvider serviceProvider, IDvrStore dvrStore, ThumbnailService thumbnailService)
|
||||
IServiceProvider serviceProvider, Config config)
|
||||
{
|
||||
_db = db;
|
||||
_loggerFactory = loggerFactory;
|
||||
_eventBuilder = eventBuilder;
|
||||
_serviceProvider = serviceProvider;
|
||||
_dvrStore = dvrStore;
|
||||
_thumbnailService = thumbnailService;
|
||||
_config = config;
|
||||
}
|
||||
|
||||
public async Task<IStreamManager> CreateStream(StreamInfo info)
|
||||
@ -47,7 +44,12 @@ public class StreamManagerFactory
|
||||
|
||||
if (user.Balance <= 0)
|
||||
{
|
||||
throw new Exception("Cannot start stream with empty balance");
|
||||
throw new LowBalanceException("Cannot start stream with empty balance");
|
||||
}
|
||||
|
||||
if (user.TosAccepted == null || user.TosAccepted < _config.TosDate)
|
||||
{
|
||||
throw new Exception("TOS not accepted");
|
||||
}
|
||||
|
||||
var stream = new UserStream
|
||||
@ -82,7 +84,7 @@ public class StreamManagerFactory
|
||||
EdgeApi = new SrsApi(_serviceProvider.GetRequiredService<HttpClient>(), new Uri($"http://{stream.EdgeIp}:1985"))
|
||||
};
|
||||
|
||||
return new NostrStreamManager(_loggerFactory.CreateLogger<NostrStreamManager>(), ctx, _eventBuilder, _dvrStore, _thumbnailService);
|
||||
return new NostrStreamManager(_loggerFactory.CreateLogger<NostrStreamManager>(), ctx, _serviceProvider);
|
||||
}
|
||||
|
||||
public async Task<IStreamManager> ForStream(Guid id)
|
||||
@ -102,7 +104,7 @@ public class StreamManagerFactory
|
||||
EdgeApi = new SrsApi(_serviceProvider.GetRequiredService<HttpClient>(), new Uri($"http://{stream.EdgeIp}:1985"))
|
||||
};
|
||||
|
||||
return new NostrStreamManager(_loggerFactory.CreateLogger<NostrStreamManager>(), ctx, _eventBuilder, _dvrStore, _thumbnailService);
|
||||
return new NostrStreamManager(_loggerFactory.CreateLogger<NostrStreamManager>(), ctx, _serviceProvider);
|
||||
}
|
||||
|
||||
public async Task<IStreamManager> ForCurrentStream(string pubkey)
|
||||
@ -122,7 +124,7 @@ public class StreamManagerFactory
|
||||
EdgeApi = new SrsApi(_serviceProvider.GetRequiredService<HttpClient>(), new Uri($"http://{stream.EdgeIp}:1985"))
|
||||
};
|
||||
|
||||
return new NostrStreamManager(_loggerFactory.CreateLogger<NostrStreamManager>(), ctx, _eventBuilder, _dvrStore, _thumbnailService);
|
||||
return new NostrStreamManager(_loggerFactory.CreateLogger<NostrStreamManager>(), ctx, _serviceProvider);
|
||||
}
|
||||
|
||||
public async Task<IStreamManager> ForStream(StreamInfo info)
|
||||
@ -150,6 +152,6 @@ public class StreamManagerFactory
|
||||
EdgeApi = new SrsApi(_serviceProvider.GetRequiredService<HttpClient>(), new Uri($"http://{stream.EdgeIp}:1985"))
|
||||
};
|
||||
|
||||
return new NostrStreamManager(_loggerFactory.CreateLogger<NostrStreamManager>(), ctx, _eventBuilder, _dvrStore, _thumbnailService);
|
||||
return new NostrStreamManager(_loggerFactory.CreateLogger<NostrStreamManager>(), ctx, _serviceProvider);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Nostr.Client.Utils;
|
||||
using NostrStreamer.ApiModel;
|
||||
using NostrStreamer.Database;
|
||||
|
||||
namespace NostrStreamer.Services;
|
||||
@ -69,4 +70,23 @@ public class UserService
|
||||
return await _db.Users.AsNoTracking()
|
||||
.SingleOrDefaultAsync(a => a.PubKey.Equals(pubkey));
|
||||
}
|
||||
|
||||
public async Task AcceptTos(string pubkey)
|
||||
{
|
||||
var change = await _db.Users.Where(a => a.PubKey.Equals(pubkey))
|
||||
.ExecuteUpdateAsync(o => o.SetProperty(v => v.TosAccepted, DateTime.UtcNow));
|
||||
|
||||
if (change != 1) throw new Exception($"Failed to accept TOS, {change} rows updated.");
|
||||
}
|
||||
|
||||
public async Task UpdateStreamInfo(string pubkey, PatchEvent req)
|
||||
{
|
||||
await _db.Users
|
||||
.Where(a => a.PubKey == pubkey)
|
||||
.ExecuteUpdateAsync(o => o.SetProperty(v => v.Title, req.Title)
|
||||
.SetProperty(v => v.Summary, req.Summary)
|
||||
.SetProperty(v => v.Image, req.Image)
|
||||
.SetProperty(v => v.Tags, req.Tags.Length > 0 ? string.Join(",", req.Tags) : null)
|
||||
.SetProperty(v => v.ContentWarning, req.ContentWarning));
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@
|
||||
"SrsApiHost": "http://localhost:9002",
|
||||
"DataHost": "http://localhost:5295/api/playlist/",
|
||||
"ApiHost": "http://localhost:5295",
|
||||
"TosDate": "2023-07-06",
|
||||
"Relays": [
|
||||
"ws://localhost:8081"
|
||||
],
|
||||
|
Reference in New Issue
Block a user