Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(artistalley): rework rbac and backoffice styling #180

Merged
merged 6 commits into from
Aug 25, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ef-app_backend-dotnet-core.code-workspace
Original file line number Diff line number Diff line change
@@ -9,5 +9,6 @@
"editor.insertSpaces": true,
"editor.indentSize": "tabSize",
"editor.tabSize": 4,
"editor.formatOnSave": false,
}
}
Original file line number Diff line number Diff line change
@@ -13,29 +13,33 @@
{
<MudItem xs="6">
<MudLink Href="@Record.Image.Url">
<MudImage ObjectFit="ObjectFit.Contain" Height="500" Width="500" Class="ml-2" Src="@Record.Image.Url"/>
<MudImage ObjectFit="ObjectFit.Contain" Height="500" Width="500" Class="ml-2"
Src="@Record.Image.Url" />
</MudLink>
</MudItem>
}
<MudItem xs="6">
<MudTextField ReadOnly="true" Value="Record.Id" Label="ID"/>
<MudTextField ReadOnly="true" Value="Record.CreatedDateTimeUtc" Label="Application submitted at"/>
<MudTextField ReadOnly="true" Value="Record.OwnerUid" Label="Applicant UID"/>
<MudTextField ReadOnly="true" Value="Record.OwnerUsername" Label="Applicant's username"/>
<MudTextField ReadOnly="true" Value="Record.WebsiteUrl" Label="Applicants Website"/>
<MudTextField ReadOnly="true" AutoGrow Value="Record.ShortDescription" Label="Short Description"/>
<MudTextField ReadOnly="true" Value="Record.TelegramHandle" Label="Applicant's Telegram handle"/>
<MudTextField ReadOnly="true" Value="Record.Location" Label="Location" Class="mb-4"/>
<MudTextField ReadOnly="true" Value="Record.Id" Label="ID" />
<MudTextField ReadOnly="true" Value="Record.CreatedDateTimeUtc" Label="Application submitted at" Format="yyyy-MM-dd HH:mm:ss zzz" />
<MudTextField ReadOnly="true" Value="Record.OwnerUid" Label="Applicant UID" />
<MudTextField ReadOnly="true" Value="Record.OwnerUsername" Label="Applicant's username" />
<MudTextField ReadOnly="true" Value="Record.WebsiteUrl" Label="Applicants Website" />
<MudTextField ReadOnly="true" AutoGrow Value="Record.ShortDescription" Label="Short Description" />
<MudTextField ReadOnly="true" Value="Record.TelegramHandle" Label="Applicant's Telegram handle" />
<MudTextField ReadOnly="true" Value="Record.Location" Label="Location" Class="mb-4" />
@switch (Record.State)
{
case TableRegistrationRecord.RegistrationStateEnum.Pending:
<MudAlert Severity="Severity.Info">Pending</MudAlert>
<MudButton StartIcon="@Icons.Material.Filled.CheckCircle" @onclick="() => TryChangeRegistrationStatus(Record, false)">Approve</MudButton>
<MudButton StartIcon="@Icons.Material.Filled.Cancel" @onclick="() => TryChangeRegistrationStatus(Record, true)">Reject</MudButton>
<MudButton Color="Color.Success" StartIcon="@Icons.Material.Filled.CheckCircle"
@onclick="() => TryChangeRegistrationStatus(Record, false)">Approve</MudButton>
<MudButton Color="Color.Warning" StartIcon="@Icons.Material.Filled.Cancel"
@onclick="() => TryChangeRegistrationStatus(Record, true)">Reject</MudButton>
@*Show the delete button only for admins*@
<AuthorizeView Roles="Admin">
<AuthorizeView Policy="RequireArtistAlleyAdmin">
<Authorized>
<MudButton StartIcon="@Icons.Material.Filled.Delete" OnClick="args => DeleteRegistration()">Delete</MudButton>
<MudButton Color="Color.Error" StartIcon="@Icons.Material.Filled.Delete" OnClick="args => DeleteRegistration()">
Delete</MudButton>
</Authorized>
</AuthorizeView>
break;
@@ -57,7 +61,7 @@

@code {

[Parameter] public TableRegistrationRecord Record { get; set; }
[Parameter] public TableRegistrationRecord? Record { get; set; }


private async Task DeleteRegistration()
@@ -69,9 +73,9 @@
};
var options = new DialogOptions() { CloseButton = true, MaxWidth = MaxWidth.ExtraSmall };
IDialogReference dialogRef = await DialogService.ShowAsync<ConfirmDialog>("Confirm", dialog, options);
DialogResult result = await dialogRef.Result;
DialogResult? result = await dialogRef.Result;

if (!result.Canceled)
if (result is { Canceled: false } && Record != null)
{
await TableRegService.DeleteTableRegistrationAsync(Record);
Snackbar.Add("Application Deleted", Severity.Success);
@@ -92,11 +96,12 @@
};
var options = new DialogOptions() { CloseButton = true, MaxWidth = MaxWidth.ExtraSmall };
IDialogReference dialogRef = await DialogService.ShowAsync<ConfirmDialog>("Confirm", dialog, options);
DialogResult result = await dialogRef.Result;
DialogResult? result = await dialogRef.Result;

if (!result.Canceled)
if (result is { Canceled: false } && Record != null)
{
var state = reject ? TableRegistrationRecord.RegistrationStateEnum.Rejected : TableRegistrationRecord.RegistrationStateEnum.Accepted;
var state = reject ? TableRegistrationRecord.RegistrationStateEnum.Rejected :
TableRegistrationRecord.RegistrationStateEnum.Accepted;
await TableRegService.PutTableRegistrationStatusAsync(registrationRecord, state);

Snackbar.Add($"Application was {(reject ? "rejected" : "approved")}.");
7 changes: 5 additions & 2 deletions src/Eurofurence.App.Backoffice/Layout/NavMenu.razor
Original file line number Diff line number Diff line change
@@ -3,9 +3,12 @@
<AuthorizeView Policy="RequireKnowledgeBaseEditor">
<MudNavLink Href="knowledgebase" Icon="@Icons.Material.Outlined.Info" Match="NavLinkMatch.Prefix">Knowledge Base
</MudNavLink>
</AuthorizeView>
<AuthorizeView Roles="Admin">
<MudNavLink Href="images" Icon="@Icons.Material.Outlined.Image" Match="NavLinkMatch.Prefix">Images</MudNavLink>
</AuthorizeView>
<AuthorizeView Roles="ArtistAlleyModerator">
<MudNavLink Href="artistAlleyModeration" Icon="@Icons.Material.Outlined.AddModerator">Artist Alley Moderation</MudNavLink>
<AuthorizeView Policy="RequireArtistAlleyModerator">
<MudNavLink Href="artistAlleyModeration" Icon="@Icons.Material.Outlined.AddModerator">Artist Alley Moderation
</MudNavLink>
</AuthorizeView>
</MudNavMenu>
74 changes: 39 additions & 35 deletions src/Eurofurence.App.Backoffice/Pages/ArtistAlleyModeration.razor
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@
@using Microsoft.AspNetCore.Authorization
@using Color = MudBlazor.Color

@attribute [Authorize(Roles = "ArtistAlleyModerator")]
@attribute [Authorize(Policy = "RequireArtistAlleyModerator")]

@inject IArtistAlleyService TableRegService
@inject IDialogService DialogService
@@ -14,51 +14,56 @@

<MudToolBar>
<MudText Typo="Typo.h6">Artist Alley Moderation</MudText>
<MudSpacer/>
<MudSpacer/>
<MudSpacer />
<MudSpacer />
<MudTextField T="string" ValueChanged="Search" Label="Search" Variant="Variant.Outlined" Margin="Margin.Dense"
Adornment="Adornment.End" AdornmentIcon="@Icons.Material.Filled.Search" AdornmentColor="Color.Secondary"/>
Adornment="Adornment.End" AdornmentIcon="@Icons.Material.Filled.Search" AdornmentColor="Color.Secondary" />
</MudToolBar>
@*
Table showing the basic informations for each application.

More data of the applicant shall be visble an extra dialog
*@
<MudDataGrid T="TableRegistrationRecord" @ref="_dataGrid" Items="_items" Filterable="true" FilterMode="@DataGridFilterMode.ColumnFilterMenu">
*@
<MudDataGrid T="TableRegistrationRecord" @ref="_dataGrid" Items="_items" Filterable="true"
FilterMode="@DataGridFilterMode.ColumnFilterMenu">
<Columns>
<TemplateColumn Title="Preview" CellClass="d-flex justify-center">
<TemplateColumn Title="Preview" CellClass="justify-center">
<CellTemplate>
@if (context.Item.Image != null && !string.IsNullOrEmpty(context.Item.Image.Url))
@if (context.Item.Image != null && !string.IsNullOrEmpty(context.Item.Image.Url))
{
<MudLink Href="@context.Item.Image.Url">
<MudImage Class="ml-2" Width="100" Height="100" ObjectFit="ObjectFit.Contain"
Src="@context.Item.Image.Url"/>
Src="@context.Item.Image.Url" />
</MudLink>
}
</CellTemplate>
</TemplateColumn>
<PropertyColumn Title="Applicant's Username" Property="arg => arg.OwnerUsername" Filterable="false"/>
<PropertyColumn Title="Applicant's Username" Property="arg => arg.OwnerUsername" Filterable="false" />
<PropertyColumn Title="Applicant's Name" Property="arg => arg.DisplayName" Filterable="false" />
<PropertyColumn Title="Applicant's Location" Property="arg => arg.Location" Filterable="false" />
<PropertyColumn Title="Applicant's Descriptions" Property="arg => arg.ShortDescription" Filterable="false" />
<PropertyColumn Title="Status" Property="arg => arg.State" Filterable="true">
<PropertyColumn Title="Submitted" Property="arg => arg.CreatedDateTimeUtc" Filterable="true" Format="yyyy-MM-dd HH:mm:ss zzz">
</PropertyColumn>
<TemplateColumn Context="context2">
<TemplateColumn Title="Status" Context="context2" CellClass="justify-center" Sortable="true" SortBy="@(x => $"{x.State} - {x.CreatedDateTimeUtc.ToString("yyyy-MM-dd HH:mm:ss zzz")}")">
<CellTemplate>
<MudContainer MaxWidth="MaxWidth.Small">
<MudGrid>
<MudItem xs="10">
<MudGrid Class="justify-center">
<MudItem xs="10" Style="text-align: center;">
@switch (context2.Item?.State)
{
case TableRegistrationRecord.RegistrationStateEnum.Pending:
<MudButton StartIcon="@Icons.Material.Filled.CheckCircle" Context="bttn" ref="appBtn"OnClick="args => TryChangeRegistrationStatus(context2.Item, false)">Approve</MudButton>
<MudButton StartIcon="@Icons.Material.Filled.Cancel" OnClick="args => TryChangeRegistrationStatus(context2.Item, true)">Reject</MudButton>
@*Show the delete button only for admins*@
<AuthorizeView Roles="Admin">
<Authorized>
<MudButton StartIcon="@Icons.Material.Filled.Delete" OnClick="args => DeleteRegistration(context2.Item)">Delete</MudButton>
</Authorized>
</AuthorizeView>
<MudButtonGroup OverrideStyles="false" Vertical="true">
<MudButton Variant="Variant.Outlined" Color="Color.Success" StartIcon="@Icons.Material.Filled.CheckCircle" Context="bttn" ref="appBtn"
OnClick="args => TryChangeRegistrationStatus(context2.Item, false)">Approve</MudButton>
<MudButton Variant="Variant.Outlined" Color="Color.Warning" StartIcon="@Icons.Material.Filled.Cancel"
OnClick="args => TryChangeRegistrationStatus(context2.Item, true)">Reject</MudButton>
@*Show the delete button only for admins*@
<AuthorizeView Policy="RequireArtistAlleyAdmin">
<Authorized>
<MudButton Variant="Variant.Outlined" Color="Color.Error" StartIcon="@Icons.Material.Filled.Delete"
OnClick="args => DeleteRegistration(context2.Item)">Delete</MudButton>
</Authorized>
</AuthorizeView>
</MudButtonGroup>
break;
case TableRegistrationRecord.RegistrationStateEnum.Accepted:
<MudAlert Severity="Severity.Success">Accepted</MudAlert>
@@ -70,17 +75,16 @@ More data of the applicant shall be visble an extra dialog
<MudAlert Severity="Severity.Success">Published</MudAlert>
break;
}
</MudItem>
<MudItem xs="2">
<MudButton StartIcon="@Icons.Material.Filled.Menu" OnClick="() => OpenMoreDialog(context2.Item)">More...</MudButton>
<MudButton StartIcon="@Icons.Material.Filled.Menu"
OnClick="() => OpenMoreDialog(context2.Item)">Details</MudButton>
</MudItem>
</MudGrid>
</MudContainer>
</CellTemplate>
</TemplateColumn>
</Columns>
<PagerContent>
<MudDataGridPager T="TableRegistrationRecord"/>
<MudDataGridPager T="TableRegistrationRecord" />
</PagerContent>
</MudDataGrid>

@@ -109,7 +113,7 @@ More data of the applicant shall be visble an extra dialog
_dataGrid?.SetSortAsync(nameof(TableRegistrationRecord.State), SortDirection.Ascending, x => x.State);
}
}

private async Task DeleteRegistration(TableRegistrationRecord registrationRecord)
{
DialogParameters<ConfirmDialog> dialog = new()
@@ -119,9 +123,9 @@ More data of the applicant shall be visble an extra dialog
};
var options = new DialogOptions() { CloseButton = true, MaxWidth = MaxWidth.ExtraSmall };
IDialogReference dialogRef = await DialogService.ShowAsync<ConfirmDialog>("Confirm", dialog, options);
DialogResult result = await dialogRef.Result;
DialogResult? result = await dialogRef.Result;

if (!result.Canceled)
if (result is { Canceled: false })
{
await TableRegService.DeleteTableRegistrationAsync(registrationRecord);
_items = await GetRegistrationsAsync();
@@ -144,11 +148,12 @@ More data of the applicant shall be visble an extra dialog
};
var options = new DialogOptions() { CloseButton = true, MaxWidth = MaxWidth.ExtraSmall };
IDialogReference dialogRef = await DialogService.ShowAsync<ConfirmDialog>("Confirm", dialog, options);
DialogResult result = await dialogRef.Result;
DialogResult? result = await dialogRef.Result;

if (!result.Canceled)
if (result is { Canceled: false })
{
await TableRegService.PutTableRegistrationStatusAsync(registrationRecord, reject ? TableRegistrationRecord.RegistrationStateEnum.Rejected : TableRegistrationRecord.RegistrationStateEnum.Accepted);
await TableRegService.PutTableRegistrationStatusAsync(registrationRecord, reject ?
TableRegistrationRecord.RegistrationStateEnum.Rejected : TableRegistrationRecord.RegistrationStateEnum.Accepted);
_items = await GetRegistrationsAsync();
StateHasChanged();
Snackbar.Add($"Application was {(reject ? "rejected" : "approved")}.", Severity.Success);
@@ -161,8 +166,7 @@ More data of the applicant shall be visble an extra dialog
/// <param name="contextItem"></param>
private async Task OpenMoreDialog(TableRegistrationRecord contextItem)
{
var parameters = new DialogParameters<ArtistAlleyApplicationDialog>()
{
var parameters = new DialogParameters<ArtistAlleyApplicationDialog>() {
{ x => x.Record, contextItem }
};

14 changes: 7 additions & 7 deletions src/Eurofurence.App.Backoffice/Pages/Images.razor
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
@page "/images"
@attribute [Authorize(Policy = "RequireKnowledgeBaseEditor")]
@attribute [Authorize(Roles = "Admin")]
@using Eurofurence.App.Backoffice.Services
@using Microsoft.AspNetCore.Authorization
@using Eurofurence.App.Domain.Model.Images
@@ -141,10 +141,10 @@
searchString)
{
return string.IsNullOrEmpty(searchString)
? entries
: entries.Where(entry =>
entry.InternalReference.ToLower().Contains(searchString.ToLower())
|| entry.Id.ToString().ToLower().Contains(searchString.ToLower()));
? entries
: entries.Where(entry =>
entry.InternalReference.ToLower().Contains(searchString.ToLower())
|| entry.Id.ToString().ToLower().Contains(searchString.ToLower()));
}

private async Task AddImage()
@@ -183,9 +183,9 @@
};
var options = new DialogOptions() { CloseButton = true, MaxWidth = MaxWidth.ExtraSmall };
IDialogReference dialogRef = await DialogService.ShowAsync<ConfirmDialog>("Confirm", dialog, options);
DialogResult dialogResult = await dialogRef.Result;
DialogResult? dialogResult = await dialogRef.Result;

if (!dialogResult.Canceled)
if (dialogResult is { Canceled: false })
{
var result = await ImageService.DeleteImageAsync(id);
if (result)
8 changes: 7 additions & 1 deletion src/Eurofurence.App.Backoffice/Program.cs
Original file line number Diff line number Diff line change
@@ -38,7 +38,13 @@
builder.Services.AddAuthorizationCore(config =>
{
config.AddPolicy("RequireKnowledgeBaseEditor", policy =>
policy.RequireRole("KnowledgeBaseEditor")
policy.RequireRole(["KnowledgeBaseEditor", "Admin"])
);
config.AddPolicy("RequireArtistAlleyModerator", policy =>
policy.RequireRole(["ArtistAlleyModerator", "ArtistAlleyAdmin", "Admin"])
);
config.AddPolicy("RequireArtistAlleyAdmin", policy =>
policy.RequireRole(["ArtistAlleyAdmin", "Admin"])
);
}
);
Original file line number Diff line number Diff line change
@@ -85,9 +85,8 @@ public async Task RegisterTableAsync(ClaimsPrincipal user, TableRegistrationRequ

foreach (var registration in activeRegistrations)
{
var stateChange = registration.ChangeState(TableRegistrationRecord.RegistrationStateEnum.Rejected, subject);
_appDbContext.StateChangeRecord.Add(stateChange);
registration.Touch();
if (registration.ImageId is { } imageId) await _imageService.DeleteOneAsync(imageId);
await DeleteOneAsync(registration.Id);
}

var record = new TableRegistrationRecord()
@@ -118,7 +117,7 @@ public async Task ApproveByIdAsync(Guid id, string operatorUid)
{
var record = await _appDbContext.TableRegistrations.FirstOrDefaultAsync(a => a.Id == id
&& a.State == TableRegistrationRecord.RegistrationStateEnum.Pending);
var stateChange =record.ChangeState(TableRegistrationRecord.RegistrationStateEnum.Accepted, operatorUid);
var stateChange = record.ChangeState(TableRegistrationRecord.RegistrationStateEnum.Accepted, operatorUid);
_appDbContext.StateChangeRecord.Add(stateChange);
record.Touch();

Original file line number Diff line number Diff line change
@@ -27,8 +27,8 @@ public ArtistsAlleyController(ITableRegistrationService tableRegistrationService
}

[HttpPut("{id}/:status")]
[Authorize(Roles = "Admin, ArtistAlleyAdmin")]
public async Task<ActionResult> PutTableRegistrationStatusAsync([EnsureNotNull] [FromRoute] Guid id,
[Authorize(Roles = "Admin, ArtistAlleyAdmin, ArtistAlleyModerator")]
public async Task<ActionResult> PutTableRegistrationStatusAsync([EnsureNotNull][FromRoute] Guid id,
[FromBody] TableRegistrationRecord.RegistrationStateEnum state)
{
TableRegistrationRecord record =
@@ -79,7 +79,7 @@ [EnsureNotNull][FromRoute] Guid id
return (await _tableRegistrationService.FindOneAsync(id)).Transient404(HttpContext);
}

[Authorize(Roles = "Attendee")]
[Authorize(Roles = "AttendeeCheckedIn")]
[HttpPost("TableRegistrationRequest")]
public async Task<ActionResult> PostTableRegistrationRequestAsync([EnsureNotNull][FromForm] TableRegistrationRequest request, IFormFile requestImageFile)
{
@@ -104,7 +104,7 @@ public async Task<ActionResult> PostTableRegistrationRequestAsync([EnsureNotNull
return NoContent();
}

[Authorize(Roles = "Attendee")]
[Authorize(Roles = "AttendeeCheckedIn")]
[HttpGet("TableRegistration/:my-latest")]
public async Task<TableRegistrationRecord> GetMyLatestTableRegistrationRequestAsync()
{
Original file line number Diff line number Diff line change
@@ -19,9 +19,10 @@ public class AuthorizationOptions

/// <summary>
/// Artist alley moderators may approve or reject table applications from attendees.
///
/// </summary>
public HashSet<string> ArtistAlleyModerator { get; set; } = new();

public HashSet<string> ArtistAlleyAdmin { get; set; } = new();

public HashSet<string> PrivateMessageSender { get; set; } = new();
}
Original file line number Diff line number Diff line change
@@ -86,6 +86,11 @@ public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
roles.Add("AttendeeCheckedIn");
}

if (authorizationOptions.Value.ArtistAlleyAdmin.Contains(claim.Value))
{
roles.Add("ArtistAlleyAdmin");
}

if (authorizationOptions.Value.ArtistAlleyModerator.Contains(claim.Value))
{
roles.Add("ArtistAlleyModerator");