Skip to content

Commit

Permalink
User admin (#233)
Browse files Browse the repository at this point in the history
  • Loading branch information
tschumpr authored Jul 29, 2024
2 parents 1fdf195 + 6233267 commit f9cf58c
Show file tree
Hide file tree
Showing 12 changed files with 353 additions and 12 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- Added separate administration area and user navigation menu to switch between delivery, administration and STAC browser.
- Added grid to manage mandates in administration area.
- Added grid to manage organisations in administration area.
- Added grid to manage users in administration area.

### Changed

Expand Down
13 changes: 13 additions & 0 deletions src/Geopilot.Api/Context.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,19 @@ public Context(DbContextOptions<Context> options)
/// </summary>
public DbSet<User> Users { get; set; }

/// <summary>
/// Gets the <see cref="User"/> entity with all includes.
/// </summary>
public IQueryable<User> UsersWithIncludes
{
get
{
return Users
.Include(u => u.Organisations)
.Include(u => u.Deliveries);
}
}

/// <summary>
/// Set of all <see cref="Organisation"/>.
/// </summary>
Expand Down
72 changes: 70 additions & 2 deletions src/Geopilot.Api/Controllers/UserController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,7 @@ public List<User> Get()
{
logger.LogInformation("Getting users.");

return context.Users
.Include(u => u.Organisations)
return context.UsersWithIncludes
.AsNoTracking()
.ToList();
}
Expand All @@ -71,6 +70,25 @@ public List<User> Get()
return user;
}

/// <summary>
/// Get a user with the specified <paramref name="id"/>.
/// </summary>
[HttpGet("{id}")]
[Authorize(Policy = GeopilotPolicies.Admin)]
[SwaggerResponse(StatusCodes.Status200OK, "Returns the user with the specified id.", typeof(User), new[] { "application/json" })]
[SwaggerResponse(StatusCodes.Status404NotFound, "The user could not be found.")]
public async Task<IActionResult> GetById(int id)
{
var user = await context.UsersWithIncludes
.AsNoTracking()
.SingleOrDefaultAsync(u => u.Id == id);

if (user == default)
return NotFound();

return Ok(user);
}

/// <summary>
/// Gets the specified auth options.
/// </summary>
Expand All @@ -83,4 +101,54 @@ public BrowserAuthOptions GetAuthOptions()
logger.LogInformation("Getting auth options.");
return authOptions;
}

/// <summary>
/// Asynchronously updates the <paramref name="user"/> specified.
/// </summary>
/// <param name="user">The user to update.</param>
[HttpPut]
[Authorize(Policy = GeopilotPolicies.Admin)]
[SwaggerResponse(StatusCodes.Status200OK, "Returns the updated user.", typeof(User), new[] { "application/json" })]
[SwaggerResponse(StatusCodes.Status404NotFound, "The user could not be found.")]
[SwaggerResponse(StatusCodes.Status400BadRequest, "The user could not be updated due to invalid input.")]
[SwaggerResponse(StatusCodes.Status401Unauthorized, "The current user is not authorized to update the user.")]
[SwaggerResponse(StatusCodes.Status500InternalServerError, "The user could not be updated due to an internal server error.", typeof(ProblemDetails), new[] { "application/json" })]
public async Task<IActionResult> Edit(User user)
{
try
{
if (user == null)
return BadRequest();

var existingUser = await context.UsersWithIncludes.SingleOrDefaultAsync(u => u.Id == user.Id);

if (existingUser == null)
return NotFound();

existingUser.IsAdmin = user.IsAdmin;

var organisationIds = user.Organisations.Select(o => o.Id).ToList();
var organisations = await context.Organisations
.Where(o => organisationIds.Contains(o.Id))
.ToListAsync();
existingUser.Organisations.Clear();
foreach (var organisation in organisations)
{
existingUser.Organisations.Add(organisation);
}

await context.SaveChangesAsync().ConfigureAwait(false);

var result = await context.UsersWithIncludes
.AsNoTracking()
.FirstOrDefaultAsync(u => u.Id == user.Id);

return Ok(result);
}
catch (Exception e)
{
logger.LogError(e, "An error occurred while updating the user.");
return Problem(e.Message);
}
}
}
5 changes: 5 additions & 0 deletions src/Geopilot.Frontend/public/locale/de/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"dropZoneErrorNotSupported": "Der Dateityp wird nicht unterstützt. {{genericError}}",
"dropZoneErrorTooManyFiles": "Es kann nur eine Datei aufs Mal geprüft werden.",
"edit": "Bearbeiten",
"email": "E-Mail",
"errors": "Fehler",
"file": "Datei",
"fileTypes": "Formate",
Expand All @@ -43,6 +44,7 @@
"id": "ID",
"impressum": "Impressum",
"info": "Info",
"isAdmin": "Ist Admin",
"latitude": "Breite",
"licenses": "Lizenzen",
"licenseInformation": "Lizenzinformationen",
Expand Down Expand Up @@ -77,6 +79,9 @@
"type": "Typ",
"uploadFile": "{{fileName}} hochladen...",
"uploadNotSuccessful": "Der Upload war nicht erfolgreich. Die Validierung wurde abgebrochen.",
"userDisconnectMessage": "Der Benutzer wird von allen Organisationen getrennt. Diese Aktion kann nicht rückgängig gemacht werden.",
"userDisconnectTitle": "Möchten Sie den Benutzer wirklich inaktiv setzen?",
"userSaveError": "Beim Speichern des Benutzers ist ein Fehler aufgetreten: {{error}}",
"users": "Benutzer:innen",
"usersLoadingError": "Beim Laden der Benutzer:innen ist ein Fehler aufgetreten: {{error}}",
"validate": "Validieren",
Expand Down
5 changes: 5 additions & 0 deletions src/Geopilot.Frontend/public/locale/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"dropZoneErrorNotSupported": "The file type is not supported. {{genericError}}",
"dropZoneErrorTooManyFiles": "Only one file can be checked at a time.",
"edit": "Edit",
"email": "Email",
"errors": "Errors",
"file": "File",
"fileTypes": "Formats",
Expand All @@ -43,6 +44,7 @@
"id": "ID",
"impressum": "Imprint",
"info": "Info",
"isAdmin": "Is Admin",
"latitude": "Latitude",
"licenses": "Licenses",
"licenseInformation": "License Information",
Expand Down Expand Up @@ -77,6 +79,9 @@
"type": "Type",
"uploadFile": "Upload {{fileName}}...",
"uploadNotSuccessful": "The upload was not successful. Validation has been aborted.",
"userDisconnectMessage": "This will remove all connections to organisations and cannot be undone.",
"userDisconnectTitle": "Do you really want to disconnect the user?",
"userSaveError": "An error occurred while saving the user: {{error}}",
"users": "Users",
"usersLoadingError": "An error occurred while loading the users: {{error}}",
"validate": "Validate",
Expand Down
9 changes: 7 additions & 2 deletions src/Geopilot.Frontend/public/locale/fr/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"dropZoneErrorNotSupported": "Le type de fichier n'est pas pris en charge. {{genericError}}",
"dropZoneErrorTooManyFiles": "Seul un fichier peut être vérifié à la fois.",
"edit": "Éditer",
"email": "E-mail",
"errors": "Erreurs",
"file": "Fichier",
"fileTypes": "Formats",
Expand All @@ -43,6 +44,7 @@
"id": "ID",
"impressum": "Mentions légales",
"info": "Info",
"isAdmin": "Est administrateur",
"latitude": "Latitude",
"licenses": "Licences",
"licenseInformation": "Informations sur la licence",
Expand All @@ -52,14 +54,14 @@
"logOut": "Se déconnecter",
"longitude": "Longitude",
"mandate": "Mandat",
"mandateDisconnectMessage": "Cette opération supprime toutes les connexions avec les organisations et ne peut être annulée.",
"mandateDisconnectMessage": "Cette opération supprime toutes les connexions avec les organisations. Cette action ne peut pas être annulée.",
"mandateDisconnectTitle": "Voulez-vous vraiment déconnecter le mandat?",
"mandateSaveError": "Une erreur s'est produite lors de l'enregistrement du mandat: {{error}}",
"mandates": "Mandats",
"mandatesLoadingError": "Une erreur s'est produite lors du chargement des mandats: {{error}}",
"name": "Nom",
"noErrors": "Pas d'erreurs",
"organisationDisconnectMessage": "Cette action supprime toutes les connexions avec les mandats et les utilisateurs, et ne peut être annulée.",
"organisationDisconnectMessage": "Cette action supprime toutes les connexions avec les mandats et les utilisateurs. Cette action ne peut pas être annulée.",
"organisationDisconnectTitle": "Voulez-vous vraiment déconnecter l'organisation?",
"organisationSaveError": "Une erreur s'est produite lors de l'enregistrement de l'organisation: {{error}}",
"organisations": "Organisations",
Expand All @@ -77,6 +79,9 @@
"type": "Type",
"uploadFile": "Télécharger {{fileName}}...",
"uploadNotSuccessful": "Le téléchargement n'a pas réussi. La validation a été annulée.",
"userDisconnectMessage": "Cet utilisateur sera déconnecté de toutes les organisations. Cette action ne peut pas être annulée.",
"userDisconnectTitle": "Voulez-vous vraiment déconnecter l'utilisateur?",
"userSaveError": "Une erreur s'est produite lors de l'enregistrement de l'utilisateur: {{error}}",
"users": "Utilisateurs",
"usersLoadingError": "Une erreur s'est produite lors du chargement des utilisateurs: {{error}}",
"validate": "Valider",
Expand Down
5 changes: 5 additions & 0 deletions src/Geopilot.Frontend/public/locale/it/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"dropZoneErrorNotSupported": "Tipo di file non supportato. {{genericError}}",
"dropZoneErrorTooManyFiles": "Puoi verificare solo un file alla volta.",
"edit": "Modifica",
"email": "E-mail",
"errors": "Errori",
"file": "File",
"fileTypes": "Formati",
Expand All @@ -43,6 +44,7 @@
"id": "ID",
"impressum": "Impressum",
"info": "Informazioni",
"isAdmin": "È amministratore",
"latitude": "Latitudine",
"licenses": "Licenze",
"licenseInformation": "Informazioni sulla licenza",
Expand Down Expand Up @@ -77,6 +79,9 @@
"type": "Tipo",
"uploadFile": "Carica {{fileName}}...",
"uploadNotSuccessful": "Caricamento non riuscito. La validazione è stata interrotta.",
"userDisconnectMessage": "Questa operazione rimuove tutti i collegamenti alle organizzazioni e non può essere annullata.",
"userDisconnectTitle": "Volete davvero scollegare l'utente?",
"userSaveError": "Si è verificato un errore durante il salvataggio dell'utente: {{error}}",
"users": "Utenti",
"usersLoadingError": "Si è verificato un errore durante il caricamento degli utenti: {{error}}",
"validate": "Valida",
Expand Down
2 changes: 1 addition & 1 deletion src/Geopilot.Frontend/src/AppInterfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,6 @@ export interface User {
fullName: string;
isAdmin: boolean;
email: string;
organisations: Organisation[];
organisations: Organisation[] | number[];
deliveries: Delivery[];
}
6 changes: 5 additions & 1 deletion src/Geopilot.Frontend/src/components/adminGrid/AdminGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import {
} from "../dataGrid/DataGridMultiSelectColumn.tsx";
import { IsGridSpatialExtentColDef, TransformToSpatialExtentColumn } from "../dataGrid/DataGridSpatialExtentColumn.tsx";

export const AdminGrid: FC<AdminGridProps> = ({ addLabel, data, columns, onSave, onDisconnect }) => {
export const AdminGrid: FC<AdminGridProps> = ({ addLabel, data, columns, onSave, onDisconnect, disableRow }) => {
const { t } = useTranslation();
const [rows, setRows] = useState<DataRow[]>([]);
const [rowModesModel, setRowModesModel] = useState<GridRowModesModel>({});
Expand All @@ -51,6 +51,10 @@ export const AdminGrid: FC<AdminGridProps> = ({ addLabel, data, columns, onSave,
resizable: false,
cellClassName: "actions",
getActions: ({ id }) => {
if (id === disableRow) {
return [];
}

const isInEditMode = rowModesModel[id]?.mode === GridRowModes.Edit;

if (isInEditMode) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export interface AdminGridProps {
columns: GridColDef[];
onSave: (row: DataRow) => void | Promise<void>;
onDisconnect: (row: DataRow) => void | Promise<void>;
disableRow?: number;
}

export interface DataRow {
Expand Down
Loading

0 comments on commit f9cf58c

Please sign in to comment.