Refactored some forms. Added service shares
This commit is contained in:
parent
48c95d4ec6
commit
3d4f22f6f6
21 changed files with 347 additions and 50 deletions
|
@ -1,6 +1,6 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Moonlight.App.Models.Forms;
|
||||
namespace Moonlight.App.Models.Forms.Admin.Users;
|
||||
|
||||
public class UpdateUserForm
|
||||
{
|
|
@ -1,6 +1,6 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Moonlight.App.Models.Forms;
|
||||
namespace Moonlight.App.Models.Forms.Admin.Users;
|
||||
|
||||
public class UpdateUserPasswordForm
|
||||
{
|
|
@ -1,6 +1,6 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Moonlight.App.Models.Forms;
|
||||
namespace Moonlight.App.Models.Forms.Auth;
|
||||
|
||||
public class LoginForm
|
||||
{
|
|
@ -1,6 +1,6 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Moonlight.App.Models.Forms;
|
||||
namespace Moonlight.App.Models.Forms.Auth;
|
||||
|
||||
public class RegisterForm
|
||||
{
|
|
@ -1,6 +1,6 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Moonlight.App.Models.Forms;
|
||||
namespace Moonlight.App.Models.Forms.Auth;
|
||||
|
||||
public class ResetPasswordForm
|
||||
{
|
|
@ -1,6 +1,6 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Moonlight.App.Models.Forms;
|
||||
namespace Moonlight.App.Models.Forms.Auth;
|
||||
|
||||
public class TwoFactorCodeForm
|
||||
{
|
|
@ -1,6 +1,6 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Moonlight.App.Models.Forms;
|
||||
namespace Moonlight.App.Models.Forms.Auth;
|
||||
|
||||
public class UpdateAccountForm
|
||||
{
|
|
@ -1,6 +1,6 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Moonlight.App.Models.Forms;
|
||||
namespace Moonlight.App.Models.Forms.Auth;
|
||||
|
||||
public class UpdateAccountPasswordForm
|
||||
{
|
13
Moonlight/App/Models/Forms/Services/AddUserToServiceForm.cs
Normal file
13
Moonlight/App/Models/Forms/Services/AddUserToServiceForm.cs
Normal file
|
@ -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; } = "";
|
||||
}
|
|
@ -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<Repository<Service>>();
|
||||
var serviceShareRepo = scope.ServiceProvider.GetRequiredService<Repository<ServiceShare>>();
|
||||
|
||||
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);
|
||||
|
|
|
@ -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<Service> ServiceRepository;
|
||||
private readonly Repository<User> UserRepository;
|
||||
|
||||
public ServiceAdminService Admin => ServiceProvider.GetRequiredService<ServiceAdminService>();
|
||||
|
||||
public ServiceService(IServiceProvider serviceProvider, Repository<Service> serviceRepository)
|
||||
public ServiceService(IServiceProvider serviceProvider, Repository<Service> serviceRepository, Repository<User> userRepository)
|
||||
{
|
||||
ServiceProvider = serviceProvider;
|
||||
ServiceRepository = serviceRepository;
|
||||
UserRepository = userRepository;
|
||||
}
|
||||
|
||||
public Task<Service[]> 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<User[]> 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;
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
@using Moonlight.App.Services.Users
|
||||
@using Moonlight.App.Models.Forms
|
||||
@using Moonlight.App.Models.Forms.Auth
|
||||
|
||||
@inject UserService UserService
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
105
Moonlight/Shared/Components/Modals/ManageServiceShareModal.razor
Normal file
105
Moonlight/Shared/Components/Modals/ManageServiceShareModal.razor
Normal file
|
@ -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
|
||||
|
||||
<SmartModal @ref="Modal" CssClasses="modal-dialog-centered modal-lg">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">Manage shared users</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||
<div class="mb-3">
|
||||
<SmartForm Model="Form" OnValidSubmit="Add">
|
||||
<div class="input-group">
|
||||
<input @bind="Form.Username" type="text" placeholder="Enter a username..." class="form-control"/>
|
||||
<button type="submit" class="btn btn-primary">Add</button>
|
||||
</div>
|
||||
</SmartForm>
|
||||
</div>
|
||||
@if (Users.Any())
|
||||
{
|
||||
<Table TableItem="User"
|
||||
Items="Users"
|
||||
PageSize="50"
|
||||
TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3 fs-6"
|
||||
TableHeadClass="fw-bold text-muted">
|
||||
<Column TableItem="User" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false">
|
||||
<Template>
|
||||
<div class="symbol symbol-50px me-5">
|
||||
@if (context.Avatar == null)
|
||||
{
|
||||
<img src="/assets/img/avatar.png" alt="Avatar">
|
||||
}
|
||||
else
|
||||
{
|
||||
<img src="/api/bucket/avatars/@(context.Avatar)" alt="Avatar">
|
||||
}
|
||||
</div>
|
||||
<span class="ms-3">@(context.Username)</span>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="User" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false">
|
||||
<Template>
|
||||
<div class="text-end me-3">
|
||||
<a @onclick="() => Remove(context)" @onclick:preventDefault href="#" class="text-danger">Remove</a>
|
||||
</div>
|
||||
</Template>
|
||||
</Column>
|
||||
</Table>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="d-flex justify-content-center py-4">
|
||||
<span class="mt-3 fs-5">No users found</span>
|
||||
</div>
|
||||
}
|
||||
</LazyLoader>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</SmartModal>
|
||||
|
||||
@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();
|
||||
}
|
||||
}
|
|
@ -3,7 +3,10 @@
|
|||
<div class="modal fade" id="modal@(Id)" tabindex="-1">
|
||||
<div class="modal-dialog @(CssClasses)">
|
||||
<div class="modal-content">
|
||||
@ChildContent
|
||||
@if (ShouldShow)
|
||||
{
|
||||
@ChildContent
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
101
Moonlight/Shared/Components/Service/ServiceItem.razor
Normal file
101
Moonlight/Shared/Components/Service/ServiceItem.razor
Normal file
|
@ -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)
|
||||
{
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title align-items-start flex-column">
|
||||
<span class="card-label fw-bold text-dark fs-3">Do you really wan to delete @(Service.Nickname ?? $"Service {Service.Id}")</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="text-gray-400 fs-5 fw-semibold">
|
||||
This action cannot be undone. Your service data will be deleted and cannot be restored
|
||||
</p>
|
||||
</div>
|
||||
<div class="card-footer p-3">
|
||||
<div class="btn-group w-100">
|
||||
<WButton OnClick="Delete" Text="Delete" CssClasses="btn btn-danger w-50 me-3" />
|
||||
<button @onclick="() => SetShowDeletion(false)" class="btn btn-secondary w-50">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title align-items-start flex-column">
|
||||
<span class="card-label fw-bold text-dark fs-3">@(Service.Nickname ?? $"Service {Service.Id}")</span>
|
||||
<span class="text-gray-400 mt-1 fw-semibold fs-6">@(Service.Product.Name)</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-body fs-6">
|
||||
<div class="d-flex flex-stack">
|
||||
<div class="text-gray-700 fw-semibold me-2">Renew price</div>
|
||||
<div class="d-flex align-items-senter">
|
||||
<span class="fw-bold">@(ConfigService.Get().Store.Currency) @(Service.Product.Price)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex flex-stack">
|
||||
<div class="text-gray-700 fw-semibold me-2">Renew at</div>
|
||||
<div class="d-flex align-items-senter">
|
||||
<span class="fw-bold">@(Formatter.FormatDate(Service.RenewAt))</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex flex-stack">
|
||||
<div class="text-gray-700 fw-semibold me-2">Created at</div>
|
||||
<div class="d-flex align-items-senter">
|
||||
<span class="fw-bold">@(Formatter.FormatDate(Service.CreatedAt))</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer p-3">
|
||||
<div class="btn-group w-100 mb-3">
|
||||
<a href="/service/@(Service.Id)" class="btn btn-primary w-50 me-3">Manage</a>
|
||||
<button @onclick="() => ShareModal.Show()" class="btn btn-secondary w-50">Manage shares</button>
|
||||
</div>
|
||||
<div class="btn-group w-100">
|
||||
<button class="btn btn-warning w-50 me-3">Renew</button>
|
||||
<button @onclick="() => SetShowDeletion(true)" class="btn btn-danger w-50">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ManageServiceShareModal @ref="ShareModal" Service="Service" />
|
||||
}
|
||||
|
||||
|
||||
|
||||
@code
|
||||
{
|
||||
[Parameter]
|
||||
public Service Service { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<Task> 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();
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
<LazyLoader ShowAsCard="true" Load="Load">
|
||||
<LazyLoader @ref="LazyLoader" ShowAsCard="true" Load="Load">
|
||||
<div class="row mb-5">
|
||||
@foreach (var service in MyServices)
|
||||
{
|
||||
<div class="col-md-3 col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title align-items-start flex-column">
|
||||
<span class="card-label fw-bold text-dark fs-3">@(service.Nickname ?? $"Service {service.Id}")</span>
|
||||
<span class="text-gray-400 mt-1 fw-semibold fs-6">@(service.Product.Name)</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-body fs-6">
|
||||
<div class="d-flex flex-stack">
|
||||
<div class="text-gray-700 fw-semibold me-2">Price</div>
|
||||
<div class="d-flex align-items-senter">
|
||||
<span class="fw-bold">@(ConfigService.Get().Store.Currency) @(service.Product.Price)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex flex-stack">
|
||||
<div class="text-gray-700 fw-semibold me-2">Renew at</div>
|
||||
<div class="d-flex align-items-senter">
|
||||
<span class="fw-bold">@(Formatter.FormatDate(service.RenewAt))</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex flex-stack">
|
||||
<div class="text-gray-700 fw-semibold me-2">Created at</div>
|
||||
<div class="d-flex align-items-senter">
|
||||
<span class="fw-bold">@(Formatter.FormatDate(service.CreatedAt))</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer p-3">
|
||||
<div class="btn-group w-100 mb-3">
|
||||
<button class="btn btn-primary w-50 me-3">Manage</button>
|
||||
<button class="btn btn-secondary w-50">Manage shares</button>
|
||||
</div>
|
||||
<div class="btn-group w-100">
|
||||
<button class="btn btn-warning w-50 me-3">Renew</button>
|
||||
<button class="btn btn-danger w-50">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ServiceItem Service="service" OnChange="LazyLoader.Reload" />
|
||||
</div>
|
||||
}
|
||||
|
||||
|
@ -90,6 +53,8 @@
|
|||
|
||||
@code
|
||||
{
|
||||
private LazyLoader LazyLoader;
|
||||
|
||||
private Service[] MyServices;
|
||||
private Service[] SharedServices;
|
||||
|
||||
|
|
Loading…
Reference in a new issue