From 7abcdcf8f1c57af1cd3738cf38471c3fae9f27d4 Mon Sep 17 00:00:00 2001 From: Christian Werner Date: Fri, 31 Oct 2025 13:28:14 +0100 Subject: [PATCH] add teanant apps list --- .../gandalf/mithrandir/dtos/app-list-dto.ts | 5 ++ .../{subject => }/dtos/subject-list-dto.ts | 0 .../{tenant => }/dtos/tenant-grid-view-dto.ts | 0 .../mithrandir/subject/subject.service.ts | 2 +- .../mithrandir/tenant/tenant.service.ts | 11 +++- .../app/app-list/app-list.component.html | 12 ++++ .../app/app-list/app-list.component.scss | 21 +++++++ .../app/app-list/app-list.component.spec.ts | 23 +++++++ .../app/app-list/app-list.component.ts | 36 +++++++++++ .../subject-list/subject-list.component.ts | 2 +- .../tenant-detail.component.html | 2 +- .../tenant-detail/tenant-detail.component.ts | 4 +- .../tenant-grid/tenant-grid.component.ts | 3 +- .../src/app/resolvers/tenantResolver.ts | 2 +- .../Controllers/TenantController.cs | 63 ++++++++++++++++--- .../Dto/App/AppListDto.cs | 10 +++ 16 files changed, 179 insertions(+), 17 deletions(-) create mode 100644 src/angular/frontend/src/app/clients/gandalf/mithrandir/dtos/app-list-dto.ts rename src/angular/frontend/src/app/clients/gandalf/mithrandir/{subject => }/dtos/subject-list-dto.ts (100%) rename src/angular/frontend/src/app/clients/gandalf/mithrandir/{tenant => }/dtos/tenant-grid-view-dto.ts (100%) create mode 100644 src/angular/frontend/src/app/components/app/app-list/app-list.component.html create mode 100644 src/angular/frontend/src/app/components/app/app-list/app-list.component.scss create mode 100644 src/angular/frontend/src/app/components/app/app-list/app-list.component.spec.ts create mode 100644 src/angular/frontend/src/app/components/app/app-list/app-list.component.ts create mode 100644 src/dotnet/Suspectus.Gandalf.Palantir.Data/Dto/App/AppListDto.cs diff --git a/src/angular/frontend/src/app/clients/gandalf/mithrandir/dtos/app-list-dto.ts b/src/angular/frontend/src/app/clients/gandalf/mithrandir/dtos/app-list-dto.ts new file mode 100644 index 0000000..22a44c7 --- /dev/null +++ b/src/angular/frontend/src/app/clients/gandalf/mithrandir/dtos/app-list-dto.ts @@ -0,0 +1,5 @@ +export interface AppListDto { + id: string; + name: string; + visibility: string; +} diff --git a/src/angular/frontend/src/app/clients/gandalf/mithrandir/subject/dtos/subject-list-dto.ts b/src/angular/frontend/src/app/clients/gandalf/mithrandir/dtos/subject-list-dto.ts similarity index 100% rename from src/angular/frontend/src/app/clients/gandalf/mithrandir/subject/dtos/subject-list-dto.ts rename to src/angular/frontend/src/app/clients/gandalf/mithrandir/dtos/subject-list-dto.ts diff --git a/src/angular/frontend/src/app/clients/gandalf/mithrandir/tenant/dtos/tenant-grid-view-dto.ts b/src/angular/frontend/src/app/clients/gandalf/mithrandir/dtos/tenant-grid-view-dto.ts similarity index 100% rename from src/angular/frontend/src/app/clients/gandalf/mithrandir/tenant/dtos/tenant-grid-view-dto.ts rename to src/angular/frontend/src/app/clients/gandalf/mithrandir/dtos/tenant-grid-view-dto.ts diff --git a/src/angular/frontend/src/app/clients/gandalf/mithrandir/subject/subject.service.ts b/src/angular/frontend/src/app/clients/gandalf/mithrandir/subject/subject.service.ts index 2d45df3..1ffbac2 100644 --- a/src/angular/frontend/src/app/clients/gandalf/mithrandir/subject/subject.service.ts +++ b/src/angular/frontend/src/app/clients/gandalf/mithrandir/subject/subject.service.ts @@ -1,7 +1,7 @@ import {Injectable} from "@angular/core"; import {lastValueFrom, Observable} from "rxjs"; import {PalantirService} from '../palantir.service'; -import {SubjectListDto} from './dtos/subject-list-dto'; +import {SubjectListDto} from '../dtos/subject-list-dto'; @Injectable({ providedIn: 'root' diff --git a/src/angular/frontend/src/app/clients/gandalf/mithrandir/tenant/tenant.service.ts b/src/angular/frontend/src/app/clients/gandalf/mithrandir/tenant/tenant.service.ts index 90db4ad..737e3d5 100644 --- a/src/angular/frontend/src/app/clients/gandalf/mithrandir/tenant/tenant.service.ts +++ b/src/angular/frontend/src/app/clients/gandalf/mithrandir/tenant/tenant.service.ts @@ -1,7 +1,8 @@ import {Injectable} from "@angular/core"; import {lastValueFrom, Observable, of} from "rxjs"; import {PalantirService} from '../palantir.service'; -import {TenantGridViewDto} from './dtos/tenant-grid-view-dto'; +import {AppListDto} from '../dtos/app-list-dto'; +import {TenantGridViewDto} from '../dtos/tenant-grid-view-dto'; @Injectable({ providedIn: 'root' @@ -30,4 +31,12 @@ export class TenantService extends PalantirService { getTenantAsync(id: string | null, onError?: (error: any) => void): Promise { return lastValueFrom(this.getTenant$(id, onError)); } + + getTenantApps$(id: string, onError?: (error: any) => void): Observable { + return this.handleRequest(this.http.get(this.baseUrl + `/${id}/apps`), onError); + } + + getTenantAppsAsync(id: string, onError?: (error: any) => void): Promise { + return lastValueFrom(this.getTenantApps$(id, onError)); + } } diff --git a/src/angular/frontend/src/app/components/app/app-list/app-list.component.html b/src/angular/frontend/src/app/components/app/app-list/app-list.component.html new file mode 100644 index 0000000..922fec3 --- /dev/null +++ b/src/angular/frontend/src/app/components/app/app-list/app-list.component.html @@ -0,0 +1,12 @@ +@if (loading()) { + Loading... +} @else { + @for (app of apps(); track app.id) { + + {{app.name}} + + + + + } +} diff --git a/src/angular/frontend/src/app/components/app/app-list/app-list.component.scss b/src/angular/frontend/src/app/components/app/app-list/app-list.component.scss new file mode 100644 index 0000000..69fa547 --- /dev/null +++ b/src/angular/frontend/src/app/components/app/app-list/app-list.component.scss @@ -0,0 +1,21 @@ +:host { + display: flex; + flex-direction: column; + gap: .5rem; + + app-panel { + display: flex; + align-items: center; + } + + .title { + + display: flex; + align-items: center; + + } + + .actions { + margin-left: auto; + } +} diff --git a/src/angular/frontend/src/app/components/app/app-list/app-list.component.spec.ts b/src/angular/frontend/src/app/components/app/app-list/app-list.component.spec.ts new file mode 100644 index 0000000..c47141b --- /dev/null +++ b/src/angular/frontend/src/app/components/app/app-list/app-list.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AppListComponent } from './app-list.component'; + +describe('AppListComponent', () => { + let component: AppListComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [AppListComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(AppListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/angular/frontend/src/app/components/app/app-list/app-list.component.ts b/src/angular/frontend/src/app/components/app/app-list/app-list.component.ts new file mode 100644 index 0000000..706edb7 --- /dev/null +++ b/src/angular/frontend/src/app/components/app/app-list/app-list.component.ts @@ -0,0 +1,36 @@ +import {Component, inject, input, OnInit, signal} from '@angular/core'; +import {AppListDto} from '../../../clients/gandalf/mithrandir/dtos/app-list-dto'; +import {TenantService} from '../../../clients/gandalf/mithrandir/tenant/tenant.service'; +import {PanelComponent} from '../../panel/panel.component'; +import {RouterLink} from '@angular/router'; + +@Component({ + selector: 'app-app-list', + imports: [ + PanelComponent, + RouterLink + ], + templateUrl: './app-list.component.html', + styleUrl: './app-list.component.scss', +}) +export class AppListComponent implements OnInit { + + public tenantId = input() + + protected loading = signal(true); + protected apps = signal([]); + + private tenantService = inject(TenantService); + + async ngOnInit(): Promise { + + const tenantId = this.tenantId(); + + if (tenantId) { + const apps = await this.tenantService.getTenantAppsAsync(tenantId); + this.apps.set(apps); + this.loading.set(false); + } + } + +} diff --git a/src/angular/frontend/src/app/components/subject/subject-list/subject-list.component.ts b/src/angular/frontend/src/app/components/subject/subject-list/subject-list.component.ts index d5dcbed..054ce47 100644 --- a/src/angular/frontend/src/app/components/subject/subject-list/subject-list.component.ts +++ b/src/angular/frontend/src/app/components/subject/subject-list/subject-list.component.ts @@ -3,7 +3,7 @@ import {PanelComponent} from '../../panel/panel.component'; import {RouterLink} from '@angular/router'; import {NgClass} from '@angular/common'; import {SubjectService} from '../../../clients/gandalf/mithrandir/subject/subject.service'; -import {SubjectListDto} from '../../../clients/gandalf/mithrandir/subject/dtos/subject-list-dto'; +import {SubjectListDto} from '../../../clients/gandalf/mithrandir/dtos/subject-list-dto'; @Component({ selector: 'app-subject-list', diff --git a/src/angular/frontend/src/app/components/tenant/tenant-detail/tenant-detail.component.html b/src/angular/frontend/src/app/components/tenant/tenant-detail/tenant-detail.component.html index 3e22eff..38801e9 100644 --- a/src/angular/frontend/src/app/components/tenant/tenant-detail/tenant-detail.component.html +++ b/src/angular/frontend/src/app/components/tenant/tenant-detail/tenant-detail.component.html @@ -5,7 +5,7 @@ - {{tab.name}} - content + diff --git a/src/angular/frontend/src/app/components/tenant/tenant-detail/tenant-detail.component.ts b/src/angular/frontend/src/app/components/tenant/tenant-detail/tenant-detail.component.ts index f98d67a..ebf0e21 100644 --- a/src/angular/frontend/src/app/components/tenant/tenant-detail/tenant-detail.component.ts +++ b/src/angular/frontend/src/app/components/tenant/tenant-detail/tenant-detail.component.ts @@ -3,13 +3,15 @@ import {TabGroup, TabGroupComponent} from '../../tab-group/tab-group.component'; import {ActiveTabDirective} from '../../tab-group/active-tab.directive'; import {ActivatedRoute, Router} from '@angular/router'; import {SubjectListComponent} from '../../subject/subject-list/subject-list.component'; +import {AppListComponent} from '../../app/app-list/app-list.component'; @Component({ selector: 'app-tenant-detail', imports: [ TabGroupComponent, ActiveTabDirective, - SubjectListComponent + SubjectListComponent, + AppListComponent ], templateUrl: './tenant-detail.component.html', styleUrl: './tenant-detail.component.scss', diff --git a/src/angular/frontend/src/app/components/tenant/tenant-grid/tenant-grid.component.ts b/src/angular/frontend/src/app/components/tenant/tenant-grid/tenant-grid.component.ts index 2455577..6f3a1a5 100644 --- a/src/angular/frontend/src/app/components/tenant/tenant-grid/tenant-grid.component.ts +++ b/src/angular/frontend/src/app/components/tenant/tenant-grid/tenant-grid.component.ts @@ -1,10 +1,9 @@ import {Component, inject, OnInit, signal} from '@angular/core'; import {TenantService} from '../../../clients/gandalf/mithrandir/tenant/tenant.service'; -import {TenantGridViewDto} from '../../../clients/gandalf/mithrandir/tenant/dtos/tenant-grid-view-dto'; import {PanelComponent} from '../../panel/panel.component'; -import {LinkComponent} from '../../link/link.component'; import {RouterLink} from '@angular/router'; import {NgClass} from '@angular/common'; +import {TenantGridViewDto} from '../../../clients/gandalf/mithrandir/dtos/tenant-grid-view-dto'; @Component({ selector: 'app-tenant-grid', diff --git a/src/angular/frontend/src/app/resolvers/tenantResolver.ts b/src/angular/frontend/src/app/resolvers/tenantResolver.ts index b5c4e7c..3d39513 100644 --- a/src/angular/frontend/src/app/resolvers/tenantResolver.ts +++ b/src/angular/frontend/src/app/resolvers/tenantResolver.ts @@ -1,8 +1,8 @@ import {ResolveFn} from "@angular/router"; -import {TenantGridViewDto} from "../clients/gandalf/mithrandir/tenant/dtos/tenant-grid-view-dto"; import {inject} from "@angular/core"; import {TenantService} from "../clients/gandalf/mithrandir/tenant/tenant.service"; import {map} from 'rxjs'; +import {TenantGridViewDto} from '../clients/gandalf/mithrandir/dtos/tenant-grid-view-dto'; export const tenantResolver: ResolveFn = (route, state) => { const tenantService = inject(TenantService); diff --git a/src/dotnet/Suspectus.Gandalf.Palantir.Api/Controllers/TenantController.cs b/src/dotnet/Suspectus.Gandalf.Palantir.Api/Controllers/TenantController.cs index 83a81c5..af64ea6 100644 --- a/src/dotnet/Suspectus.Gandalf.Palantir.Api/Controllers/TenantController.cs +++ b/src/dotnet/Suspectus.Gandalf.Palantir.Api/Controllers/TenantController.cs @@ -4,7 +4,9 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Suspectus.Gandalf.Palantir.Abstractions; using Suspectus.Gandalf.Palantir.Data.Database; +using Suspectus.Gandalf.Palantir.Data.Dto.App; using Suspectus.Gandalf.Palantir.Data.Dto.Tenant; +using Suspectus.Gandalf.Palantir.Data.Entities.Base; namespace Suspectus.Gandalf.Palantir.Api.Controllers; @@ -21,7 +23,7 @@ public class TenantController : ControllerBase _context = context; _hashids = hashids; } - + [HttpGet] public async Task Get(InvokerContext invokerContext, CancellationToken cancellationToken) { @@ -29,7 +31,7 @@ public class TenantController : ControllerBase .Where(x => x.Id!.Value == invokerContext.Invoker!.SubjectId) .SelectMany(x => x.Tenants) .ToListAsync(cancellationToken); - + var dtos = tenantEntities.Select(x => new TenantGridViewDto { Id = _hashids.EncodeLong(x.Id!.Value), @@ -41,23 +43,26 @@ public class TenantController : ControllerBase }); return Ok(dtos); } - + [HttpGet("{idHash}")] - public async Task Get(CancellationToken cancellationToken, string idHash, InvokerContext invokerContext) + public async Task Get(CancellationToken cancellationToken, string idHash, + InvokerContext invokerContext) { if (!_hashids.TryDecodeSingleLong(idHash, out var id)) { return BadRequest(); } - + var tenant = await _context.Tenants.SingleOrDefaultAsync(x => x.Id == id, cancellationToken); - + if (tenant is null) { return NotFound(); } - - var userHasRelation = await _context.TenantSubjectRelations.AnyAsync(x => x.SubjectId == invokerContext.Invoker!.SubjectId && x.TenantId == id, cancellationToken: cancellationToken); + + var userHasRelation = await _context.TenantSubjectRelations.AnyAsync( + x => x.SubjectId == invokerContext.Invoker!.SubjectId && x.TenantId == id, + cancellationToken: cancellationToken); if (!userHasRelation) { @@ -73,7 +78,47 @@ public class TenantController : ControllerBase OwnerId = _hashids.EncodeLong(tenant.OwnerId), Visibility = tenant.Visibility }; - + return Ok(dto); } + + [HttpGet("{tenantIdHash}/apps")] + public async Task GetTenantApps(InvokerContext invokerContext, string tenantIdHash, + CancellationToken cancellationToken) + { + if (!_hashids.TryDecodeSingleLong(tenantIdHash, out var tenantId)) + { + return BadRequest(); + } + + var tenantExists = await _context.Tenants.AnyAsync(x => x.Id == tenantId, cancellationToken); + + if (!tenantExists) + { + return NotFound(); + } + + var relationExists = await _context.TenantSubjectRelations + .AnyAsync(x => x.SubjectId == invokerContext.Invoker!.SubjectId, cancellationToken); + + if (!relationExists) + { + return Forbid(); + } + + var apps = await _context.AppSubjectRelations + .Where(x => x.SubjectId == invokerContext.Invoker!.SubjectId) + .Where(x => x.App!.TenantId == tenantId) + .Select(x => x.App!) + .ToListAsync(cancellationToken); + + var dtos = apps.Select(x => new AppListDto + { + Id = _hashids.EncodeLong(x.Id!.Value), + Name = x.Name, + Visibility = x.Visibility + }); + + return Ok(dtos); + } } \ No newline at end of file diff --git a/src/dotnet/Suspectus.Gandalf.Palantir.Data/Dto/App/AppListDto.cs b/src/dotnet/Suspectus.Gandalf.Palantir.Data/Dto/App/AppListDto.cs new file mode 100644 index 0000000..447afaa --- /dev/null +++ b/src/dotnet/Suspectus.Gandalf.Palantir.Data/Dto/App/AppListDto.cs @@ -0,0 +1,10 @@ +using Suspectus.Gandalf.Palantir.Data.Entities.Base; + +namespace Suspectus.Gandalf.Palantir.Data.Dto.App; + +public class AppListDto +{ + public required string Id { get; set; } + public required string Name { get; set; } + public required EntityVisibility Visibility { get; set; } +} \ No newline at end of file