add app detail
This commit is contained in:
parent
548d874221
commit
afc7662a62
@ -9,6 +9,8 @@ import {TenantGridComponent} from './components/tenant/tenant-grid/tenant-grid.c
|
||||
import {OutletComponent} from './components/outlet/outlet.component';
|
||||
import {tenantNameResolver, tenantResolver} from './resolvers/tenantResolver';
|
||||
import {TenantDetailComponent} from './components/tenant/tenant-detail/tenant-detail.component';
|
||||
import {appNameResolver} from './resolvers/app.resolver';
|
||||
import {AppDetailComponent} from './components/app/app-detail/app-detail.component';
|
||||
|
||||
export const routes: Routes = [
|
||||
{
|
||||
@ -109,9 +111,11 @@ export const routes: Routes = [
|
||||
path: 'app',
|
||||
children: [
|
||||
{
|
||||
title: 'App TODO',
|
||||
path: ':appId',
|
||||
component: HomeComponent
|
||||
component: AppDetailComponent,
|
||||
resolve: {
|
||||
title: appNameResolver
|
||||
}
|
||||
}
|
||||
],
|
||||
data: {
|
||||
|
||||
@ -0,0 +1,60 @@
|
||||
import {Injectable} from "@angular/core";
|
||||
import {lastValueFrom, Observable, of} from "rxjs";
|
||||
import {PalantirService} from '../palantir.service';
|
||||
import {AppListDto} from '../dtos/app-list-dto';
|
||||
import {AuthorityDto} from '../dtos/authority-dto';
|
||||
import {RoleDto} from '../dtos/role-dto';
|
||||
import {GroupDto} from '../dtos/group-dto';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class AppService extends PalantirService {
|
||||
|
||||
protected baseUrl = this.base + '/api/app';
|
||||
|
||||
tenants$(onError?: (error: any) => void): Observable<AppListDto[]> {
|
||||
return this.handleRequest(this.http.get<AppListDto[]>(this.baseUrl), onError);
|
||||
}
|
||||
|
||||
tenantsAsync(onError?: (error: any) => void): Promise<AppListDto[]> {
|
||||
return lastValueFrom(this.tenants$(onError));
|
||||
}
|
||||
|
||||
getTenant$(id: string | null, onError?: (error: any) => void): Observable<AppListDto | null> {
|
||||
|
||||
if (id === null) {
|
||||
return of(null);
|
||||
}
|
||||
|
||||
return this.handleRequest(this.http.get<AppListDto | null>(this.baseUrl + `/${id}`), onError);
|
||||
}
|
||||
|
||||
getTenantAsync(id: string | null, onError?: (error: any) => void): Promise<AppListDto | null> {
|
||||
return lastValueFrom(this.getTenant$(id, onError));
|
||||
}
|
||||
|
||||
getTenantAuthorities$(id: string, onError?: (error: any) => void): Observable<AuthorityDto[]> {
|
||||
return this.handleRequest(this.http.get<AuthorityDto[]>(this.baseUrl + `/${id}/authorities`), onError);
|
||||
}
|
||||
|
||||
getTenantAuthoritiesAsync(id: string, onError?: (error: any) => void): Promise<AuthorityDto[]> {
|
||||
return lastValueFrom(this.getTenantAuthorities$(id, onError));
|
||||
}
|
||||
|
||||
getTenantRoles$(id: string, onError?: (error: any) => void): Observable<RoleDto[]> {
|
||||
return this.handleRequest(this.http.get<RoleDto[]>(this.baseUrl + `/${id}/roles`), onError);
|
||||
}
|
||||
|
||||
getTenantRolesAsync(id: string, onError?: (error: any) => void): Promise<RoleDto[]> {
|
||||
return lastValueFrom(this.getTenantRoles$(id, onError));
|
||||
}
|
||||
|
||||
getTenantGroups$(id: string, onError?: (error: any) => void): Observable<GroupDto[]> {
|
||||
return this.handleRequest(this.http.get<GroupDto[]>(this.baseUrl + `/${id}/groups`), onError);
|
||||
}
|
||||
|
||||
getTenantGroupsAsync(id: string, onError?: (error: any) => void): Promise<GroupDto[]> {
|
||||
return lastValueFrom(this.getTenantGroups$(id, onError));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,30 @@
|
||||
<app-tab-group [tabs]="tabs" (tabChanged)="tabChanged($event)" [activeTabId]="activeTabId()">
|
||||
|
||||
<ng-template tabId="groups" let-tab>
|
||||
<app-list [itemTemplate]="authorityItem" [items]="groups()" [loading]="groupsLoading()" metadata="group"></app-list>
|
||||
</ng-template>
|
||||
|
||||
<ng-template tabId="roles" let-tab>
|
||||
<app-list [itemTemplate]="authorityItem" [items]="roles()" [loading]="rolesLoading()" metadata="role"></app-list>
|
||||
</ng-template>
|
||||
|
||||
<ng-template tabId="authorities" let-tab>
|
||||
<app-list [itemTemplate]="authorityItem" [items]="authorities()" [loading]="authoritiesLoading()" metadata="authority"></app-list>
|
||||
</ng-template>
|
||||
|
||||
</app-tab-group>
|
||||
|
||||
<ng-template #authorityItem let-item let-metadata="metadata">
|
||||
<app-panel class="neutral-80 authority-item">
|
||||
<span class="title">{{ item.name }}</span>
|
||||
|
||||
@if (item.description) {
|
||||
<span class="spacer">|</span>
|
||||
<span class="description">{{ item.description }}</span>
|
||||
}
|
||||
|
||||
<span class="actions">
|
||||
<button [routerLink]="metadata + '/' + item.id" class="primary outline">View Details</button>
|
||||
</span>
|
||||
</app-panel>
|
||||
</ng-template>
|
||||
@ -0,0 +1,14 @@
|
||||
:host {
|
||||
.authority-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.description, .spacer {
|
||||
color: var(--neutral-40);
|
||||
}
|
||||
|
||||
.actions {
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { AppDetailComponent } from './app-detail.component';
|
||||
|
||||
describe('AppDetailComponent', () => {
|
||||
let component: AppDetailComponent;
|
||||
let fixture: ComponentFixture<AppDetailComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [AppDetailComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(AppDetailComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,108 @@
|
||||
import {Component, inject, signal} from '@angular/core';
|
||||
import {TabGroup, TabGroupComponent} from '../../tab-group/tab-group.component';
|
||||
import {SubjectListComponent} from '../../subject/subject-list/subject-list.component';
|
||||
import {ListComponent} from '../../list/list.component';
|
||||
import {GroupDto} from '../../../clients/gandalf/mithrandir/dtos/group-dto';
|
||||
import {RoleDto} from '../../../clients/gandalf/mithrandir/dtos/role-dto';
|
||||
import {AuthorityDto} from '../../../clients/gandalf/mithrandir/dtos/authority-dto';
|
||||
import {ActivatedRoute, Router, RouterLink} from '@angular/router';
|
||||
import {TenantService} from '../../../clients/gandalf/mithrandir/tenant/tenant.service';
|
||||
import {PanelComponent} from '../../panel/panel.component';
|
||||
import {AppService} from '../../../clients/gandalf/mithrandir/app/app.service';
|
||||
import {ActiveTabDirective} from '../../tab-group/active-tab.directive';
|
||||
|
||||
@Component({
|
||||
selector: 'app-app-detail',
|
||||
imports: [
|
||||
TabGroupComponent,
|
||||
SubjectListComponent,
|
||||
ListComponent,
|
||||
PanelComponent,
|
||||
RouterLink,
|
||||
ActiveTabDirective
|
||||
],
|
||||
templateUrl: './app-detail.component.html',
|
||||
styleUrl: './app-detail.component.scss',
|
||||
})
|
||||
export class AppDetailComponent {
|
||||
|
||||
protected tabs = [
|
||||
{
|
||||
id: "groups",
|
||||
name: "Groups"
|
||||
} satisfies TabGroup,
|
||||
{
|
||||
id: "roles",
|
||||
name: "Roles"
|
||||
} satisfies TabGroup,
|
||||
{
|
||||
id: "authorities",
|
||||
name: "Authorities"
|
||||
} satisfies TabGroup
|
||||
]
|
||||
|
||||
protected activeTabId = signal<string | null>(null)
|
||||
protected appId = signal<string | null>(null)
|
||||
|
||||
protected groups = signal<GroupDto[]>([]);
|
||||
protected groupsLoading = signal<boolean>(true);
|
||||
protected roles = signal<RoleDto[]>([]);
|
||||
protected rolesLoading = signal<boolean>(true);
|
||||
protected authorities = signal<AuthorityDto[]>([]);
|
||||
protected authoritiesLoading = signal<boolean>(true);
|
||||
|
||||
private route = inject(ActivatedRoute);
|
||||
private router = inject(Router)
|
||||
private appService = inject(AppService);
|
||||
|
||||
constructor() {
|
||||
const routeSnapshot = this.route.snapshot;
|
||||
const appId = routeSnapshot.paramMap.get('appId');
|
||||
const tabId = routeSnapshot.queryParamMap.get('tab');
|
||||
this.activeTabId.set(tabId)
|
||||
this.appId.set(appId);
|
||||
}
|
||||
|
||||
async tabChanged(tab: TabGroup) {
|
||||
await this.router.navigate([], {
|
||||
relativeTo: this.route,
|
||||
queryParams: {
|
||||
tab: tab.id
|
||||
},
|
||||
queryParamsHandling: 'merge',
|
||||
skipLocationChange: false,
|
||||
replaceUrl: true
|
||||
});
|
||||
|
||||
const tenantId = this.appId();
|
||||
|
||||
if (!tenantId) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (tab.id) {
|
||||
case 'groups':
|
||||
if (this.groupsLoading()) {
|
||||
const groups = await this.appService.getTenantGroupsAsync(tenantId);
|
||||
this.groups.set(groups);
|
||||
this.groupsLoading.set(false);
|
||||
}
|
||||
break;
|
||||
case 'roles':
|
||||
if (this.rolesLoading()) {
|
||||
const roles = await this.appService.getTenantRolesAsync(tenantId);
|
||||
this.roles.set(roles);
|
||||
this.rolesLoading.set(false);
|
||||
}
|
||||
break;
|
||||
case 'authorities':
|
||||
if (this.authoritiesLoading()) {
|
||||
const authorities = await this.appService.getTenantAuthoritiesAsync(tenantId);
|
||||
this.authorities.set(authorities);
|
||||
this.authoritiesLoading.set(false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
@ -20,7 +20,7 @@
|
||||
|
||||
<span> / </span>
|
||||
|
||||
@for (crumb of breadcrumbs(); let last = $last; track crumb.label) {
|
||||
@for (crumb of breadcrumbs(); let last = $last; track crumb.id) {
|
||||
|
||||
@if (crumb.url && !last) {
|
||||
<app-link [href]="crumb.url">{{ crumb.label }}</app-link>
|
||||
|
||||
@ -69,7 +69,8 @@ export class ShellComponent implements OnInit {
|
||||
.filter(r => r.path === '')
|
||||
.flatMap(r => r.children ?? [])
|
||||
.filter(r => r.data?.['showInNav'])
|
||||
.map(r => ({
|
||||
.map((r, id) => ({
|
||||
id,
|
||||
label: r.data?.['title'] || r.title || '',
|
||||
url: '/' + (r.path ?? '')
|
||||
}));
|
||||
@ -105,10 +106,8 @@ export class ShellComponent implements OnInit {
|
||||
|| child.snapshot.data['title']
|
||||
|| '';
|
||||
|
||||
|
||||
|
||||
if (label) {
|
||||
breadcrumbs.push({ label, url });
|
||||
breadcrumbs.push({ id: breadcrumbs.length, label, url});
|
||||
}
|
||||
|
||||
return this.buildBreadcrumbs(child, url, breadcrumbs);
|
||||
@ -116,6 +115,7 @@ export class ShellComponent implements OnInit {
|
||||
}
|
||||
|
||||
export class Breadcrumb {
|
||||
id: number = 0;
|
||||
label: string = 'undefined';
|
||||
url: string = '';
|
||||
}
|
||||
|
||||
21
src/angular/frontend/src/app/resolvers/app.resolver.ts
Normal file
21
src/angular/frontend/src/app/resolvers/app.resolver.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import {ResolveFn} from '@angular/router';
|
||||
import {inject} from '@angular/core';
|
||||
import {map} from 'rxjs';
|
||||
import {AppListDto} from '../clients/gandalf/mithrandir/dtos/app-list-dto';
|
||||
import {AppService} from '../clients/gandalf/mithrandir/app/app.service';
|
||||
|
||||
export const appResolver: ResolveFn<AppListDto | null> = (route, state) => {
|
||||
const appService = inject(AppService);
|
||||
|
||||
const id = route.paramMap.get('appId')
|
||||
|
||||
return appService.getTenant$(id);
|
||||
};
|
||||
|
||||
export const appNameResolver: ResolveFn<string> = (route, state) => {
|
||||
const appService = inject(AppService);
|
||||
|
||||
const id = route.paramMap.get('appId')
|
||||
|
||||
return appService.getTenant$(id).pipe(map(app => app?.name ?? 'Unnamed App'));
|
||||
};
|
||||
@ -1,17 +0,0 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { ResolveFn } from '@angular/router';
|
||||
|
||||
import { subjectResolver } from './subject.resolver';
|
||||
|
||||
describe('subjectResolver', () => {
|
||||
const executeResolver: ResolveFn<boolean> = (...resolverParameters) =>
|
||||
TestBed.runInInjectionContext(() => subjectResolver(...resolverParameters));
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(executeResolver).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,210 @@
|
||||
using HashidsNet;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
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.Group;
|
||||
using Suspectus.Gandalf.Palantir.Data.Dto.Role;
|
||||
using Suspectus.Gandalf.Palantir.Data.Dto.Tenant;
|
||||
using Suspectus.Gandalf.Palantir.Data.Entities.Base;
|
||||
using Suspectus.Gandalf.Palantir.Data.Entities.Security.Permission.Data;
|
||||
|
||||
namespace Suspectus.Gandalf.Palantir.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
[Authorize]
|
||||
public class AppController : ControllerBase
|
||||
{
|
||||
private readonly ApplicationContext _context;
|
||||
private readonly IHashids _hashids;
|
||||
|
||||
public AppController(ApplicationContext context, IHashids hashids)
|
||||
{
|
||||
_context = context;
|
||||
_hashids = hashids;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> Get(InvokerContext invokerContext, CancellationToken cancellationToken)
|
||||
{
|
||||
var appEntities = await _context.Subjects
|
||||
.Where(x => x.Id!.Value == invokerContext.Invoker!.SubjectId)
|
||||
.SelectMany(x => x.Apps)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
var dtos = appEntities.Select(x => new AppListDto
|
||||
{
|
||||
Id = _hashids.EncodeLong(x.Id!.Value),
|
||||
Name = x.Name,
|
||||
Visibility = x.Visibility
|
||||
});
|
||||
|
||||
return Ok(dtos);
|
||||
}
|
||||
|
||||
[HttpGet("{idHash}")]
|
||||
public async Task<IActionResult> GetById(string idHash, InvokerContext invokerContext, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!_hashids.TryDecodeSingleLong(idHash, out var appId))
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
var appEntity = await _context.Apps.SingleOrDefaultAsync(x => x.Id == appId, cancellationToken);
|
||||
|
||||
if (appEntity is null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var userHasRelation = await _context.AppSubjectRelations.AnyAsync(
|
||||
x => x.SubjectId == invokerContext.Invoker!.SubjectId && x.AppId == appId,
|
||||
cancellationToken: cancellationToken);
|
||||
|
||||
if (!userHasRelation)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
|
||||
var dto = new AppListDto
|
||||
{
|
||||
Id = _hashids.EncodeLong(appEntity.Id!.Value),
|
||||
Name = appEntity.Name,
|
||||
Visibility = appEntity.Visibility
|
||||
};
|
||||
|
||||
return Ok(dto);
|
||||
}
|
||||
|
||||
[HttpGet("{appIdHash}/groups")]
|
||||
public async Task<IActionResult> GetTenantGroups(InvokerContext invokerContext, string appIdHash,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (!_hashids.TryDecodeSingleLong(appIdHash, out var appId))
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
var appExists = await _context.Apps.AnyAsync(x => x.Id == appId, cancellationToken);
|
||||
|
||||
if (!appExists)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var relationExists = await _context.AppSubjectRelations
|
||||
.AnyAsync(x => x.SubjectId == invokerContext.Invoker!.SubjectId && x.AppId == appId, cancellationToken);
|
||||
|
||||
if (!relationExists)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
|
||||
var groups = await _context.Groups
|
||||
.Where(x => x.AppId == appId && x.Type == AuthorityType.App)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
var dtos = groups.Select(x => new GroupDto
|
||||
{
|
||||
Id = _hashids.EncodeLong(x.Id!.Value),
|
||||
Name = x.Name,
|
||||
Visibility = x.Visibility,
|
||||
TenantId = _hashids.EncodeLong(x.TenantId),
|
||||
AppId = _hashids.EncodeLong(x.AppId!.Value),
|
||||
CategoryPath = x.CategoryPath,
|
||||
Description = x.Description,
|
||||
Type = x.Type
|
||||
});
|
||||
|
||||
return Ok(dtos);
|
||||
}
|
||||
|
||||
[HttpGet("{appIdHash}/roles")]
|
||||
public async Task<IActionResult> GetTenantRoles(InvokerContext invokerContext, string appIdHash,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (!_hashids.TryDecodeSingleLong(appIdHash, out var appId))
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
var appExists = await _context.Apps.AnyAsync(x => x.Id == appId, cancellationToken);
|
||||
|
||||
if (!appExists)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var relationExists = await _context.AppSubjectRelations
|
||||
.AnyAsync(x => x.SubjectId == invokerContext.Invoker!.SubjectId && x.AppId == appId, cancellationToken);
|
||||
|
||||
if (!relationExists)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
|
||||
var groups = await _context.Roles
|
||||
.Where(x => x.AppId == appId && x.Type == AuthorityType.App)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
var dtos = groups.Select(x => new RoleDto
|
||||
{
|
||||
Id = _hashids.EncodeLong(x.Id!.Value),
|
||||
Name = x.Name,
|
||||
Visibility = x.Visibility,
|
||||
TenantId = _hashids.EncodeLong(x.TenantId),
|
||||
AppId = _hashids.EncodeLong(x.AppId!.Value),
|
||||
CategoryPath = x.CategoryPath,
|
||||
Description = x.Description,
|
||||
Type = x.Type
|
||||
});
|
||||
|
||||
return Ok(dtos);
|
||||
}
|
||||
|
||||
[HttpGet("{appIdHash}/authorities")]
|
||||
public async Task<IActionResult> GetTenantAuthorities(InvokerContext invokerContext, string appIdHash,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (!_hashids.TryDecodeSingleLong(appIdHash, out var appId))
|
||||
{
|
||||
return BadRequest();
|
||||
}
|
||||
|
||||
var appExists = await _context.Apps.AnyAsync(x => x.Id == appId, cancellationToken);
|
||||
|
||||
if (!appExists)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
var relationExists = await _context.AppSubjectRelations
|
||||
.AnyAsync(x => x.SubjectId == invokerContext.Invoker!.SubjectId && x.AppId == appId, cancellationToken);
|
||||
|
||||
if (!relationExists)
|
||||
{
|
||||
return Forbid();
|
||||
}
|
||||
|
||||
var groups = await _context.Authorities
|
||||
.Where(x => x.AppId == appId && x.Type == AuthorityType.App)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
var dtos = groups.Select(x => new GroupDto
|
||||
{
|
||||
Id = _hashids.EncodeLong(x.Id!.Value),
|
||||
Name = x.Name,
|
||||
Visibility = x.Visibility,
|
||||
TenantId = _hashids.EncodeLong(x.TenantId),
|
||||
AppId = _hashids.EncodeLong(x.AppId!.Value),
|
||||
CategoryPath = x.CategoryPath,
|
||||
Description = x.Description,
|
||||
Type = x.Type
|
||||
});
|
||||
|
||||
return Ok(dtos);
|
||||
}
|
||||
}
|
||||
@ -142,7 +142,7 @@ public class TenantController : ControllerBase
|
||||
}
|
||||
|
||||
var relationExists = await _context.TenantSubjectRelations
|
||||
.AnyAsync(x => x.SubjectId == invokerContext.Invoker!.SubjectId, cancellationToken);
|
||||
.AnyAsync(x => x.SubjectId == invokerContext.Invoker!.SubjectId && x.TenantId == tenantId, cancellationToken);
|
||||
|
||||
if (!relationExists)
|
||||
{
|
||||
@ -184,7 +184,7 @@ public class TenantController : ControllerBase
|
||||
}
|
||||
|
||||
var relationExists = await _context.TenantSubjectRelations
|
||||
.AnyAsync(x => x.SubjectId == invokerContext.Invoker!.SubjectId, cancellationToken);
|
||||
.AnyAsync(x => x.SubjectId == invokerContext.Invoker!.SubjectId && x.TenantId == tenantId, cancellationToken);
|
||||
|
||||
if (!relationExists)
|
||||
{
|
||||
@ -226,7 +226,7 @@ public class TenantController : ControllerBase
|
||||
}
|
||||
|
||||
var relationExists = await _context.TenantSubjectRelations
|
||||
.AnyAsync(x => x.SubjectId == invokerContext.Invoker!.SubjectId, cancellationToken);
|
||||
.AnyAsync(x => x.SubjectId == invokerContext.Invoker!.SubjectId && x.TenantId == tenantId, cancellationToken);
|
||||
|
||||
if (!relationExists)
|
||||
{
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user