diff --git a/Moonlight/App/Models/Forms/UpdateUserForm.cs b/Moonlight/App/Models/Forms/Admin/Users/UpdateUserForm.cs similarity index 92% rename from Moonlight/App/Models/Forms/UpdateUserForm.cs rename to Moonlight/App/Models/Forms/Admin/Users/UpdateUserForm.cs index 9075426..c8f092a 100644 --- a/Moonlight/App/Models/Forms/UpdateUserForm.cs +++ b/Moonlight/App/Models/Forms/Admin/Users/UpdateUserForm.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations; -namespace Moonlight.App.Models.Forms; +namespace Moonlight.App.Models.Forms.Admin.Users; public class UpdateUserForm { diff --git a/Moonlight/App/Models/Forms/UpdateUserPasswordForm.cs b/Moonlight/App/Models/Forms/Admin/Users/UpdateUserPasswordForm.cs similarity index 88% rename from Moonlight/App/Models/Forms/UpdateUserPasswordForm.cs rename to Moonlight/App/Models/Forms/Admin/Users/UpdateUserPasswordForm.cs index 8b6ae11..e6bfb7e 100644 --- a/Moonlight/App/Models/Forms/UpdateUserPasswordForm.cs +++ b/Moonlight/App/Models/Forms/Admin/Users/UpdateUserPasswordForm.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations; -namespace Moonlight.App.Models.Forms; +namespace Moonlight.App.Models.Forms.Admin.Users; public class UpdateUserPasswordForm { diff --git a/Moonlight/App/Models/Forms/LoginForm.cs b/Moonlight/App/Models/Forms/Auth/LoginForm.cs similarity index 89% rename from Moonlight/App/Models/Forms/LoginForm.cs rename to Moonlight/App/Models/Forms/Auth/LoginForm.cs index c9787a4..32cd2be 100644 --- a/Moonlight/App/Models/Forms/LoginForm.cs +++ b/Moonlight/App/Models/Forms/Auth/LoginForm.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations; -namespace Moonlight.App.Models.Forms; +namespace Moonlight.App.Models.Forms.Auth; public class LoginForm { diff --git a/Moonlight/App/Models/Forms/RegisterForm.cs b/Moonlight/App/Models/Forms/Auth/RegisterForm.cs similarity index 96% rename from Moonlight/App/Models/Forms/RegisterForm.cs rename to Moonlight/App/Models/Forms/Auth/RegisterForm.cs index b870fc9..6ed3dc2 100644 --- a/Moonlight/App/Models/Forms/RegisterForm.cs +++ b/Moonlight/App/Models/Forms/Auth/RegisterForm.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations; -namespace Moonlight.App.Models.Forms; +namespace Moonlight.App.Models.Forms.Auth; public class RegisterForm { diff --git a/Moonlight/App/Models/Forms/ResetPasswordForm.cs b/Moonlight/App/Models/Forms/Auth/ResetPasswordForm.cs similarity index 86% rename from Moonlight/App/Models/Forms/ResetPasswordForm.cs rename to Moonlight/App/Models/Forms/Auth/ResetPasswordForm.cs index 657eeea..8bcca4a 100644 --- a/Moonlight/App/Models/Forms/ResetPasswordForm.cs +++ b/Moonlight/App/Models/Forms/Auth/ResetPasswordForm.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations; -namespace Moonlight.App.Models.Forms; +namespace Moonlight.App.Models.Forms.Auth; public class ResetPasswordForm { diff --git a/Moonlight/App/Models/Forms/TwoFactorCodeForm.cs b/Moonlight/App/Models/Forms/Auth/TwoFactorCodeForm.cs similarity index 81% rename from Moonlight/App/Models/Forms/TwoFactorCodeForm.cs rename to Moonlight/App/Models/Forms/Auth/TwoFactorCodeForm.cs index 36e0c05..ed84da3 100644 --- a/Moonlight/App/Models/Forms/TwoFactorCodeForm.cs +++ b/Moonlight/App/Models/Forms/Auth/TwoFactorCodeForm.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations; -namespace Moonlight.App.Models.Forms; +namespace Moonlight.App.Models.Forms.Auth; public class TwoFactorCodeForm { diff --git a/Moonlight/App/Models/Forms/UpdateAccountForm.cs b/Moonlight/App/Models/Forms/Auth/UpdateAccountForm.cs similarity index 93% rename from Moonlight/App/Models/Forms/UpdateAccountForm.cs rename to Moonlight/App/Models/Forms/Auth/UpdateAccountForm.cs index 7725994..09ec62d 100644 --- a/Moonlight/App/Models/Forms/UpdateAccountForm.cs +++ b/Moonlight/App/Models/Forms/Auth/UpdateAccountForm.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations; -namespace Moonlight.App.Models.Forms; +namespace Moonlight.App.Models.Forms.Auth; public class UpdateAccountForm { diff --git a/Moonlight/App/Models/Forms/UpdateAccountPasswordForm.cs b/Moonlight/App/Models/Forms/Auth/UpdateAccountPasswordForm.cs similarity index 93% rename from Moonlight/App/Models/Forms/UpdateAccountPasswordForm.cs rename to Moonlight/App/Models/Forms/Auth/UpdateAccountPasswordForm.cs index 49f3e1b..ed2b187 100644 --- a/Moonlight/App/Models/Forms/UpdateAccountPasswordForm.cs +++ b/Moonlight/App/Models/Forms/Auth/UpdateAccountPasswordForm.cs @@ -1,6 +1,6 @@ using System.ComponentModel.DataAnnotations; -namespace Moonlight.App.Models.Forms; +namespace Moonlight.App.Models.Forms.Auth; public class UpdateAccountPasswordForm { diff --git a/Moonlight/App/Models/Forms/Services/AddUserToServiceForm.cs b/Moonlight/App/Models/Forms/Services/AddUserToServiceForm.cs new file mode 100644 index 0000000..9109f1c --- /dev/null +++ b/Moonlight/App/Models/Forms/Services/AddUserToServiceForm.cs @@ -0,0 +1,13 @@ +using System.ComponentModel.DataAnnotations; + +namespace Moonlight.App.Models.Forms.Services; + +public class AddUserToServiceForm +{ + [Required(ErrorMessage = "You need to provide an username")] + [MinLength(7, ErrorMessage = "The username is too short")] + [MaxLength(20, ErrorMessage = "The username cannot be longer than 20 characters")] + [RegularExpression("^[a-z][a-z0-9]*$", + ErrorMessage = "Usernames can only contain lowercase characters and numbers")] + public string Username { get; set; } = ""; +} \ No newline at end of file diff --git a/Moonlight/App/Services/ServiceManage/ServiceAdminService.cs b/Moonlight/App/Services/ServiceManage/ServiceAdminService.cs index b06b717..d189f24 100644 --- a/Moonlight/App/Services/ServiceManage/ServiceAdminService.cs +++ b/Moonlight/App/Services/ServiceManage/ServiceAdminService.cs @@ -55,6 +55,34 @@ public class ServiceAdminService return finishedService; } + public async Task Delete(Service s) + { + using var scope = ServiceScopeFactory.CreateScope(); + var serviceRepo = scope.ServiceProvider.GetRequiredService>(); + var serviceShareRepo = scope.ServiceProvider.GetRequiredService>(); + + var service = serviceRepo + .Get() + .Include(x => x.Product) + .Include(x => x.Shares) + .FirstOrDefault(x => x.Id == s.Id); + + if (service == null) + throw new DisplayException("Service does not exist anymore"); + + if (!Actions.ContainsKey(service.Product.Type)) + throw new DisplayException($"The product type {service.Product.Type} is not registered"); + + await Actions[service.Product.Type].Delete(scope.ServiceProvider, service); + + foreach (var share in service.Shares) + { + serviceShareRepo.Delete(share); + } + + serviceRepo.Delete(service); + } + public Task RegisterAction(ServiceType type, ServiceActions actions) // Use this function to register service types { Actions.Add(type, actions); diff --git a/Moonlight/App/Services/ServiceManage/ServiceService.cs b/Moonlight/App/Services/ServiceManage/ServiceService.cs index 26e917d..c0e4a71 100644 --- a/Moonlight/App/Services/ServiceManage/ServiceService.cs +++ b/Moonlight/App/Services/ServiceManage/ServiceService.cs @@ -1,6 +1,7 @@ using Microsoft.EntityFrameworkCore; using Moonlight.App.Database.Entities; using Moonlight.App.Database.Entities.Store; +using Moonlight.App.Exceptions; using Moonlight.App.Repositories; namespace Moonlight.App.Services.ServiceManage; @@ -9,13 +10,15 @@ public class ServiceService // This service is used for managing services and cr { private readonly IServiceProvider ServiceProvider; private readonly Repository ServiceRepository; + private readonly Repository UserRepository; public ServiceAdminService Admin => ServiceProvider.GetRequiredService(); - public ServiceService(IServiceProvider serviceProvider, Repository serviceRepository) + public ServiceService(IServiceProvider serviceProvider, Repository serviceRepository, Repository userRepository) { ServiceProvider = serviceProvider; ServiceRepository = serviceRepository; + UserRepository = userRepository; } public Task Get(User user) @@ -40,4 +43,70 @@ public class ServiceService // This service is used for managing services and cr return Task.FromResult(result); } + + public Task AddSharedUser(Service s, string username) + { + var userToAdd = UserRepository + .Get() + .FirstOrDefault(x => x.Username == username); + + if (userToAdd == null) + throw new DisplayException("No user found with this username"); + + var service = ServiceRepository + .Get() + .Include(x => x.Owner) + .Include(x => x.Shares) + .ThenInclude(x => x.User) + .First(x => x.Id == s.Id); + + if (service.Owner.Id == userToAdd.Id) + throw new DisplayException("The owner cannot be added as a shared user"); + + if (service.Shares.Any(x => x.User.Id == userToAdd.Id)) + throw new DisplayException("The user has already access to this service"); + + service.Shares.Add(new () + { + User = userToAdd + }); + + ServiceRepository.Update(service); + + return Task.CompletedTask; + } + + public Task GetSharedUsers(Service s) + { + var service = ServiceRepository + .Get() + .Include(x => x.Shares) + .ThenInclude(x => x.User) + .First(x => x.Id == s.Id); + + var result = service.Shares + .Select(x => x.User) + .ToArray(); + + return Task.FromResult(result); + } + + public Task RemoveSharedUser(Service s, User user) + { + var service = ServiceRepository + .Get() + .Include(x => x.Shares) + .ThenInclude(x => x.User) + .First(x => x.Id == s.Id); + + var shareToRemove = service.Shares.FirstOrDefault(x => x.User.Id == user.Id); + + if (shareToRemove == null) + throw new DisplayException("This user does not have access to this service"); + + service.Shares.Remove(shareToRemove); + ServiceRepository.Update(service); + + return Task.CompletedTask; + } } \ No newline at end of file diff --git a/Moonlight/Shared/Components/Auth/ChangePassword.razor b/Moonlight/Shared/Components/Auth/ChangePassword.razor index 0c97afb..a509d08 100644 --- a/Moonlight/Shared/Components/Auth/ChangePassword.razor +++ b/Moonlight/Shared/Components/Auth/ChangePassword.razor @@ -2,6 +2,7 @@ @using Moonlight.App.Services.Users @using Moonlight.App.Models.Forms @using Moonlight.App.Models.Enums +@using Moonlight.App.Models.Forms.Auth @inject IdentityService IdentityService @inject UserService UserService diff --git a/Moonlight/Shared/Components/Auth/Login.razor b/Moonlight/Shared/Components/Auth/Login.razor index 8b1043e..41c4b4b 100644 --- a/Moonlight/Shared/Components/Auth/Login.razor +++ b/Moonlight/Shared/Components/Auth/Login.razor @@ -3,6 +3,7 @@ @using Moonlight.App.Services @using Moonlight.App.Models.Forms +@using Moonlight.App.Models.Forms.Auth @inject IdentityService IdentityService @inject CookieService CookieService diff --git a/Moonlight/Shared/Components/Auth/PasswordReset.razor b/Moonlight/Shared/Components/Auth/PasswordReset.razor index f80632b..946dd39 100644 --- a/Moonlight/Shared/Components/Auth/PasswordReset.razor +++ b/Moonlight/Shared/Components/Auth/PasswordReset.razor @@ -2,6 +2,7 @@ @using Moonlight.App.Services.Users @using Moonlight.App.Models.Forms +@using Moonlight.App.Models.Forms.Auth @inject UserService UserService diff --git a/Moonlight/Shared/Components/Auth/Register.razor b/Moonlight/Shared/Components/Auth/Register.razor index 9ce32d1..a685ca1 100644 --- a/Moonlight/Shared/Components/Auth/Register.razor +++ b/Moonlight/Shared/Components/Auth/Register.razor @@ -5,6 +5,7 @@ @using Moonlight.App.Models.Forms @using Moonlight.App.Services.Users @using Moonlight.App.Exceptions +@using Moonlight.App.Models.Forms.Auth @inject IdentityService IdentityService @inject UserService UserService diff --git a/Moonlight/Shared/Components/Modals/ManageServiceShareModal.razor b/Moonlight/Shared/Components/Modals/ManageServiceShareModal.razor new file mode 100644 index 0000000..7c25bc5 --- /dev/null +++ b/Moonlight/Shared/Components/Modals/ManageServiceShareModal.razor @@ -0,0 +1,105 @@ +@using Moonlight.App.Services.ServiceManage +@using Moonlight.App.Database.Entities.Store +@using BlazorTable +@using Moonlight.App.Models.Forms.Services + +@inject ServiceService ServiceService + + + + + + + +@code +{ + [Parameter] + public Service Service { get; set; } + + private SmartModal Modal; + private LazyLoader LazyLoader; + private User[] Users; + + private AddUserToServiceForm Form = new(); + + private async Task Load(LazyLoader _) + { + Users = await ServiceService.GetSharedUsers(Service); + } + + private async Task Add() + { + await ServiceService.AddSharedUser(Service, Form.Username); + Form = new(); + await LazyLoader.Reload(); + } + + private async Task Remove(User user) + { + await ServiceService.RemoveSharedUser(Service, user); + await LazyLoader.Reload(); + } + + public async Task Show() + { + await Modal.Show(); + } + + public async Task Hide() + { + await Modal.Hide(); + } +} \ No newline at end of file diff --git a/Moonlight/Shared/Components/Partials/SmartModal.razor b/Moonlight/Shared/Components/Partials/SmartModal.razor index 0735110..5697cc2 100644 --- a/Moonlight/Shared/Components/Partials/SmartModal.razor +++ b/Moonlight/Shared/Components/Partials/SmartModal.razor @@ -3,7 +3,10 @@ @@ -17,6 +20,7 @@ public RenderFragment ChildContent { get; set; } private int Id; + private bool ShouldShow = false; protected override void OnInitialized() { @@ -25,11 +29,17 @@ public async Task Show() { + ShouldShow = true; + await InvokeAsync(StateHasChanged); + await ModalService.Show("modal" + Id); } public async Task Hide() { await ModalService.Hide("modal" + Id); + + ShouldShow = false; + await InvokeAsync(StateHasChanged); } } diff --git a/Moonlight/Shared/Components/Service/ServiceItem.razor b/Moonlight/Shared/Components/Service/ServiceItem.razor new file mode 100644 index 0000000..8a0f5de --- /dev/null +++ b/Moonlight/Shared/Components/Service/ServiceItem.razor @@ -0,0 +1,101 @@ +@using Moonlight.App.Database.Entities.Store +@using Moonlight.App.Services +@using Moonlight.App.Services.ServiceManage +@using Moonlight.Shared.Components.Modals + +@inject ConfigService ConfigService +@inject ToastService ToastService +@inject ServiceService ServiceService + +@if (ShowDeletionScreen) +{ +
+
+

+ Do you really wan to delete @(Service.Nickname ?? $"Service {Service.Id}") +

+
+
+

+ This action cannot be undone. Your service data will be deleted and cannot be restored +

+
+ +
+} +else +{ +
+
+

+ @(Service.Nickname ?? $"Service {Service.Id}") + @(Service.Product.Name) +

+
+
+
+
Renew price
+
+ @(ConfigService.Get().Store.Currency) @(Service.Product.Price) +
+
+
+
Renew at
+
+ @(Formatter.FormatDate(Service.RenewAt)) +
+
+
+
Created at
+
+ @(Formatter.FormatDate(Service.CreatedAt)) +
+
+
+ +
+ + +} + + + +@code +{ + [Parameter] + public Service Service { get; set; } + + [Parameter] + public Func OnChange { get; set; } + + private bool ShowDeletionScreen = false; + private ManageServiceShareModal ShareModal; + + private async Task SetShowDeletion(bool b) + { + ShowDeletionScreen = b; + await InvokeAsync(StateHasChanged); + } + + public async Task Delete() + { + await ServiceService.Admin.Delete(Service); + + await ToastService.Success("Successfully deleted service"); + await OnChange.Invoke(); + } +} diff --git a/Moonlight/Shared/Views/Account/Index.razor b/Moonlight/Shared/Views/Account/Index.razor index 4c38afd..4b50822 100644 --- a/Moonlight/Shared/Views/Account/Index.razor +++ b/Moonlight/Shared/Views/Account/Index.razor @@ -3,6 +3,7 @@ @using Moonlight.App.Services @using Moonlight.App.Services.Users @using Moonlight.App.Models.Forms +@using Moonlight.App.Models.Forms.Auth @inject IdentityService IdentityService @inject UserService UserService diff --git a/Moonlight/Shared/Views/Account/Security.razor b/Moonlight/Shared/Views/Account/Security.razor index 41dce9f..98b6cc7 100644 --- a/Moonlight/Shared/Views/Account/Security.razor +++ b/Moonlight/Shared/Views/Account/Security.razor @@ -6,6 +6,7 @@ @using OtpNet @using QRCoder @using Moonlight.App.Models.Enums +@using Moonlight.App.Models.Forms.Auth @inject IdentityService IdentityService @inject UserService UserService diff --git a/Moonlight/Shared/Views/Services/Index.razor b/Moonlight/Shared/Views/Services/Index.razor index 583df09..3d06ea4 100644 --- a/Moonlight/Shared/Views/Services/Index.razor +++ b/Moonlight/Shared/Views/Services/Index.razor @@ -3,54 +3,17 @@ @using Moonlight.App.Services.ServiceManage @using Moonlight.App.Database.Entities.Store @using Moonlight.App.Services +@using Moonlight.Shared.Components.Service @inject ServiceService ServiceService @inject IdentityService IdentityService -@inject ConfigService ConfigService - +
@foreach (var service in MyServices) {
-
-
-

- @(service.Nickname ?? $"Service {service.Id}") - @(service.Product.Name) -

-
-
-
-
Price
-
- @(ConfigService.Get().Store.Currency) @(service.Product.Price) -
-
-
-
Renew at
-
- @(Formatter.FormatDate(service.RenewAt)) -
-
-
-
Created at
-
- @(Formatter.FormatDate(service.CreatedAt)) -
-
-
- -
+
} @@ -90,6 +53,8 @@ @code { + private LazyLoader LazyLoader; + private Service[] MyServices; private Service[] SharedServices;