diff --git a/.gitattributes b/.gitattributes index 30d4b6dd..6fb47de9 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,10 @@ # Auto detect text files and perform LF normalization * text=auto -Moonlight/wwwroot/* linguist-vendored +Moonlight/wwwroot/** linguist-vendored +Moonlight/wwwroot/assets/js/scripts.bundle.js linguist-vendored +Moonlight/wwwroot/assets/js/widgets.bundle.js linguist-vendored +Moonlight/wwwroot/assets/js/theme.js linguist-vendored +Moonlight/wwwroot/assets/css/boxicons.min.css linguist-vendored +Moonlight/wwwroot/assets/css/style.bundle.css linguist-vendored +Moonlight/wwwroot/assets/plugins/** linguist-vendored +Moonlight/wwwroot/assets/fonts/** linguist-vendored diff --git a/.idea/.idea.Moonlight/.idea/.name b/.idea/.idea.Moonlight/.idea/.name new file mode 100644 index 00000000..9d9b9a74 --- /dev/null +++ b/.idea/.idea.Moonlight/.idea/.name @@ -0,0 +1 @@ +Moonlight \ No newline at end of file diff --git a/Moonlight/App/Configuration/ConfigV1.cs b/Moonlight/App/Configuration/ConfigV1.cs index 4c07e7d4..b6709c8b 100644 --- a/Moonlight/App/Configuration/ConfigV1.cs +++ b/Moonlight/App/Configuration/ConfigV1.cs @@ -17,6 +17,15 @@ public class ConfigV1 [Description("The url moonlight is accesible with from the internet")] public string AppUrl { get; set; } = "http://your-moonlight-url-without-slash"; + [JsonProperty("EnableLatencyCheck")] + [Description( + "This will enable a latency check for connections to moonlight. Users with an too high latency will be warned that moonlight might be buggy for them")] + public bool EnableLatencyCheck { get; set; } = true; + + [JsonProperty("LatencyCheckThreshold")] + [Description("Specify the latency threshold which has to be reached in order to trigger the warning message")] + public int LatencyCheckThreshold { get; set; } = 500; + [JsonProperty("Auth")] public AuthData Auth { get; set; } = new(); [JsonProperty("Database")] public DatabaseData Database { get; set; } = new(); @@ -38,9 +47,7 @@ public class ConfigV1 [JsonProperty("Mail")] public MailData Mail { get; set; } = new(); [JsonProperty("Cleanup")] public CleanupData Cleanup { get; set; } = new(); - - [JsonProperty("Subscriptions")] public SubscriptionsData Subscriptions { get; set; } = new(); - + [JsonProperty("DiscordNotifications")] public DiscordNotificationsData DiscordNotifications { get; set; } = new(); [JsonProperty("Statistics")] public StatisticsData Statistics { get; set; } = new(); @@ -50,6 +57,24 @@ public class ConfigV1 [JsonProperty("SmartDeploy")] public SmartDeployData SmartDeploy { get; set; } = new(); [JsonProperty("Sentry")] public SentryData Sentry { get; set; } = new(); + + [JsonProperty("Stripe")] public StripeData Stripe { get; set; } = new(); + + [JsonProperty("Tickets")] public TicketsData Tickets { get; set; } = new(); + } + + public class TicketsData + { + [JsonProperty("WelcomeMessage")] + [Description("The message that will be sent when a user created a ticket")] + public string WelcomeMessage { get; set; } = "Welcome to the support"; + } + + public class StripeData + { + [JsonProperty("ApiKey")] + [Description("Put here your stripe api key if you add subscriptions. Currently the only billing option is stripe which is enabled by default and cannot be turned off. This feature is still experimental")] + public string ApiKey { get; set; } = ""; } public class AuthData @@ -269,6 +294,10 @@ public class ConfigV1 [Blur] public string Token { get; set; } = Guid.NewGuid().ToString(); + [JsonProperty("BlockIpDuration")] + [Description("The duration in minutes a ip will be blocked by the anti ddos system")] + public int BlockIpDuration { get; set; } = 15; + [JsonProperty("ReCaptcha")] public ReCaptchaData ReCaptcha { get; set; } = new(); } @@ -318,11 +347,6 @@ public class ConfigV1 [JsonProperty("Wait")] public long Wait { get; set; } = 15; } - public class SubscriptionsData - { - [JsonProperty("SellPass")] public SellPassData SellPass { get; set; } = new(); - } - public class SellPassData { [JsonProperty("Enable")] public bool Enable { get; set; } = false; diff --git a/Moonlight/App/Database/DataContext.cs b/Moonlight/App/Database/DataContext.cs index 750ba18c..d872f3e5 100644 --- a/Moonlight/App/Database/DataContext.cs +++ b/Moonlight/App/Database/DataContext.cs @@ -1,9 +1,7 @@ using Microsoft.EntityFrameworkCore; using Moonlight.App.Database.Entities; -using Moonlight.App.Database.Entities.LogsEntries; using Moonlight.App.Database.Entities.Notification; using Moonlight.App.Database.Interceptors; -using Moonlight.App.Models.Misc; using Moonlight.App.Services; namespace Moonlight.App.Database; @@ -27,10 +25,6 @@ public class DataContext : DbContext public DbSet ServerVariables { get; set; } public DbSet Users { get; set; } public DbSet LoadingMessages { get; set; } - public DbSet AuditLog { get; set; } - public DbSet ErrorLog { get; set; } - public DbSet SecurityLog { get; set; } - public DbSet SharedDomains { get; set; } public DbSet Domains { get; set; } public DbSet Revokes { get; set; } @@ -46,6 +40,13 @@ public class DataContext : DbContext public DbSet WebSpaces { get; set; } public DbSet SupportChatMessages { get; set; } public DbSet IpBans { get; set; } + public DbSet PermissionGroups { get; set; } + public DbSet SecurityLogs { get; set; } + public DbSet BlocklistIps { get; set; } + public DbSet WhitelistIps { get; set; } + + public DbSet Tickets { get; set; } + public DbSet TicketMessages { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { diff --git a/Moonlight/App/Database/Entities/BlocklistIp.cs b/Moonlight/App/Database/Entities/BlocklistIp.cs new file mode 100644 index 00000000..5e80daf2 --- /dev/null +++ b/Moonlight/App/Database/Entities/BlocklistIp.cs @@ -0,0 +1,10 @@ +namespace Moonlight.App.Database.Entities; + +public class BlocklistIp +{ + public int Id { get; set; } + public string Ip { get; set; } = ""; + public DateTime ExpiresAt { get; set; } + public long Packets { get; set; } + public DateTime CreatedAt { get; set; } +} \ No newline at end of file diff --git a/Moonlight/App/Database/Entities/LogsEntries/AuditLogEntry.cs b/Moonlight/App/Database/Entities/LogsEntries/AuditLogEntry.cs deleted file mode 100644 index 5b6f8085..00000000 --- a/Moonlight/App/Database/Entities/LogsEntries/AuditLogEntry.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Moonlight.App.Models.Misc; - -namespace Moonlight.App.Database.Entities.LogsEntries; - -public class AuditLogEntry -{ - public int Id { get; set; } - public AuditLogType Type { get; set; } - public string JsonData { get; set; } = ""; - public bool System { get; set; } - public string Ip { get; set; } = ""; - public DateTime CreatedAt { get; set; } = DateTime.UtcNow; -} \ No newline at end of file diff --git a/Moonlight/App/Database/Entities/LogsEntries/ErrorLogEntry.cs b/Moonlight/App/Database/Entities/LogsEntries/ErrorLogEntry.cs deleted file mode 100644 index ea9b7ee4..00000000 --- a/Moonlight/App/Database/Entities/LogsEntries/ErrorLogEntry.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Moonlight.App.Database.Entities.LogsEntries; - -public class ErrorLogEntry -{ - public int Id { get; set; } - public string Stacktrace { get; set; } = ""; - public bool System { get; set; } - public string JsonData { get; set; } = ""; - public string Ip { get; set; } = ""; - public string Class { get; set; } = ""; - public DateTime CreatedAt { get; set; } = DateTime.UtcNow; -} \ No newline at end of file diff --git a/Moonlight/App/Database/Entities/LogsEntries/SecurityLogEntry.cs b/Moonlight/App/Database/Entities/LogsEntries/SecurityLogEntry.cs deleted file mode 100644 index 9d1750bb..00000000 --- a/Moonlight/App/Database/Entities/LogsEntries/SecurityLogEntry.cs +++ /dev/null @@ -1,13 +0,0 @@ -using Moonlight.App.Models.Misc; - -namespace Moonlight.App.Database.Entities.LogsEntries; - -public class SecurityLogEntry -{ - public int Id { get; set; } - public bool System { get; set; } - public string Ip { get; set; } = ""; - public SecurityLogType Type { get; set; } - public string JsonData { get; set; } = ""; - public DateTime CreatedAt { get; set; } = DateTime.UtcNow; -} \ No newline at end of file diff --git a/Moonlight/App/Database/Entities/PermissionGroup.cs b/Moonlight/App/Database/Entities/PermissionGroup.cs new file mode 100644 index 00000000..f6b44bab --- /dev/null +++ b/Moonlight/App/Database/Entities/PermissionGroup.cs @@ -0,0 +1,8 @@ +namespace Moonlight.App.Database.Entities; + +public class PermissionGroup +{ + public int Id { get; set; } + public string Name { get; set; } = ""; + public byte[] Permissions { get; set; } = Array.Empty(); +} \ No newline at end of file diff --git a/Moonlight/App/Database/Entities/SecurityLog.cs b/Moonlight/App/Database/Entities/SecurityLog.cs new file mode 100644 index 00000000..4d83890b --- /dev/null +++ b/Moonlight/App/Database/Entities/SecurityLog.cs @@ -0,0 +1,8 @@ +namespace Moonlight.App.Database.Entities; + +public class SecurityLog +{ + public int Id { get; set; } + public string Text { get; set; } = ""; + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; +} \ No newline at end of file diff --git a/Moonlight/App/Database/Entities/Subscription.cs b/Moonlight/App/Database/Entities/Subscription.cs index ab0b8c77..5e1fd059 100644 --- a/Moonlight/App/Database/Entities/Subscription.cs +++ b/Moonlight/App/Database/Entities/Subscription.cs @@ -1,9 +1,16 @@ -namespace Moonlight.App.Database.Entities; +using Moonlight.App.Models.Misc; + +namespace Moonlight.App.Database.Entities; public class Subscription { public int Id { get; set; } public string Name { get; set; } = ""; public string Description { get; set; } = ""; + public Currency Currency { get; set; } = Currency.USD; + public double Price { get; set; } + public string StripeProductId { get; set; } = ""; + public string StripePriceId { get; set; } = ""; public string LimitsJson { get; set; } = ""; + public int Duration { get; set; } = 30; } \ No newline at end of file diff --git a/Moonlight/App/Database/Entities/Ticket.cs b/Moonlight/App/Database/Entities/Ticket.cs new file mode 100644 index 00000000..2a90d917 --- /dev/null +++ b/Moonlight/App/Database/Entities/Ticket.cs @@ -0,0 +1,19 @@ +using Moonlight.App.Models.Misc; + +namespace Moonlight.App.Database.Entities; + +public class Ticket +{ + public int Id { get; set; } + public string IssueTopic { get; set; } = ""; + public string IssueDescription { get; set; } = ""; + public string IssueTries { get; set; } = ""; + public User CreatedBy { get; set; } + public User? AssignedTo { get; set; } + public TicketPriority Priority { get; set; } + public TicketStatus Status { get; set; } + public TicketSubject Subject { get; set; } + public int SubjectId { get; set; } + public List Messages { get; set; } = new(); + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; +} \ No newline at end of file diff --git a/Moonlight/App/Database/Entities/TicketMessage.cs b/Moonlight/App/Database/Entities/TicketMessage.cs new file mode 100644 index 00000000..940631bc --- /dev/null +++ b/Moonlight/App/Database/Entities/TicketMessage.cs @@ -0,0 +1,14 @@ +namespace Moonlight.App.Database.Entities; + +public class TicketMessage +{ + public int Id { get; set; } + public string Content { get; set; } = ""; + public string? AttachmentUrl { get; set; } + public User? Sender { get; set; } + public bool IsSystemMessage { get; set; } + public bool IsEdited { get; set; } + public bool IsSupportMessage { get; set; } + public DateTime CreatedAt { get; set; } = DateTime.UtcNow; + public DateTime UpdatedAt { get; set; } = DateTime.UtcNow; +} \ No newline at end of file diff --git a/Moonlight/App/Database/Entities/User.cs b/Moonlight/App/Database/Entities/User.cs index 1ed0b5c8..be6036f4 100644 --- a/Moonlight/App/Database/Entities/User.cs +++ b/Moonlight/App/Database/Entities/User.cs @@ -1,4 +1,5 @@ -using Moonlight.App.Models.Misc; +using System.ComponentModel.DataAnnotations; +using Moonlight.App.Models.Misc; namespace Moonlight.App.Database.Entities; @@ -39,6 +40,8 @@ public class User public bool TotpEnabled { get; set; } = false; public string TotpSecret { get; set; } = ""; public DateTime TokenValidTime { get; set; } = DateTime.UtcNow; + public byte[] Permissions { get; set; } = Array.Empty(); + public PermissionGroup? PermissionGroup { get; set; } // Discord public ulong DiscordId { get; set; } @@ -51,8 +54,8 @@ public class User // Subscriptions public Subscription? CurrentSubscription { get; set; } = null; - public DateTime SubscriptionSince { get; set; } = DateTime.Now; - public int SubscriptionDuration { get; set; } + public DateTime SubscriptionSince { get; set; } = DateTime.UtcNow; + public DateTime SubscriptionExpires { get; set; } = DateTime.UtcNow; // Ip logs public string RegisterIp { get; set; } = ""; diff --git a/Moonlight/App/Database/Entities/WhitelistIp.cs b/Moonlight/App/Database/Entities/WhitelistIp.cs new file mode 100644 index 00000000..571263a1 --- /dev/null +++ b/Moonlight/App/Database/Entities/WhitelistIp.cs @@ -0,0 +1,7 @@ +namespace Moonlight.App.Database.Entities; + +public class WhitelistIp +{ + public int Id { get; set; } + public string Ip { get; set; } = ""; +} \ No newline at end of file diff --git a/Moonlight/App/Database/Migrations/20230705171914_AddedStripeIntegration.Designer.cs b/Moonlight/App/Database/Migrations/20230705171914_AddedStripeIntegration.Designer.cs new file mode 100644 index 00000000..18ebe32a --- /dev/null +++ b/Moonlight/App/Database/Migrations/20230705171914_AddedStripeIntegration.Designer.cs @@ -0,0 +1,1105 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Moonlight.App.Database; + +#nullable disable + +namespace Moonlight.App.Database.Migrations +{ + [DbContext(typeof(DataContext))] + [Migration("20230705171914_AddedStripeIntegration")] + partial class AddedStripeIntegration + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("Moonlight.App.Database.Entities.CloudPanel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("ApiKey") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ApiUrl") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Host") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("CloudPanels"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.DdosAttack", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("bigint"); + + b.Property("Ip") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("NodeId") + .HasColumnType("int"); + + b.Property("Ongoing") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("NodeId"); + + b.ToTable("DdosAttacks"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.DockerImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Default") + .HasColumnType("tinyint(1)"); + + b.Property("ImageId") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("ImageId"); + + b.ToTable("DockerImages"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Domain", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OwnerId") + .HasColumnType("int"); + + b.Property("SharedDomainId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OwnerId"); + + b.HasIndex("SharedDomainId"); + + b.ToTable("Domains"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Image", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Allocations") + .HasColumnType("int"); + + b.Property("BackgroundImageUrl") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ConfigFiles") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("InstallDockerImage") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("InstallEntrypoint") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("InstallScript") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Startup") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StartupDetection") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StopCommand") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("TagsJson") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Uuid") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.ToTable("Images"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.ImageVariable", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("DefaultValue") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ImageId") + .HasColumnType("int"); + + b.Property("Key") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("ImageId"); + + b.ToTable("ImageVariables"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.IpBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Ip") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("IpBans"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.LoadingMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Message") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("LoadingMessages"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.LogsEntries.AuditLogEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Ip") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("JsonData") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("System") + .HasColumnType("tinyint(1)"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("AuditLog"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.LogsEntries.ErrorLogEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Class") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Ip") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("JsonData") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Stacktrace") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("System") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("ErrorLog"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.LogsEntries.SecurityLogEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Ip") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("JsonData") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("System") + .HasColumnType("tinyint(1)"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("SecurityLog"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.MySqlDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Password") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("WebSpaceId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("WebSpaceId"); + + b.ToTable("Databases"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.NewsEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Date") + .HasColumnType("datetime(6)"); + + b.Property("Markdown") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Title") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("NewsEntries"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Node", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Fqdn") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("HttpPort") + .HasColumnType("int"); + + b.Property("MoonlightDaemonPort") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("SftpPort") + .HasColumnType("int"); + + b.Property("Ssl") + .HasColumnType("tinyint(1)"); + + b.Property("Token") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("TokenId") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Nodes"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.NodeAllocation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("NodeId") + .HasColumnType("int"); + + b.Property("Port") + .HasColumnType("int"); + + b.Property("ServerId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("NodeId"); + + b.HasIndex("ServerId"); + + b.ToTable("NodeAllocations"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Notification.NotificationAction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Action") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("NotificationClientId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("NotificationClientId"); + + b.ToTable("NotificationActions"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Notification.NotificationClient", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("NotificationClients"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Revoke", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Identifier") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Revokes"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Server", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("ArchiveId") + .HasColumnType("int"); + + b.Property("Cpu") + .HasColumnType("int"); + + b.Property("Disk") + .HasColumnType("bigint"); + + b.Property("DockerImageIndex") + .HasColumnType("int"); + + b.Property("ImageId") + .HasColumnType("int"); + + b.Property("Installing") + .HasColumnType("tinyint(1)"); + + b.Property("IsArchived") + .HasColumnType("tinyint(1)"); + + b.Property("IsCleanupException") + .HasColumnType("tinyint(1)"); + + b.Property("MainAllocationId") + .HasColumnType("int"); + + b.Property("Memory") + .HasColumnType("bigint"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("NodeId") + .HasColumnType("int"); + + b.Property("OverrideStartup") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OwnerId") + .HasColumnType("int"); + + b.Property("Suspended") + .HasColumnType("tinyint(1)"); + + b.Property("Uuid") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("ArchiveId"); + + b.HasIndex("ImageId"); + + b.HasIndex("MainAllocationId"); + + b.HasIndex("NodeId"); + + b.HasIndex("OwnerId"); + + b.ToTable("Servers"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.ServerBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Bytes") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("tinyint(1)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ServerId") + .HasColumnType("int"); + + b.Property("Uuid") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("ServerId"); + + b.ToTable("ServerBackups"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.ServerVariable", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Key") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ServerId") + .HasColumnType("int"); + + b.Property("Value") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("ServerId"); + + b.ToTable("ServerVariables"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.SharedDomain", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CloudflareId") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("SharedDomains"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.StatisticsData", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Chart") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Date") + .HasColumnType("datetime(6)"); + + b.Property("Value") + .HasColumnType("double"); + + b.HasKey("Id"); + + b.ToTable("Statistics"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Subscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Currency") + .HasColumnType("int"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Duration") + .HasColumnType("int"); + + b.Property("LimitsJson") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Price") + .HasColumnType("double"); + + b.Property("StripePriceId") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StripeProductId") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Subscriptions"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.SupportChatMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Answer") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Attachment") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Content") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsQuestion") + .HasColumnType("tinyint(1)"); + + b.Property("QuestionType") + .HasColumnType("int"); + + b.Property("RecipientId") + .HasColumnType("int"); + + b.Property("SenderId") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("RecipientId"); + + b.HasIndex("SenderId"); + + b.ToTable("SupportChatMessages"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Address") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Admin") + .HasColumnType("tinyint(1)"); + + b.Property("City") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Country") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CurrentSubscriptionId") + .HasColumnType("int"); + + b.Property("DiscordId") + .HasColumnType("bigint unsigned"); + + b.Property("Email") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("HasRated") + .HasColumnType("tinyint(1)"); + + b.Property("LastIp") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("LastVisitedAt") + .HasColumnType("datetime(6)"); + + b.Property("Password") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Rating") + .HasColumnType("int"); + + b.Property("RegisterIp") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ServerListLayoutJson") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("State") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StreamerMode") + .HasColumnType("tinyint(1)"); + + b.Property("SubscriptionExpires") + .HasColumnType("datetime(6)"); + + b.Property("SubscriptionSince") + .HasColumnType("datetime(6)"); + + b.Property("SupportPending") + .HasColumnType("tinyint(1)"); + + b.Property("TokenValidTime") + .HasColumnType("datetime(6)"); + + b.Property("TotpEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("TotpSecret") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("CurrentSubscriptionId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.WebSpace", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CloudPanelId") + .HasColumnType("int"); + + b.Property("Domain") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OwnerId") + .HasColumnType("int"); + + b.Property("Password") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("VHostTemplate") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("CloudPanelId"); + + b.HasIndex("OwnerId"); + + b.ToTable("WebSpaces"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.DdosAttack", b => + { + b.HasOne("Moonlight.App.Database.Entities.Node", "Node") + .WithMany() + .HasForeignKey("NodeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Node"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.DockerImage", b => + { + b.HasOne("Moonlight.App.Database.Entities.Image", null) + .WithMany("DockerImages") + .HasForeignKey("ImageId"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Domain", b => + { + b.HasOne("Moonlight.App.Database.Entities.User", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.App.Database.Entities.SharedDomain", "SharedDomain") + .WithMany() + .HasForeignKey("SharedDomainId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + + b.Navigation("SharedDomain"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.ImageVariable", b => + { + b.HasOne("Moonlight.App.Database.Entities.Image", null) + .WithMany("Variables") + .HasForeignKey("ImageId"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.MySqlDatabase", b => + { + b.HasOne("Moonlight.App.Database.Entities.WebSpace", "WebSpace") + .WithMany("Databases") + .HasForeignKey("WebSpaceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("WebSpace"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.NodeAllocation", b => + { + b.HasOne("Moonlight.App.Database.Entities.Node", null) + .WithMany("Allocations") + .HasForeignKey("NodeId"); + + b.HasOne("Moonlight.App.Database.Entities.Server", null) + .WithMany("Allocations") + .HasForeignKey("ServerId"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Notification.NotificationAction", b => + { + b.HasOne("Moonlight.App.Database.Entities.Notification.NotificationClient", "NotificationClient") + .WithMany() + .HasForeignKey("NotificationClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("NotificationClient"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Notification.NotificationClient", b => + { + b.HasOne("Moonlight.App.Database.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Server", b => + { + b.HasOne("Moonlight.App.Database.Entities.ServerBackup", "Archive") + .WithMany() + .HasForeignKey("ArchiveId"); + + b.HasOne("Moonlight.App.Database.Entities.Image", "Image") + .WithMany() + .HasForeignKey("ImageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.App.Database.Entities.NodeAllocation", "MainAllocation") + .WithMany() + .HasForeignKey("MainAllocationId"); + + b.HasOne("Moonlight.App.Database.Entities.Node", "Node") + .WithMany() + .HasForeignKey("NodeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.App.Database.Entities.User", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Archive"); + + b.Navigation("Image"); + + b.Navigation("MainAllocation"); + + b.Navigation("Node"); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.ServerBackup", b => + { + b.HasOne("Moonlight.App.Database.Entities.Server", null) + .WithMany("Backups") + .HasForeignKey("ServerId"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.ServerVariable", b => + { + b.HasOne("Moonlight.App.Database.Entities.Server", null) + .WithMany("Variables") + .HasForeignKey("ServerId"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.SupportChatMessage", b => + { + b.HasOne("Moonlight.App.Database.Entities.User", "Recipient") + .WithMany() + .HasForeignKey("RecipientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.App.Database.Entities.User", "Sender") + .WithMany() + .HasForeignKey("SenderId"); + + b.Navigation("Recipient"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.User", b => + { + b.HasOne("Moonlight.App.Database.Entities.Subscription", "CurrentSubscription") + .WithMany() + .HasForeignKey("CurrentSubscriptionId"); + + b.Navigation("CurrentSubscription"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.WebSpace", b => + { + b.HasOne("Moonlight.App.Database.Entities.CloudPanel", "CloudPanel") + .WithMany() + .HasForeignKey("CloudPanelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.App.Database.Entities.User", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CloudPanel"); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Image", b => + { + b.Navigation("DockerImages"); + + b.Navigation("Variables"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Node", b => + { + b.Navigation("Allocations"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Server", b => + { + b.Navigation("Allocations"); + + b.Navigation("Backups"); + + b.Navigation("Variables"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.WebSpace", b => + { + b.Navigation("Databases"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Moonlight/App/Database/Migrations/20230705171914_AddedStripeIntegration.cs b/Moonlight/App/Database/Migrations/20230705171914_AddedStripeIntegration.cs new file mode 100644 index 00000000..ec21c0ce --- /dev/null +++ b/Moonlight/App/Database/Migrations/20230705171914_AddedStripeIntegration.cs @@ -0,0 +1,96 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Moonlight.App.Database.Migrations +{ + /// + public partial class AddedStripeIntegration : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "SubscriptionDuration", + table: "Users"); + + migrationBuilder.AddColumn( + name: "SubscriptionExpires", + table: "Users", + type: "datetime(6)", + nullable: false, + defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); + + migrationBuilder.AddColumn( + name: "Currency", + table: "Subscriptions", + type: "int", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "Duration", + table: "Subscriptions", + type: "int", + nullable: false, + defaultValue: 0); + + migrationBuilder.AddColumn( + name: "Price", + table: "Subscriptions", + type: "double", + nullable: false, + defaultValue: 0.0); + + migrationBuilder.AddColumn( + name: "StripePriceId", + table: "Subscriptions", + type: "longtext", + nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.AddColumn( + name: "StripeProductId", + table: "Subscriptions", + type: "longtext", + nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "SubscriptionExpires", + table: "Users"); + + migrationBuilder.DropColumn( + name: "Currency", + table: "Subscriptions"); + + migrationBuilder.DropColumn( + name: "Duration", + table: "Subscriptions"); + + migrationBuilder.DropColumn( + name: "Price", + table: "Subscriptions"); + + migrationBuilder.DropColumn( + name: "StripePriceId", + table: "Subscriptions"); + + migrationBuilder.DropColumn( + name: "StripeProductId", + table: "Subscriptions"); + + migrationBuilder.AddColumn( + name: "SubscriptionDuration", + table: "Users", + type: "int", + nullable: false, + defaultValue: 0); + } + } +} diff --git a/Moonlight/App/Database/Migrations/20230715095531_AddPermissions.Designer.cs b/Moonlight/App/Database/Migrations/20230715095531_AddPermissions.Designer.cs new file mode 100644 index 00000000..d1edbaa4 --- /dev/null +++ b/Moonlight/App/Database/Migrations/20230715095531_AddPermissions.Designer.cs @@ -0,0 +1,1109 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Moonlight.App.Database; + +#nullable disable + +namespace Moonlight.App.Database.Migrations +{ + [DbContext(typeof(DataContext))] + [Migration("20230715095531_AddPermissions")] + partial class AddPermissions + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("Moonlight.App.Database.Entities.CloudPanel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("ApiKey") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ApiUrl") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Host") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("CloudPanels"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.DdosAttack", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("bigint"); + + b.Property("Ip") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("NodeId") + .HasColumnType("int"); + + b.Property("Ongoing") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("NodeId"); + + b.ToTable("DdosAttacks"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.DockerImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Default") + .HasColumnType("tinyint(1)"); + + b.Property("ImageId") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("ImageId"); + + b.ToTable("DockerImages"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Domain", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OwnerId") + .HasColumnType("int"); + + b.Property("SharedDomainId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OwnerId"); + + b.HasIndex("SharedDomainId"); + + b.ToTable("Domains"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Image", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Allocations") + .HasColumnType("int"); + + b.Property("BackgroundImageUrl") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ConfigFiles") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("InstallDockerImage") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("InstallEntrypoint") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("InstallScript") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Startup") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StartupDetection") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StopCommand") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("TagsJson") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Uuid") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.ToTable("Images"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.ImageVariable", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("DefaultValue") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ImageId") + .HasColumnType("int"); + + b.Property("Key") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("ImageId"); + + b.ToTable("ImageVariables"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.IpBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Ip") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("IpBans"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.LoadingMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Message") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("LoadingMessages"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.LogsEntries.AuditLogEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Ip") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("JsonData") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("System") + .HasColumnType("tinyint(1)"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("AuditLog"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.LogsEntries.ErrorLogEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Class") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Ip") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("JsonData") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Stacktrace") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("System") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("ErrorLog"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.LogsEntries.SecurityLogEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Ip") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("JsonData") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("System") + .HasColumnType("tinyint(1)"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("SecurityLog"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.MySqlDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Password") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("WebSpaceId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("WebSpaceId"); + + b.ToTable("Databases"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.NewsEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Date") + .HasColumnType("datetime(6)"); + + b.Property("Markdown") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Title") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("NewsEntries"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Node", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Fqdn") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("HttpPort") + .HasColumnType("int"); + + b.Property("MoonlightDaemonPort") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("SftpPort") + .HasColumnType("int"); + + b.Property("Ssl") + .HasColumnType("tinyint(1)"); + + b.Property("Token") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("TokenId") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Nodes"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.NodeAllocation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("NodeId") + .HasColumnType("int"); + + b.Property("Port") + .HasColumnType("int"); + + b.Property("ServerId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("NodeId"); + + b.HasIndex("ServerId"); + + b.ToTable("NodeAllocations"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Notification.NotificationAction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Action") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("NotificationClientId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("NotificationClientId"); + + b.ToTable("NotificationActions"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Notification.NotificationClient", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("NotificationClients"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Revoke", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Identifier") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Revokes"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Server", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("ArchiveId") + .HasColumnType("int"); + + b.Property("Cpu") + .HasColumnType("int"); + + b.Property("Disk") + .HasColumnType("bigint"); + + b.Property("DockerImageIndex") + .HasColumnType("int"); + + b.Property("ImageId") + .HasColumnType("int"); + + b.Property("Installing") + .HasColumnType("tinyint(1)"); + + b.Property("IsArchived") + .HasColumnType("tinyint(1)"); + + b.Property("IsCleanupException") + .HasColumnType("tinyint(1)"); + + b.Property("MainAllocationId") + .HasColumnType("int"); + + b.Property("Memory") + .HasColumnType("bigint"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("NodeId") + .HasColumnType("int"); + + b.Property("OverrideStartup") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OwnerId") + .HasColumnType("int"); + + b.Property("Suspended") + .HasColumnType("tinyint(1)"); + + b.Property("Uuid") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("ArchiveId"); + + b.HasIndex("ImageId"); + + b.HasIndex("MainAllocationId"); + + b.HasIndex("NodeId"); + + b.HasIndex("OwnerId"); + + b.ToTable("Servers"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.ServerBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Bytes") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("tinyint(1)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ServerId") + .HasColumnType("int"); + + b.Property("Uuid") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("ServerId"); + + b.ToTable("ServerBackups"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.ServerVariable", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Key") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ServerId") + .HasColumnType("int"); + + b.Property("Value") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("ServerId"); + + b.ToTable("ServerVariables"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.SharedDomain", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CloudflareId") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("SharedDomains"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.StatisticsData", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Chart") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Date") + .HasColumnType("datetime(6)"); + + b.Property("Value") + .HasColumnType("double"); + + b.HasKey("Id"); + + b.ToTable("Statistics"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Subscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Currency") + .HasColumnType("int"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Duration") + .HasColumnType("int"); + + b.Property("LimitsJson") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Price") + .HasColumnType("double"); + + b.Property("StripePriceId") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StripeProductId") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Subscriptions"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.SupportChatMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Answer") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Attachment") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Content") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsQuestion") + .HasColumnType("tinyint(1)"); + + b.Property("QuestionType") + .HasColumnType("int"); + + b.Property("RecipientId") + .HasColumnType("int"); + + b.Property("SenderId") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("RecipientId"); + + b.HasIndex("SenderId"); + + b.ToTable("SupportChatMessages"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Address") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Admin") + .HasColumnType("tinyint(1)"); + + b.Property("City") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Country") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CurrentSubscriptionId") + .HasColumnType("int"); + + b.Property("DiscordId") + .HasColumnType("bigint unsigned"); + + b.Property("Email") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("HasRated") + .HasColumnType("tinyint(1)"); + + b.Property("LastIp") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("LastVisitedAt") + .HasColumnType("datetime(6)"); + + b.Property("Password") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Permissions") + .IsRequired() + .HasColumnType("longblob"); + + b.Property("Rating") + .HasColumnType("int"); + + b.Property("RegisterIp") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ServerListLayoutJson") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("State") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StreamerMode") + .HasColumnType("tinyint(1)"); + + b.Property("SubscriptionExpires") + .HasColumnType("datetime(6)"); + + b.Property("SubscriptionSince") + .HasColumnType("datetime(6)"); + + b.Property("SupportPending") + .HasColumnType("tinyint(1)"); + + b.Property("TokenValidTime") + .HasColumnType("datetime(6)"); + + b.Property("TotpEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("TotpSecret") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("CurrentSubscriptionId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.WebSpace", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CloudPanelId") + .HasColumnType("int"); + + b.Property("Domain") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OwnerId") + .HasColumnType("int"); + + b.Property("Password") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("VHostTemplate") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("CloudPanelId"); + + b.HasIndex("OwnerId"); + + b.ToTable("WebSpaces"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.DdosAttack", b => + { + b.HasOne("Moonlight.App.Database.Entities.Node", "Node") + .WithMany() + .HasForeignKey("NodeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Node"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.DockerImage", b => + { + b.HasOne("Moonlight.App.Database.Entities.Image", null) + .WithMany("DockerImages") + .HasForeignKey("ImageId"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Domain", b => + { + b.HasOne("Moonlight.App.Database.Entities.User", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.App.Database.Entities.SharedDomain", "SharedDomain") + .WithMany() + .HasForeignKey("SharedDomainId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + + b.Navigation("SharedDomain"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.ImageVariable", b => + { + b.HasOne("Moonlight.App.Database.Entities.Image", null) + .WithMany("Variables") + .HasForeignKey("ImageId"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.MySqlDatabase", b => + { + b.HasOne("Moonlight.App.Database.Entities.WebSpace", "WebSpace") + .WithMany("Databases") + .HasForeignKey("WebSpaceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("WebSpace"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.NodeAllocation", b => + { + b.HasOne("Moonlight.App.Database.Entities.Node", null) + .WithMany("Allocations") + .HasForeignKey("NodeId"); + + b.HasOne("Moonlight.App.Database.Entities.Server", null) + .WithMany("Allocations") + .HasForeignKey("ServerId"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Notification.NotificationAction", b => + { + b.HasOne("Moonlight.App.Database.Entities.Notification.NotificationClient", "NotificationClient") + .WithMany() + .HasForeignKey("NotificationClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("NotificationClient"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Notification.NotificationClient", b => + { + b.HasOne("Moonlight.App.Database.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Server", b => + { + b.HasOne("Moonlight.App.Database.Entities.ServerBackup", "Archive") + .WithMany() + .HasForeignKey("ArchiveId"); + + b.HasOne("Moonlight.App.Database.Entities.Image", "Image") + .WithMany() + .HasForeignKey("ImageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.App.Database.Entities.NodeAllocation", "MainAllocation") + .WithMany() + .HasForeignKey("MainAllocationId"); + + b.HasOne("Moonlight.App.Database.Entities.Node", "Node") + .WithMany() + .HasForeignKey("NodeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.App.Database.Entities.User", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Archive"); + + b.Navigation("Image"); + + b.Navigation("MainAllocation"); + + b.Navigation("Node"); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.ServerBackup", b => + { + b.HasOne("Moonlight.App.Database.Entities.Server", null) + .WithMany("Backups") + .HasForeignKey("ServerId"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.ServerVariable", b => + { + b.HasOne("Moonlight.App.Database.Entities.Server", null) + .WithMany("Variables") + .HasForeignKey("ServerId"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.SupportChatMessage", b => + { + b.HasOne("Moonlight.App.Database.Entities.User", "Recipient") + .WithMany() + .HasForeignKey("RecipientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.App.Database.Entities.User", "Sender") + .WithMany() + .HasForeignKey("SenderId"); + + b.Navigation("Recipient"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.User", b => + { + b.HasOne("Moonlight.App.Database.Entities.Subscription", "CurrentSubscription") + .WithMany() + .HasForeignKey("CurrentSubscriptionId"); + + b.Navigation("CurrentSubscription"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.WebSpace", b => + { + b.HasOne("Moonlight.App.Database.Entities.CloudPanel", "CloudPanel") + .WithMany() + .HasForeignKey("CloudPanelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.App.Database.Entities.User", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CloudPanel"); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Image", b => + { + b.Navigation("DockerImages"); + + b.Navigation("Variables"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Node", b => + { + b.Navigation("Allocations"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Server", b => + { + b.Navigation("Allocations"); + + b.Navigation("Backups"); + + b.Navigation("Variables"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.WebSpace", b => + { + b.Navigation("Databases"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Moonlight/App/Database/Migrations/20230715095531_AddPermissions.cs b/Moonlight/App/Database/Migrations/20230715095531_AddPermissions.cs new file mode 100644 index 00000000..05c6c80e --- /dev/null +++ b/Moonlight/App/Database/Migrations/20230715095531_AddPermissions.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Moonlight.App.Database.Migrations +{ + /// + public partial class AddPermissions : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "Permissions", + table: "Users", + type: "longblob", + nullable: false); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropColumn( + name: "Permissions", + table: "Users"); + } + } +} diff --git a/Moonlight/App/Database/Migrations/20230715214550_AddPermissionGroup.Designer.cs b/Moonlight/App/Database/Migrations/20230715214550_AddPermissionGroup.Designer.cs new file mode 100644 index 00000000..0e593f36 --- /dev/null +++ b/Moonlight/App/Database/Migrations/20230715214550_AddPermissionGroup.Designer.cs @@ -0,0 +1,1139 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Moonlight.App.Database; + +#nullable disable + +namespace Moonlight.App.Database.Migrations +{ + [DbContext(typeof(DataContext))] + [Migration("20230715214550_AddPermissionGroup")] + partial class AddPermissionGroup + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("Moonlight.App.Database.Entities.CloudPanel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("ApiKey") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ApiUrl") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Host") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("CloudPanels"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.DdosAttack", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("bigint"); + + b.Property("Ip") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("NodeId") + .HasColumnType("int"); + + b.Property("Ongoing") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("NodeId"); + + b.ToTable("DdosAttacks"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.DockerImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Default") + .HasColumnType("tinyint(1)"); + + b.Property("ImageId") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("ImageId"); + + b.ToTable("DockerImages"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Domain", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OwnerId") + .HasColumnType("int"); + + b.Property("SharedDomainId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OwnerId"); + + b.HasIndex("SharedDomainId"); + + b.ToTable("Domains"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Image", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Allocations") + .HasColumnType("int"); + + b.Property("BackgroundImageUrl") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ConfigFiles") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("InstallDockerImage") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("InstallEntrypoint") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("InstallScript") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Startup") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StartupDetection") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StopCommand") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("TagsJson") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Uuid") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.ToTable("Images"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.ImageVariable", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("DefaultValue") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ImageId") + .HasColumnType("int"); + + b.Property("Key") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("ImageId"); + + b.ToTable("ImageVariables"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.IpBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Ip") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("IpBans"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.LoadingMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Message") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("LoadingMessages"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.LogsEntries.AuditLogEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Ip") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("JsonData") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("System") + .HasColumnType("tinyint(1)"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("AuditLog"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.LogsEntries.ErrorLogEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Class") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Ip") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("JsonData") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Stacktrace") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("System") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.ToTable("ErrorLog"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.LogsEntries.SecurityLogEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Ip") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("JsonData") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("System") + .HasColumnType("tinyint(1)"); + + b.Property("Type") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.ToTable("SecurityLog"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.MySqlDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Password") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("WebSpaceId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("WebSpaceId"); + + b.ToTable("Databases"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.NewsEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Date") + .HasColumnType("datetime(6)"); + + b.Property("Markdown") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Title") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("NewsEntries"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Node", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Fqdn") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("HttpPort") + .HasColumnType("int"); + + b.Property("MoonlightDaemonPort") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("SftpPort") + .HasColumnType("int"); + + b.Property("Ssl") + .HasColumnType("tinyint(1)"); + + b.Property("Token") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("TokenId") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Nodes"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.NodeAllocation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("NodeId") + .HasColumnType("int"); + + b.Property("Port") + .HasColumnType("int"); + + b.Property("ServerId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("NodeId"); + + b.HasIndex("ServerId"); + + b.ToTable("NodeAllocations"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Notification.NotificationAction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Action") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("NotificationClientId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("NotificationClientId"); + + b.ToTable("NotificationActions"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Notification.NotificationClient", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("NotificationClients"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.PermissionGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Permissions") + .IsRequired() + .HasColumnType("longblob"); + + b.HasKey("Id"); + + b.ToTable("PermissionGroups"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Revoke", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Identifier") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Revokes"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Server", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("ArchiveId") + .HasColumnType("int"); + + b.Property("Cpu") + .HasColumnType("int"); + + b.Property("Disk") + .HasColumnType("bigint"); + + b.Property("DockerImageIndex") + .HasColumnType("int"); + + b.Property("ImageId") + .HasColumnType("int"); + + b.Property("Installing") + .HasColumnType("tinyint(1)"); + + b.Property("IsArchived") + .HasColumnType("tinyint(1)"); + + b.Property("IsCleanupException") + .HasColumnType("tinyint(1)"); + + b.Property("MainAllocationId") + .HasColumnType("int"); + + b.Property("Memory") + .HasColumnType("bigint"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("NodeId") + .HasColumnType("int"); + + b.Property("OverrideStartup") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OwnerId") + .HasColumnType("int"); + + b.Property("Suspended") + .HasColumnType("tinyint(1)"); + + b.Property("Uuid") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("ArchiveId"); + + b.HasIndex("ImageId"); + + b.HasIndex("MainAllocationId"); + + b.HasIndex("NodeId"); + + b.HasIndex("OwnerId"); + + b.ToTable("Servers"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.ServerBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Bytes") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("tinyint(1)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ServerId") + .HasColumnType("int"); + + b.Property("Uuid") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("ServerId"); + + b.ToTable("ServerBackups"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.ServerVariable", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Key") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ServerId") + .HasColumnType("int"); + + b.Property("Value") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("ServerId"); + + b.ToTable("ServerVariables"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.SharedDomain", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CloudflareId") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("SharedDomains"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.StatisticsData", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Chart") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Date") + .HasColumnType("datetime(6)"); + + b.Property("Value") + .HasColumnType("double"); + + b.HasKey("Id"); + + b.ToTable("Statistics"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Subscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Currency") + .HasColumnType("int"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Duration") + .HasColumnType("int"); + + b.Property("LimitsJson") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Price") + .HasColumnType("double"); + + b.Property("StripePriceId") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StripeProductId") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Subscriptions"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.SupportChatMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Answer") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Attachment") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Content") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsQuestion") + .HasColumnType("tinyint(1)"); + + b.Property("QuestionType") + .HasColumnType("int"); + + b.Property("RecipientId") + .HasColumnType("int"); + + b.Property("SenderId") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("RecipientId"); + + b.HasIndex("SenderId"); + + b.ToTable("SupportChatMessages"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Address") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Admin") + .HasColumnType("tinyint(1)"); + + b.Property("City") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Country") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CurrentSubscriptionId") + .HasColumnType("int"); + + b.Property("DiscordId") + .HasColumnType("bigint unsigned"); + + b.Property("Email") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("HasRated") + .HasColumnType("tinyint(1)"); + + b.Property("LastIp") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("LastVisitedAt") + .HasColumnType("datetime(6)"); + + b.Property("Password") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("PermissionGroupId") + .HasColumnType("int"); + + b.Property("Permissions") + .IsRequired() + .HasColumnType("longblob"); + + b.Property("Rating") + .HasColumnType("int"); + + b.Property("RegisterIp") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ServerListLayoutJson") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("State") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StreamerMode") + .HasColumnType("tinyint(1)"); + + b.Property("SubscriptionExpires") + .HasColumnType("datetime(6)"); + + b.Property("SubscriptionSince") + .HasColumnType("datetime(6)"); + + b.Property("SupportPending") + .HasColumnType("tinyint(1)"); + + b.Property("TokenValidTime") + .HasColumnType("datetime(6)"); + + b.Property("TotpEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("TotpSecret") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("CurrentSubscriptionId"); + + b.HasIndex("PermissionGroupId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.WebSpace", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CloudPanelId") + .HasColumnType("int"); + + b.Property("Domain") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OwnerId") + .HasColumnType("int"); + + b.Property("Password") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("VHostTemplate") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("CloudPanelId"); + + b.HasIndex("OwnerId"); + + b.ToTable("WebSpaces"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.DdosAttack", b => + { + b.HasOne("Moonlight.App.Database.Entities.Node", "Node") + .WithMany() + .HasForeignKey("NodeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Node"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.DockerImage", b => + { + b.HasOne("Moonlight.App.Database.Entities.Image", null) + .WithMany("DockerImages") + .HasForeignKey("ImageId"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Domain", b => + { + b.HasOne("Moonlight.App.Database.Entities.User", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.App.Database.Entities.SharedDomain", "SharedDomain") + .WithMany() + .HasForeignKey("SharedDomainId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + + b.Navigation("SharedDomain"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.ImageVariable", b => + { + b.HasOne("Moonlight.App.Database.Entities.Image", null) + .WithMany("Variables") + .HasForeignKey("ImageId"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.MySqlDatabase", b => + { + b.HasOne("Moonlight.App.Database.Entities.WebSpace", "WebSpace") + .WithMany("Databases") + .HasForeignKey("WebSpaceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("WebSpace"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.NodeAllocation", b => + { + b.HasOne("Moonlight.App.Database.Entities.Node", null) + .WithMany("Allocations") + .HasForeignKey("NodeId"); + + b.HasOne("Moonlight.App.Database.Entities.Server", null) + .WithMany("Allocations") + .HasForeignKey("ServerId"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Notification.NotificationAction", b => + { + b.HasOne("Moonlight.App.Database.Entities.Notification.NotificationClient", "NotificationClient") + .WithMany() + .HasForeignKey("NotificationClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("NotificationClient"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Notification.NotificationClient", b => + { + b.HasOne("Moonlight.App.Database.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Server", b => + { + b.HasOne("Moonlight.App.Database.Entities.ServerBackup", "Archive") + .WithMany() + .HasForeignKey("ArchiveId"); + + b.HasOne("Moonlight.App.Database.Entities.Image", "Image") + .WithMany() + .HasForeignKey("ImageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.App.Database.Entities.NodeAllocation", "MainAllocation") + .WithMany() + .HasForeignKey("MainAllocationId"); + + b.HasOne("Moonlight.App.Database.Entities.Node", "Node") + .WithMany() + .HasForeignKey("NodeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.App.Database.Entities.User", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Archive"); + + b.Navigation("Image"); + + b.Navigation("MainAllocation"); + + b.Navigation("Node"); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.ServerBackup", b => + { + b.HasOne("Moonlight.App.Database.Entities.Server", null) + .WithMany("Backups") + .HasForeignKey("ServerId"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.ServerVariable", b => + { + b.HasOne("Moonlight.App.Database.Entities.Server", null) + .WithMany("Variables") + .HasForeignKey("ServerId"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.SupportChatMessage", b => + { + b.HasOne("Moonlight.App.Database.Entities.User", "Recipient") + .WithMany() + .HasForeignKey("RecipientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.App.Database.Entities.User", "Sender") + .WithMany() + .HasForeignKey("SenderId"); + + b.Navigation("Recipient"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.User", b => + { + b.HasOne("Moonlight.App.Database.Entities.Subscription", "CurrentSubscription") + .WithMany() + .HasForeignKey("CurrentSubscriptionId"); + + b.HasOne("Moonlight.App.Database.Entities.PermissionGroup", "PermissionGroup") + .WithMany() + .HasForeignKey("PermissionGroupId"); + + b.Navigation("CurrentSubscription"); + + b.Navigation("PermissionGroup"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.WebSpace", b => + { + b.HasOne("Moonlight.App.Database.Entities.CloudPanel", "CloudPanel") + .WithMany() + .HasForeignKey("CloudPanelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.App.Database.Entities.User", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CloudPanel"); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Image", b => + { + b.Navigation("DockerImages"); + + b.Navigation("Variables"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Node", b => + { + b.Navigation("Allocations"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Server", b => + { + b.Navigation("Allocations"); + + b.Navigation("Backups"); + + b.Navigation("Variables"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.WebSpace", b => + { + b.Navigation("Databases"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Moonlight/App/Database/Migrations/20230715214550_AddPermissionGroup.cs b/Moonlight/App/Database/Migrations/20230715214550_AddPermissionGroup.cs new file mode 100644 index 00000000..c7201a48 --- /dev/null +++ b/Moonlight/App/Database/Migrations/20230715214550_AddPermissionGroup.cs @@ -0,0 +1,68 @@ +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Moonlight.App.Database.Migrations +{ + /// + public partial class AddPermissionGroup : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "PermissionGroupId", + table: "Users", + type: "int", + nullable: true); + + migrationBuilder.CreateTable( + name: "PermissionGroups", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + Name = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + Permissions = table.Column(type: "longblob", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_PermissionGroups", x => x.Id); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateIndex( + name: "IX_Users_PermissionGroupId", + table: "Users", + column: "PermissionGroupId"); + + migrationBuilder.AddForeignKey( + name: "FK_Users_PermissionGroups_PermissionGroupId", + table: "Users", + column: "PermissionGroupId", + principalTable: "PermissionGroups", + principalColumn: "Id"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropForeignKey( + name: "FK_Users_PermissionGroups_PermissionGroupId", + table: "Users"); + + migrationBuilder.DropTable( + name: "PermissionGroups"); + + migrationBuilder.DropIndex( + name: "IX_Users_PermissionGroupId", + table: "Users"); + + migrationBuilder.DropColumn( + name: "PermissionGroupId", + table: "Users"); + } + } +} diff --git a/Moonlight/App/Database/Migrations/20230718123232_RemovedOldLogsAndAddedErrorLog.Designer.cs b/Moonlight/App/Database/Migrations/20230718123232_RemovedOldLogsAndAddedErrorLog.Designer.cs new file mode 100644 index 00000000..63c7352f --- /dev/null +++ b/Moonlight/App/Database/Migrations/20230718123232_RemovedOldLogsAndAddedErrorLog.Designer.cs @@ -0,0 +1,1068 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Moonlight.App.Database; + +#nullable disable + +namespace Moonlight.App.Database.Migrations +{ + [DbContext(typeof(DataContext))] + [Migration("20230718123232_RemovedOldLogsAndAddedErrorLog")] + partial class RemovedOldLogsAndAddedErrorLog + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("Moonlight.App.Database.Entities.CloudPanel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("ApiKey") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ApiUrl") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Host") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("CloudPanels"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.DdosAttack", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("bigint"); + + b.Property("Ip") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("NodeId") + .HasColumnType("int"); + + b.Property("Ongoing") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("NodeId"); + + b.ToTable("DdosAttacks"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.DockerImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Default") + .HasColumnType("tinyint(1)"); + + b.Property("ImageId") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("ImageId"); + + b.ToTable("DockerImages"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Domain", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OwnerId") + .HasColumnType("int"); + + b.Property("SharedDomainId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OwnerId"); + + b.HasIndex("SharedDomainId"); + + b.ToTable("Domains"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Image", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Allocations") + .HasColumnType("int"); + + b.Property("BackgroundImageUrl") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ConfigFiles") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("InstallDockerImage") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("InstallEntrypoint") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("InstallScript") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Startup") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StartupDetection") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StopCommand") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("TagsJson") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Uuid") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.ToTable("Images"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.ImageVariable", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("DefaultValue") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ImageId") + .HasColumnType("int"); + + b.Property("Key") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("ImageId"); + + b.ToTable("ImageVariables"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.IpBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Ip") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("IpBans"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.LoadingMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Message") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("LoadingMessages"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.MySqlDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Password") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("WebSpaceId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("WebSpaceId"); + + b.ToTable("Databases"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.NewsEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Date") + .HasColumnType("datetime(6)"); + + b.Property("Markdown") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Title") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("NewsEntries"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Node", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Fqdn") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("HttpPort") + .HasColumnType("int"); + + b.Property("MoonlightDaemonPort") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("SftpPort") + .HasColumnType("int"); + + b.Property("Ssl") + .HasColumnType("tinyint(1)"); + + b.Property("Token") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("TokenId") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Nodes"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.NodeAllocation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("NodeId") + .HasColumnType("int"); + + b.Property("Port") + .HasColumnType("int"); + + b.Property("ServerId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("NodeId"); + + b.HasIndex("ServerId"); + + b.ToTable("NodeAllocations"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Notification.NotificationAction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Action") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("NotificationClientId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("NotificationClientId"); + + b.ToTable("NotificationActions"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Notification.NotificationClient", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("NotificationClients"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.PermissionGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Permissions") + .IsRequired() + .HasColumnType("longblob"); + + b.HasKey("Id"); + + b.ToTable("PermissionGroups"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Revoke", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Identifier") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Revokes"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.SecurityLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Text") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("SecurityLogs"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Server", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("ArchiveId") + .HasColumnType("int"); + + b.Property("Cpu") + .HasColumnType("int"); + + b.Property("Disk") + .HasColumnType("bigint"); + + b.Property("DockerImageIndex") + .HasColumnType("int"); + + b.Property("ImageId") + .HasColumnType("int"); + + b.Property("Installing") + .HasColumnType("tinyint(1)"); + + b.Property("IsArchived") + .HasColumnType("tinyint(1)"); + + b.Property("IsCleanupException") + .HasColumnType("tinyint(1)"); + + b.Property("MainAllocationId") + .HasColumnType("int"); + + b.Property("Memory") + .HasColumnType("bigint"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("NodeId") + .HasColumnType("int"); + + b.Property("OverrideStartup") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OwnerId") + .HasColumnType("int"); + + b.Property("Suspended") + .HasColumnType("tinyint(1)"); + + b.Property("Uuid") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("ArchiveId"); + + b.HasIndex("ImageId"); + + b.HasIndex("MainAllocationId"); + + b.HasIndex("NodeId"); + + b.HasIndex("OwnerId"); + + b.ToTable("Servers"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.ServerBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Bytes") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("tinyint(1)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ServerId") + .HasColumnType("int"); + + b.Property("Uuid") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("ServerId"); + + b.ToTable("ServerBackups"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.ServerVariable", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Key") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ServerId") + .HasColumnType("int"); + + b.Property("Value") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("ServerId"); + + b.ToTable("ServerVariables"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.SharedDomain", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CloudflareId") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("SharedDomains"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.StatisticsData", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Chart") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Date") + .HasColumnType("datetime(6)"); + + b.Property("Value") + .HasColumnType("double"); + + b.HasKey("Id"); + + b.ToTable("Statistics"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Subscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Currency") + .HasColumnType("int"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Duration") + .HasColumnType("int"); + + b.Property("LimitsJson") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Price") + .HasColumnType("double"); + + b.Property("StripePriceId") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StripeProductId") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Subscriptions"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.SupportChatMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Answer") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Attachment") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Content") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsQuestion") + .HasColumnType("tinyint(1)"); + + b.Property("QuestionType") + .HasColumnType("int"); + + b.Property("RecipientId") + .HasColumnType("int"); + + b.Property("SenderId") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("RecipientId"); + + b.HasIndex("SenderId"); + + b.ToTable("SupportChatMessages"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Address") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Admin") + .HasColumnType("tinyint(1)"); + + b.Property("City") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Country") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CurrentSubscriptionId") + .HasColumnType("int"); + + b.Property("DiscordId") + .HasColumnType("bigint unsigned"); + + b.Property("Email") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("HasRated") + .HasColumnType("tinyint(1)"); + + b.Property("LastIp") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("LastVisitedAt") + .HasColumnType("datetime(6)"); + + b.Property("Password") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("PermissionGroupId") + .HasColumnType("int"); + + b.Property("Permissions") + .IsRequired() + .HasColumnType("longblob"); + + b.Property("Rating") + .HasColumnType("int"); + + b.Property("RegisterIp") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ServerListLayoutJson") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("State") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StreamerMode") + .HasColumnType("tinyint(1)"); + + b.Property("SubscriptionExpires") + .HasColumnType("datetime(6)"); + + b.Property("SubscriptionSince") + .HasColumnType("datetime(6)"); + + b.Property("SupportPending") + .HasColumnType("tinyint(1)"); + + b.Property("TokenValidTime") + .HasColumnType("datetime(6)"); + + b.Property("TotpEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("TotpSecret") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("CurrentSubscriptionId"); + + b.HasIndex("PermissionGroupId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.WebSpace", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CloudPanelId") + .HasColumnType("int"); + + b.Property("Domain") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OwnerId") + .HasColumnType("int"); + + b.Property("Password") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("VHostTemplate") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("CloudPanelId"); + + b.HasIndex("OwnerId"); + + b.ToTable("WebSpaces"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.DdosAttack", b => + { + b.HasOne("Moonlight.App.Database.Entities.Node", "Node") + .WithMany() + .HasForeignKey("NodeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Node"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.DockerImage", b => + { + b.HasOne("Moonlight.App.Database.Entities.Image", null) + .WithMany("DockerImages") + .HasForeignKey("ImageId"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Domain", b => + { + b.HasOne("Moonlight.App.Database.Entities.User", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.App.Database.Entities.SharedDomain", "SharedDomain") + .WithMany() + .HasForeignKey("SharedDomainId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + + b.Navigation("SharedDomain"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.ImageVariable", b => + { + b.HasOne("Moonlight.App.Database.Entities.Image", null) + .WithMany("Variables") + .HasForeignKey("ImageId"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.MySqlDatabase", b => + { + b.HasOne("Moonlight.App.Database.Entities.WebSpace", "WebSpace") + .WithMany("Databases") + .HasForeignKey("WebSpaceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("WebSpace"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.NodeAllocation", b => + { + b.HasOne("Moonlight.App.Database.Entities.Node", null) + .WithMany("Allocations") + .HasForeignKey("NodeId"); + + b.HasOne("Moonlight.App.Database.Entities.Server", null) + .WithMany("Allocations") + .HasForeignKey("ServerId"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Notification.NotificationAction", b => + { + b.HasOne("Moonlight.App.Database.Entities.Notification.NotificationClient", "NotificationClient") + .WithMany() + .HasForeignKey("NotificationClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("NotificationClient"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Notification.NotificationClient", b => + { + b.HasOne("Moonlight.App.Database.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Server", b => + { + b.HasOne("Moonlight.App.Database.Entities.ServerBackup", "Archive") + .WithMany() + .HasForeignKey("ArchiveId"); + + b.HasOne("Moonlight.App.Database.Entities.Image", "Image") + .WithMany() + .HasForeignKey("ImageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.App.Database.Entities.NodeAllocation", "MainAllocation") + .WithMany() + .HasForeignKey("MainAllocationId"); + + b.HasOne("Moonlight.App.Database.Entities.Node", "Node") + .WithMany() + .HasForeignKey("NodeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.App.Database.Entities.User", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Archive"); + + b.Navigation("Image"); + + b.Navigation("MainAllocation"); + + b.Navigation("Node"); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.ServerBackup", b => + { + b.HasOne("Moonlight.App.Database.Entities.Server", null) + .WithMany("Backups") + .HasForeignKey("ServerId"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.ServerVariable", b => + { + b.HasOne("Moonlight.App.Database.Entities.Server", null) + .WithMany("Variables") + .HasForeignKey("ServerId"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.SupportChatMessage", b => + { + b.HasOne("Moonlight.App.Database.Entities.User", "Recipient") + .WithMany() + .HasForeignKey("RecipientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.App.Database.Entities.User", "Sender") + .WithMany() + .HasForeignKey("SenderId"); + + b.Navigation("Recipient"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.User", b => + { + b.HasOne("Moonlight.App.Database.Entities.Subscription", "CurrentSubscription") + .WithMany() + .HasForeignKey("CurrentSubscriptionId"); + + b.HasOne("Moonlight.App.Database.Entities.PermissionGroup", "PermissionGroup") + .WithMany() + .HasForeignKey("PermissionGroupId"); + + b.Navigation("CurrentSubscription"); + + b.Navigation("PermissionGroup"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.WebSpace", b => + { + b.HasOne("Moonlight.App.Database.Entities.CloudPanel", "CloudPanel") + .WithMany() + .HasForeignKey("CloudPanelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.App.Database.Entities.User", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CloudPanel"); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Image", b => + { + b.Navigation("DockerImages"); + + b.Navigation("Variables"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Node", b => + { + b.Navigation("Allocations"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Server", b => + { + b.Navigation("Allocations"); + + b.Navigation("Backups"); + + b.Navigation("Variables"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.WebSpace", b => + { + b.Navigation("Databases"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Moonlight/App/Database/Migrations/20230718123232_RemovedOldLogsAndAddedErrorLog.cs b/Moonlight/App/Database/Migrations/20230718123232_RemovedOldLogsAndAddedErrorLog.cs new file mode 100644 index 00000000..4dbf8103 --- /dev/null +++ b/Moonlight/App/Database/Migrations/20230718123232_RemovedOldLogsAndAddedErrorLog.cs @@ -0,0 +1,111 @@ +using System; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Moonlight.App.Database.Migrations +{ + /// + public partial class RemovedOldLogsAndAddedErrorLog : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "AuditLog"); + + migrationBuilder.DropTable( + name: "ErrorLog"); + + migrationBuilder.DropTable( + name: "SecurityLog"); + + migrationBuilder.CreateTable( + name: "SecurityLogs", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + Text = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + CreatedAt = table.Column(type: "datetime(6)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SecurityLogs", x => x.Id); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "SecurityLogs"); + + migrationBuilder.CreateTable( + name: "AuditLog", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + CreatedAt = table.Column(type: "datetime(6)", nullable: false), + Ip = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + JsonData = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + System = table.Column(type: "tinyint(1)", nullable: false), + Type = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_AuditLog", x => x.Id); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "ErrorLog", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + Class = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + CreatedAt = table.Column(type: "datetime(6)", nullable: false), + Ip = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + JsonData = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + Stacktrace = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + System = table.Column(type: "tinyint(1)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ErrorLog", x => x.Id); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "SecurityLog", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + CreatedAt = table.Column(type: "datetime(6)", nullable: false), + Ip = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + JsonData = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + System = table.Column(type: "tinyint(1)", nullable: false), + Type = table.Column(type: "int", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SecurityLog", x => x.Id); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + } + } +} diff --git a/Moonlight/App/Database/Migrations/20230721201950_AddIpRules.Designer.cs b/Moonlight/App/Database/Migrations/20230721201950_AddIpRules.Designer.cs new file mode 100644 index 00000000..2633a17a --- /dev/null +++ b/Moonlight/App/Database/Migrations/20230721201950_AddIpRules.Designer.cs @@ -0,0 +1,1107 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Moonlight.App.Database; + +#nullable disable + +namespace Moonlight.App.Database.Migrations +{ + [DbContext(typeof(DataContext))] + [Migration("20230721201950_AddIpRules")] + partial class AddIpRules + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("Moonlight.App.Database.Entities.BlocklistIp", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ExpiresAt") + .HasColumnType("datetime(6)"); + + b.Property("Ip") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Packets") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.ToTable("BlocklistIps"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.CloudPanel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("ApiKey") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ApiUrl") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Host") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("CloudPanels"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.DdosAttack", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("bigint"); + + b.Property("Ip") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("NodeId") + .HasColumnType("int"); + + b.Property("Ongoing") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("NodeId"); + + b.ToTable("DdosAttacks"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.DockerImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Default") + .HasColumnType("tinyint(1)"); + + b.Property("ImageId") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("ImageId"); + + b.ToTable("DockerImages"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Domain", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OwnerId") + .HasColumnType("int"); + + b.Property("SharedDomainId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OwnerId"); + + b.HasIndex("SharedDomainId"); + + b.ToTable("Domains"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Image", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Allocations") + .HasColumnType("int"); + + b.Property("BackgroundImageUrl") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ConfigFiles") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("InstallDockerImage") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("InstallEntrypoint") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("InstallScript") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Startup") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StartupDetection") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StopCommand") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("TagsJson") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Uuid") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.ToTable("Images"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.ImageVariable", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("DefaultValue") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ImageId") + .HasColumnType("int"); + + b.Property("Key") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("ImageId"); + + b.ToTable("ImageVariables"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.IpBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Ip") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("IpBans"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.LoadingMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Message") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("LoadingMessages"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.MySqlDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Password") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("WebSpaceId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("WebSpaceId"); + + b.ToTable("Databases"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.NewsEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Date") + .HasColumnType("datetime(6)"); + + b.Property("Markdown") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Title") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("NewsEntries"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Node", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Fqdn") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("HttpPort") + .HasColumnType("int"); + + b.Property("MoonlightDaemonPort") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("SftpPort") + .HasColumnType("int"); + + b.Property("Ssl") + .HasColumnType("tinyint(1)"); + + b.Property("Token") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("TokenId") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Nodes"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.NodeAllocation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("NodeId") + .HasColumnType("int"); + + b.Property("Port") + .HasColumnType("int"); + + b.Property("ServerId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("NodeId"); + + b.HasIndex("ServerId"); + + b.ToTable("NodeAllocations"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Notification.NotificationAction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Action") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("NotificationClientId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("NotificationClientId"); + + b.ToTable("NotificationActions"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Notification.NotificationClient", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("NotificationClients"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.PermissionGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Permissions") + .IsRequired() + .HasColumnType("longblob"); + + b.HasKey("Id"); + + b.ToTable("PermissionGroups"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Revoke", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Identifier") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Revokes"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.SecurityLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Text") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("SecurityLogs"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Server", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("ArchiveId") + .HasColumnType("int"); + + b.Property("Cpu") + .HasColumnType("int"); + + b.Property("Disk") + .HasColumnType("bigint"); + + b.Property("DockerImageIndex") + .HasColumnType("int"); + + b.Property("ImageId") + .HasColumnType("int"); + + b.Property("Installing") + .HasColumnType("tinyint(1)"); + + b.Property("IsArchived") + .HasColumnType("tinyint(1)"); + + b.Property("IsCleanupException") + .HasColumnType("tinyint(1)"); + + b.Property("MainAllocationId") + .HasColumnType("int"); + + b.Property("Memory") + .HasColumnType("bigint"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("NodeId") + .HasColumnType("int"); + + b.Property("OverrideStartup") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OwnerId") + .HasColumnType("int"); + + b.Property("Suspended") + .HasColumnType("tinyint(1)"); + + b.Property("Uuid") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("ArchiveId"); + + b.HasIndex("ImageId"); + + b.HasIndex("MainAllocationId"); + + b.HasIndex("NodeId"); + + b.HasIndex("OwnerId"); + + b.ToTable("Servers"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.ServerBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Bytes") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("tinyint(1)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ServerId") + .HasColumnType("int"); + + b.Property("Uuid") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("ServerId"); + + b.ToTable("ServerBackups"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.ServerVariable", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Key") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ServerId") + .HasColumnType("int"); + + b.Property("Value") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("ServerId"); + + b.ToTable("ServerVariables"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.SharedDomain", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CloudflareId") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("SharedDomains"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.StatisticsData", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Chart") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Date") + .HasColumnType("datetime(6)"); + + b.Property("Value") + .HasColumnType("double"); + + b.HasKey("Id"); + + b.ToTable("Statistics"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Subscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Currency") + .HasColumnType("int"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Duration") + .HasColumnType("int"); + + b.Property("LimitsJson") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Price") + .HasColumnType("double"); + + b.Property("StripePriceId") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StripeProductId") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Subscriptions"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.SupportChatMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Answer") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Attachment") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Content") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsQuestion") + .HasColumnType("tinyint(1)"); + + b.Property("QuestionType") + .HasColumnType("int"); + + b.Property("RecipientId") + .HasColumnType("int"); + + b.Property("SenderId") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("RecipientId"); + + b.HasIndex("SenderId"); + + b.ToTable("SupportChatMessages"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Address") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Admin") + .HasColumnType("tinyint(1)"); + + b.Property("City") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Country") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CurrentSubscriptionId") + .HasColumnType("int"); + + b.Property("DiscordId") + .HasColumnType("bigint unsigned"); + + b.Property("Email") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("HasRated") + .HasColumnType("tinyint(1)"); + + b.Property("LastIp") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("LastVisitedAt") + .HasColumnType("datetime(6)"); + + b.Property("Password") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("PermissionGroupId") + .HasColumnType("int"); + + b.Property("Permissions") + .IsRequired() + .HasColumnType("longblob"); + + b.Property("Rating") + .HasColumnType("int"); + + b.Property("RegisterIp") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ServerListLayoutJson") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("State") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StreamerMode") + .HasColumnType("tinyint(1)"); + + b.Property("SubscriptionExpires") + .HasColumnType("datetime(6)"); + + b.Property("SubscriptionSince") + .HasColumnType("datetime(6)"); + + b.Property("SupportPending") + .HasColumnType("tinyint(1)"); + + b.Property("TokenValidTime") + .HasColumnType("datetime(6)"); + + b.Property("TotpEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("TotpSecret") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("CurrentSubscriptionId"); + + b.HasIndex("PermissionGroupId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.WebSpace", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CloudPanelId") + .HasColumnType("int"); + + b.Property("Domain") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OwnerId") + .HasColumnType("int"); + + b.Property("Password") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("VHostTemplate") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("CloudPanelId"); + + b.HasIndex("OwnerId"); + + b.ToTable("WebSpaces"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.WhitelistIp", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Ip") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("WhitelistIps"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.DdosAttack", b => + { + b.HasOne("Moonlight.App.Database.Entities.Node", "Node") + .WithMany() + .HasForeignKey("NodeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Node"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.DockerImage", b => + { + b.HasOne("Moonlight.App.Database.Entities.Image", null) + .WithMany("DockerImages") + .HasForeignKey("ImageId"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Domain", b => + { + b.HasOne("Moonlight.App.Database.Entities.User", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.App.Database.Entities.SharedDomain", "SharedDomain") + .WithMany() + .HasForeignKey("SharedDomainId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + + b.Navigation("SharedDomain"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.ImageVariable", b => + { + b.HasOne("Moonlight.App.Database.Entities.Image", null) + .WithMany("Variables") + .HasForeignKey("ImageId"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.MySqlDatabase", b => + { + b.HasOne("Moonlight.App.Database.Entities.WebSpace", "WebSpace") + .WithMany("Databases") + .HasForeignKey("WebSpaceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("WebSpace"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.NodeAllocation", b => + { + b.HasOne("Moonlight.App.Database.Entities.Node", null) + .WithMany("Allocations") + .HasForeignKey("NodeId"); + + b.HasOne("Moonlight.App.Database.Entities.Server", null) + .WithMany("Allocations") + .HasForeignKey("ServerId"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Notification.NotificationAction", b => + { + b.HasOne("Moonlight.App.Database.Entities.Notification.NotificationClient", "NotificationClient") + .WithMany() + .HasForeignKey("NotificationClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("NotificationClient"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Notification.NotificationClient", b => + { + b.HasOne("Moonlight.App.Database.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Server", b => + { + b.HasOne("Moonlight.App.Database.Entities.ServerBackup", "Archive") + .WithMany() + .HasForeignKey("ArchiveId"); + + b.HasOne("Moonlight.App.Database.Entities.Image", "Image") + .WithMany() + .HasForeignKey("ImageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.App.Database.Entities.NodeAllocation", "MainAllocation") + .WithMany() + .HasForeignKey("MainAllocationId"); + + b.HasOne("Moonlight.App.Database.Entities.Node", "Node") + .WithMany() + .HasForeignKey("NodeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.App.Database.Entities.User", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Archive"); + + b.Navigation("Image"); + + b.Navigation("MainAllocation"); + + b.Navigation("Node"); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.ServerBackup", b => + { + b.HasOne("Moonlight.App.Database.Entities.Server", null) + .WithMany("Backups") + .HasForeignKey("ServerId"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.ServerVariable", b => + { + b.HasOne("Moonlight.App.Database.Entities.Server", null) + .WithMany("Variables") + .HasForeignKey("ServerId"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.SupportChatMessage", b => + { + b.HasOne("Moonlight.App.Database.Entities.User", "Recipient") + .WithMany() + .HasForeignKey("RecipientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.App.Database.Entities.User", "Sender") + .WithMany() + .HasForeignKey("SenderId"); + + b.Navigation("Recipient"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.User", b => + { + b.HasOne("Moonlight.App.Database.Entities.Subscription", "CurrentSubscription") + .WithMany() + .HasForeignKey("CurrentSubscriptionId"); + + b.HasOne("Moonlight.App.Database.Entities.PermissionGroup", "PermissionGroup") + .WithMany() + .HasForeignKey("PermissionGroupId"); + + b.Navigation("CurrentSubscription"); + + b.Navigation("PermissionGroup"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.WebSpace", b => + { + b.HasOne("Moonlight.App.Database.Entities.CloudPanel", "CloudPanel") + .WithMany() + .HasForeignKey("CloudPanelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.App.Database.Entities.User", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CloudPanel"); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Image", b => + { + b.Navigation("DockerImages"); + + b.Navigation("Variables"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Node", b => + { + b.Navigation("Allocations"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Server", b => + { + b.Navigation("Allocations"); + + b.Navigation("Backups"); + + b.Navigation("Variables"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.WebSpace", b => + { + b.Navigation("Databases"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Moonlight/App/Database/Migrations/20230721201950_AddIpRules.cs b/Moonlight/App/Database/Migrations/20230721201950_AddIpRules.cs new file mode 100644 index 00000000..fd59d4db --- /dev/null +++ b/Moonlight/App/Database/Migrations/20230721201950_AddIpRules.cs @@ -0,0 +1,59 @@ +using System; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Moonlight.App.Database.Migrations +{ + /// + public partial class AddIpRules : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "BlocklistIps", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + Ip = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + ExpiresAt = table.Column(type: "datetime(6)", nullable: false), + Packets = table.Column(type: "bigint", nullable: false), + CreatedAt = table.Column(type: "datetime(6)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_BlocklistIps", x => x.Id); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "WhitelistIps", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + Ip = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4") + }, + constraints: table => + { + table.PrimaryKey("PK_WhitelistIps", x => x.Id); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "BlocklistIps"); + + migrationBuilder.DropTable( + name: "WhitelistIps"); + } + } +} diff --git a/Moonlight/App/Database/Migrations/20230803012947_AddNewTicketModels.Designer.cs b/Moonlight/App/Database/Migrations/20230803012947_AddNewTicketModels.Designer.cs new file mode 100644 index 00000000..90562359 --- /dev/null +++ b/Moonlight/App/Database/Migrations/20230803012947_AddNewTicketModels.Designer.cs @@ -0,0 +1,1233 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Moonlight.App.Database; + +#nullable disable + +namespace Moonlight.App.Database.Migrations +{ + [DbContext(typeof(DataContext))] + [Migration("20230803012947_AddNewTicketModels")] + partial class AddNewTicketModels + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "7.0.3") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("Moonlight.App.Database.Entities.BlocklistIp", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ExpiresAt") + .HasColumnType("datetime(6)"); + + b.Property("Ip") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Packets") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.ToTable("BlocklistIps"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.CloudPanel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("ApiKey") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ApiUrl") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Host") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("CloudPanels"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.DdosAttack", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Data") + .HasColumnType("bigint"); + + b.Property("Ip") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("NodeId") + .HasColumnType("int"); + + b.Property("Ongoing") + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("NodeId"); + + b.ToTable("DdosAttacks"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.DockerImage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Default") + .HasColumnType("tinyint(1)"); + + b.Property("ImageId") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("ImageId"); + + b.ToTable("DockerImages"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Domain", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OwnerId") + .HasColumnType("int"); + + b.Property("SharedDomainId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("OwnerId"); + + b.HasIndex("SharedDomainId"); + + b.ToTable("Domains"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Image", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Allocations") + .HasColumnType("int"); + + b.Property("BackgroundImageUrl") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ConfigFiles") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("InstallDockerImage") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("InstallEntrypoint") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("InstallScript") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Startup") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StartupDetection") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StopCommand") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("TagsJson") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Uuid") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.ToTable("Images"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.ImageVariable", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("DefaultValue") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ImageId") + .HasColumnType("int"); + + b.Property("Key") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("ImageId"); + + b.ToTable("ImageVariables"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.IpBan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Ip") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("IpBans"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.LoadingMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Message") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("LoadingMessages"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.MySqlDatabase", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Password") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("WebSpaceId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("WebSpaceId"); + + b.ToTable("Databases"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.NewsEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Date") + .HasColumnType("datetime(6)"); + + b.Property("Markdown") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Title") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("NewsEntries"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Node", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Fqdn") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("HttpPort") + .HasColumnType("int"); + + b.Property("MoonlightDaemonPort") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("SftpPort") + .HasColumnType("int"); + + b.Property("Ssl") + .HasColumnType("tinyint(1)"); + + b.Property("Token") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("TokenId") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Nodes"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.NodeAllocation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("NodeId") + .HasColumnType("int"); + + b.Property("Port") + .HasColumnType("int"); + + b.Property("ServerId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("NodeId"); + + b.HasIndex("ServerId"); + + b.ToTable("NodeAllocations"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Notification.NotificationAction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Action") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("NotificationClientId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("NotificationClientId"); + + b.ToTable("NotificationActions"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Notification.NotificationClient", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("NotificationClients"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.PermissionGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Permissions") + .IsRequired() + .HasColumnType("longblob"); + + b.HasKey("Id"); + + b.ToTable("PermissionGroups"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Revoke", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Identifier") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Revokes"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.SecurityLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Text") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("SecurityLogs"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Server", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("ArchiveId") + .HasColumnType("int"); + + b.Property("Cpu") + .HasColumnType("int"); + + b.Property("Disk") + .HasColumnType("bigint"); + + b.Property("DockerImageIndex") + .HasColumnType("int"); + + b.Property("ImageId") + .HasColumnType("int"); + + b.Property("Installing") + .HasColumnType("tinyint(1)"); + + b.Property("IsArchived") + .HasColumnType("tinyint(1)"); + + b.Property("IsCleanupException") + .HasColumnType("tinyint(1)"); + + b.Property("MainAllocationId") + .HasColumnType("int"); + + b.Property("Memory") + .HasColumnType("bigint"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("NodeId") + .HasColumnType("int"); + + b.Property("OverrideStartup") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OwnerId") + .HasColumnType("int"); + + b.Property("Suspended") + .HasColumnType("tinyint(1)"); + + b.Property("Uuid") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("ArchiveId"); + + b.HasIndex("ImageId"); + + b.HasIndex("MainAllocationId"); + + b.HasIndex("NodeId"); + + b.HasIndex("OwnerId"); + + b.ToTable("Servers"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.ServerBackup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Bytes") + .HasColumnType("bigint"); + + b.Property("Created") + .HasColumnType("tinyint(1)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ServerId") + .HasColumnType("int"); + + b.Property("Uuid") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("ServerId"); + + b.ToTable("ServerBackups"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.ServerVariable", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Key") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ServerId") + .HasColumnType("int"); + + b.Property("Value") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("ServerId"); + + b.ToTable("ServerVariables"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.SharedDomain", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CloudflareId") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("SharedDomains"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.StatisticsData", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Chart") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Date") + .HasColumnType("datetime(6)"); + + b.Property("Value") + .HasColumnType("double"); + + b.HasKey("Id"); + + b.ToTable("Statistics"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Subscription", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Currency") + .HasColumnType("int"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Duration") + .HasColumnType("int"); + + b.Property("LimitsJson") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Price") + .HasColumnType("double"); + + b.Property("StripePriceId") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StripeProductId") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("Subscriptions"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.SupportChatMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Answer") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Attachment") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Content") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsQuestion") + .HasColumnType("tinyint(1)"); + + b.Property("QuestionType") + .HasColumnType("int"); + + b.Property("RecipientId") + .HasColumnType("int"); + + b.Property("SenderId") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("RecipientId"); + + b.HasIndex("SenderId"); + + b.ToTable("SupportChatMessages"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Ticket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("AssignedToId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedById") + .HasColumnType("int"); + + b.Property("IssueDescription") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("IssueTopic") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("IssueTries") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Priority") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Subject") + .HasColumnType("int"); + + b.Property("SubjectId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("AssignedToId"); + + b.HasIndex("CreatedById"); + + b.ToTable("Tickets"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.TicketMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("AttachmentUrl") + .HasColumnType("longtext"); + + b.Property("Content") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsEdited") + .HasColumnType("tinyint(1)"); + + b.Property("IsSupportMessage") + .HasColumnType("tinyint(1)"); + + b.Property("IsSystemMessage") + .HasColumnType("tinyint(1)"); + + b.Property("SenderId") + .HasColumnType("int"); + + b.Property("TicketId") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("SenderId"); + + b.HasIndex("TicketId"); + + b.ToTable("TicketMessages"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Address") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Admin") + .HasColumnType("tinyint(1)"); + + b.Property("City") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Country") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CurrentSubscriptionId") + .HasColumnType("int"); + + b.Property("DiscordId") + .HasColumnType("bigint unsigned"); + + b.Property("Email") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("FirstName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("HasRated") + .HasColumnType("tinyint(1)"); + + b.Property("LastIp") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("LastName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("LastVisitedAt") + .HasColumnType("datetime(6)"); + + b.Property("Password") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("PermissionGroupId") + .HasColumnType("int"); + + b.Property("Permissions") + .IsRequired() + .HasColumnType("longblob"); + + b.Property("Rating") + .HasColumnType("int"); + + b.Property("RegisterIp") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ServerListLayoutJson") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("State") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StreamerMode") + .HasColumnType("tinyint(1)"); + + b.Property("SubscriptionExpires") + .HasColumnType("datetime(6)"); + + b.Property("SubscriptionSince") + .HasColumnType("datetime(6)"); + + b.Property("SupportPending") + .HasColumnType("tinyint(1)"); + + b.Property("TokenValidTime") + .HasColumnType("datetime(6)"); + + b.Property("TotpEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("TotpSecret") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("CurrentSubscriptionId"); + + b.HasIndex("PermissionGroupId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.WebSpace", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CloudPanelId") + .HasColumnType("int"); + + b.Property("Domain") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("OwnerId") + .HasColumnType("int"); + + b.Property("Password") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("UserName") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("VHostTemplate") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.HasIndex("CloudPanelId"); + + b.HasIndex("OwnerId"); + + b.ToTable("WebSpaces"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.WhitelistIp", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Ip") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("WhitelistIps"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.DdosAttack", b => + { + b.HasOne("Moonlight.App.Database.Entities.Node", "Node") + .WithMany() + .HasForeignKey("NodeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Node"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.DockerImage", b => + { + b.HasOne("Moonlight.App.Database.Entities.Image", null) + .WithMany("DockerImages") + .HasForeignKey("ImageId"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Domain", b => + { + b.HasOne("Moonlight.App.Database.Entities.User", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.App.Database.Entities.SharedDomain", "SharedDomain") + .WithMany() + .HasForeignKey("SharedDomainId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Owner"); + + b.Navigation("SharedDomain"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.ImageVariable", b => + { + b.HasOne("Moonlight.App.Database.Entities.Image", null) + .WithMany("Variables") + .HasForeignKey("ImageId"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.MySqlDatabase", b => + { + b.HasOne("Moonlight.App.Database.Entities.WebSpace", "WebSpace") + .WithMany("Databases") + .HasForeignKey("WebSpaceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("WebSpace"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.NodeAllocation", b => + { + b.HasOne("Moonlight.App.Database.Entities.Node", null) + .WithMany("Allocations") + .HasForeignKey("NodeId"); + + b.HasOne("Moonlight.App.Database.Entities.Server", null) + .WithMany("Allocations") + .HasForeignKey("ServerId"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Notification.NotificationAction", b => + { + b.HasOne("Moonlight.App.Database.Entities.Notification.NotificationClient", "NotificationClient") + .WithMany() + .HasForeignKey("NotificationClientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("NotificationClient"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Notification.NotificationClient", b => + { + b.HasOne("Moonlight.App.Database.Entities.User", "User") + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("User"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Server", b => + { + b.HasOne("Moonlight.App.Database.Entities.ServerBackup", "Archive") + .WithMany() + .HasForeignKey("ArchiveId"); + + b.HasOne("Moonlight.App.Database.Entities.Image", "Image") + .WithMany() + .HasForeignKey("ImageId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.App.Database.Entities.NodeAllocation", "MainAllocation") + .WithMany() + .HasForeignKey("MainAllocationId"); + + b.HasOne("Moonlight.App.Database.Entities.Node", "Node") + .WithMany() + .HasForeignKey("NodeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.App.Database.Entities.User", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Archive"); + + b.Navigation("Image"); + + b.Navigation("MainAllocation"); + + b.Navigation("Node"); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.ServerBackup", b => + { + b.HasOne("Moonlight.App.Database.Entities.Server", null) + .WithMany("Backups") + .HasForeignKey("ServerId"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.ServerVariable", b => + { + b.HasOne("Moonlight.App.Database.Entities.Server", null) + .WithMany("Variables") + .HasForeignKey("ServerId"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.SupportChatMessage", b => + { + b.HasOne("Moonlight.App.Database.Entities.User", "Recipient") + .WithMany() + .HasForeignKey("RecipientId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.App.Database.Entities.User", "Sender") + .WithMany() + .HasForeignKey("SenderId"); + + b.Navigation("Recipient"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Ticket", b => + { + b.HasOne("Moonlight.App.Database.Entities.User", "AssignedTo") + .WithMany() + .HasForeignKey("AssignedToId"); + + b.HasOne("Moonlight.App.Database.Entities.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AssignedTo"); + + b.Navigation("CreatedBy"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.TicketMessage", b => + { + b.HasOne("Moonlight.App.Database.Entities.User", "Sender") + .WithMany() + .HasForeignKey("SenderId"); + + b.HasOne("Moonlight.App.Database.Entities.Ticket", null) + .WithMany("Messages") + .HasForeignKey("TicketId"); + + b.Navigation("Sender"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.User", b => + { + b.HasOne("Moonlight.App.Database.Entities.Subscription", "CurrentSubscription") + .WithMany() + .HasForeignKey("CurrentSubscriptionId"); + + b.HasOne("Moonlight.App.Database.Entities.PermissionGroup", "PermissionGroup") + .WithMany() + .HasForeignKey("PermissionGroupId"); + + b.Navigation("CurrentSubscription"); + + b.Navigation("PermissionGroup"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.WebSpace", b => + { + b.HasOne("Moonlight.App.Database.Entities.CloudPanel", "CloudPanel") + .WithMany() + .HasForeignKey("CloudPanelId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Moonlight.App.Database.Entities.User", "Owner") + .WithMany() + .HasForeignKey("OwnerId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CloudPanel"); + + b.Navigation("Owner"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Image", b => + { + b.Navigation("DockerImages"); + + b.Navigation("Variables"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Node", b => + { + b.Navigation("Allocations"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Server", b => + { + b.Navigation("Allocations"); + + b.Navigation("Backups"); + + b.Navigation("Variables"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.Ticket", b => + { + b.Navigation("Messages"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.WebSpace", b => + { + b.Navigation("Databases"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Moonlight/App/Database/Migrations/20230803012947_AddNewTicketModels.cs b/Moonlight/App/Database/Migrations/20230803012947_AddNewTicketModels.cs new file mode 100644 index 00000000..abbd006c --- /dev/null +++ b/Moonlight/App/Database/Migrations/20230803012947_AddNewTicketModels.cs @@ -0,0 +1,117 @@ +using System; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Moonlight.App.Database.Migrations +{ + /// + public partial class AddNewTicketModels : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Tickets", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + IssueTopic = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + IssueDescription = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + IssueTries = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + CreatedById = table.Column(type: "int", nullable: false), + AssignedToId = table.Column(type: "int", nullable: true), + Priority = table.Column(type: "int", nullable: false), + Status = table.Column(type: "int", nullable: false), + Subject = table.Column(type: "int", nullable: false), + SubjectId = table.Column(type: "int", nullable: false), + CreatedAt = table.Column(type: "datetime(6)", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Tickets", x => x.Id); + table.ForeignKey( + name: "FK_Tickets_Users_AssignedToId", + column: x => x.AssignedToId, + principalTable: "Users", + principalColumn: "Id"); + table.ForeignKey( + name: "FK_Tickets_Users_CreatedById", + column: x => x.CreatedById, + principalTable: "Users", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateTable( + name: "TicketMessages", + columns: table => new + { + Id = table.Column(type: "int", nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + Content = table.Column(type: "longtext", nullable: false) + .Annotation("MySql:CharSet", "utf8mb4"), + AttachmentUrl = table.Column(type: "longtext", nullable: true) + .Annotation("MySql:CharSet", "utf8mb4"), + SenderId = table.Column(type: "int", nullable: true), + IsSystemMessage = table.Column(type: "tinyint(1)", nullable: false), + IsEdited = table.Column(type: "tinyint(1)", nullable: false), + IsSupportMessage = table.Column(type: "tinyint(1)", nullable: false), + CreatedAt = table.Column(type: "datetime(6)", nullable: false), + UpdatedAt = table.Column(type: "datetime(6)", nullable: false), + TicketId = table.Column(type: "int", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_TicketMessages", x => x.Id); + table.ForeignKey( + name: "FK_TicketMessages_Tickets_TicketId", + column: x => x.TicketId, + principalTable: "Tickets", + principalColumn: "Id"); + table.ForeignKey( + name: "FK_TicketMessages_Users_SenderId", + column: x => x.SenderId, + principalTable: "Users", + principalColumn: "Id"); + }) + .Annotation("MySql:CharSet", "utf8mb4"); + + migrationBuilder.CreateIndex( + name: "IX_TicketMessages_SenderId", + table: "TicketMessages", + column: "SenderId"); + + migrationBuilder.CreateIndex( + name: "IX_TicketMessages_TicketId", + table: "TicketMessages", + column: "TicketId"); + + migrationBuilder.CreateIndex( + name: "IX_Tickets_AssignedToId", + table: "Tickets", + column: "AssignedToId"); + + migrationBuilder.CreateIndex( + name: "IX_Tickets_CreatedById", + table: "Tickets", + column: "CreatedById"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "TicketMessages"); + + migrationBuilder.DropTable( + name: "Tickets"); + } + } +} diff --git a/Moonlight/App/Database/Migrations/DataContextModelSnapshot.cs b/Moonlight/App/Database/Migrations/DataContextModelSnapshot.cs index dce16c4f..4a21928a 100644 --- a/Moonlight/App/Database/Migrations/DataContextModelSnapshot.cs +++ b/Moonlight/App/Database/Migrations/DataContextModelSnapshot.cs @@ -19,6 +19,30 @@ namespace Moonlight.App.Database.Migrations .HasAnnotation("ProductVersion", "7.0.3") .HasAnnotation("Relational:MaxIdentifierLength", 64); + modelBuilder.Entity("Moonlight.App.Database.Entities.BlocklistIp", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ExpiresAt") + .HasColumnType("datetime(6)"); + + b.Property("Ip") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Packets") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.ToTable("BlocklistIps"); + }); + modelBuilder.Entity("Moonlight.App.Database.Entities.CloudPanel", b => { b.Property("Id") @@ -241,95 +265,6 @@ namespace Moonlight.App.Database.Migrations b.ToTable("LoadingMessages"); }); - modelBuilder.Entity("Moonlight.App.Database.Entities.LogsEntries.AuditLogEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - b.Property("CreatedAt") - .HasColumnType("datetime(6)"); - - b.Property("Ip") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("JsonData") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("System") - .HasColumnType("tinyint(1)"); - - b.Property("Type") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.ToTable("AuditLog"); - }); - - modelBuilder.Entity("Moonlight.App.Database.Entities.LogsEntries.ErrorLogEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - b.Property("Class") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("CreatedAt") - .HasColumnType("datetime(6)"); - - b.Property("Ip") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("JsonData") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("Stacktrace") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("System") - .HasColumnType("tinyint(1)"); - - b.HasKey("Id"); - - b.ToTable("ErrorLog"); - }); - - modelBuilder.Entity("Moonlight.App.Database.Entities.LogsEntries.SecurityLogEntry", b => - { - b.Property("Id") - .ValueGeneratedOnAdd() - .HasColumnType("int"); - - b.Property("CreatedAt") - .HasColumnType("datetime(6)"); - - b.Property("Ip") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("JsonData") - .IsRequired() - .HasColumnType("longtext"); - - b.Property("System") - .HasColumnType("tinyint(1)"); - - b.Property("Type") - .HasColumnType("int"); - - b.HasKey("Id"); - - b.ToTable("SecurityLog"); - }); - modelBuilder.Entity("Moonlight.App.Database.Entities.MySqlDatabase", b => { b.Property("Id") @@ -475,6 +410,25 @@ namespace Moonlight.App.Database.Migrations b.ToTable("NotificationClients"); }); + modelBuilder.Entity("Moonlight.App.Database.Entities.PermissionGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Permissions") + .IsRequired() + .HasColumnType("longblob"); + + b.HasKey("Id"); + + b.ToTable("PermissionGroups"); + }); + modelBuilder.Entity("Moonlight.App.Database.Entities.Revoke", b => { b.Property("Id") @@ -490,6 +444,24 @@ namespace Moonlight.App.Database.Migrations b.ToTable("Revokes"); }); + modelBuilder.Entity("Moonlight.App.Database.Entities.SecurityLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Text") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("SecurityLogs"); + }); + modelBuilder.Entity("Moonlight.App.Database.Entities.Server", b => { b.Property("Id") @@ -663,10 +635,16 @@ namespace Moonlight.App.Database.Migrations .ValueGeneratedOnAdd() .HasColumnType("int"); + b.Property("Currency") + .HasColumnType("int"); + b.Property("Description") .IsRequired() .HasColumnType("longtext"); + b.Property("Duration") + .HasColumnType("int"); + b.Property("LimitsJson") .IsRequired() .HasColumnType("longtext"); @@ -675,6 +653,17 @@ namespace Moonlight.App.Database.Migrations .IsRequired() .HasColumnType("longtext"); + b.Property("Price") + .HasColumnType("double"); + + b.Property("StripePriceId") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("StripeProductId") + .IsRequired() + .HasColumnType("longtext"); + b.HasKey("Id"); b.ToTable("Subscriptions"); @@ -725,6 +714,97 @@ namespace Moonlight.App.Database.Migrations b.ToTable("SupportChatMessages"); }); + modelBuilder.Entity("Moonlight.App.Database.Entities.Ticket", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("AssignedToId") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedById") + .HasColumnType("int"); + + b.Property("IssueDescription") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("IssueTopic") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("IssueTries") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Priority") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("Subject") + .HasColumnType("int"); + + b.Property("SubjectId") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("AssignedToId"); + + b.HasIndex("CreatedById"); + + b.ToTable("Tickets"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.TicketMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("AttachmentUrl") + .HasColumnType("longtext"); + + b.Property("Content") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsEdited") + .HasColumnType("tinyint(1)"); + + b.Property("IsSupportMessage") + .HasColumnType("tinyint(1)"); + + b.Property("IsSystemMessage") + .HasColumnType("tinyint(1)"); + + b.Property("SenderId") + .HasColumnType("int"); + + b.Property("TicketId") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("SenderId"); + + b.HasIndex("TicketId"); + + b.ToTable("TicketMessages"); + }); + modelBuilder.Entity("Moonlight.App.Database.Entities.User", b => { b.Property("Id") @@ -781,6 +861,13 @@ namespace Moonlight.App.Database.Migrations .IsRequired() .HasColumnType("longtext"); + b.Property("PermissionGroupId") + .HasColumnType("int"); + + b.Property("Permissions") + .IsRequired() + .HasColumnType("longblob"); + b.Property("Rating") .HasColumnType("int"); @@ -802,8 +889,8 @@ namespace Moonlight.App.Database.Migrations b.Property("StreamerMode") .HasColumnType("tinyint(1)"); - b.Property("SubscriptionDuration") - .HasColumnType("int"); + b.Property("SubscriptionExpires") + .HasColumnType("datetime(6)"); b.Property("SubscriptionSince") .HasColumnType("datetime(6)"); @@ -828,6 +915,8 @@ namespace Moonlight.App.Database.Migrations b.HasIndex("CurrentSubscriptionId"); + b.HasIndex("PermissionGroupId"); + b.ToTable("Users"); }); @@ -868,6 +957,21 @@ namespace Moonlight.App.Database.Migrations b.ToTable("WebSpaces"); }); + modelBuilder.Entity("Moonlight.App.Database.Entities.WhitelistIp", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("Ip") + .IsRequired() + .HasColumnType("longtext"); + + b.HasKey("Id"); + + b.ToTable("WhitelistIps"); + }); + modelBuilder.Entity("Moonlight.App.Database.Entities.DdosAttack", b => { b.HasOne("Moonlight.App.Database.Entities.Node", "Node") @@ -1026,13 +1130,49 @@ namespace Moonlight.App.Database.Migrations b.Navigation("Sender"); }); + modelBuilder.Entity("Moonlight.App.Database.Entities.Ticket", b => + { + b.HasOne("Moonlight.App.Database.Entities.User", "AssignedTo") + .WithMany() + .HasForeignKey("AssignedToId"); + + b.HasOne("Moonlight.App.Database.Entities.User", "CreatedBy") + .WithMany() + .HasForeignKey("CreatedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AssignedTo"); + + b.Navigation("CreatedBy"); + }); + + modelBuilder.Entity("Moonlight.App.Database.Entities.TicketMessage", b => + { + b.HasOne("Moonlight.App.Database.Entities.User", "Sender") + .WithMany() + .HasForeignKey("SenderId"); + + b.HasOne("Moonlight.App.Database.Entities.Ticket", null) + .WithMany("Messages") + .HasForeignKey("TicketId"); + + b.Navigation("Sender"); + }); + modelBuilder.Entity("Moonlight.App.Database.Entities.User", b => { b.HasOne("Moonlight.App.Database.Entities.Subscription", "CurrentSubscription") .WithMany() .HasForeignKey("CurrentSubscriptionId"); + b.HasOne("Moonlight.App.Database.Entities.PermissionGroup", "PermissionGroup") + .WithMany() + .HasForeignKey("PermissionGroupId"); + b.Navigation("CurrentSubscription"); + + b.Navigation("PermissionGroup"); }); modelBuilder.Entity("Moonlight.App.Database.Entities.WebSpace", b => @@ -1075,6 +1215,11 @@ namespace Moonlight.App.Database.Migrations b.Navigation("Variables"); }); + modelBuilder.Entity("Moonlight.App.Database.Entities.Ticket", b => + { + b.Navigation("Messages"); + }); + modelBuilder.Entity("Moonlight.App.Database.Entities.WebSpace", b => { b.Navigation("Databases"); diff --git a/Moonlight/App/Helpers/BackupHelper.cs b/Moonlight/App/Helpers/BackupHelper.cs new file mode 100644 index 00000000..635bcfde --- /dev/null +++ b/Moonlight/App/Helpers/BackupHelper.cs @@ -0,0 +1,119 @@ +using System.Diagnostics; +using System.IO.Compression; +using Microsoft.EntityFrameworkCore; +using Moonlight.App.Database; +using Moonlight.App.Services; +using MySql.Data.MySqlClient; + +namespace Moonlight.App.Helpers; + +public class BackupHelper +{ + public async Task CreateBackup(string path) + { + Logger.Info("Started moonlight backup creation"); + Logger.Info($"This backup will be saved to '{path}'"); + + var stopWatch = new Stopwatch(); + stopWatch.Start(); + + var cachePath = PathBuilder.Dir("storage", "backups", "cache"); + + Directory.CreateDirectory(cachePath); + + // + // Exporting database + // + + Logger.Info("Exporting database"); + + var configService = new ConfigService(new()); + var dataContext = new DataContext(configService); + + await using MySqlConnection conn = new MySqlConnection(dataContext.Database.GetConnectionString()); + await using MySqlCommand cmd = new MySqlCommand(); + using MySqlBackup mb = new MySqlBackup(cmd); + + cmd.Connection = conn; + await conn.OpenAsync(); + mb.ExportToFile(PathBuilder.File(cachePath, "database.sql")); + await conn.CloseAsync(); + + // + // Saving config + // + + Logger.Info("Saving configuration"); + File.Copy( + PathBuilder.File("storage", "configs", "config.json"), + PathBuilder.File(cachePath, "config.json")); + + // + // Saving all storage items needed to restore the panel + // + + Logger.Info("Saving resources"); + CopyDirectory( + PathBuilder.Dir("storage", "resources"), + PathBuilder.Dir(cachePath, "resources")); + + Logger.Info("Saving logs"); + CopyDirectory( + PathBuilder.Dir("storage", "logs"), + PathBuilder.Dir(cachePath, "logs")); + + Logger.Info("Saving uploads"); + CopyDirectory( + PathBuilder.Dir("storage", "uploads"), + PathBuilder.Dir(cachePath, "uploads")); + + // + // Compressing the backup to a single file + // + + Logger.Info("Compressing"); + ZipFile.CreateFromDirectory(cachePath, + path, + CompressionLevel.Fastest, + false); + + Directory.Delete(cachePath, true); + + stopWatch.Stop(); + Logger.Info($"Backup successfully created. Took {stopWatch.Elapsed.TotalSeconds} seconds"); + } + + private void CopyDirectory(string sourceDirName, string destDirName, bool copySubDirs = true) + { + DirectoryInfo dir = new DirectoryInfo(sourceDirName); + + if (!dir.Exists) + { + throw new DirectoryNotFoundException($"Source directory does not exist or could not be found: {sourceDirName}"); + } + + if (!Directory.Exists(destDirName)) + { + Directory.CreateDirectory(destDirName); + } + + FileInfo[] files = dir.GetFiles(); + + foreach (FileInfo file in files) + { + string tempPath = Path.Combine(destDirName, file.Name); + file.CopyTo(tempPath, false); + } + + if (copySubDirs) + { + DirectoryInfo[] dirs = dir.GetDirectories(); + + foreach (DirectoryInfo subdir in dirs) + { + string tempPath = Path.Combine(destDirName, subdir.Name); + CopyDirectory(subdir.FullName, tempPath, copySubDirs); + } + } + } +} \ No newline at end of file diff --git a/Moonlight/App/Helpers/BitHelper.cs b/Moonlight/App/Helpers/BitHelper.cs new file mode 100644 index 00000000..4f0dacd3 --- /dev/null +++ b/Moonlight/App/Helpers/BitHelper.cs @@ -0,0 +1,88 @@ +namespace Moonlight.App.Helpers; + +public class BitHelper +{ + public static bool ReadBit(byte[] byteArray, int bitIndex) + { + if (bitIndex < 0) + throw new ArgumentOutOfRangeException("bitIndex"); + + int byteIndex = bitIndex / 8; + if (byteIndex >= byteArray.Length) + throw new ArgumentOutOfRangeException("bitIndex"); + + int bitNumber = bitIndex % 8; + byte mask = (byte)(1 << bitNumber); + + return (byteArray[byteIndex] & mask) != 0; + } + + public static byte[] WriteBit(byte[] byteArray, int bitIndex, bool value) + { + if (bitIndex < 0) + throw new ArgumentOutOfRangeException("bitIndex"); + + int byteIndex = bitIndex / 8; + byte[] resultArray; + + if (byteIndex >= byteArray.Length) + { + // Create a new array with increased size and copy elements from old array + resultArray = new byte[byteIndex + 1]; + Array.Copy(byteArray, resultArray, byteArray.Length); + } + else + { + // Create a new array and copy elements from old array + resultArray = new byte[byteArray.Length]; + Array.Copy(byteArray, resultArray, byteArray.Length); + } + + int bitNumber = bitIndex % 8; + byte mask = (byte)(1 << bitNumber); + + if (value) + resultArray[byteIndex] |= mask; // Set the bit to 1 + else + resultArray[byteIndex] &= (byte)~mask; // Set the bit to 0 + + return resultArray; + } + + public static byte[] OverwriteByteArrays(byte[] targetArray, byte[] overwriteArray) + { + int targetLength = targetArray.Length; + int overwriteLength = overwriteArray.Length; + + int maxLength = Math.Max(targetLength, overwriteLength); + + byte[] resultArray = new byte[maxLength]; + + for (int i = 0; i < maxLength; i++) + { + byte targetByte = i < targetLength ? targetArray[i] : (byte)0; + byte overwriteByte = i < overwriteLength ? overwriteArray[i] : (byte)0; + + for (int j = 0; j < 8; j++) + { + bool overwriteBit = (overwriteByte & (1 << j)) != 0; + if (i < targetLength) + { + bool targetBit = (targetByte & (1 << j)) != 0; + if (overwriteBit) + { + targetByte = targetBit ? (byte)(targetByte | (1 << j)) : (byte)(targetByte & ~(1 << j)); + } + } + else if (overwriteBit) + { + targetByte |= (byte)(1 << j); + } + } + + resultArray[i] = targetByte; + } + + return resultArray; + } +} \ No newline at end of file diff --git a/Moonlight/App/Helpers/ByteSizeValue.cs b/Moonlight/App/Helpers/ByteSizeValue.cs new file mode 100644 index 00000000..d761b0d7 --- /dev/null +++ b/Moonlight/App/Helpers/ByteSizeValue.cs @@ -0,0 +1,56 @@ +namespace Moonlight.App.Helpers; + +public class ByteSizeValue +{ + public long Bytes { get; set; } + + public long KiloBytes + { + get => Bytes / 1024; + set => Bytes = value * 1024; + } + + public long MegaBytes + { + get => KiloBytes / 1024; + set => KiloBytes = value * 1024; + } + + public long GigaBytes + { + get => MegaBytes / 1024; + set => MegaBytes = value * 1024; + } + + public static ByteSizeValue FromBytes(long bytes) + { + return new() + { + Bytes = bytes + }; + } + + public static ByteSizeValue FromKiloBytes(long kiloBytes) + { + return new() + { + KiloBytes = kiloBytes + }; + } + + public static ByteSizeValue FromMegaBytes(long megaBytes) + { + return new() + { + MegaBytes = megaBytes + }; + } + + public static ByteSizeValue FromGigaBytes(long gigaBytes) + { + return new() + { + GigaBytes = gigaBytes + }; + } +} \ No newline at end of file diff --git a/Moonlight/App/Helpers/ComponentHelper.cs b/Moonlight/App/Helpers/ComponentHelper.cs new file mode 100644 index 00000000..830c5958 --- /dev/null +++ b/Moonlight/App/Helpers/ComponentHelper.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Components; + +namespace Moonlight.App.Helpers; + +public class ComponentHelper +{ + public static RenderFragment FromType(Type type) => builder => + { + builder.OpenComponent(0, type); + builder.CloseComponent(); + }; +} \ No newline at end of file diff --git a/Moonlight/App/Helpers/DatabaseCheckupService.cs b/Moonlight/App/Helpers/DatabaseCheckupService.cs index dbb7ba32..829e5310 100644 --- a/Moonlight/App/Helpers/DatabaseCheckupService.cs +++ b/Moonlight/App/Helpers/DatabaseCheckupService.cs @@ -44,8 +44,19 @@ public class DatabaseCheckupService if (migrations.Any()) { Logger.Info($"{migrations.Length} migrations pending. Updating now"); - - await BackupDatabase(); + + try + { + var backupHelper = new BackupHelper(); + await backupHelper.CreateBackup( + PathBuilder.File("storage", "backups", $"{new DateTimeOffset(DateTime.UtcNow).ToUnixTimeMilliseconds()}.zip")); + } + catch (Exception e) + { + Logger.Fatal("Unable to create backup"); + Logger.Fatal(e); + Logger.Fatal("Moonlight will continue to start and apply the migrations without a backup"); + } Logger.Info("Applying migrations"); @@ -58,53 +69,4 @@ public class DatabaseCheckupService Logger.Info("Database is up-to-date. No migrations have been performed"); } } - - public async Task BackupDatabase() - { - Logger.Info("Creating backup from database"); - - var configService = new ConfigService(new StorageService()); - var dateTimeService = new DateTimeService(); - - var config = configService.Get().Moonlight.Database; - - var connectionString = $"host={config.Host};" + - $"port={config.Port};" + - $"database={config.Database};" + - $"uid={config.Username};" + - $"pwd={config.Password}"; - - string file = PathBuilder.File("storage", "backups", $"{dateTimeService.GetCurrentUnix()}-mysql.sql"); - - Logger.Info($"Saving it to: {file}"); - Logger.Info("Starting backup..."); - - try - { - var sw = new Stopwatch(); - sw.Start(); - - await using MySqlConnection conn = new MySqlConnection(connectionString); - await using MySqlCommand cmd = new MySqlCommand(); - using MySqlBackup mb = new MySqlBackup(cmd); - - cmd.Connection = conn; - await conn.OpenAsync(); - mb.ExportToFile(file); - await conn.CloseAsync(); - - sw.Stop(); - Logger.Info($"Done. {sw.Elapsed.TotalSeconds}s"); - } - catch (Exception e) - { - Logger.Fatal("-----------------------------------------------"); - Logger.Fatal("Unable to create backup for moonlight database"); - Logger.Fatal("Moonlight will start anyways in 30 seconds"); - Logger.Fatal("-----------------------------------------------"); - Logger.Fatal(e); - - Thread.Sleep(TimeSpan.FromSeconds(30)); - } - } } \ No newline at end of file diff --git a/Moonlight/App/Helpers/EggConverter.cs b/Moonlight/App/Helpers/EggConverter.cs new file mode 100644 index 00000000..d97647c2 --- /dev/null +++ b/Moonlight/App/Helpers/EggConverter.cs @@ -0,0 +1,71 @@ +using System.Text; +using Moonlight.App.Database.Entities; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Moonlight.App.Helpers; + +public static class EggConverter +{ + public static Image Convert(string json) + { + var result = new Image(); + + var data = new ConfigurationBuilder().AddJsonStream( + new MemoryStream(Encoding.ASCII.GetBytes(json)) + ).Build(); + + result.Allocations = 1; + result.Description = data.GetValue("description") ?? ""; + result.Uuid = Guid.NewGuid(); + result.Startup = data.GetValue("startup") ?? ""; + result.Name = data.GetValue("name") ?? "Ptero Egg"; + + foreach (var variable in data.GetSection("variables").GetChildren()) + { + result.Variables.Add(new() + { + Key = variable.GetValue("env_variable") ?? "", + DefaultValue = variable.GetValue("default_value") ?? "" + }); + } + + var configData = data.GetSection("config"); + + result.ConfigFiles = configData.GetValue("files") ?? "{}"; + + var dImagesData = JObject.Parse(json); + var dImages = (JObject)dImagesData["docker_images"]!; + + foreach (var dockerImage in dImages) + { + var di = new DockerImage() + { + Default = dockerImage.Key == dImages.Properties().Last().Name, + Name = dockerImage.Value!.ToString() + }; + + result.DockerImages.Add(di); + } + + var installSection = data.GetSection("scripts").GetSection("installation"); + + result.InstallEntrypoint = installSection.GetValue("entrypoint") ?? "bash"; + result.InstallScript = installSection.GetValue("script") ?? ""; + result.InstallDockerImage = installSection.GetValue("container") ?? ""; + + var rawJson = configData.GetValue("startup"); + + var startupData = new ConfigurationBuilder().AddJsonStream( + new MemoryStream(Encoding.ASCII.GetBytes(rawJson!)) + ).Build(); + + result.StartupDetection = startupData.GetValue("done", "") ?? ""; + result.StopCommand = configData.GetValue("stop") ?? ""; + + result.TagsJson = "[]"; + result.BackgroundImageUrl = ""; + + return result; + } +} \ No newline at end of file diff --git a/Moonlight/App/Helpers/Files/WingsFileAccess.cs b/Moonlight/App/Helpers/Files/WingsFileAccess.cs index 70bebf6b..8070a507 100644 --- a/Moonlight/App/Helpers/Files/WingsFileAccess.cs +++ b/Moonlight/App/Helpers/Files/WingsFileAccess.cs @@ -43,19 +43,22 @@ public class WingsFileAccess : FileAccess $"api/servers/{Server.Uuid}/files/list-directory?directory={CurrentPath}" ); - var x = new List(); + var result = new List(); - foreach (var response in res) + foreach (var resGrouped in res.GroupBy(x => x.Directory)) { - x.Add(new() + foreach (var resItem in resGrouped.OrderBy(x => x.Name)) { - Name = response.Name, - Size = response.File ? response.Size : 0, - IsFile = response.File, - }); + result.Add(new() + { + Name = resItem.Name, + Size = resItem.File ? resItem.Size : 0, + IsFile = resItem.File, + }); + } } - return x.ToArray(); + return result.ToArray(); } public override Task Cd(string dir) diff --git a/Moonlight/App/Helpers/Formatter.cs b/Moonlight/App/Helpers/Formatter.cs index 44d08f50..132a6fd9 100644 --- a/Moonlight/App/Helpers/Formatter.cs +++ b/Moonlight/App/Helpers/Formatter.cs @@ -1,4 +1,5 @@ using System.Text; +using Microsoft.AspNetCore.Components; using Moonlight.App.Services; namespace Moonlight.App.Helpers; @@ -155,12 +156,22 @@ public static class Formatter return (i / (1024D * 1024D)).Round(2) + " GB"; } } - - public static double BytesToGb(long bytes) - { - const double gbMultiplier = 1024 * 1024 * 1024; // 1 GB = 1024 MB * 1024 KB * 1024 B - double gigabytes = (double)bytes / gbMultiplier; - return gigabytes; + public static RenderFragment FormatLineBreaks(string content) + { + return builder => + { + int i = 0; + var arr = content.Split("\n", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + + foreach (var line in arr) + { + builder.AddContent(i, line); + if (i++ != arr.Length - 1) + { + builder.AddMarkupContent(i, "
"); + } + } + }; } } \ No newline at end of file diff --git a/Moonlight/App/Helpers/Logger.cs b/Moonlight/App/Helpers/Logger.cs index 8221e082..df88bfcb 100644 --- a/Moonlight/App/Helpers/Logger.cs +++ b/Moonlight/App/Helpers/Logger.cs @@ -1,46 +1,70 @@ using System.Diagnostics; using System.Reflection; +using Moonlight.App.Database; +using Moonlight.App.Services; +using Moonlight.App.Services.Files; using Serilog; namespace Moonlight.App.Helpers; public static class Logger { + // The private static instance of the config service, because we have no di here + private static ConfigService ConfigService = new(new StorageService()); + #region String method calls public static void Verbose(string message, string channel = "default") { Log.ForContext("SourceContext", GetNameOfCallingClass()) .Verbose("{Message}", message); + + if(channel == "security") + LogSecurityInDb(message); } public static void Info(string message, string channel = "default") { Log.ForContext("SourceContext", GetNameOfCallingClass()) .Information("{Message}", message); + + if(channel == "security") + LogSecurityInDb(message); } public static void Debug(string message, string channel = "default") { Log.ForContext("SourceContext", GetNameOfCallingClass()) .Debug("{Message}", message); + + if(channel == "security") + LogSecurityInDb(message); } public static void Error(string message, string channel = "default") { Log.ForContext("SourceContext", GetNameOfCallingClass()) .Error("{Message}", message); + + if(channel == "security") + LogSecurityInDb(message); } public static void Warn(string message, string channel = "default") { Log.ForContext("SourceContext", GetNameOfCallingClass()) .Warning("{Message}", message); + + if(channel == "security") + LogSecurityInDb(message); } public static void Fatal(string message, string channel = "default") { Log.ForContext("SourceContext", GetNameOfCallingClass()) .Fatal("{Message}", message); + + if(channel == "security") + LogSecurityInDb(message); } #endregion @@ -49,36 +73,54 @@ public static class Logger { Log.ForContext("SourceContext", GetNameOfCallingClass()) .Verbose(exception, ""); + + if(channel == "security") + LogSecurityInDb(exception); } public static void Info(Exception exception, string channel = "default") { Log.ForContext("SourceContext", GetNameOfCallingClass()) .Information(exception, ""); + + if(channel == "security") + LogSecurityInDb(exception); } public static void Debug(Exception exception, string channel = "default") { Log.ForContext("SourceContext", GetNameOfCallingClass()) .Debug(exception, ""); + + if(channel == "security") + LogSecurityInDb(exception); } public static void Error(Exception exception, string channel = "default") { Log.ForContext("SourceContext", GetNameOfCallingClass()) .Error(exception, ""); + + if(channel == "security") + LogSecurityInDb(exception); } public static void Warn(Exception exception, string channel = "default") { Log.ForContext("SourceContext", GetNameOfCallingClass()) .Warning(exception, ""); + + if(channel == "security") + LogSecurityInDb(exception); } public static void Fatal(Exception exception, string channel = "default") { Log.ForContext("SourceContext", GetNameOfCallingClass()) .Fatal(exception, ""); + + if(channel == "security") + LogSecurityInDb(exception); } #endregion @@ -105,4 +147,25 @@ public static class Logger return fullName; } + + + private static void LogSecurityInDb(Exception exception) + { + LogSecurityInDb(exception.ToStringDemystified()); + } + private static void LogSecurityInDb(string text) + { + Task.Run(() => + { + var dataContext = new DataContext(ConfigService); + + dataContext.SecurityLogs.Add(new() + { + Text = text + }); + + dataContext.SaveChanges(); + dataContext.Dispose(); + }); + } } \ No newline at end of file diff --git a/Moonlight/App/Http/Controllers/Api/Moonlight/AvatarController.cs b/Moonlight/App/Http/Controllers/Api/Moonlight/AvatarController.cs index 4a289071..5de6ca77 100644 --- a/Moonlight/App/Http/Controllers/Api/Moonlight/AvatarController.cs +++ b/Moonlight/App/Http/Controllers/Api/Moonlight/AvatarController.cs @@ -25,7 +25,7 @@ public class AvatarController : Controller try { - var url = GravatarController.GetImageUrl(user.Email, 100); + var url = GravatarController.GetImageUrl(user.Email.ToLower(), 100); using var client = new HttpClient(); var res = await client.GetByteArrayAsync(url); diff --git a/Moonlight/App/Http/Controllers/Api/Moonlight/BillingController.cs b/Moonlight/App/Http/Controllers/Api/Moonlight/BillingController.cs new file mode 100644 index 00000000..d31dd427 --- /dev/null +++ b/Moonlight/App/Http/Controllers/Api/Moonlight/BillingController.cs @@ -0,0 +1,47 @@ +using Microsoft.AspNetCore.Mvc; +using Moonlight.App.Services; +using Moonlight.App.Services.Sessions; +using Stripe; +using Stripe.Checkout; + +namespace Moonlight.App.Http.Controllers.Api.Moonlight; + +[ApiController] +[Route("api/moonlight/billing")] +public class BillingController : Controller +{ + private readonly IdentityService IdentityService; + private readonly BillingService BillingService; + + public BillingController( + IdentityService identityService, + BillingService billingService) + { + IdentityService = identityService; + BillingService = billingService; + } + + [HttpGet("cancel")] + public async Task Cancel() + { + var user = IdentityService.User; + + if (user == null) + return Redirect("/login"); + + return Redirect("/profile/subscriptions/close"); + } + + [HttpGet("success")] + public async Task Success() + { + var user = IdentityService.User; + + if (user == null) + return Redirect("/login"); + + await BillingService.CompleteCheckout(user); + + return Redirect("/profile/subscriptions/close"); + } +} \ No newline at end of file diff --git a/Moonlight/App/Http/Controllers/Api/Moonlight/Notifications/RegisterController.cs b/Moonlight/App/Http/Controllers/Api/Moonlight/Notifications/RegisterController.cs index 9cbade42..8fc13ffb 100644 --- a/Moonlight/App/Http/Controllers/Api/Moonlight/Notifications/RegisterController.cs +++ b/Moonlight/App/Http/Controllers/Api/Moonlight/Notifications/RegisterController.cs @@ -25,7 +25,7 @@ public class RegisterController : Controller [HttpGet] public async Task> Register() { - var user = await IdentityService.Get(); + var user = IdentityService.User; if (user == null) return NotFound(); diff --git a/Moonlight/App/Http/Controllers/Api/Moonlight/OAuth2Controller.cs b/Moonlight/App/Http/Controllers/Api/Moonlight/OAuth2Controller.cs index bae68b49..a21b804f 100644 --- a/Moonlight/App/Http/Controllers/Api/Moonlight/OAuth2Controller.cs +++ b/Moonlight/App/Http/Controllers/Api/Moonlight/OAuth2Controller.cs @@ -54,7 +54,7 @@ public class OAuth2Controller : Controller { try { - var currentUser = await IdentityService.Get(); + var currentUser = IdentityService.User; if (currentUser != null) { diff --git a/Moonlight/App/Http/Controllers/Api/Remote/DdosController.cs b/Moonlight/App/Http/Controllers/Api/Remote/DdosController.cs index 9ada8cd7..55987b9e 100644 --- a/Moonlight/App/Http/Controllers/Api/Remote/DdosController.cs +++ b/Moonlight/App/Http/Controllers/Api/Remote/DdosController.cs @@ -3,6 +3,7 @@ using Moonlight.App.Database.Entities; using Moonlight.App.Events; using Moonlight.App.Http.Requests.Daemon; using Moonlight.App.Repositories; +using Moonlight.App.Services.Background; namespace Moonlight.App.Http.Controllers.Api.Remote; @@ -10,19 +11,17 @@ namespace Moonlight.App.Http.Controllers.Api.Remote; [Route("api/remote/ddos")] public class DdosController : Controller { - private readonly NodeRepository NodeRepository; - private readonly EventSystem Event; - private readonly DdosAttackRepository DdosAttackRepository; + private readonly Repository NodeRepository; + private readonly DdosProtectionService DdosProtectionService; - public DdosController(NodeRepository nodeRepository, EventSystem eventSystem, DdosAttackRepository ddosAttackRepository) + public DdosController(Repository nodeRepository, DdosProtectionService ddosProtectionService) { NodeRepository = nodeRepository; - Event = eventSystem; - DdosAttackRepository = ddosAttackRepository; + DdosProtectionService = ddosProtectionService; } - [HttpPost("update")] - public async Task Update([FromBody] DdosStatus ddosStatus) + [HttpPost("start")] + public async Task Start([FromBody] DdosStart ddosStart) { var tokenData = Request.Headers.Authorization.ToString().Replace("Bearer ", ""); var id = tokenData.Split(".")[0]; @@ -35,18 +34,26 @@ public class DdosController : Controller if (token != node.Token) return Unauthorized(); + + await DdosProtectionService.ProcessDdosSignal(ddosStart.Ip, ddosStart.Packets); + + return Ok(); + } - var ddosAttack = new DdosAttack() - { - Ongoing = ddosStatus.Ongoing, - Data = ddosStatus.Data, - Ip = ddosStatus.Ip, - Node = node - }; + [HttpPost("stop")] + public async Task Stop([FromBody] DdosStop ddosStop) + { + var tokenData = Request.Headers.Authorization.ToString().Replace("Bearer ", ""); + var id = tokenData.Split(".")[0]; + var token = tokenData.Split(".")[1]; - ddosAttack = DdosAttackRepository.Add(ddosAttack); + var node = NodeRepository.Get().FirstOrDefault(x => x.TokenId == id); - await Event.Emit("node.ddos", ddosAttack); + if (node == null) + return NotFound(); + + if (token != node.Token) + return Unauthorized(); return Ok(); } diff --git a/Moonlight/App/Http/Requests/Daemon/DdosStart.cs b/Moonlight/App/Http/Requests/Daemon/DdosStart.cs new file mode 100644 index 00000000..aab3c54e --- /dev/null +++ b/Moonlight/App/Http/Requests/Daemon/DdosStart.cs @@ -0,0 +1,7 @@ +namespace Moonlight.App.Http.Requests.Daemon; + +public class DdosStart +{ + public string Ip { get; set; } = ""; + public long Packets { get; set; } +} \ No newline at end of file diff --git a/Moonlight/App/Http/Requests/Daemon/DdosStatus.cs b/Moonlight/App/Http/Requests/Daemon/DdosStatus.cs deleted file mode 100644 index b08a5762..00000000 --- a/Moonlight/App/Http/Requests/Daemon/DdosStatus.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Moonlight.App.Http.Requests.Daemon; - -public class DdosStatus -{ - public bool Ongoing { get; set; } - public long Data { get; set; } - public string Ip { get; set; } = ""; -} \ No newline at end of file diff --git a/Moonlight/App/Http/Requests/Daemon/DdosStop.cs b/Moonlight/App/Http/Requests/Daemon/DdosStop.cs new file mode 100644 index 00000000..f131f204 --- /dev/null +++ b/Moonlight/App/Http/Requests/Daemon/DdosStop.cs @@ -0,0 +1,7 @@ +namespace Moonlight.App.Http.Requests.Daemon; + +public class DdosStop +{ + public string Ip { get; set; } = ""; + public long Traffic { get; set; } +} \ No newline at end of file diff --git a/Moonlight/App/Models/Forms/CreateTicketDataModel.cs b/Moonlight/App/Models/Forms/CreateTicketDataModel.cs new file mode 100644 index 00000000..e733cc72 --- /dev/null +++ b/Moonlight/App/Models/Forms/CreateTicketDataModel.cs @@ -0,0 +1,21 @@ +using System.ComponentModel.DataAnnotations; +using Moonlight.App.Models.Misc; + +namespace Moonlight.App.Models.Forms; + +public class CreateTicketDataModel +{ + [Required(ErrorMessage = "You need to specify a issue topic")] + [MinLength(5, ErrorMessage = "The issue topic needs to be longer than 5 characters")] + public string IssueTopic { get; set; } + + [Required(ErrorMessage = "You need to specify a issue description")] + [MinLength(10, ErrorMessage = "The issue description needs to be longer than 10 characters")] + public string IssueDescription { get; set; } + + [Required(ErrorMessage = "You need to specify your tries to solve this issue")] + public string IssueTries { get; set; } + + public TicketSubject Subject { get; set; } + public int SubjectId { get; set; } +} \ No newline at end of file diff --git a/Moonlight/App/Models/Forms/SubscriptionDataModel.cs b/Moonlight/App/Models/Forms/SubscriptionDataModel.cs index 92bf8ebc..2f7c7185 100644 --- a/Moonlight/App/Models/Forms/SubscriptionDataModel.cs +++ b/Moonlight/App/Models/Forms/SubscriptionDataModel.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations; +using Moonlight.App.Models.Misc; namespace Moonlight.App.Models.Forms; @@ -10,4 +11,8 @@ public class SubscriptionDataModel [Required(ErrorMessage = "You need to enter a description")] public string Description { get; set; } = ""; + + public double Price { get; set; } = 0; + public Currency Currency { get; set; } = Currency.USD; + public int Duration { get; set; } = 30; } \ No newline at end of file diff --git a/Moonlight/App/Models/Forms/UserEditDataModel.cs b/Moonlight/App/Models/Forms/UserEditDataModel.cs new file mode 100644 index 00000000..1af0ccb0 --- /dev/null +++ b/Moonlight/App/Models/Forms/UserEditDataModel.cs @@ -0,0 +1,34 @@ +using System.ComponentModel.DataAnnotations; +using Moonlight.App.Database.Entities; +using Moonlight.App.Models.Misc; + +namespace Moonlight.App.Models.Forms; + +public class UserEditDataModel +{ + [Required] + public string FirstName { get; set; } = ""; + + [Required] + public string LastName { get; set; } = ""; + + [Required] + public string Email { get; set; } = ""; + + [Required] + public string Address { get; set; } = ""; + + [Required] + public string City { get; set; } = ""; + + [Required] + public string State { get; set; } = ""; + + [Required] + public string Country { get; set; } = ""; + + public bool Admin { get; set; } + public bool TotpEnabled { get; set; } + public ulong DiscordId { get; set; } + public PermissionGroup? PermissionGroup { get; set; } +} \ No newline at end of file diff --git a/Moonlight/App/Models/Misc/AuditLogType.cs b/Moonlight/App/Models/Misc/AuditLogType.cs deleted file mode 100644 index a2ccf6d4..00000000 --- a/Moonlight/App/Models/Misc/AuditLogType.cs +++ /dev/null @@ -1,27 +0,0 @@ -namespace Moonlight.App.Models.Misc; - -public enum AuditLogType -{ - Login, - Register, - ChangePassword, - ChangePowerState, - CreateBackup, - RestoreBackup, - DeleteBackup, - DownloadBackup, - CreateServer, - ReinstallServer, - CancelSubscription, - ApplySubscriptionCode, - EnableTotp, - DisableTotp, - AddDomainRecord, - UpdateDomainRecord, - DeleteDomainRecord, - PasswordReset, - CleanupEnabled, - CleanupDisabled, - CleanupTriggered, - PasswordChange, -} \ No newline at end of file diff --git a/Moonlight/App/Models/Misc/Currency.cs b/Moonlight/App/Models/Misc/Currency.cs new file mode 100644 index 00000000..195ae285 --- /dev/null +++ b/Moonlight/App/Models/Misc/Currency.cs @@ -0,0 +1,7 @@ +namespace Moonlight.App.Models.Misc; + +public enum Currency +{ + USD = 1, + EUR = 2 +} \ No newline at end of file diff --git a/Moonlight/App/Models/Misc/MailTemplate.cs b/Moonlight/App/Models/Misc/MailTemplate.cs new file mode 100644 index 00000000..1d67cdcf --- /dev/null +++ b/Moonlight/App/Models/Misc/MailTemplate.cs @@ -0,0 +1,9 @@ +using Moonlight.App.Helpers.Files; + +namespace Moonlight.App.Models.Misc; + +public class MailTemplate // This is just for the blazor table at /admin/system/mail +{ + public string Name { get; set; } = ""; + public FileData File { get; set; } +} \ No newline at end of file diff --git a/Moonlight/App/Models/Misc/OfficialMoonlightPlugin.cs b/Moonlight/App/Models/Misc/OfficialMoonlightPlugin.cs new file mode 100644 index 00000000..2b3d090d --- /dev/null +++ b/Moonlight/App/Models/Misc/OfficialMoonlightPlugin.cs @@ -0,0 +1,6 @@ +namespace Moonlight.App.Models.Misc; + +public class OfficialMoonlightPlugin +{ + public string Name { get; set; } +} \ No newline at end of file diff --git a/Moonlight/App/Models/Misc/SecurityLogType.cs b/Moonlight/App/Models/Misc/SecurityLogType.cs deleted file mode 100644 index 74ea78c1..00000000 --- a/Moonlight/App/Models/Misc/SecurityLogType.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Moonlight.App.Models.Misc; - -public enum SecurityLogType -{ - ManipulatedJwt, - PathTransversal, - SftpBruteForce, - LoginFail -} \ No newline at end of file diff --git a/Moonlight/App/Models/Misc/TicketPriority.cs b/Moonlight/App/Models/Misc/TicketPriority.cs new file mode 100644 index 00000000..97fc5489 --- /dev/null +++ b/Moonlight/App/Models/Misc/TicketPriority.cs @@ -0,0 +1,9 @@ +namespace Moonlight.App.Models.Misc; + +public enum TicketPriority +{ + Low = 0, + Medium = 1, + High = 2, + Critical = 3 +} \ No newline at end of file diff --git a/Moonlight/App/Models/Misc/TicketStatus.cs b/Moonlight/App/Models/Misc/TicketStatus.cs new file mode 100644 index 00000000..2a59fae9 --- /dev/null +++ b/Moonlight/App/Models/Misc/TicketStatus.cs @@ -0,0 +1,9 @@ +namespace Moonlight.App.Models.Misc; + +public enum TicketStatus +{ + Closed = 0, + Open = 1, + WaitingForUser = 2, + Pending = 3 +} \ No newline at end of file diff --git a/Moonlight/App/Models/Misc/TicketSubject.cs b/Moonlight/App/Models/Misc/TicketSubject.cs new file mode 100644 index 00000000..ed6d4579 --- /dev/null +++ b/Moonlight/App/Models/Misc/TicketSubject.cs @@ -0,0 +1,9 @@ +namespace Moonlight.App.Models.Misc; + +public enum TicketSubject +{ + Webspace = 0, + Server = 1, + Domain = 2, + Other = 3 +} \ No newline at end of file diff --git a/Moonlight/App/Perms/Permission.cs b/Moonlight/App/Perms/Permission.cs new file mode 100644 index 00000000..affffd75 --- /dev/null +++ b/Moonlight/App/Perms/Permission.cs @@ -0,0 +1,10 @@ +namespace Moonlight.App.Perms; + +public class Permission +{ + public int Index { get; set; } = 0; + public string Name { get; set; } = ""; + public string Description { get; set; } = ""; + + public static implicit operator int(Permission permission) => permission.Index; +} \ No newline at end of file diff --git a/Moonlight/App/Perms/PermissionRequired.cs b/Moonlight/App/Perms/PermissionRequired.cs new file mode 100644 index 00000000..c6e321b8 --- /dev/null +++ b/Moonlight/App/Perms/PermissionRequired.cs @@ -0,0 +1,11 @@ +namespace Moonlight.App.Perms; + +public class PermissionRequired : Attribute +{ + public string Name { get; private set; } + + public PermissionRequired(string name) + { + Name = name; + } +} \ No newline at end of file diff --git a/Moonlight/App/Perms/PermissionStorage.cs b/Moonlight/App/Perms/PermissionStorage.cs new file mode 100644 index 00000000..f5ddaa91 --- /dev/null +++ b/Moonlight/App/Perms/PermissionStorage.cs @@ -0,0 +1,55 @@ +using System.Data; +using Moonlight.App.Helpers; + +namespace Moonlight.App.Perms; + +public class PermissionStorage +{ + public byte[] Data; + public bool IsReadyOnly; + + public PermissionStorage(byte[] data, bool isReadyOnly = false) + { + Data = data; + IsReadyOnly = isReadyOnly; + } + + public bool this[Permission permission] + { + get + { + try + { + return BitHelper.ReadBit(Data, permission.Index); + } + catch (ArgumentOutOfRangeException) + { + return false; + } + catch (Exception e) + { + Logger.Verbose("Error reading permissions. (Can be intentional)"); + Logger.Verbose(e); + return false; + } + } + set + { + if (IsReadyOnly) + throw new ReadOnlyException(); + + Data = BitHelper.WriteBit(Data, permission.Index, value); + } + } + + public bool HasAnyPermissions() + { + foreach (var permission in Permissions.GetAllPermissions()) + { + if (this[permission]) + return true; + } + + return false; + } +} \ No newline at end of file diff --git a/Moonlight/App/Perms/Permissions.cs b/Moonlight/App/Perms/Permissions.cs new file mode 100644 index 00000000..77c9153c --- /dev/null +++ b/Moonlight/App/Perms/Permissions.cs @@ -0,0 +1,438 @@ +namespace Moonlight.App.Perms; + +public static class Permissions +{ + public static Permission AdminDashboard = new() + { + Index = 0, + Name = "Admin Dashboard", + Description = "Access the main admin dashboard page" + }; + + public static Permission AdminStatistics = new() + { + Index = 1, + Name = "Admin Statistics", + Description = "View statistical information about the moonlight instance" + }; + + public static Permission AdminSysPlugins = new() + { + Index = 2, + Name = "Admin system plugins", + Description = "View and install plugins" + }; + + public static Permission AdminDomains = new() + { + Index = 4, + Name = "Admin Domains", + Description = "Manage domains in the admin area" + }; + + public static Permission AdminNewDomain = new() + { + Index = 5, + Name = "Admin New Domain", + Description = "Create a new domain in the admin area" + }; + + public static Permission AdminSharedDomains = new() + { + Index = 6, + Name = "Admin Shared Domains", + Description = "Manage shared domains in the admin area" + }; + + public static Permission AdminNewSharedDomain = new() + { + Index = 7, + Name = "Admin New Shared Domain", + Description = "Create a new shared domain in the admin area" + }; + + public static Permission AdminNodeEdit = new() + { + Index = 9, + Name = "Admin Node Edit", + Description = "Edit node settings in the admin area" + }; + + public static Permission AdminNodes = new() + { + Index = 10, + Name = "Admin Node", + Description = "Access the node management page in the admin area" + }; + + public static Permission AdminNewNode = new() + { + Index = 11, + Name = "Admin New Node", + Description = "Create a new node in the admin area" + }; + + public static Permission AdminNodeSetup = new() + { + Index = 12, + Name = "Admin Node Setup", + Description = "Set up a node in the admin area" + }; + + public static Permission AdminNodeView = new() + { + Index = 13, + Name = "Admin Node View", + Description = "View node details in the admin area" + }; + + public static Permission AdminNotificationDebugging = new() + { + Index = 14, + Name = "Admin Notification Debugging", + Description = "Manage debugging notifications in the admin area" + }; + + public static Permission AdminServerCleanup = new() + { + Index = 15, + Name = "Admin Server Cleanup", + Description = "Perform server cleanup tasks in the admin area" + }; + + public static Permission AdminServerEdit = new() + { + Index = 16, + Name = "Admin Server Edit", + Description = "Edit server settings in the admin area" + }; + + public static Permission AdminServers = new() + { + Index = 17, + Name = "Admin Server", + Description = "Access the server management page in the admin area" + }; + + public static Permission AdminServerManager = new() + { + Index = 18, + Name = "Admin Server Manager", + Description = "Manage servers in the admin area" + }; + + public static Permission AdminNewServer = new() + { + Index = 19, + Name = "Admin New Server", + Description = "Create a new server in the admin area" + }; + + public static Permission AdminServerImageEdit = new() + { + Index = 20, + Name = "Admin Server Image Edit", + Description = "Edit server image settings in the admin area" + }; + + public static Permission AdminServerImages = new() + { + Index = 21, + Name = "Admin Server Images", + Description = "Access the server image management page in the admin area" + }; + + public static Permission AdminServerImageNew = new() + { + Index = 22, + Name = "Admin Server Image New", + Description = "Create a new server image in the admin area" + }; + + public static Permission AdminServerViewAllocations = new() + { + Index = 23, + Name = "Admin Server View Allocations", + Description = "View server allocations in the admin area" + }; + + public static Permission AdminServerViewArchive = new() + { + Index = 24, + Name = "Admin Server View Archive", + Description = "View server archive in the admin area" + }; + + public static Permission AdminServerViewDebug = new() + { + Index = 25, + Name = "Admin Server View Debug", + Description = "View server debugging information in the admin area" + }; + + public static Permission AdminServerViewImage = new() + { + Index = 26, + Name = "Admin Server View Image", + Description = "View server image details in the admin area" + }; + + public static Permission AdminServerViewIndex = new() + { + Index = 27, + Name = "Admin Server View", + Description = "Access the server view page in the admin area" + }; + + public static Permission AdminServerViewOverview = new() + { + Index = 28, + Name = "Admin Server View Overview", + Description = "View server overview in the admin area" + }; + + public static Permission AdminServerViewResources = new() + { + Index = 29, + Name = "Admin Server View Resources", + Description = "View server resources in the admin area" + }; + + public static Permission AdminSubscriptionEdit = new() + { + Index = 30, + Name = "Admin Subscription Edit", + Description = "Edit subscription settings in the admin area" + }; + + public static Permission AdminSubscriptions = new() + { + Index = 31, + Name = "Admin Subscriptions", + Description = "Access the subscription management page in the admin area" + }; + + public static Permission AdminNewSubscription = new() + { + Index = 32, + Name = "Admin New Subscription", + Description = "Create a new subscription in the admin area" + }; + + public static Permission AdminSupport = new() + { + Index = 33, + Name = "Admin Support", + Description = "Access the support page in the admin area" + }; + + public static Permission AdminSupportView = new() + { + Index = 34, + Name = "Admin Support View", + Description = "View support details in the admin area" + }; + + public static Permission AdminSysConfiguration = new() + { + Index = 35, + Name = "Admin system Configuration", + Description = "Access system configuration settings in the admin area" + }; + + public static Permission AdminSysDiscordBot = new() + { + Index = 36, + Name = "Admin system Discord Bot", + Description = "Manage Discord bot settings in the admin area" + }; + + public static Permission AdminSystem = new() + { + Index = 37, + Name = "Admin system", + Description = "Access the system management page in the admin area" + }; + + public static Permission AdminSysMail = new() + { + Index = 38, + Name = "Admin system Mail", + Description = "Manage mail settings in the admin area" + }; + + public static Permission AdminSecurityMalware = new() + { + Index = 39, + Name = "Admin security Malware", + Description = "Manage malware settings in the admin area" + }; + + public static Permission AdminSysResources = new() + { + Index = 40, + Name = "Admin system Resources", + Description = "View system resources in the admin area" + }; + + public static Permission AdminSecurity = new() + { + Index = 41, + Name = "Admin Security", + Description = "View security logs in the admin area" + }; + + public static Permission AdminSysSentry = new() + { + Index = 42, + Name = "Admin system Sentry", + Description = "Manage Sentry settings in the admin area" + }; + + public static Permission AdminSysNewsEdit = new() + { + Index = 43, + Name = "Admin system News Edit", + Description = "Edit system news in the admin area" + }; + + public static Permission AdminSysNews = new() + { + Index = 44, + Name = "Admin system News", + Description = "Access the system news management page in the admin area" + }; + + public static Permission AdminSysNewsNew = new() + { + Index = 45, + Name = "Admin system News New", + Description = "Create new system news in the admin area" + }; + + public static Permission AdminUserEdit = new() + { + Index = 46, + Name = "Admin User Edit", + Description = "Edit user settings in the admin area" + }; + + public static Permission AdminUsers = new() + { + Index = 47, + Name = "Admin Users", + Description = "Access the user management page in the admin area" + }; + + public static Permission AdminNewUser = new() + { + Index = 48, + Name = "Admin New User", + Description = "Create a new user in the admin area" + }; + + public static Permission AdminUserSessions = new() + { + Index = 49, + Name = "Admin User Sessions", + Description = "View user sessions in the admin area" + }; + + public static Permission AdminUserView = new() + { + Index = 50, + Name = "Admin User View", + Description = "View user details in the admin area" + }; + + public static Permission AdminWebspaces = new() + { + Index = 51, + Name = "Admin Webspaces", + Description = "Access the webspaces management page in the admin area" + }; + + public static Permission AdminNewWebspace = new() + { + Index = 52, + Name = "Admin New Webspace", + Description = "Create a new webspace in the admin area" + }; + + public static Permission AdminWebspacesServerEdit = new() + { + Index = 53, + Name = "Admin Webspaces Server Edit", + Description = "Edit webspace server settings in the admin area" + }; + + public static Permission AdminWebspacesServers = new() + { + Index = 54, + Name = "Admin Webspaces Servers", + Description = "Access the webspace server management page in the admin area" + }; + + public static Permission AdminWebspacesServerNew = new() + { + Index = 55, + Name = "Admin Webspaces Server New", + Description = "Create a new webspace server in the admin area" + }; + + public static Permission AdminSecurityIpBans = new() + { + Index = 56, + Name = "Admin security ip bans", + Description = "Manage ip bans in the admin area" + }; + + public static Permission AdminSecurityPermissionGroups = new() + { + Index = 57, + Name = "Admin security permission groups", + Description = "View, add and delete permission groups" + }; + + public static Permission AdminSecurityLogs = new() + { + Index = 58, + Name = "Admin security logs", + Description = "View the security logs" + }; + + public static Permission AdminSecurityDdos = new() + { + Index = 59, + Name = "Admin security ddos", + Description = "Manage the integrated ddos protection" + }; + + public static Permission? FromString(string name) + { + var type = typeof(Permissions); + + var field = type + .GetFields() + .FirstOrDefault(x => x.FieldType == typeof(Permission) && x.Name == name); + + if (field != null) + { + var value = field.GetValue(null); + return value as Permission; + } + + return null; + } + + public static Permission[] GetAllPermissions() + { + var type = typeof(Permissions); + + return type + .GetFields() + .Where(x => x.FieldType == typeof(Permission)) + .Select(x => (x.GetValue(null) as Permission)!) + .ToArray(); + } +} \ No newline at end of file diff --git a/Moonlight/App/Plugin/MoonlightPlugin.cs b/Moonlight/App/Plugin/MoonlightPlugin.cs new file mode 100644 index 00000000..875c33ee --- /dev/null +++ b/Moonlight/App/Plugin/MoonlightPlugin.cs @@ -0,0 +1,15 @@ +using Moonlight.App.Plugin.UI.Servers; +using Moonlight.App.Plugin.UI.Webspaces; + +namespace Moonlight.App.Plugin; + +public abstract class MoonlightPlugin +{ + public string Name { get; set; } = "N/A"; + public string Author { get; set; } = "N/A"; + public string Version { get; set; } = "N/A"; + + public Func? OnBuildServerPage { get; set; } + public Func? OnBuildWebspacePage { get; set; } + public Func? OnBuildServices { get; set; } +} \ No newline at end of file diff --git a/Moonlight/App/Plugin/UI/Servers/ServerPageContext.cs b/Moonlight/App/Plugin/UI/Servers/ServerPageContext.cs new file mode 100644 index 00000000..8179035a --- /dev/null +++ b/Moonlight/App/Plugin/UI/Servers/ServerPageContext.cs @@ -0,0 +1,12 @@ +using Moonlight.App.Database.Entities; + +namespace Moonlight.App.Plugin.UI.Servers; + +public class ServerPageContext +{ + public List Tabs { get; set; } = new(); + public List Settings { get; set; } = new(); + public Server Server { get; set; } + public User User { get; set; } + public string[] ImageTags { get; set; } +} \ No newline at end of file diff --git a/Moonlight/App/Plugin/UI/Servers/ServerSetting.cs b/Moonlight/App/Plugin/UI/Servers/ServerSetting.cs new file mode 100644 index 00000000..b032c66d --- /dev/null +++ b/Moonlight/App/Plugin/UI/Servers/ServerSetting.cs @@ -0,0 +1,9 @@ +using Microsoft.AspNetCore.Components; + +namespace Moonlight.App.Plugin.UI.Servers; + +public class ServerSetting +{ + public string Name { get; set; } + public RenderFragment Component { get; set; } +} \ No newline at end of file diff --git a/Moonlight/App/Plugin/UI/Servers/ServerTab.cs b/Moonlight/App/Plugin/UI/Servers/ServerTab.cs new file mode 100644 index 00000000..8a31e6f9 --- /dev/null +++ b/Moonlight/App/Plugin/UI/Servers/ServerTab.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Components; + +namespace Moonlight.App.Plugin.UI.Servers; + +public class ServerTab +{ + public string Name { get; set; } + public string Route { get; set; } + public string Icon { get; set; } + public RenderFragment Component { get; set; } +} \ No newline at end of file diff --git a/Moonlight/App/Plugin/UI/Webspaces/WebspacePageContext.cs b/Moonlight/App/Plugin/UI/Webspaces/WebspacePageContext.cs new file mode 100644 index 00000000..a8b8d700 --- /dev/null +++ b/Moonlight/App/Plugin/UI/Webspaces/WebspacePageContext.cs @@ -0,0 +1,10 @@ +using Moonlight.App.Database.Entities; + +namespace Moonlight.App.Plugin.UI.Webspaces; + +public class WebspacePageContext +{ + public List Tabs { get; set; } = new(); + public User User { get; set; } + public WebSpace WebSpace { get; set; } +} \ No newline at end of file diff --git a/Moonlight/App/Plugin/UI/Webspaces/WebspaceTab.cs b/Moonlight/App/Plugin/UI/Webspaces/WebspaceTab.cs new file mode 100644 index 00000000..70d02c88 --- /dev/null +++ b/Moonlight/App/Plugin/UI/Webspaces/WebspaceTab.cs @@ -0,0 +1,10 @@ +using Microsoft.AspNetCore.Components; + +namespace Moonlight.App.Plugin.UI.Webspaces; + +public class WebspaceTab +{ + public string Name { get; set; } = "N/A"; + public string Route { get; set; } = "/"; + public RenderFragment Component { get; set; } +} \ No newline at end of file diff --git a/Moonlight/App/Repositories/LogEntries/AuditLogEntryRepository.cs b/Moonlight/App/Repositories/LogEntries/AuditLogEntryRepository.cs deleted file mode 100644 index cfefef2e..00000000 --- a/Moonlight/App/Repositories/LogEntries/AuditLogEntryRepository.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Moonlight.App.Database; -using Moonlight.App.Database.Entities.LogsEntries; - -namespace Moonlight.App.Repositories.LogEntries; - -public class AuditLogEntryRepository : IDisposable -{ - private readonly DataContext DataContext; - - public AuditLogEntryRepository(DataContext dataContext) - { - DataContext = dataContext; - } - - public AuditLogEntry Add(AuditLogEntry entry) - { - var x = DataContext.AuditLog.Add(entry); - DataContext.SaveChanges(); - return x.Entity; - } - - public DbSet Get() - { - return DataContext.AuditLog; - } - - public void Dispose() - { - DataContext.Dispose(); - } -} \ No newline at end of file diff --git a/Moonlight/App/Repositories/LogEntries/ErrorLogEntryRepository.cs b/Moonlight/App/Repositories/LogEntries/ErrorLogEntryRepository.cs deleted file mode 100644 index adda4f92..00000000 --- a/Moonlight/App/Repositories/LogEntries/ErrorLogEntryRepository.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Moonlight.App.Database; -using Moonlight.App.Database.Entities.LogsEntries; - -namespace Moonlight.App.Repositories.LogEntries; - -public class ErrorLogEntryRepository : IDisposable -{ - private readonly DataContext DataContext; - - public ErrorLogEntryRepository(DataContext dataContext) - { - DataContext = dataContext; - } - - public ErrorLogEntry Add(ErrorLogEntry errorLogEntry) - { - var x = DataContext.ErrorLog.Add(errorLogEntry); - DataContext.SaveChanges(); - return x.Entity; - } - - public DbSet Get() - { - return DataContext.ErrorLog; - } - - public void Dispose() - { - DataContext.Dispose(); - } -} \ No newline at end of file diff --git a/Moonlight/App/Repositories/LogEntries/SecurityLogEntryRepository.cs b/Moonlight/App/Repositories/LogEntries/SecurityLogEntryRepository.cs deleted file mode 100644 index 17a15201..00000000 --- a/Moonlight/App/Repositories/LogEntries/SecurityLogEntryRepository.cs +++ /dev/null @@ -1,32 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Moonlight.App.Database; -using Moonlight.App.Database.Entities.LogsEntries; - -namespace Moonlight.App.Repositories.LogEntries; - -public class SecurityLogEntryRepository : IDisposable -{ - private readonly DataContext DataContext; - - public SecurityLogEntryRepository(DataContext dataContext) - { - DataContext = dataContext; - } - - public SecurityLogEntry Add(SecurityLogEntry securityLogEntry) - { - var x = DataContext.SecurityLog.Add(securityLogEntry); - DataContext.SaveChanges(); - return x.Entity; - } - - public DbSet Get() - { - return DataContext.SecurityLog; - } - - public void Dispose() - { - DataContext.Dispose(); - } -} \ No newline at end of file diff --git a/Moonlight/App/Services/Background/CleanupService.cs b/Moonlight/App/Services/Background/CleanupService.cs index 43543470..0492c5f4 100644 --- a/Moonlight/App/Services/Background/CleanupService.cs +++ b/Moonlight/App/Services/Background/CleanupService.cs @@ -93,14 +93,14 @@ public class CleanupService Logger.Debug($"Max CPU: {maxCpu}"); executeCleanup = true; } - else if (Formatter.BytesToGb(memoryMetrics.Total) - Formatter.BytesToGb(memoryMetrics.Used) < - minMemory / 1024D) + else if (ByteSizeValue.FromKiloBytes(memoryMetrics.Total).MegaBytes - ByteSizeValue.FromKiloBytes(memoryMetrics.Used).MegaBytes < + minMemory) { Logger.Debug($"{node.Name}: Memory Usage is too high"); - Logger.Debug($"Memory (Total): {Formatter.BytesToGb(memoryMetrics.Total)}"); - Logger.Debug($"Memory (Used): {Formatter.BytesToGb(memoryMetrics.Used)}"); + Logger.Debug($"Memory (Total): {ByteSizeValue.FromKiloBytes(memoryMetrics.Total).MegaBytes}"); + Logger.Debug($"Memory (Used): {ByteSizeValue.FromKiloBytes(memoryMetrics.Used).MegaBytes}"); Logger.Debug( - $"Memory (Free): {Formatter.BytesToGb(memoryMetrics.Total) - Formatter.BytesToGb(memoryMetrics.Used)}"); + $"Memory (Free): {ByteSizeValue.FromKiloBytes(memoryMetrics.Total).MegaBytes - ByteSizeValue.FromKiloBytes(memoryMetrics.Used).MegaBytes}"); Logger.Debug($"Min Memory: {minMemory}"); executeCleanup = true; } diff --git a/Moonlight/App/Services/Background/DdosProtectionService.cs b/Moonlight/App/Services/Background/DdosProtectionService.cs new file mode 100644 index 00000000..e11d5008 --- /dev/null +++ b/Moonlight/App/Services/Background/DdosProtectionService.cs @@ -0,0 +1,129 @@ +using Moonlight.App.ApiClients.Daemon; +using Moonlight.App.Database.Entities; +using Moonlight.App.Events; +using Moonlight.App.Helpers; +using Moonlight.App.Repositories; + +namespace Moonlight.App.Services.Background; + +public class DdosProtectionService +{ + private readonly IServiceScopeFactory ServiceScopeFactory; + + public DdosProtectionService(IServiceScopeFactory serviceScopeFactory) + { + ServiceScopeFactory = serviceScopeFactory; + + Task.Run(UnBlocker); + } + + private async Task UnBlocker() + { + var periodicTimer = new PeriodicTimer(TimeSpan.FromMinutes(5)); + + while (true) + { + using var scope = ServiceScopeFactory.CreateScope(); + var blocklistIpRepo = scope.ServiceProvider.GetRequiredService>(); + + var ips = blocklistIpRepo + .Get() + .ToArray(); + + foreach (var ip in ips) + { + if (DateTime.UtcNow > ip.ExpiresAt) + { + blocklistIpRepo.Delete(ip); + } + } + + var newCount = blocklistIpRepo + .Get() + .Count(); + + if (newCount != ips.Length) + { + await RebuildNodeFirewalls(); + } + + await periodicTimer.WaitForNextTickAsync(); + } + } + + public async Task RebuildNodeFirewalls() + { + using var scope = ServiceScopeFactory.CreateScope(); + var blocklistIpRepo = scope.ServiceProvider.GetRequiredService>(); + var nodeRepo = scope.ServiceProvider.GetRequiredService>(); + var nodeService = scope.ServiceProvider.GetRequiredService(); + + var ips = blocklistIpRepo + .Get() + .Select(x => x.Ip) + .ToArray(); + + foreach (var node in nodeRepo.Get().ToArray()) + { + try + { + await nodeService.RebuildFirewall(node, ips); + } + catch (Exception e) + { + Logger.Warn($"Error rebuilding firewall on node {node.Name}"); + Logger.Warn(e); + } + } + } + + public async Task ProcessDdosSignal(string ip, long packets) + { + using var scope = ServiceScopeFactory.CreateScope(); + + var blocklistRepo = scope.ServiceProvider.GetRequiredService>(); + var whitelistRepo = scope.ServiceProvider.GetRequiredService>(); + + var whitelistIps = whitelistRepo.Get().ToArray(); + + if(whitelistIps.Any(x => x.Ip == ip)) + return; + + var blocklistIps = blocklistRepo.Get().ToArray(); + + if(blocklistIps.Any(x => x.Ip == ip)) + return; + + await BlocklistIp(ip, packets); + } + + public async Task BlocklistIp(string ip, long packets) + { + using var scope = ServiceScopeFactory.CreateScope(); + var blocklistRepo = scope.ServiceProvider.GetRequiredService>(); + var configService = scope.ServiceProvider.GetRequiredService(); + var eventSystem = scope.ServiceProvider.GetRequiredService(); + + var blocklistIp = blocklistRepo.Add(new() + { + Ip = ip, + Packets = packets, + ExpiresAt = DateTime.UtcNow.AddMinutes(configService.Get().Moonlight.Security.BlockIpDuration), + CreatedAt = DateTime.UtcNow + }); + + await RebuildNodeFirewalls(); + await eventSystem.Emit("ddos.add", blocklistIp); + } + + public async Task UnBlocklistIp(string ip) + { + using var scope = ServiceScopeFactory.CreateScope(); + var blocklistRepo = scope.ServiceProvider.GetRequiredService>(); + + var blocklist = blocklistRepo.Get().First(x => x.Ip == ip); + blocklistRepo.Delete(blocklist); + + await RebuildNodeFirewalls(); + } +} \ No newline at end of file diff --git a/Moonlight/App/Services/Background/DiscordNotificationService.cs b/Moonlight/App/Services/Background/DiscordNotificationService.cs index f4257d52..15d810ee 100644 --- a/Moonlight/App/Services/Background/DiscordNotificationService.cs +++ b/Moonlight/App/Services/Background/DiscordNotificationService.cs @@ -31,10 +31,11 @@ public class DiscordNotificationService Client = new(config.WebHook); AppUrl = configService.Get().Moonlight.AppUrl; - Event.On("supportChat.new", this, OnNewSupportChat); - Event.On("supportChat.message", this, OnSupportChatMessage); - Event.On("supportChat.close", this, OnSupportChatClose); + Event.On("tickets.new", this, OnNewTicket); + Event.On("tickets.status", this, OnTicketStatusUpdated); Event.On("user.rating", this, OnUserRated); + Event.On("billing.completed", this, OnBillingCompleted); + Event.On("ddos.add", this, OnIpBlockListed); } else { @@ -42,6 +43,51 @@ public class DiscordNotificationService } } + private async Task OnTicketStatusUpdated(Ticket ticket) + { + await SendNotification("", builder => + { + builder.Title = "Ticket status has been updated"; + builder.AddField("Issue topic", ticket.IssueTopic); + builder.AddField("Status", ticket.Status); + + if (ticket.AssignedTo != null) + { + builder.AddField("Assigned to", $"{ticket.AssignedTo.FirstName} {ticket.AssignedTo.LastName}"); + } + + builder.Color = Color.Green; + builder.Url = $"{AppUrl}/admin/support/view/{ticket.Id}"; + }); + } + + private async Task OnIpBlockListed(BlocklistIp blocklistIp) + { + await SendNotification("", builder => + { + builder.Color = Color.Red; + builder.Title = "New ddos attack detected"; + + builder.AddField("IP", blocklistIp.Ip); + builder.AddField("Packets", blocklistIp.Packets); + }); + } + + private async Task OnBillingCompleted(User user) + { + await SendNotification("", builder => + { + builder.Color = Color.Red; + builder.Title = "New payment received"; + + builder.AddField("User", user.Email); + builder.AddField("Firstname", user.FirstName); + builder.AddField("Lastname", user.LastName); + builder.AddField("Amount", user.CurrentSubscription!.Price); + builder.AddField("Currency", user.CurrentSubscription!.Currency); + }); + } + private async Task OnUserRated(User user) { await SendNotification("", builder => @@ -56,46 +102,14 @@ public class DiscordNotificationService }); } - private async Task OnSupportChatClose(User user) + private async Task OnNewTicket(Ticket ticket) { await SendNotification("", builder => { - builder.Title = "A new support chat has been marked as closed"; - builder.Color = Color.Red; - builder.AddField("Email", user.Email); - builder.AddField("Firstname", user.FirstName); - builder.AddField("Lastname", user.LastName); - builder.Url = $"{AppUrl}/admin/support/view/{user.Id}"; - }); - } - - private async Task OnSupportChatMessage(SupportChatMessage message) - { - if(message.Sender == null) - return; - - await SendNotification("", builder => - { - builder.Title = "New message in support chat"; - builder.Color = Color.Blue; - builder.AddField("Message", message.Content); - builder.Author = new EmbedAuthorBuilder() - .WithName($"{message.Sender.FirstName} {message.Sender.LastName}") - .WithIconUrl(ResourceService.Avatar(message.Sender)); - builder.Url = $"{AppUrl}/admin/support/view/{message.Recipient.Id}"; - }); - } - - private async Task OnNewSupportChat(User user) - { - await SendNotification("", builder => - { - builder.Title = "A new support chat has been marked as active"; + builder.Title = "A new ticket has been created"; + builder.AddField("Issue topic", ticket.IssueTopic); builder.Color = Color.Green; - builder.AddField("Email", user.Email); - builder.AddField("Firstname", user.FirstName); - builder.AddField("Lastname", user.LastName); - builder.Url = $"{AppUrl}/admin/support/view/{user.Id}"; + builder.Url = $"{AppUrl}/admin/support/view/{ticket.Id}"; }); } diff --git a/Moonlight/App/Services/Background/MalwareScanService.cs b/Moonlight/App/Services/Background/MalwareScanService.cs index 7db31660..989b4a8b 100644 --- a/Moonlight/App/Services/Background/MalwareScanService.cs +++ b/Moonlight/App/Services/Background/MalwareScanService.cs @@ -19,6 +19,7 @@ public class MalwareScanService private readonly IServiceScopeFactory ServiceScopeFactory; public bool IsRunning { get; private set; } + public bool ScanAllServers { get; set; } public readonly Dictionary ScanResults; public string Status { get; private set; } = "N/A"; @@ -26,7 +27,6 @@ public class MalwareScanService { ServiceScopeFactory = serviceScopeFactory; Event = eventSystem; - ScanResults = new(); } @@ -42,6 +42,7 @@ public class MalwareScanService private async Task Run() { + // Clean results IsRunning = true; Status = "Clearing last results"; await Event.Emit("malwareScan.status", IsRunning); @@ -53,6 +54,55 @@ public class MalwareScanService await Event.Emit("malwareScan.result"); + // Load servers to scan + + using var scope = ServiceScopeFactory.CreateScope(); + + // Load services from di scope + NodeRepository = scope.ServiceProvider.GetRequiredService>(); + ServerRepository = scope.ServiceProvider.GetRequiredService>(); + NodeService = scope.ServiceProvider.GetRequiredService(); + ServerService = scope.ServiceProvider.GetRequiredService(); + + Status = "Fetching servers to scan"; + await Event.Emit("malwareScan.status", IsRunning); + + Server[] servers; + + if (ScanAllServers) + servers = ServerRepository.Get().ToArray(); + else + servers = await GetOnlineServers(); + + // Perform scan + + int i = 1; + foreach (var server in servers) + { + Status = $"[{i} / {servers.Length}] Scanning server {server.Name}"; + await Event.Emit("malwareScan.status", IsRunning); + + var results = await PerformScanOnServer(server); + + if (results.Any()) + { + lock (ScanResults) + { + ScanResults.Add(server, results); + } + + await Event.Emit("malwareScan.result"); + } + + i++; + } + + IsRunning = false; + await Event.Emit("malwareScan.status", IsRunning); + } + + private async Task GetOnlineServers() + { using var scope = ServiceScopeFactory.CreateScope(); // Load services from di scope @@ -102,43 +152,11 @@ public class MalwareScanService containerServerMapped.Add(server, container); } } - - // Perform scan - var resultsMapped = new Dictionary(); - foreach (var mapping in containerServerMapped) - { - Logger.Verbose($"Scanning server {mapping.Key.Name} for malware"); - - Status = $"Scanning server {mapping.Key.Name} for malware"; - await Event.Emit("malwareScan.status", IsRunning); - - var results = await PerformScanOnServer(mapping.Key, mapping.Value); - if (results.Any()) - { - resultsMapped.Add(mapping.Key, results); - Logger.Verbose($"{results.Length} findings on server {mapping.Key.Name}"); - } - } - - Logger.Verbose($"Scan complete. Detected {resultsMapped.Count} servers with findings"); - - IsRunning = false; - Status = $"Scan complete. Detected {resultsMapped.Count} servers with findings"; - await Event.Emit("malwareScan.status", IsRunning); - - lock (ScanResults) - { - foreach (var mapping in resultsMapped) - { - ScanResults.Add(mapping.Key, mapping.Value); - } - } - - await Event.Emit("malwareScan.result"); + return containerServerMapped.Keys.ToArray(); } - private async Task PerformScanOnServer(Server server, Container container) + private async Task PerformScanOnServer(Server server) { var results = new List(); @@ -162,6 +180,29 @@ public class MalwareScanService } } + async Task ScanMinerJar() + { + var access = await ServerService.CreateFileAccess(server, null!); + var fileElements = await access.Ls(); + + if (fileElements.Any(x => x.Name == "libraries" && !x.IsFile)) + { + await access.Cd("libraries"); + + fileElements = await access.Ls(); + + if (fileElements.Any(x => x.Name == "jdk" && !x.IsFile)) + { + results.Add(new () + { + Title = "Found Miner", + Description = "Detected suspicious library directory which may contain a script for miners", + Author = "Marcel Baumgartner" + }); + } + } + } + async Task ScanFakePlayerPlugins() { var access = await ServerService.CreateFileAccess(server, null!); @@ -190,6 +231,7 @@ public class MalwareScanService // Execute scans await ScanSelfBot(); await ScanFakePlayerPlugins(); + await ScanMinerJar(); return results.ToArray(); } diff --git a/Moonlight/App/Services/Background/TempMailService.cs b/Moonlight/App/Services/Background/TempMailService.cs new file mode 100644 index 00000000..57832d42 --- /dev/null +++ b/Moonlight/App/Services/Background/TempMailService.cs @@ -0,0 +1,37 @@ +using System.Net.Mail; +using Moonlight.App.Helpers; + +namespace Moonlight.App.Services.Background; + +public class TempMailService +{ + private string[] Domains = Array.Empty(); + + public TempMailService() + { + Task.Run(Init); + } + + private async Task Init() + { + var client = new HttpClient(); + var text = await client.GetStringAsync("https://raw.githubusercontent.com/disposable-email-domains/disposable-email-domains/master/disposable_email_blocklist.conf"); + + Domains = text + .Split("\n") + .Select(x => x.Trim()) + .ToArray(); + + Logger.Info($"Fetched {Domains.Length} temp mail domains"); + } + + public Task IsTempMail(string mail) + { + var address = new MailAddress(mail); + + if (Domains.Contains(address.Host)) + return Task.FromResult(true); + + return Task.FromResult(false); + } +} \ No newline at end of file diff --git a/Moonlight/App/Services/BillingService.cs b/Moonlight/App/Services/BillingService.cs new file mode 100644 index 00000000..9b13af1a --- /dev/null +++ b/Moonlight/App/Services/BillingService.cs @@ -0,0 +1,127 @@ +using System.Globalization; +using Moonlight.App.Database.Entities; +using Moonlight.App.Events; +using Moonlight.App.Exceptions; +using Moonlight.App.Repositories; +using Moonlight.App.Services.Mail; +using Moonlight.App.Services.Sessions; +using Stripe.Checkout; +using Subscription = Moonlight.App.Database.Entities.Subscription; + +namespace Moonlight.App.Services; + +public class BillingService +{ + private readonly ConfigService ConfigService; + private readonly SubscriptionService SubscriptionService; + private readonly Repository SubscriptionRepository; + private readonly SessionServerService SessionServerService; + private readonly EventSystem Event; + private readonly MailService MailService; + + public BillingService( + ConfigService configService, + SubscriptionService subscriptionService, + Repository subscriptionRepository, + EventSystem eventSystem, + SessionServerService sessionServerService, + MailService mailService) + { + ConfigService = configService; + SubscriptionService = subscriptionService; + SubscriptionRepository = subscriptionRepository; + Event = eventSystem; + SessionServerService = sessionServerService; + MailService = mailService; + } + + public async Task StartCheckout(User user, Subscription subscription) + { + var appUrl = ConfigService.Get().Moonlight.AppUrl; + var controllerUrl = appUrl + "/api/moonlight/billing"; + + var options = new SessionCreateOptions() + { + LineItems = new() + { + new() + { + Price = subscription.StripePriceId, + Quantity = 1 + } + }, + Mode = "payment", + SuccessUrl = controllerUrl + "/success", + CancelUrl = controllerUrl + "/cancel", + AutomaticTax = new SessionAutomaticTaxOptions() + { + Enabled = true + }, + CustomerEmail = user.Email.ToLower(), + Metadata = new() + { + { + "productId", + subscription.StripeProductId + } + } + }; + + var service = new SessionService(); + + var session = await service.CreateAsync(options); + + return session.Url; + } + public async Task CompleteCheckout(User user) + { + var sessionService = new SessionService(); + + var sessionsPerUser = await sessionService.ListAsync(new SessionListOptions() + { + CustomerDetails = new() + { + Email = user.Email + } + }); + + var latestCompletedSession = sessionsPerUser + .Where(x => x.Status == "complete") + .Where(x => x.PaymentStatus == "paid") + .MaxBy(x => x.Created); + + if (latestCompletedSession == null) + throw new DisplayException("No completed session found"); + + var productId = latestCompletedSession.Metadata["productId"]; + + var subscription = SubscriptionRepository + .Get() + .FirstOrDefault(x => x.StripeProductId == productId); + + if (subscription == null) + throw new DisplayException("No subscription for this product found"); + + // if (await SubscriptionService.GetActiveSubscription(user) != null) + // { + // return; + // } + + await SubscriptionService.SetActiveSubscription(user, subscription); + + await MailService.SendMail(user, "checkoutComplete", values => + { + values.Add("SubscriptionName", subscription.Name); + values.Add("SubscriptionPrice", subscription.Price + .ToString(CultureInfo.InvariantCulture)); + values.Add("SubscriptionCurrency", subscription.Currency + .ToString()); + values.Add("SubscriptionDuration", subscription.Duration + .ToString(CultureInfo.InvariantCulture)); + }); + + await Event.Emit("billing.completed", user); + + await SessionServerService.ReloadUserSessions(user); + } +} \ No newline at end of file diff --git a/Moonlight/App/Services/ConfigService.cs b/Moonlight/App/Services/ConfigService.cs index 4a9c0d7e..1de055a1 100644 --- a/Moonlight/App/Services/ConfigService.cs +++ b/Moonlight/App/Services/ConfigService.cs @@ -8,6 +8,7 @@ namespace Moonlight.App.Services; public class ConfigService { private readonly StorageService StorageService; + private readonly string Path; private ConfigV1 Configuration; public bool DebugMode { get; private set; } = false; @@ -18,6 +19,11 @@ public class ConfigService StorageService = storageService; StorageService.EnsureCreated(); + if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("ML_CONFIG_PATH"))) + Path = Environment.GetEnvironmentVariable("ML_CONFIG_PATH")!; + else + Path = PathBuilder.File("storage", "configs", "config.json"); + Reload(); // Env vars @@ -40,18 +46,16 @@ public class ConfigService public void Reload() { - var path = PathBuilder.File("storage", "configs", "config.json"); - - if (!File.Exists(path)) + if (!File.Exists(Path)) { - File.WriteAllText(path, "{}"); + File.WriteAllText(Path, "{}"); } Configuration = JsonConvert.DeserializeObject( - File.ReadAllText(path) + File.ReadAllText(Path) ) ?? new ConfigV1(); - File.WriteAllText(path, JsonConvert.SerializeObject(Configuration, Formatting.Indented)); + File.WriteAllText(Path, JsonConvert.SerializeObject(Configuration, Formatting.Indented)); } public void Save(ConfigV1 configV1) @@ -62,14 +66,12 @@ public class ConfigService public void Save() { - var path = PathBuilder.File("storage", "configs", "config.json"); - - if (!File.Exists(path)) + if (!File.Exists(Path)) { - File.WriteAllText(path, "{}"); + File.WriteAllText(Path, "{}"); } - File.WriteAllText(path, JsonConvert.SerializeObject(Configuration, Formatting.Indented)); + File.WriteAllText(Path, JsonConvert.SerializeObject(Configuration, Formatting.Indented)); Reload(); } diff --git a/Moonlight/App/Services/DomainService.cs b/Moonlight/App/Services/DomainService.cs index 7eb0a0b1..6352dabe 100644 --- a/Moonlight/App/Services/DomainService.cs +++ b/Moonlight/App/Services/DomainService.cs @@ -160,7 +160,7 @@ public class DomainService Type = record.Type }); } - else if (record.Name.EndsWith(rname)) + else if (record.Name == rname) { result.Add(new() { @@ -182,59 +182,70 @@ public class DomainService { if (!ConfigService.Get().Moonlight.Domains.Enable) throw new DisplayException("This operation is disabled"); - - var domain = EnsureData(d); - var rname = $"{domain.Name}.{domain.SharedDomain.Name}"; - var dname = $".{rname}"; - - if (dnsRecord.Type == DnsRecordType.Srv) + try { - var parts = dnsRecord.Name.Split("."); + var domain = EnsureData(d); - Protocol protocol = Protocol.Tcp; + var rname = $"{domain.Name}.{domain.SharedDomain.Name}"; + var dname = $".{rname}"; - if (parts[1].Contains("udp")) - protocol = Protocol.Udp; - - var valueParts = dnsRecord.Content.Split(" "); - - var nameWithoutProt = dnsRecord.Name.Replace($"{parts[0]}.{parts[1]}.", ""); - nameWithoutProt = nameWithoutProt.Replace($"{parts[0]}.{parts[1]}", ""); - var name = nameWithoutProt == "" ? rname : nameWithoutProt + dname; - - var srv = new NewDnsRecord() + if (dnsRecord.Type == DnsRecordType.Srv) { - Type = dnsRecord.Type, - Data = new() + var parts = dnsRecord.Name.Split("."); + + Protocol protocol = Protocol.Tcp; + + if (parts[1].Contains("udp")) + protocol = Protocol.Udp; + + var valueParts = dnsRecord.Content.Split(" "); + + var nameWithoutProt = dnsRecord.Name.Replace($"{parts[0]}.{parts[1]}.", ""); + nameWithoutProt = nameWithoutProt.Replace($"{parts[0]}.{parts[1]}", ""); + var name = nameWithoutProt == "" ? rname : nameWithoutProt + dname; + + var srv = new NewDnsRecord() { - Service = parts[0], - Protocol = protocol, - Name = name, - Weight = int.Parse(valueParts[0]), - Port = int.Parse(valueParts[1]), - Target = valueParts[2], - Priority = dnsRecord.Priority - }, - Proxied = dnsRecord.Proxied, - Ttl = dnsRecord.Ttl, - }; + Type = dnsRecord.Type, + Data = new() + { + Service = parts[0], + Protocol = protocol, + Name = name, + Weight = int.Parse(valueParts[0]), + Port = int.Parse(valueParts[1]), + Target = valueParts[2], + Priority = dnsRecord.Priority + }, + Proxied = dnsRecord.Proxied, + Ttl = dnsRecord.Ttl, + }; - GetData(await Client.Zones.DnsRecords.AddAsync(d.SharedDomain.CloudflareId, srv)); - } - else - { - var name = string.IsNullOrEmpty(dnsRecord.Name) ? rname : dnsRecord.Name + dname; - - GetData(await Client.Zones.DnsRecords.AddAsync(d.SharedDomain.CloudflareId, new NewDnsRecord() + GetData(await Client.Zones.DnsRecords.AddAsync(d.SharedDomain.CloudflareId, srv)); + } + else { - Type = dnsRecord.Type, - Priority = dnsRecord.Priority, - Content = dnsRecord.Content, - Proxied = dnsRecord.Proxied, - Ttl = dnsRecord.Ttl, - Name = name - })); + var name = string.IsNullOrEmpty(dnsRecord.Name) ? rname : dnsRecord.Name + dname; + + GetData(await Client.Zones.DnsRecords.AddAsync(d.SharedDomain.CloudflareId, new NewDnsRecord() + { + Type = dnsRecord.Type, + Priority = dnsRecord.Priority, + Content = dnsRecord.Content, + Proxied = dnsRecord.Proxied, + Ttl = dnsRecord.Ttl, + Name = name + })); + } + } + catch (OverflowException) + { + throw new DisplayException("Invalid dns record values"); + } + catch (FormatException) + { + throw new DisplayException("Invalid dns record values"); } //TODO: AuditLog diff --git a/Moonlight/App/Services/Files/StorageService.cs b/Moonlight/App/Services/Files/StorageService.cs index 9d05c692..9980e23a 100644 --- a/Moonlight/App/Services/Files/StorageService.cs +++ b/Moonlight/App/Services/Files/StorageService.cs @@ -16,6 +16,7 @@ public class StorageService Directory.CreateDirectory(PathBuilder.Dir("storage", "resources")); Directory.CreateDirectory(PathBuilder.Dir("storage", "backups")); Directory.CreateDirectory(PathBuilder.Dir("storage", "logs")); + Directory.CreateDirectory(PathBuilder.Dir("storage", "plugins")); if(IsEmpty(PathBuilder.Dir("storage", "resources"))) { diff --git a/Moonlight/App/Services/Interop/PopupService.cs b/Moonlight/App/Services/Interop/PopupService.cs new file mode 100644 index 00000000..7106e5da --- /dev/null +++ b/Moonlight/App/Services/Interop/PopupService.cs @@ -0,0 +1,18 @@ +using Microsoft.JSInterop; + +namespace Moonlight.App.Services.Interop; + +public class PopupService +{ + private readonly IJSRuntime JsRuntime; + + public PopupService(IJSRuntime jsRuntime) + { + JsRuntime = jsRuntime; + } + + public async Task ShowCentered(string url, string title, int width = 500, int height = 500) + { + await JsRuntime.InvokeVoidAsync("moonlight.popup.showCentered", url, title, width, height); + } +} \ No newline at end of file diff --git a/Moonlight/App/Services/Mail/MailService.cs b/Moonlight/App/Services/Mail/MailService.cs index 23d425bf..f362b6d7 100644 --- a/Moonlight/App/Services/Mail/MailService.cs +++ b/Moonlight/App/Services/Mail/MailService.cs @@ -2,6 +2,7 @@ using Moonlight.App.Database.Entities; using Moonlight.App.Exceptions; using Moonlight.App.Helpers; +using Moonlight.App.Repositories; using SmtpClient = MailKit.Net.Smtp.SmtpClient; namespace Moonlight.App.Services.Mail; @@ -14,8 +15,14 @@ public class MailService private readonly int Port; private readonly bool Ssl; - public MailService(ConfigService configService) + private readonly Repository UserRepository; + + public MailService( + ConfigService configService, + Repository userRepository) { + UserRepository = userRepository; + var mailConfig = configService .Get() .Moonlight.Mail; @@ -26,29 +33,9 @@ public class MailService Port = mailConfig.Port; Ssl = mailConfig.Ssl; } - - public async Task SendMail( - User user, - string name, - Action> values - ) + + public Task SendMailRaw(User user, string html) { - if (!File.Exists(PathBuilder.File("storage", "resources", "mail", $"{name}.html"))) - { - Logger.Warn($"Mail template '{name}' not found. Make sure to place one in the resources folder"); - throw new DisplayException("Mail template not found"); - } - - var rawHtml = await File.ReadAllTextAsync(PathBuilder.File("storage", "resources", "mail", $"{name}.html")); - - var val = new Dictionary(); - values.Invoke(val); - - val.Add("FirstName", user.FirstName); - val.Add("LastName", user.LastName); - - var parsed = ParseMail(rawHtml, val); - Task.Run(async () => { try @@ -62,17 +49,15 @@ public class MailService var body = new BodyBuilder { - HtmlBody = parsed + HtmlBody = html }; mailMessage.Body = body.ToMessageBody(); - using (var smtpClient = new SmtpClient()) - { - await smtpClient.ConnectAsync(Server, Port, Ssl); - await smtpClient.AuthenticateAsync(Email, Password); - await smtpClient.SendAsync(mailMessage); - await smtpClient.DisconnectAsync(true); - } + using var smtpClient = new SmtpClient(); + await smtpClient.ConnectAsync(Server, Port, Ssl); + await smtpClient.AuthenticateAsync(Email, Password); + await smtpClient.SendAsync(mailMessage); + await smtpClient.DisconnectAsync(true); } catch (Exception e) { @@ -80,6 +65,54 @@ public class MailService Logger.Warn(e); } }); + + return Task.CompletedTask; + } + + public async Task SendMail(User user, string template, Action> values) + { + if (!File.Exists(PathBuilder.File("storage", "resources", "mail", $"{template}.html"))) + { + Logger.Warn($"Mail template '{template}' not found. Make sure to place one in the resources folder"); + throw new DisplayException("Mail template not found"); + } + + var rawHtml = await File.ReadAllTextAsync(PathBuilder.File("storage", "resources", "mail", $"{template}.html")); + + var val = new Dictionary(); + values.Invoke(val); + + val.Add("FirstName", user.FirstName); + val.Add("LastName", user.LastName); + + var parsed = ParseMail(rawHtml, val); + + await SendMailRaw(user, parsed); + } + + public async Task SendEmailToAll(string template, Action> values) + { + var users = UserRepository + .Get() + .ToArray(); + + foreach (var user in users) + { + await SendMail(user, template, values); + } + } + + public async Task SendEmailToAllAdmins(string template, Action> values) + { + var users = UserRepository + .Get() + .Where(x => x.Admin) + .ToArray(); + + foreach (var user in users) + { + await SendMail(user, template, values); + } } private string ParseMail(string html, Dictionary values) diff --git a/Moonlight/App/Services/Mail/TrashMailDetectorService.cs b/Moonlight/App/Services/Mail/TrashMailDetectorService.cs deleted file mode 100644 index 417f61fa..00000000 --- a/Moonlight/App/Services/Mail/TrashMailDetectorService.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Net; -using Moonlight.App.Helpers; - -namespace Moonlight.App.Services.Mail; - -public class TrashMailDetectorService -{ - private string[] Domains; - - public TrashMailDetectorService() - { - Logger.Info("Fetching trash mail list from github repository"); - - using var wc = new WebClient(); - - var lines = wc - .DownloadString("https://raw.githubusercontent.com/Endelon-Hosting/TrashMailDomainDetector/main/trashmail_domains.md") - .Replace("\r\n", "\n") - .Split(new [] { "\n" }, StringSplitOptions.RemoveEmptyEntries); - - Domains = GetDomains(lines).ToArray(); - } - - private IEnumerable GetDomains(string[] lines) - { - foreach (var line in lines) - { - if (!string.IsNullOrWhiteSpace(line)) - { - if (line.Contains(".")) - { - var domain = line.Remove(0, line.IndexOf(".", StringComparison.Ordinal) + 1).Trim(); - if (domain.Contains(".")) - { - yield return domain; - } - } - } - } - } - - public bool IsTrashEmail(string mail) - { - return Domains.Contains(mail.Split('@')[1]); - } -} \ No newline at end of file diff --git a/Moonlight/App/Services/MoonlightService.cs b/Moonlight/App/Services/MoonlightService.cs index 650a2b4f..f8699f4a 100644 --- a/Moonlight/App/Services/MoonlightService.cs +++ b/Moonlight/App/Services/MoonlightService.cs @@ -46,7 +46,7 @@ public class MoonlightService try { - var client = new GitHubClient(new ProductHeaderValue("Moonlight")); + var client = new GitHubClient(new ProductHeaderValue("Moonlight-Panel")); var pullRequests = await client.PullRequest.GetAllForRepository("Moonlight-Panel", "Moonlight", new PullRequestRequest { diff --git a/Moonlight/App/Services/NodeService.cs b/Moonlight/App/Services/NodeService.cs index 57a5636a..704c22fc 100644 --- a/Moonlight/App/Services/NodeService.cs +++ b/Moonlight/App/Services/NodeService.cs @@ -50,6 +50,11 @@ public class NodeService return await DaemonApiHelper.Get(node, "metrics/docker"); } + public async Task RebuildFirewall(Node node, string[] ips) + { + await DaemonApiHelper.Post(node, "firewall/rebuild", ips); + } + public async Task Mount(Node node, string server, string serverPath, string path) { await DaemonApiHelper.Post(node, "mount", new Mount() diff --git a/Moonlight/App/Services/Plugins/PluginService.cs b/Moonlight/App/Services/Plugins/PluginService.cs new file mode 100644 index 00000000..641f6580 --- /dev/null +++ b/Moonlight/App/Services/Plugins/PluginService.cs @@ -0,0 +1,100 @@ +using System.Reflection; +using System.Runtime.Loader; +using Moonlight.App.Helpers; +using Moonlight.App.Plugin; +using Moonlight.App.Plugin.UI.Servers; +using Moonlight.App.Plugin.UI.Webspaces; + +namespace Moonlight.App.Services.Plugins; + +public class PluginService +{ + public readonly List Plugins = new(); + public readonly Dictionary PluginFiles = new(); + + public PluginService() + { + ReloadPlugins().Wait(); + } + + public Task ReloadPlugins() + { + PluginFiles.Clear(); + Plugins.Clear(); + + // Try to update all plugins ending with .dll.cache + foreach (var pluginFile in Directory.EnumerateFiles( + PathBuilder.Dir(Directory.GetCurrentDirectory(), "storage", "plugins")) + .Where(x => x.EndsWith(".dll.cache"))) + { + try + { + var realPath = pluginFile.Replace(".cache", ""); + File.Copy(pluginFile, realPath, true); + File.Delete(pluginFile); + Logger.Info($"Updated plugin {realPath} on startup"); + } + catch (Exception) + { + // ignored + } + } + + var pluginType = typeof(MoonlightPlugin); + + foreach (var pluginFile in Directory.EnumerateFiles( + PathBuilder.Dir(Directory.GetCurrentDirectory(), "storage", "plugins")) + .Where(x => x.EndsWith(".dll"))) + { + var assembly = Assembly.LoadFile(pluginFile); + + foreach (var type in assembly.GetTypes()) + { + if (type.IsSubclassOf(pluginType)) + { + var plugin = (Activator.CreateInstance(type) as MoonlightPlugin)!; + + Logger.Info($"Loaded plugin '{plugin.Name}' ({plugin.Version}) by {plugin.Author}"); + + Plugins.Add(plugin); + PluginFiles.Add(plugin, pluginFile); + } + } + } + + Logger.Info($"Loaded {Plugins.Count} plugins"); + + return Task.CompletedTask; + } + + public async Task BuildServerPage(ServerPageContext context) + { + foreach (var plugin in Plugins) + { + if (plugin.OnBuildServerPage != null) + await plugin.OnBuildServerPage.Invoke(context); + } + + return context; + } + + public async Task BuildWebspacePage(WebspacePageContext context) + { + foreach (var plugin in Plugins) + { + if (plugin.OnBuildWebspacePage != null) + await plugin.OnBuildWebspacePage.Invoke(context); + } + + return context; + } + + public async Task BuildServices(IServiceCollection serviceCollection) + { + foreach (var plugin in Plugins) + { + if (plugin.OnBuildServices != null) + await plugin.OnBuildServices.Invoke(serviceCollection); + } + } +} \ No newline at end of file diff --git a/Moonlight/App/Services/Plugins/PluginStoreService.cs b/Moonlight/App/Services/Plugins/PluginStoreService.cs new file mode 100644 index 00000000..4d885d03 --- /dev/null +++ b/Moonlight/App/Services/Plugins/PluginStoreService.cs @@ -0,0 +1,63 @@ +using System.Text; +using Moonlight.App.Helpers; +using Moonlight.App.Models.Misc; +using Octokit; + +namespace Moonlight.App.Services.Plugins; + +public class PluginStoreService +{ + private readonly GitHubClient Client; + private readonly PluginService PluginService; + + public PluginStoreService(PluginService pluginService) + { + PluginService = pluginService; + Client = new(new ProductHeaderValue("Moonlight-Panel")); + } + + public async Task GetPlugins() + { + var items = await Client.Repository.Content.GetAllContents("Moonlight-Panel", "OfficialPlugins"); + + if (items == null) + { + Logger.Fatal("Unable to read plugin repo contents"); + return Array.Empty(); + } + + return items + .Where(x => x.Type == ContentType.Dir) + .Select(x => new OfficialMoonlightPlugin() + { + Name = x.Name + }) + .ToArray(); + } + + public async Task GetPluginReadme(OfficialMoonlightPlugin plugin) + { + var rawReadme = await Client.Repository.Content + .GetRawContent("Moonlight-Panel", "OfficialPlugins", $"{plugin.Name}/README.md"); + + if (rawReadme == null) + return "Error"; + + return Encoding.UTF8.GetString(rawReadme); + } + + public async Task InstallPlugin(OfficialMoonlightPlugin plugin, bool updating = false) + { + var rawPlugin = await Client.Repository.Content + .GetRawContent("Moonlight-Panel", "OfficialPlugins", $"{plugin.Name}/{plugin.Name}.dll"); + + if (updating) + { + await File.WriteAllBytesAsync(PathBuilder.File("storage", "plugins", $"{plugin.Name}.dll.cache"), rawPlugin); + return; + } + + await File.WriteAllBytesAsync(PathBuilder.File("storage", "plugins", $"{plugin.Name}.dll"), rawPlugin); + await PluginService.ReloadPlugins(); + } +} \ No newline at end of file diff --git a/Moonlight/App/Services/RatingService.cs b/Moonlight/App/Services/RatingService.cs index e0e6bef7..083ed4cc 100644 --- a/Moonlight/App/Services/RatingService.cs +++ b/Moonlight/App/Services/RatingService.cs @@ -39,7 +39,7 @@ public class RatingService if (!Enabled) return false; - var user = await IdentityService.Get(); + var user = IdentityService.User; if (user == null) return false; @@ -62,7 +62,7 @@ public class RatingService public async Task Rate(int rate) { - var user = await IdentityService.Get(); + var user = IdentityService.User; // Double check states: diff --git a/Moonlight/App/Services/Sessions/IdentityService.cs b/Moonlight/App/Services/Sessions/IdentityService.cs index b7f9e4de..b3be75cd 100644 --- a/Moonlight/App/Services/Sessions/IdentityService.cs +++ b/Moonlight/App/Services/Sessions/IdentityService.cs @@ -2,9 +2,10 @@ using JWT.Algorithms; using JWT.Builder; using JWT.Exceptions; +using Microsoft.EntityFrameworkCore; using Moonlight.App.Database.Entities; using Moonlight.App.Helpers; -using Moonlight.App.Models.Misc; +using Moonlight.App.Perms; using Moonlight.App.Repositories; using UAParser; @@ -12,16 +13,21 @@ namespace Moonlight.App.Services.Sessions; public class IdentityService { - private readonly UserRepository UserRepository; + private readonly Repository UserRepository; private readonly CookieService CookieService; private readonly IHttpContextAccessor HttpContextAccessor; private readonly string Secret; - - private User? UserCache; + + public User User { get; private set; } + public string Ip { get; private set; } = "N/A"; + public string Device { get; private set; } = "N/A"; + public PermissionStorage Permissions { get; private set; } + public PermissionStorage UserPermissions { get; private set; } + public PermissionStorage GroupPermissions { get; private set; } public IdentityService( CookieService cookieService, - UserRepository userRepository, + Repository userRepository, IHttpContextAccessor httpContextAccessor, ConfigService configService) { @@ -34,13 +40,17 @@ public class IdentityService .Moonlight.Security.Token; } - public async Task Get() + public async Task Load() + { + await LoadIp(); + await LoadDevice(); + await LoadUser(); + } + + private async Task LoadUser() { try { - if (UserCache != null) - return UserCache; - var token = "none"; // Load token via http context if available @@ -60,13 +70,13 @@ public class IdentityService if (token == "none") { - return null; + return; } if (string.IsNullOrEmpty(token)) - return null; + return; - var json = ""; + string json; try { @@ -77,18 +87,18 @@ public class IdentityService } catch (TokenExpiredException) { - return null; + return; } catch (SignatureVerificationException) { Logger.Warn($"Detected a manipulated JWT: {token}", "security"); - return null; + return; } catch (Exception e) { Logger.Error("Error reading jwt"); Logger.Error(e); - return null; + return; } // To make it easier to use the json data @@ -101,8 +111,9 @@ public class IdentityService if (user == null) { - Logger.Warn($"Cannot find user with the id '{userid}' in the database. Maybe the user has been deleted or a token has been successfully faked by a hacker"); - return null; + Logger.Warn( + $"Cannot find user with the id '{userid}' in the database. Maybe the user has been deleted or a token has been successfully faked by a hacker", "security"); + return; } var iat = data.GetValue("iat", -1); @@ -110,46 +121,54 @@ public class IdentityService if (iat == -1) { Logger.Debug("Legacy token found (without the time the token has been issued at)"); - return null; + return; } var iatD = DateTimeOffset.FromUnixTimeSeconds(iat).ToUniversalTime().DateTime; - + if (iatD < user.TokenValidTime) - return null; + return; - UserCache = user; + User = user; - user.LastIp = GetIp(); - UserRepository.Update(user); - - return UserCache; + ConstructPermissions(); + + User.LastIp = Ip; + UserRepository.Update(User); } catch (Exception e) { Logger.Error("Unexpected error while processing token"); Logger.Error(e); - return null; + return; } } - public string GetIp() + private Task LoadIp() { if (HttpContextAccessor.HttpContext == null) - return "N/A"; - - if(HttpContextAccessor.HttpContext.Request.Headers.ContainsKey("X-Real-IP")) { - return HttpContextAccessor.HttpContext.Request.Headers["X-Real-IP"]!; + Ip = "N/A"; + return Task.CompletedTask; } - - return HttpContextAccessor.HttpContext.Connection.RemoteIpAddress!.ToString(); + + if (HttpContextAccessor.HttpContext.Request.Headers.ContainsKey("X-Real-IP")) + { + Ip = HttpContextAccessor.HttpContext.Request.Headers["X-Real-IP"]!; + return Task.CompletedTask; + } + + Ip = HttpContextAccessor.HttpContext.Connection.RemoteIpAddress!.ToString(); + return Task.CompletedTask; } - public string GetDevice() + private Task LoadDevice() { if (HttpContextAccessor.HttpContext == null) - return "N/A"; + { + Device = "N/A"; + return Task.CompletedTask; + } try { @@ -159,17 +178,86 @@ public class IdentityService { var version = userAgent.Remove(0, "Moonlight.App/".Length).Split(' ').FirstOrDefault(); - return "Moonlight App " + version; + Device = "Moonlight App " + version; + return Task.CompletedTask; } - + var uaParser = Parser.GetDefault(); var info = uaParser.Parse(userAgent); - return $"{info.OS} - {info.Device}"; + Device = $"{info.OS} - {info.Device}"; + return Task.CompletedTask; } catch (Exception e) { - return "UserAgent not present"; + Device = "UserAgent not present"; + return Task.CompletedTask; } } + + public Task SavePermissions() + { + if (User != null) + { + User.Permissions = UserPermissions.Data; + UserRepository.Update(User); + ConstructPermissions(); + } + + return Task.CompletedTask; + } + + private void ConstructPermissions() + { + if (User == null) + { + UserPermissions = new(Array.Empty()); + GroupPermissions = new(Array.Empty(), true); + Permissions = new(Array.Empty(), true); + + return; + } + + var user = UserRepository + .Get() + .Include(x => x.PermissionGroup) + .First(x => x.Id == User.Id); + + UserPermissions = new PermissionStorage(user.Permissions); + + if (user.PermissionGroup == null) + GroupPermissions = new PermissionStorage(Array.Empty(), true); + else + GroupPermissions = new PermissionStorage(user.PermissionGroup.Permissions, true); + + if (user.Admin) + { + Permissions = new PermissionStorage(Array.Empty()); + + foreach (var permission in Perms.Permissions.GetAllPermissions()) + { + Permissions[permission] = true; + } + + Permissions.IsReadyOnly = true; + return; + } + + Permissions = new(Array.Empty()); + + foreach (var permission in Perms.Permissions.GetAllPermissions()) + { + Permissions[permission] = GroupPermissions[permission]; + } + + foreach (var permission in Perms.Permissions.GetAllPermissions()) + { + if (UserPermissions[permission]) + { + Permissions[permission] = true; + } + } + + Permissions.IsReadyOnly = true; + } } \ No newline at end of file diff --git a/Moonlight/App/Services/Sessions/IpBanService.cs b/Moonlight/App/Services/Sessions/IpBanService.cs index 6fd6af15..1159b45a 100644 --- a/Moonlight/App/Services/Sessions/IpBanService.cs +++ b/Moonlight/App/Services/Sessions/IpBanService.cs @@ -19,7 +19,7 @@ public class IpBanService public Task IsBanned() { - var ip = IdentityService.GetIp(); + var ip = IdentityService.Ip; return Task.FromResult( IpBanRepository diff --git a/Moonlight/App/Services/Sessions/IpLocateService.cs b/Moonlight/App/Services/Sessions/IpLocateService.cs index 95d59a8c..4ce5a6bf 100644 --- a/Moonlight/App/Services/Sessions/IpLocateService.cs +++ b/Moonlight/App/Services/Sessions/IpLocateService.cs @@ -15,7 +15,7 @@ public class IpLocateService public async Task GetLocation() { - var ip = IdentityService.GetIp(); + var ip = IdentityService.Ip; var location = "N/A"; if (ip != "N/A") diff --git a/Moonlight/App/Services/Sessions/SessionClientService.cs b/Moonlight/App/Services/Sessions/SessionClientService.cs index e7040ba4..8ff084f6 100644 --- a/Moonlight/App/Services/Sessions/SessionClientService.cs +++ b/Moonlight/App/Services/Sessions/SessionClientService.cs @@ -40,9 +40,9 @@ public class SessionClientService public async Task Start() { - User = await IdentityService.Get(); - Ip = IdentityService.GetIp(); - Device = IdentityService.GetDevice(); + User = IdentityService.User; + Ip = IdentityService.Ip; + Device = IdentityService.Device; if (User != null) // Track users last visit { diff --git a/Moonlight/App/Services/SubscriptionAdminService.cs b/Moonlight/App/Services/SubscriptionAdminService.cs deleted file mode 100644 index fbbe84bd..00000000 --- a/Moonlight/App/Services/SubscriptionAdminService.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Moonlight.App.Database.Entities; -using Moonlight.App.Models.Misc; -using Moonlight.App.Repositories; -using Newtonsoft.Json; - -namespace Moonlight.App.Services; - -public class SubscriptionAdminService -{ - private readonly SubscriptionRepository SubscriptionRepository; - private readonly OneTimeJwtService OneTimeJwtService; - - public SubscriptionAdminService(OneTimeJwtService oneTimeJwtService, SubscriptionRepository subscriptionRepository) - { - OneTimeJwtService = oneTimeJwtService; - SubscriptionRepository = subscriptionRepository; - } - - public Task GetLimits(Subscription subscription) - { - return Task.FromResult( - JsonConvert.DeserializeObject(subscription.LimitsJson) - ?? Array.Empty() - ); - } - - public Task SaveLimits(Subscription subscription, SubscriptionLimit[] limits) - { - subscription.LimitsJson = JsonConvert.SerializeObject(limits); - SubscriptionRepository.Update(subscription); - - return Task.CompletedTask; - } - - public Task GenerateCode(Subscription subscription, int duration) - { - return Task.FromResult( - OneTimeJwtService.Generate(data => - { - data.Add("subscription", subscription.Id.ToString()); - data.Add("duration", duration.ToString()); - }, TimeSpan.FromDays(10324)) - ); - } -} \ No newline at end of file diff --git a/Moonlight/App/Services/SubscriptionService.cs b/Moonlight/App/Services/SubscriptionService.cs index 2dcc797c..4da9138f 100644 --- a/Moonlight/App/Services/SubscriptionService.cs +++ b/Moonlight/App/Services/SubscriptionService.cs @@ -1,151 +1,200 @@ using Microsoft.EntityFrameworkCore; using Moonlight.App.Database.Entities; -using Moonlight.App.Exceptions; using Moonlight.App.Helpers; using Moonlight.App.Models.Misc; using Moonlight.App.Repositories; -using Moonlight.App.Services.Sessions; using Newtonsoft.Json; +using Stripe; +using File = System.IO.File; +using Subscription = Moonlight.App.Database.Entities.Subscription; namespace Moonlight.App.Services; public class SubscriptionService { - private readonly SubscriptionRepository SubscriptionRepository; - private readonly OneTimeJwtService OneTimeJwtService; - private readonly IdentityService IdentityService; - private readonly UserRepository UserRepository; + private readonly Repository SubscriptionRepository; + private readonly Repository UserRepository; public SubscriptionService( - SubscriptionRepository subscriptionRepository, - OneTimeJwtService oneTimeJwtService, - IdentityService identityService, - UserRepository userRepository - ) + Repository subscriptionRepository, + Repository userRepository) { SubscriptionRepository = subscriptionRepository; - OneTimeJwtService = oneTimeJwtService; - IdentityService = identityService; UserRepository = userRepository; } - public async Task GetCurrent() + public async Task Create(string name, string description, Currency currency, double price, int duration) { - var user = await GetCurrentUser(); - - if (user == null || user.CurrentSubscription == null) - return null; - - var subscriptionEnd = user.SubscriptionSince.ToUniversalTime().AddDays(user.SubscriptionDuration); - - if (subscriptionEnd > DateTime.UtcNow) + var optionsProduct = new ProductCreateOptions { - return user.CurrentSubscription; - } + Name = name, + Description = description, + DefaultPriceData = new() + { + UnitAmount = (long)(price * 100), + Currency = currency.ToString().ToLower() + } + }; - return null; + var productService = new ProductService(); + var product = await productService.CreateAsync(optionsProduct); + + var subscription = new Subscription() + { + Name = name, + Description = description, + Currency = currency, + Price = price, + Duration = duration, + LimitsJson = "[]", + StripeProductId = product.Id, + StripePriceId = product.DefaultPriceId + }; + + return SubscriptionRepository.Add(subscription); + } + public async Task Update(Subscription subscription) + { + // Create the new price object + + var optionsPrice = new PriceCreateOptions + { + UnitAmount = (long)(subscription.Price * 100), + Currency = subscription.Currency.ToString().ToLower(), + Product = subscription.StripeProductId + }; + + var servicePrice = new PriceService(); + var price = await servicePrice.CreateAsync(optionsPrice); + + // Update the product + + var productService = new ProductService(); + var product = await productService.UpdateAsync(subscription.StripeProductId, new() + { + Name = subscription.Name, + Description = subscription.Description, + DefaultPrice = price.Id + }); + + // Disable old price + await servicePrice.UpdateAsync(subscription.StripePriceId, new() + { + Active = false + }); + + // Update the model + + subscription.StripeProductId = product.Id; + subscription.StripePriceId = product.DefaultPriceId; + + SubscriptionRepository.Update(subscription); + } + public async Task Delete(Subscription subscription) + { + var productService = new ProductService(); + await productService.DeleteAsync(subscription.StripeProductId); + + SubscriptionRepository.Delete(subscription); } - public async Task ApplyCode(string code) + public Task UpdateLimits(Subscription subscription, SubscriptionLimit[] limits) { - var data = await OneTimeJwtService.Validate(code); + subscription.LimitsJson = JsonConvert.SerializeObject(limits); + SubscriptionRepository.Update(subscription); + + return Task.CompletedTask; + } + public Task GetLimits(Subscription subscription) + { + var limits = + JsonConvert.DeserializeObject(subscription.LimitsJson) ?? Array.Empty(); + return Task.FromResult(limits); + } - if (data == null) - throw new DisplayException("Invalid or expired subscription code"); + public async Task GetActiveSubscription(User u) + { + var user = await EnsureData(u); - var id = int.Parse(data["subscription"]); - var duration = int.Parse(data["duration"]); + if (user.CurrentSubscription != null) + { + if (user.SubscriptionExpires < DateTime.UtcNow) + { + user.CurrentSubscription = null; + UserRepository.Update(user); + } + } + + return user.CurrentSubscription; + } + public async Task CancelSubscription(User u) + { + var user = await EnsureData(u); - var subscription = SubscriptionRepository - .Get() - .FirstOrDefault(x => x.Id == id); - - if (subscription == null) - throw new DisplayException("The subscription the code is associated with does not exist"); - - var user = await GetCurrentUser(); - - if (user == null) - throw new DisplayException("Unable to determine current user"); - - user.CurrentSubscription = subscription; - user.SubscriptionDuration = duration; + user.CurrentSubscription = null; + UserRepository.Update(user); + } + public async Task SetActiveSubscription(User u, Subscription subscription) + { + var user = await EnsureData(u); + user.SubscriptionSince = DateTime.UtcNow; + user.SubscriptionExpires = DateTime.UtcNow.AddDays(subscription.Duration); + user.CurrentSubscription = subscription; UserRepository.Update(user); - - await OneTimeJwtService.Revoke(code); } - - public async Task Cancel() + + public async Task GetDefaultLimits() { - if (await GetCurrent() != null) + var defaultSubscriptionJson = "[]"; + var path = PathBuilder.File("storage", "configs", "default_subscription.json"); + + if (File.Exists(path)) { - var user = await GetCurrentUser(); - - user.CurrentSubscription = null; - - UserRepository.Update(user); + defaultSubscriptionJson = + await File.ReadAllTextAsync(path); } - } - public async Task GetLimit(string identifier) // Cache, optimize sql code + return JsonConvert.DeserializeObject(defaultSubscriptionJson) + ?? Array.Empty(); + } + public async Task GetLimit(User u, string identifier) { - var subscription = await GetCurrent(); + var subscription = await GetActiveSubscription(u); var defaultLimits = await GetDefaultLimits(); - if (subscription == null) + if (subscription != null) // User has a active subscriptions { - // If the default subscription limit with identifier is found, return it. if not, return empty - return defaultLimits.FirstOrDefault(x => x.Identifier == identifier) ?? new() - { - Identifier = identifier, - Amount = 0 - }; - } + var subscriptionLimits = await GetLimits(subscription); - var subscriptionLimits = - JsonConvert.DeserializeObject(subscription.LimitsJson) - ?? Array.Empty(); + var subscriptionLimit = subscriptionLimits + .FirstOrDefault(x => x.Identifier == identifier); - var foundLimit = subscriptionLimits.FirstOrDefault(x => x.Identifier == identifier); - - if (foundLimit != null) - return foundLimit; + if (subscriptionLimit != null) // Found subscription limit for the user's subscription + return subscriptionLimit; + } // If were are here, the user's subscription has no limit for this identifier, so we fallback to default - // If the default subscription limit with identifier is found, return it. if not, return empty - return defaultLimits.FirstOrDefault(x => x.Identifier == identifier) ?? new() + var defaultSubscriptionLimit = defaultLimits + .FirstOrDefault(x => x.Identifier == identifier); + + if (defaultSubscriptionLimit != null) + return defaultSubscriptionLimit; // Default subscription limit found + + return new() // No default subscription limit found { Identifier = identifier, Amount = 0 }; } - private async Task GetCurrentUser() + private Task EnsureData(User u) { - var user = await IdentityService.Get(); - - if (user == null) - return null; - - var userWithData = UserRepository + var user = UserRepository .Get() .Include(x => x.CurrentSubscription) - .First(x => x.Id == user.Id); + .First(x => x.Id == u.Id); - return userWithData; - } - - private async Task GetDefaultLimits() // Add cache and reload option - { - var defaultSubscriptionJson = "[]"; - - if (File.Exists(PathBuilder.File("storage", "configs", "default_subscription.json"))) - { - defaultSubscriptionJson = - await File.ReadAllTextAsync(PathBuilder.File("storage", "configs", "default_subscription.json")); - } - - return JsonConvert.DeserializeObject(defaultSubscriptionJson) ?? Array.Empty(); + return Task.FromResult(user); } } \ No newline at end of file diff --git a/Moonlight/App/Services/SupportChat/SupportChatAdminService.cs b/Moonlight/App/Services/SupportChat/SupportChatAdminService.cs index d9acc9ef..20c67290 100644 --- a/Moonlight/App/Services/SupportChat/SupportChatAdminService.cs +++ b/Moonlight/App/Services/SupportChat/SupportChatAdminService.cs @@ -34,7 +34,7 @@ public class SupportChatAdminService : IDisposable public async Task Start(User recipient) { - User = await IdentityService.Get(); + User = IdentityService.User; Recipient = recipient; if (User != null) diff --git a/Moonlight/App/Services/SupportChat/SupportChatClientService.cs b/Moonlight/App/Services/SupportChat/SupportChatClientService.cs index 8c079cfc..c4f695ce 100644 --- a/Moonlight/App/Services/SupportChat/SupportChatClientService.cs +++ b/Moonlight/App/Services/SupportChat/SupportChatClientService.cs @@ -33,7 +33,7 @@ public class SupportChatClientService : IDisposable public async Task Start() { - User = await IdentityService.Get(); + User = IdentityService.User; if (User != null) { diff --git a/Moonlight/App/Services/Tickets/TicketAdminService.cs b/Moonlight/App/Services/Tickets/TicketAdminService.cs new file mode 100644 index 00000000..aefbf0b9 --- /dev/null +++ b/Moonlight/App/Services/Tickets/TicketAdminService.cs @@ -0,0 +1,67 @@ +using Microsoft.AspNetCore.Components.Forms; +using Moonlight.App.Database.Entities; +using Moonlight.App.Models.Misc; +using Moonlight.App.Services.Files; +using Moonlight.App.Services.Sessions; + +namespace Moonlight.App.Services.Tickets; + +public class TicketAdminService +{ + private readonly TicketServerService TicketServerService; + private readonly IdentityService IdentityService; + private readonly BucketService BucketService; + + public Ticket? Ticket { get; set; } + + public TicketAdminService( + TicketServerService ticketServerService, + IdentityService identityService, + BucketService bucketService) + { + TicketServerService = ticketServerService; + IdentityService = identityService; + BucketService = bucketService; + } + + public async Task Send(string content, IBrowserFile? file = null) + { + string? attachment = null; + + if (file != null) + { + attachment = await BucketService.StoreFile( + "tickets", + file.OpenReadStream(1024 * 1024 * 5), + file.Name); + } + + return await TicketServerService.SendMessage( + Ticket!, + IdentityService.User, + content, + attachment, + true + ); + } + + public async Task UpdateStatus(TicketStatus status) + { + await TicketServerService.UpdateStatus(Ticket!, status); + } + + public async Task UpdatePriority(TicketPriority priority) + { + await TicketServerService.UpdatePriority(Ticket!, priority); + } + + public async Task GetMessages() + { + return await TicketServerService.GetMessages(Ticket!); + } + + public async Task SetClaim(User? user) + { + await TicketServerService.SetClaim(Ticket!, user); + } +} \ No newline at end of file diff --git a/Moonlight/App/Services/Tickets/TicketClientService.cs b/Moonlight/App/Services/Tickets/TicketClientService.cs new file mode 100644 index 00000000..dee6e7fe --- /dev/null +++ b/Moonlight/App/Services/Tickets/TicketClientService.cs @@ -0,0 +1,63 @@ +using Microsoft.AspNetCore.Components.Forms; +using Moonlight.App.Database.Entities; +using Moonlight.App.Models.Misc; +using Moonlight.App.Services.Files; +using Moonlight.App.Services.Sessions; + +namespace Moonlight.App.Services.Tickets; + +public class TicketClientService +{ + private readonly TicketServerService TicketServerService; + private readonly IdentityService IdentityService; + private readonly BucketService BucketService; + + public Ticket? Ticket { get; set; } + + public TicketClientService( + TicketServerService ticketServerService, + IdentityService identityService, + BucketService bucketService) + { + TicketServerService = ticketServerService; + IdentityService = identityService; + BucketService = bucketService; + } + + public async Task Create(string issueTopic, string issueDescription, string issueTries, TicketSubject subject, int subjectId) + { + return await TicketServerService.Create( + IdentityService.User, + issueTopic, + issueDescription, + issueTries, + subject, + subjectId + ); + } + + public async Task Send(string content, IBrowserFile? file = null) + { + string? attachment = null; + + if (file != null) + { + attachment = await BucketService.StoreFile( + "tickets", + file.OpenReadStream(1024 * 1024 * 5), + file.Name); + } + + return await TicketServerService.SendMessage( + Ticket!, + IdentityService.User, + content, + attachment + ); + } + + public async Task GetMessages() + { + return await TicketServerService.GetMessages(Ticket!); + } +} \ No newline at end of file diff --git a/Moonlight/App/Services/Tickets/TicketServerService.cs b/Moonlight/App/Services/Tickets/TicketServerService.cs new file mode 100644 index 00000000..a5e0f930 --- /dev/null +++ b/Moonlight/App/Services/Tickets/TicketServerService.cs @@ -0,0 +1,181 @@ +using Microsoft.EntityFrameworkCore; +using Moonlight.App.Database.Entities; +using Moonlight.App.Events; +using Moonlight.App.Models.Misc; +using Moonlight.App.Repositories; + +namespace Moonlight.App.Services.Tickets; + +public class TicketServerService +{ + private readonly IServiceScopeFactory ServiceScopeFactory; + private readonly EventSystem Event; + private readonly ConfigService ConfigService; + + public TicketServerService( + IServiceScopeFactory serviceScopeFactory, + EventSystem eventSystem, + ConfigService configService) + { + ServiceScopeFactory = serviceScopeFactory; + Event = eventSystem; + ConfigService = configService; + } + + public async Task Create(User creator, string issueTopic, string issueDescription, string issueTries, TicketSubject subject, int subjectId) + { + using var scope = ServiceScopeFactory.CreateScope(); + var ticketRepo = scope.ServiceProvider.GetRequiredService>(); + var userRepo = scope.ServiceProvider.GetRequiredService>(); + + var creatorUser = userRepo + .Get() + .First(x => x.Id == creator.Id); + + var ticket = ticketRepo.Add(new() + { + Priority = TicketPriority.Low, + Status = TicketStatus.Open, + AssignedTo = null, + IssueTopic = issueTopic, + IssueDescription = issueDescription, + IssueTries = issueTries, + Subject = subject, + SubjectId = subjectId, + CreatedBy = creatorUser + }); + + await Event.Emit("tickets.new", ticket); + + // Do automatic stuff here + await SendSystemMessage(ticket, ConfigService.Get().Moonlight.Tickets.WelcomeMessage); + + if (ticket.Subject != TicketSubject.Other) + { + await SendMessage(ticket, creatorUser, $"Subject :\n\n{ticket.Subject}: {ticket.SubjectId}"); + } + + //TODO: Check for opening times + + return ticket; + } + public async Task SendSystemMessage(Ticket t, string content, string? attachmentUrl = null) + { + using var scope = ServiceScopeFactory.CreateScope(); + var ticketRepo = scope.ServiceProvider.GetRequiredService>(); + + var ticket = ticketRepo.Get().First(x => x.Id == t.Id); + + var message = new TicketMessage() + { + Content = content, + Sender = null, + AttachmentUrl = attachmentUrl, + IsSystemMessage = true + }; + + ticket.Messages.Add(message); + ticketRepo.Update(ticket); + + await Event.Emit("tickets.message", message); + await Event.Emit($"tickets.{ticket.Id}.message", message); + } + public async Task UpdatePriority(Ticket t, TicketPriority priority) + { + if(t.Priority == priority) + return; + + using var scope = ServiceScopeFactory.CreateScope(); + var ticketRepo = scope.ServiceProvider.GetRequiredService>(); + + var ticket = ticketRepo.Get().First(x => x.Id == t.Id); + + ticket.Priority = priority; + + ticketRepo.Update(ticket); + + await Event.Emit("tickets.status", ticket); + await Event.Emit($"tickets.{ticket.Id}.status", ticket); + + await SendSystemMessage(ticket, $"The ticket priority has been changed to: {priority}"); + } + public async Task UpdateStatus(Ticket t, TicketStatus status) + { + if(t.Status == status) + return; + + using var scope = ServiceScopeFactory.CreateScope(); + var ticketRepo = scope.ServiceProvider.GetRequiredService>(); + + var ticket = ticketRepo.Get().First(x => x.Id == t.Id); + + ticket.Status = status; + + ticketRepo.Update(ticket); + + await Event.Emit("tickets.status", ticket); + await Event.Emit($"tickets.{ticket.Id}.status", ticket); + + await SendSystemMessage(ticket, $"The ticket status has been changed to: {status}"); + } + public async Task SendMessage(Ticket t, User sender, string content, string? attachmentUrl = null, bool isSupportMessage = false) + { + using var scope = ServiceScopeFactory.CreateScope(); + var ticketRepo = scope.ServiceProvider.GetRequiredService>(); + var userRepo = scope.ServiceProvider.GetRequiredService>(); + + var ticket = ticketRepo.Get().First(x => x.Id == t.Id); + var user = userRepo.Get().First(x => x.Id == sender.Id); + + var message = new TicketMessage() + { + Content = content, + Sender = user, + AttachmentUrl = attachmentUrl, + IsSupportMessage = isSupportMessage + }; + + ticket.Messages.Add(message); + ticketRepo.Update(ticket); + + await Event.Emit("tickets.message", message); + await Event.Emit($"tickets.{ticket.Id}.message", message); + + return message; + } + + public Task GetMessages(Ticket ticket) + { + using var scope = ServiceScopeFactory.CreateScope(); + var ticketRepo = scope.ServiceProvider.GetRequiredService>(); + + var tickets = ticketRepo + .Get() + .Include(x => x.CreatedBy) + .Include(x => x.Messages) + .ThenInclude(x => x.Sender) + .First(x => x.Id == ticket.Id); + + return Task.FromResult(tickets.Messages.ToArray()); + } + + public async Task SetClaim(Ticket t, User? u = null) + { + using var scope = ServiceScopeFactory.CreateScope(); + var ticketRepo = scope.ServiceProvider.GetRequiredService>(); + var userRepo = scope.ServiceProvider.GetRequiredService>(); + + var ticket = ticketRepo.Get().Include(x => x.AssignedTo).First(x => x.Id == t.Id); + var user = u == null ? u : userRepo.Get().First(x => x.Id == u.Id); + + ticket.AssignedTo = user; + + ticketRepo.Update(ticket); + + await Event.Emit("tickets.status", ticket); + await Event.Emit($"tickets.{ticket.Id}.status", ticket); + + var claimName = user == null ? "None" : user.FirstName + " " + user.LastName; + await SendSystemMessage(ticket, $"Ticked claim has been set to {claimName}"); + } +} \ No newline at end of file diff --git a/Moonlight/App/Services/TotpService.cs b/Moonlight/App/Services/TotpService.cs index ef46b9f0..2d624d92 100644 --- a/Moonlight/App/Services/TotpService.cs +++ b/Moonlight/App/Services/TotpService.cs @@ -25,32 +25,30 @@ public class TotpService return Task.FromResult(codeserver == code); } - public async Task GetEnabled() + public Task GetEnabled() { - var user = await IdentityService.Get(); - - return user!.TotpEnabled; + return Task.FromResult(IdentityService.User.TotpEnabled); } - public async Task GetSecret() + public Task GetSecret() { - var user = await IdentityService.Get(); - - return user!.TotpSecret; + return Task.FromResult(IdentityService.User.TotpSecret); } - public async Task GenerateSecret() + public Task GenerateSecret() { - var user = (await IdentityService.Get())!; + var user = IdentityService.User; user.TotpSecret = Base32Encoding.ToString(KeyGeneration.GenerateRandomKey(20));; UserRepository.Update(user); + + return Task.CompletedTask; } public async Task Enable(string code) { - var user = (await IdentityService.Get())!; + var user = IdentityService.User; if (!await Verify(user.TotpSecret, code)) { @@ -61,9 +59,9 @@ public class TotpService UserRepository.Update(user); } - public async Task Disable() + public Task Disable() { - var user = (await IdentityService.Get())!; + var user = IdentityService.User; user.TotpEnabled = false; user.TotpSecret = ""; @@ -71,5 +69,7 @@ public class TotpService UserRepository.Update(user); //TODO: AuditLog + + return Task.CompletedTask; } } \ No newline at end of file diff --git a/Moonlight/App/Services/UserService.cs b/Moonlight/App/Services/UserService.cs index 2a611cb9..96e5f766 100644 --- a/Moonlight/App/Services/UserService.cs +++ b/Moonlight/App/Services/UserService.cs @@ -5,6 +5,7 @@ using Moonlight.App.Exceptions; using Moonlight.App.Helpers; using Moonlight.App.Models.Misc; using Moonlight.App.Repositories; +using Moonlight.App.Services.Background; using Moonlight.App.Services.Mail; using Moonlight.App.Services.Sessions; @@ -19,6 +20,7 @@ public class UserService private readonly IpLocateService IpLocateService; private readonly DateTimeService DateTimeService; private readonly ConfigService ConfigService; + private readonly TempMailService TempMailService; private readonly string JwtSecret; @@ -29,7 +31,8 @@ public class UserService MailService mailService, IdentityService identityService, IpLocateService ipLocateService, - DateTimeService dateTimeService) + DateTimeService dateTimeService, + TempMailService tempMailService) { UserRepository = userRepository; TotpService = totpService; @@ -38,6 +41,7 @@ public class UserService IdentityService = identityService; IpLocateService = ipLocateService; DateTimeService = dateTimeService; + TempMailService = tempMailService; JwtSecret = configService .Get() @@ -48,6 +52,12 @@ public class UserService { if (ConfigService.Get().Moonlight.Auth.DenyRegister) throw new DisplayException("This operation was disabled"); + + if (await TempMailService.IsTempMail(email)) + { + Logger.Warn($"A user tried to use a blacklisted domain to register. Email: '{email}'", "security"); + throw new DisplayException("This email is blacklisted"); + } // Check if the email is already taken var emailTaken = UserRepository.Get().FirstOrDefault(x => x.Email == email) != null; @@ -56,17 +66,15 @@ public class UserService { throw new DisplayException("The email is already in use"); } - - //TODO: Validation // Add user var user = UserRepository.Add(new() { Address = "", - Admin = false, + Admin = !UserRepository.Get().Any(), City = "", Country = "", - Email = email, + Email = email.ToLower(), Password = BCrypt.Net.BCrypt.HashPassword(password), FirstName = firstname, LastName = lastname, @@ -78,8 +86,8 @@ public class UserService TotpSecret = "", UpdatedAt = DateTimeService.GetCurrent(), TokenValidTime = DateTimeService.GetCurrent().AddDays(-5), - LastIp = IdentityService.GetIp(), - RegisterIp = IdentityService.GetIp() + LastIp = IdentityService.Ip, + RegisterIp = IdentityService.Ip }); await MailService.SendMail(user!, "register", values => {}); @@ -167,8 +175,8 @@ public class UserService await MailService.SendMail(user!, "passwordChange", values => { - values.Add("Ip", IdentityService.GetIp()); - values.Add("Device", IdentityService.GetDevice()); + values.Add("Ip", IdentityService.Ip); + values.Add("Device", IdentityService.Device); values.Add("Location", location); }); @@ -205,8 +213,8 @@ public class UserService { await MailService.SendMail(user!, "login", values => { - values.Add("Ip", IdentityService.GetIp()); - values.Add("Device", IdentityService.GetDevice()); + values.Add("Ip", IdentityService.Ip); + values.Add("Device", IdentityService.Device); values.Add("Location", location); }); } @@ -242,8 +250,8 @@ public class UserService await MailService.SendMail(user, "passwordReset", values => { - values.Add("Ip", IdentityService.GetIp()); - values.Add("Device", IdentityService.GetDevice()); + values.Add("Ip", IdentityService.Ip); + values.Add("Device", IdentityService.Device); values.Add("Location", location); values.Add("Password", newPassword); }); diff --git a/Moonlight/BlazorApp.razor b/Moonlight/BlazorApp.razor index 492dcb0c..5119c0fe 100644 --- a/Moonlight/BlazorApp.razor +++ b/Moonlight/BlazorApp.razor @@ -2,12 +2,19 @@ - + + + Not found -

Sorry, there's nothing at this address.

+
-
\ No newline at end of file + + +@code +{ + +} \ No newline at end of file diff --git a/Moonlight/Moonlight.csproj b/Moonlight/Moonlight.csproj index cc520db6..1b6e5f4a 100644 --- a/Moonlight/Moonlight.csproj +++ b/Moonlight/Moonlight.csproj @@ -40,12 +40,12 @@ - + - + @@ -53,6 +53,7 @@ + @@ -76,12 +77,20 @@ <_ContentIncludedByDefault Remove="Shared\Views\Admin\Servers\Cleanup\Exceptions\Edit.razor" /> <_ContentIncludedByDefault Remove="Shared\Components\News\NewsEditor.razor" /> <_ContentIncludedByDefault Remove="Shared\News\Edit.razor" /> + <_ContentIncludedByDefault Remove="Shared\Views\Admin\AaPanels\Index.razor" /> + <_ContentIncludedByDefault Remove="Shared\Views\Admin\Databases\Index.razor" /> + <_ContentIncludedByDefault Remove="Shared\Components\StateLogic\OnlyAdmin.razor" /> + <_ContentIncludedByDefault Remove="Shared\Components\AuditLogEntrys\AuditLogEntryChangePassword.razor" /> + <_ContentIncludedByDefault Remove="Shared\Components\AuditLogEntrys\AuditLogEntryChangePowerState.razor" /> + <_ContentIncludedByDefault Remove="Shared\Components\AuditLogEntrys\AuditLogEntryLogin.razor" /> + <_ContentIncludedByDefault Remove="Shared\Components\AuditLogEntrys\AuditLogEntryRegister.razor" /> + @@ -103,12 +112,4 @@ - - - true - PreserveNewest - PreserveNewest - - - diff --git a/Moonlight/Program.cs b/Moonlight/Program.cs index fb850669..541028d8 100644 --- a/Moonlight/Program.cs +++ b/Moonlight/Program.cs @@ -1,6 +1,5 @@ using BlazorDownloadFile; using BlazorTable; -using CurrieTechnologies.Razor.SweetAlert2; using HealthChecks.UI.Client; using Moonlight.App.ApiClients.CloudPanel; using Moonlight.App.ApiClients.Daemon; @@ -16,7 +15,6 @@ using Moonlight.App.Helpers.Wings; using Moonlight.App.LogMigrator; using Moonlight.App.Repositories; using Moonlight.App.Repositories.Domains; -using Moonlight.App.Repositories.LogEntries; using Moonlight.App.Repositories.Servers; using Moonlight.App.Services; using Moonlight.App.Services.Addon; @@ -27,12 +25,16 @@ using Moonlight.App.Services.Interop; using Moonlight.App.Services.Mail; using Moonlight.App.Services.Minecraft; using Moonlight.App.Services.Notifications; +using Moonlight.App.Services.Plugins; using Moonlight.App.Services.Sessions; using Moonlight.App.Services.Statistics; using Moonlight.App.Services.SupportChat; +using Moonlight.App.Services.Tickets; using Sentry; using Serilog; using Serilog.Events; +using Stripe; +using SubscriptionService = Moonlight.App.Services.SubscriptionService; namespace Moonlight { @@ -103,8 +105,6 @@ namespace Moonlight } } - Logger.Info($"Working dir: {Directory.GetCurrentDirectory()}"); - Logger.Info("Running pre-init tasks"); var databaseCheckupService = new DatabaseCheckupService(configService); @@ -112,6 +112,9 @@ namespace Moonlight var builder = WebApplication.CreateBuilder(args); + var pluginService = new PluginService(); + await pluginService.BuildServices(builder.Services); + // Switch to logging.net injection // TODO: Enable in production builder.Logging.ClearProviders(); @@ -168,9 +171,6 @@ namespace Moonlight builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); builder.Services.AddScoped(typeof(Repository<>)); // Services @@ -210,16 +210,17 @@ namespace Moonlight builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); - + builder.Services.AddScoped(); builder.Services.AddScoped(); - builder.Services.AddScoped(); + builder.Services.AddScoped(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddScoped(); + builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddSingleton(); - - // Loggers builder.Services.AddScoped(); - builder.Services.AddSingleton(); // Support chat builder.Services.AddSingleton(); @@ -246,6 +247,9 @@ namespace Moonlight builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(); + builder.Services.AddSingleton(pluginService); // Other builder.Services.AddSingleton(); @@ -255,6 +259,10 @@ namespace Moonlight builder.Services.AddBlazorContextMenu(); builder.Services.AddBlazorDownloadFile(); + StripeConfiguration.ApiKey = configService + .Get() + .Moonlight.Stripe.ApiKey; + var app = builder.Build(); // Configure the HTTP request pipeline. @@ -291,7 +299,8 @@ namespace Moonlight _ = app.Services.GetRequiredService(); _ = app.Services.GetRequiredService(); _ = app.Services.GetRequiredService(); - + _ = app.Services.GetRequiredService(); + _ = app.Services.GetRequiredService(); _ = app.Services.GetRequiredService(); // Discord bot service diff --git a/Moonlight/Properties/launchSettings.json b/Moonlight/Properties/launchSettings.json index be9982ad..9d662090 100644 --- a/Moonlight/Properties/launchSettings.json +++ b/Moonlight/Properties/launchSettings.json @@ -1,6 +1,8 @@ { - "profiles": { - "Moonlight": { + "profiles": + { + "Default": + { "commandName": "Project", "launchBrowser": true, "environmentVariables": { @@ -10,41 +12,41 @@ "applicationUrl": "http://moonlight.testy:5118;https://localhost:7118;http://localhost:5118", "dotnetRunMessages": true }, - "Sql Debug": { + "Live DB": + { "commandName": "Project", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development", - "ML_SQL_DEBUG": "true", - "ML_DEBUG": "true" + "ML_DEBUG": "true", + "ML_CONFIG_PATH": "storage\\configs\\live_config.json" }, "applicationUrl": "http://moonlight.testy:5118;https://localhost:7118;http://localhost:5118", "dotnetRunMessages": true }, - "Discord Bot": { + "Dev DB 1": + { "commandName": "Project", - "launchBrowser": false, - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", - "ML_SQL_DEBUG": "true", - "ML_DEBUG": "true" - }, - "applicationUrl": "http://moonlight.testy:5118;https://localhost:7118;http://localhost:5118", - "dotnetRunMessages": true - }, - "Watch": { - "commandName": "Executable", - "executablePath": "dotnet", - "workingDirectory": "$(ProjectDir)", - "hotReloadEnabled": true, - "hotReloadProfile": "aspnetcore", - "commandLineArgs": "watch run", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development", - "ML_DEBUG": "true" + "ML_DEBUG": "true", + "ML_CONFIG_PATH": "storage\\configs\\dev_1_config.json" }, - "applicationUrl": "http://moonlight.testy:5118;https://localhost:7118;http://localhost:5118" + "applicationUrl": "http://moonlight.testy:5118;https://localhost:7118;http://localhost:5118", + "dotnetRunMessages": true + }, + "Dev DB 2": + { + "commandName": "Project", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development", + "ML_DEBUG": "true", + "ML_CONFIG_PATH": "storage\\configs\\dev_2_config.json" + }, + "applicationUrl": "http://moonlight.testy:5118;https://localhost:7118;http://localhost:5118", + "dotnetRunMessages": true } } } \ No newline at end of file diff --git a/Moonlight/README.md b/Moonlight/README.md index 264af4c5..2ed660f2 100644 --- a/Moonlight/README.md +++ b/Moonlight/README.md @@ -1,13 +1,8 @@ -### Some explanations - +# Some explanations defaultstorage: -This directory is for the default assets of a moonlight instance, e.g. lang files, images etc +This directory is for the default assets of a Moonlight instance, e.g., Lang files, images, etc. -storage: +Storage: -This directory is empty in fresh moonlight instances and will be populated with example config upon first run. -Before using moonlight this config file has to be modified. -Also resources are going to be copied from the default storage to this storage. -To access files in this storage we recommend to use the PathBuilder functions to ensure cross platform compatibility. -The storage directory should be mounted to a specific path when using docker container so when the container is replaced/rebuild your storage will not be modified \ No newline at end of file +This directory is empty in fresh Moonlight instances and will be populated with an example configuration upon first run. Before using Moonlight, this config file has to be modified. Also, resources are going to be copied from the default storage to this storage. To access files in this storage, we recommend using the Path Builder functions to ensure cross-platform compatibility. The storage directory should be mounted to a specific path when using a Docker container, so when the container is replaced or rebuilt,  your storage will not be modified. diff --git a/Moonlight/Shared/Components/Alerts/NoPermissionAlert.razor b/Moonlight/Shared/Components/Alerts/NoPermissionAlert.razor new file mode 100644 index 00000000..1ae6e3c5 --- /dev/null +++ b/Moonlight/Shared/Components/Alerts/NoPermissionAlert.razor @@ -0,0 +1,13 @@ +
+
+
+ Warning +
+ + You have no permission to access this resource + +

+ You have no permission to access this resource. This attempt has been logged ;) +

+
+
\ No newline at end of file diff --git a/Moonlight/Shared/Components/Alerts/NotFoundAlert.razor b/Moonlight/Shared/Components/Alerts/NotFoundAlert.razor index f97c7b45..ae5070a9 100644 --- a/Moonlight/Shared/Components/Alerts/NotFoundAlert.razor +++ b/Moonlight/Shared/Components/Alerts/NotFoundAlert.razor @@ -14,7 +14,7 @@ The resource was deleted
  • - You have to permission to access this resource + You have no permission to access this resource
  • You may have entered invalid data diff --git a/Moonlight/Shared/Components/Alerts/SetupCompletedAlert.razor b/Moonlight/Shared/Components/Alerts/SetupCompletedAlert.razor deleted file mode 100644 index c1759f9b..00000000 --- a/Moonlight/Shared/Components/Alerts/SetupCompletedAlert.razor +++ /dev/null @@ -1,10 +0,0 @@ -@using Moonlight.App.Services - -
    -
    -

    Setup complete

    -
    - It looks like this moonlight instance is ready to go -
    -
    -
    \ No newline at end of file diff --git a/Moonlight/Shared/Components/AuditLogEntrys/AuditLogEntryChangePassword.razor b/Moonlight/Shared/Components/AuditLogEntrys/AuditLogEntryChangePassword.razor deleted file mode 100644 index f9ee3ac8..00000000 --- a/Moonlight/Shared/Components/AuditLogEntrys/AuditLogEntryChangePassword.razor +++ /dev/null @@ -1,58 +0,0 @@ -@using Moonlight.App.Database.Entities.LogsEntries -@using Moonlight.App.Helpers -@using Moonlight.App.Repositories -@using Newtonsoft.Json -@using Moonlight.App.Database.Entities -@using Moonlight.App.Models.Log - -@inject UserRepository UserRepository - -
    -
    -
    -
    - -
    -
    -
    -
    -
    - @if (User == null) - { - Password change for @(Data[0].Value) - } - else - { - Password change for @(User.Email) - } -
    -
    -
    @(Formatter.FormatDate(Entry.CreatedAt)), @(Entry.System ? "System" : Entry.Ip)
    -
    -
    -
    -
    - -@code -{ - [Parameter] - public AuditLogEntry Entry { get; set; } - - private User? User; - private LogData[] Data; - - protected override void OnInitialized() - { - Data = JsonConvert.DeserializeObject(Entry.JsonData)!; - } - - protected override async Task OnAfterRenderAsync(bool firstRender) - { - if (firstRender) - { - User = UserRepository.Get().FirstOrDefault(x => x.Email == Data[0].Value); - - await InvokeAsync(StateHasChanged); - } - } -} \ No newline at end of file diff --git a/Moonlight/Shared/Components/AuditLogEntrys/AuditLogEntryChangePowerState.razor b/Moonlight/Shared/Components/AuditLogEntrys/AuditLogEntryChangePowerState.razor deleted file mode 100644 index 9dcafe02..00000000 --- a/Moonlight/Shared/Components/AuditLogEntrys/AuditLogEntryChangePowerState.razor +++ /dev/null @@ -1,58 +0,0 @@ -@using Moonlight.App.Database.Entities.LogsEntries -@using Moonlight.App.Helpers -@using Newtonsoft.Json -@using Moonlight.App.Database.Entities -@using Moonlight.App.Models.Log -@using Moonlight.App.Repositories.Servers - -@inject ServerRepository ServerRepository - -
    -
    -
    -
    - -
    -
    -
    -
    -
    - @if (Server == null) - { - Change power state for @(Data[0].Value) to @(Data[1].Value) - } - else - { - Change power state for @(Server.Name) to @(Data[1].Value) - } -
    -
    -
    @(Formatter.FormatDate(Entry.CreatedAt)), @(Entry.System ? "System" : Entry.Ip)
    -
    -
    -
    -
    - -@code -{ - [Parameter] - public AuditLogEntry Entry { get; set; } - - private Server? Server; - private LogData[] Data; - - protected override void OnInitialized() - { - Data = JsonConvert.DeserializeObject(Entry.JsonData)!; - } - - protected override async Task OnAfterRenderAsync(bool firstRender) - { - if (firstRender) - { - Server = ServerRepository.Get().FirstOrDefault(x => x.Uuid == Guid.Parse(Data[0].Value)); - - await InvokeAsync(StateHasChanged); - } - } -} \ No newline at end of file diff --git a/Moonlight/Shared/Components/AuditLogEntrys/AuditLogEntryLogin.razor b/Moonlight/Shared/Components/AuditLogEntrys/AuditLogEntryLogin.razor deleted file mode 100644 index 810a3568..00000000 --- a/Moonlight/Shared/Components/AuditLogEntrys/AuditLogEntryLogin.razor +++ /dev/null @@ -1,58 +0,0 @@ -@using Moonlight.App.Database.Entities.LogsEntries -@using Moonlight.App.Helpers -@using Moonlight.App.Repositories -@using Newtonsoft.Json -@using Moonlight.App.Database.Entities -@using Moonlight.App.Models.Log - -@inject UserRepository UserRepository - -
    -
    -
    -
    - -
    -
    -
    -
    -
    - @if (User == null) - { - New login for @(Data[0].Value) - } - else - { - New login for @(User.Email) - } -
    -
    -
    @(Formatter.FormatDate(Entry.CreatedAt)), @(Entry.System ? "System" : Entry.Ip)
    -
    -
    -
    -
    - -@code -{ - [Parameter] - public AuditLogEntry Entry { get; set; } - - private User? User; - private LogData[] Data; - - protected override void OnInitialized() - { - Data = JsonConvert.DeserializeObject(Entry.JsonData)!; - } - - protected override async Task OnAfterRenderAsync(bool firstRender) - { - if (firstRender) - { - User = UserRepository.Get().FirstOrDefault(x => x.Email == Data[0].Value); - - await InvokeAsync(StateHasChanged); - } - } -} \ No newline at end of file diff --git a/Moonlight/Shared/Components/AuditLogEntrys/AuditLogEntryRegister.razor b/Moonlight/Shared/Components/AuditLogEntrys/AuditLogEntryRegister.razor deleted file mode 100644 index 846f0d86..00000000 --- a/Moonlight/Shared/Components/AuditLogEntrys/AuditLogEntryRegister.razor +++ /dev/null @@ -1,58 +0,0 @@ -@using Moonlight.App.Database.Entities.LogsEntries -@using Moonlight.App.Helpers -@using Moonlight.App.Repositories -@using Newtonsoft.Json -@using Moonlight.App.Database.Entities -@using Moonlight.App.Models.Log - -@inject UserRepository UserRepository - -
    -
    -
    -
    - -
    -
    -
    -
    -
    - @if (User == null) - { - Register for @(Data[0].Value) - } - else - { - Register for @(User.Email) - } -
    -
    -
    @(Formatter.FormatDate(Entry.CreatedAt)), @(Entry.System ? "System" : Entry.Ip)
    -
    -
    -
    -
    - -@code -{ - [Parameter] - public AuditLogEntry Entry { get; set; } - - private User? User; - private LogData[] Data; - - protected override void OnInitialized() - { - Data = JsonConvert.DeserializeObject(Entry.JsonData)!; - } - - protected override async Task OnAfterRenderAsync(bool firstRender) - { - if (firstRender) - { - User = UserRepository.Get().FirstOrDefault(x => x.Email == Data[0].Value); - - await InvokeAsync(StateHasChanged); - } - } -} \ No newline at end of file diff --git a/Moonlight/Shared/Components/Auth/PasswordChangeView.razor b/Moonlight/Shared/Components/Auth/PasswordChangeView.razor index f57d1aa7..38e0e866 100644 --- a/Moonlight/Shared/Components/Auth/PasswordChangeView.razor +++ b/Moonlight/Shared/Components/Auth/PasswordChangeView.razor @@ -49,9 +49,10 @@ private PasswordModel Password = new(); private User User; - private async Task Load(LazyLoader loader) + private Task Load(LazyLoader loader) { - User = await IdentityService.Get(); + User = IdentityService.User; + return Task.CompletedTask; } private async Task DoChange() diff --git a/Moonlight/Shared/Components/Auth/UserDataSetView.razor b/Moonlight/Shared/Components/Auth/UserDataSetView.razor index d1191ec1..f8c64e8a 100644 --- a/Moonlight/Shared/Components/Auth/UserDataSetView.razor +++ b/Moonlight/Shared/Components/Auth/UserDataSetView.razor @@ -50,9 +50,10 @@ private User User; private NameModel Name = new (); - private async Task Load(LazyLoader loader) + private Task Load(LazyLoader loader) { - User = await IdentityService.Get(); + User = IdentityService.User; + return Task.CompletedTask; } private async Task SetName() diff --git a/Moonlight/Shared/Components/ErrorBoundaries/PageErrorBoundary.razor b/Moonlight/Shared/Components/ErrorBoundaries/PageErrorBoundary.razor index 4cc364fa..587e43f1 100644 --- a/Moonlight/Shared/Components/ErrorBoundaries/PageErrorBoundary.razor +++ b/Moonlight/Shared/Components/ErrorBoundaries/PageErrorBoundary.razor @@ -57,7 +57,7 @@ else { receivedExceptions.Add(exception); - var user = await IdentityService.Get(); + var user = IdentityService.User; var id = user == null ? -1 : user.Id; Logger.Error($"[{id}] An unhanded exception occured:"); diff --git a/Moonlight/Shared/Components/ErrorBoundaries/SoftErrorBoundary.razor b/Moonlight/Shared/Components/ErrorBoundaries/SoftErrorBoundary.razor index 7291544b..6f049421 100644 --- a/Moonlight/Shared/Components/ErrorBoundaries/SoftErrorBoundary.razor +++ b/Moonlight/Shared/Components/ErrorBoundaries/SoftErrorBoundary.razor @@ -6,6 +6,7 @@ @using Moonlight.App.ApiClients.Modrinth @using Moonlight.App.ApiClients.Wings @using Moonlight.App.Helpers +@using Stripe @inherits ErrorBoundaryBase @inject AlertService AlertService @@ -105,6 +106,13 @@ else { await AlertService.Error(SmartTranslateService.Translate("This function is not implemented")); } + else if (exception is StripeException stripeException) + { + await AlertService.Error( + SmartTranslateService.Translate("Unknown error from stripe"), + stripeException.Message + ); + } else { Logger.Warn(exception); diff --git a/Moonlight/Shared/Components/FileManagerPartials/FileEditor.razor b/Moonlight/Shared/Components/FileManagerPartials/FileEditor.razor index 31d7eaae..4f06d10a 100644 --- a/Moonlight/Shared/Components/FileManagerPartials/FileEditor.razor +++ b/Moonlight/Shared/Components/FileManagerPartials/FileEditor.razor @@ -11,22 +11,29 @@ @implements IDisposable
    + @if (ShowHeader) + { +
    + @(Header) +
    + } +
    - + @if (!HideControls) { @@ -43,6 +50,12 @@ [Parameter] public bool HideControls { get; set; } = false; + + [Parameter] + public bool ShowHeader { get; set; } = false; + + [Parameter] + public string Header { get; set; } = "Header.changeme.txt"; // Events [Parameter] diff --git a/Moonlight/Shared/Components/FileManagerPartials/FileManager.razor b/Moonlight/Shared/Components/FileManagerPartials/FileManager.razor index 95b8b04d..ddd83588 100644 --- a/Moonlight/Shared/Components/FileManagerPartials/FileManager.razor +++ b/Moonlight/Shared/Components/FileManagerPartials/FileManager.razor @@ -17,7 +17,9 @@ Language="@EditorLanguage" OnCancel="() => Cancel()" OnSubmit="(_) => Save()" - HideControls="false"> + HideControls="false" + ShowHeader="true" + Header="@(EditingFile.Name)"> } else diff --git a/Moonlight/Shared/Components/FileManagerPartials/FileView.razor b/Moonlight/Shared/Components/FileManagerPartials/FileView.razor index b1ef5e27..af60ab46 100644 --- a/Moonlight/Shared/Components/FileManagerPartials/FileView.razor +++ b/Moonlight/Shared/Components/FileManagerPartials/FileView.razor @@ -35,28 +35,31 @@ - - - - -
    - - - - - Go up - -
    - - - -
    -
    - + @if (Access.CurrentPath != "/") + { + + + + +
    + + + + + Go up +
    -
    - - + + + +
    +
    + +
    +
    + + + } @foreach (var file in Data) { diff --git a/Moonlight/Shared/Components/Navigations/AdminNodesNavigation.razor b/Moonlight/Shared/Components/Navigations/AdminNodesNavigation.razor deleted file mode 100644 index 16db4eb5..00000000 --- a/Moonlight/Shared/Components/Navigations/AdminNodesNavigation.razor +++ /dev/null @@ -1,22 +0,0 @@ -
    -
    - -
    -
    - -@code -{ - [Parameter] - public int Index { get; set; } = 0; -} \ No newline at end of file diff --git a/Moonlight/Shared/Components/Navigations/AdminSecurityNavigation.razor b/Moonlight/Shared/Components/Navigations/AdminSecurityNavigation.razor new file mode 100644 index 00000000..483a1abd --- /dev/null +++ b/Moonlight/Shared/Components/Navigations/AdminSecurityNavigation.razor @@ -0,0 +1,42 @@ + + +@code +{ + [Parameter] + public int Index { get; set; } = 0; +} \ No newline at end of file diff --git a/Moonlight/Shared/Components/Navigations/AdminServersNavigation.razor b/Moonlight/Shared/Components/Navigations/AdminServersNavigation.razor new file mode 100644 index 00000000..d307f551 --- /dev/null +++ b/Moonlight/Shared/Components/Navigations/AdminServersNavigation.razor @@ -0,0 +1,37 @@ + + +@code +{ + [Parameter] + public int Index { get; set; } = 0; +} \ No newline at end of file diff --git a/Moonlight/Shared/Components/Navigations/AdminSystemNavigation.razor b/Moonlight/Shared/Components/Navigations/AdminSystemNavigation.razor index e7f74242..5dffb252 100644 --- a/Moonlight/Shared/Components/Navigations/AdminSystemNavigation.razor +++ b/Moonlight/Shared/Components/Navigations/AdminSystemNavigation.razor @@ -9,21 +9,6 @@ Overview
  • - - - + + diff --git a/Moonlight/Shared/Components/Navigations/ProfileNavigation.razor b/Moonlight/Shared/Components/Navigations/ProfileNavigation.razor index 76caf128..e7db20a1 100644 --- a/Moonlight/Shared/Components/Navigations/ProfileNavigation.razor +++ b/Moonlight/Shared/Components/Navigations/ProfileNavigation.razor @@ -1,5 +1,8 @@ @using Moonlight.App.Database.Entities @using Moonlight.App.Models.Misc +@using Moonlight.App.Services.Sessions + +@inject IdentityService IdentityService
    @@ -8,16 +11,16 @@
    - @(User.FirstName) @(User.LastName) + @(IdentityService.User.FirstName) @(IdentityService.User.LastName) - @if (User.Status == UserStatus.Verified) + @if (IdentityService.User.Status == UserStatus.Verified) { }
    - - @(User.Email) + + @(IdentityService.User.Email)
    @@ -51,9 +54,6 @@ @code { - [CascadingParameter] - public User User { get; set; } - [Parameter] public int Index { get; set; } = 0; } \ No newline at end of file diff --git a/Moonlight/Shared/Components/Partials/Navbar.razor b/Moonlight/Shared/Components/Partials/Navbar.razor index 58e0376a..00b57ae6 100644 --- a/Moonlight/Shared/Components/Partials/Navbar.razor +++ b/Moonlight/Shared/Components/Partials/Navbar.razor @@ -106,7 +106,7 @@ { if (firstRender) { - User = await IdentityService.Get(); + User = IdentityService.User; await InvokeAsync(StateHasChanged); } diff --git a/Moonlight/Shared/Components/Partials/PermissionEditor.razor b/Moonlight/Shared/Components/Partials/PermissionEditor.razor new file mode 100644 index 00000000..fcf8be7e --- /dev/null +++ b/Moonlight/Shared/Components/Partials/PermissionEditor.razor @@ -0,0 +1,86 @@ +@using Moonlight.App.Services.Interop +@using Moonlight.App.Services +@using Moonlight.App.Perms + +@inject ModalService ModalService +@inject SmartTranslateService SmartTranslateService + + + +@code +{ + [Parameter] + public byte[] InitialData { get; set; } = Array.Empty(); + + [Parameter] + public Func? OnSave { get; set; } + + private bool Enabled = false; + private PermissionStorage Storage; + + public async Task Launch() + { + Enabled = true; + Storage = new(InitialData); + await InvokeAsync(StateHasChanged); + + await ModalService.Show("permissionEditor"); + } + + private async Task Save() + { + OnSave?.Invoke(Storage.Data); + + await ModalService.Hide("permissionEditor"); + Enabled = false; + await InvokeAsync(StateHasChanged); + } +} \ No newline at end of file diff --git a/Moonlight/Shared/Components/Partials/RenderPermissionChecker.razor b/Moonlight/Shared/Components/Partials/RenderPermissionChecker.razor new file mode 100644 index 00000000..6ccf7137 --- /dev/null +++ b/Moonlight/Shared/Components/Partials/RenderPermissionChecker.razor @@ -0,0 +1,62 @@ +@using Moonlight.App.Services.Sessions +@using Moonlight.App.Perms +@using Moonlight.App.Helpers +@using Moonlight.Shared.Components.Alerts + +@inject IdentityService IdentityService +@inject NavigationManager NavigationManager + +@if (Allowed) +{ + @ChildContent +} +else +{ + +} + +@code +{ + [CascadingParameter(Name = "TargetPageType")] + public Type TargetPageType { get; set; } + + [Parameter] + public RenderFragment ChildContent { get; set; } + + private bool Allowed = false; + + protected override Task OnParametersSetAsync() + { + var attributes = TargetPageType.GetCustomAttributes(true); + var permAttrs = attributes + .Where(x => x.GetType() == typeof(PermissionRequired)) + .Select(x => x as PermissionRequired) + .ToArray(); + + Allowed = true; + + foreach (var permissionRequired in permAttrs) + { + var permission = Permissions.FromString(permissionRequired!.Name); + + if (permission == null) + { + Allowed = false; + break; + } + + if (!IdentityService.Permissions[permission]) + { + Allowed = false; + break; + } + } + + if (!Allowed) + { + Logger.Warn($"{IdentityService.Ip} has tried to access {NavigationManager.Uri} without permission", "security"); + } + + return Task.CompletedTask; + } +} diff --git a/Moonlight/Shared/Components/Partials/Sidebar.razor b/Moonlight/Shared/Components/Partials/Sidebar.razor index 41b19160..325b7857 100644 --- a/Moonlight/Shared/Components/Partials/Sidebar.razor +++ b/Moonlight/Shared/Components/Partials/Sidebar.razor @@ -49,7 +49,7 @@ { if (firstRender) { - User = await IdentityService.Get(); + User = IdentityService.User; sidebar = await JsRuntime.InvokeAsync("document.body.getAttribute", "data-kt-app-layout"); StateHasChanged(); } diff --git a/Moonlight/Shared/Components/Partials/SidebarMenu.razor b/Moonlight/Shared/Components/Partials/SidebarMenu.razor index 9926d16e..de1240ef 100644 --- a/Moonlight/Shared/Components/Partials/SidebarMenu.razor +++ b/Moonlight/Shared/Components/Partials/SidebarMenu.razor @@ -69,7 +69,7 @@ else
    - if (User.Admin) + if (IdentityService.Permissions.HasAnyPermissions()) {