yes
This commit is contained in:
parent
7abcdcf8f1
commit
548d874221
@ -92,56 +92,32 @@ export const routes: Routes = [
|
||||
tenant: tenantResolver,
|
||||
title: tenantNameResolver,
|
||||
},
|
||||
children: [
|
||||
},
|
||||
{
|
||||
title: 'Subject TODO',
|
||||
path: 'subject/:subjectId',
|
||||
component: HomeComponent
|
||||
},
|
||||
{
|
||||
path: ':id',
|
||||
resolve: {
|
||||
title: tenantNameResolver,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
title: 'Apps',
|
||||
path: 'apps',
|
||||
component: HomeComponent,
|
||||
path: 'app',
|
||||
children: [
|
||||
{
|
||||
title: 'App TODO',
|
||||
path: ':id',
|
||||
component: HomeComponent,
|
||||
children: [
|
||||
{
|
||||
title: 'Groups',
|
||||
path: 'groups',
|
||||
component: HomeComponent,
|
||||
},
|
||||
{
|
||||
title: 'Roles',
|
||||
path: 'roles',
|
||||
component: HomeComponent,
|
||||
},
|
||||
{
|
||||
title: 'Authorities',
|
||||
path: 'authorities',
|
||||
component: HomeComponent,
|
||||
},
|
||||
]
|
||||
path: ':appId',
|
||||
component: HomeComponent
|
||||
}
|
||||
],
|
||||
data: {
|
||||
breadcrumbUrl: '?tab=apps'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Groups',
|
||||
path: 'groups',
|
||||
component: HomeComponent,
|
||||
},
|
||||
{
|
||||
title: 'Roles',
|
||||
path: 'roles',
|
||||
component: HomeComponent,
|
||||
},
|
||||
{
|
||||
title: 'Authorities',
|
||||
path: 'authorities',
|
||||
component: HomeComponent,
|
||||
},
|
||||
]
|
||||
},
|
||||
]
|
||||
|
||||
@ -0,0 +1,12 @@
|
||||
import {Identifiable} from '../../../../components/list/list.component';
|
||||
|
||||
export interface AuthorityDto extends Identifiable {
|
||||
id: string;
|
||||
tenantId: string;
|
||||
name: string;
|
||||
categoryPath: string;
|
||||
description: string;
|
||||
type: string;
|
||||
visibility: string;
|
||||
appId?: string;
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
import {AuthorityDto} from './authority-dto';
|
||||
|
||||
export interface GroupDto extends AuthorityDto {}
|
||||
@ -0,0 +1,3 @@
|
||||
import {AuthorityDto} from './authority-dto';
|
||||
|
||||
export interface RoleDto extends AuthorityDto {}
|
||||
@ -3,6 +3,9 @@ import {lastValueFrom, Observable, of} from "rxjs";
|
||||
import {PalantirService} from '../palantir.service';
|
||||
import {AppListDto} from '../dtos/app-list-dto';
|
||||
import {TenantGridViewDto} from '../dtos/tenant-grid-view-dto';
|
||||
import {AuthorityDto} from '../dtos/authority-dto';
|
||||
import {RoleDto} from '../dtos/role-dto';
|
||||
import {GroupDto} from '../dtos/group-dto';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
@ -39,4 +42,28 @@ export class TenantService extends PalantirService {
|
||||
getTenantAppsAsync(id: string, onError?: (error: any) => void): Promise<AppListDto[]> {
|
||||
return lastValueFrom(this.getTenantApps$(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));
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import {Component, inject, input} from '@angular/core';
|
||||
import {Router} from '@angular/router';
|
||||
import {ActivatedRoute, Router} from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-link',
|
||||
@ -13,11 +13,39 @@ export class LinkComponent {
|
||||
public external = input<boolean>(false);
|
||||
|
||||
private router = inject(Router);
|
||||
private route = inject(ActivatedRoute);
|
||||
|
||||
click($event: PointerEvent) {
|
||||
|
||||
if (!this.external()) {
|
||||
$event.preventDefault();
|
||||
this.router.navigate([this.href()]).then();
|
||||
|
||||
const link = this.href();
|
||||
const split = link.split('?');
|
||||
const path = split[0]
|
||||
const queryParamsStrings = split[1]?.split('&');
|
||||
|
||||
const queryParams: {[key: string]: string} = {};
|
||||
|
||||
if (queryParamsStrings) {
|
||||
for (const queryParamString of queryParamsStrings) {
|
||||
const splitQueryParam = queryParamString.split('=');
|
||||
const key = splitQueryParam[0] as string;
|
||||
const value = splitQueryParam[1];
|
||||
|
||||
if (key && value) {
|
||||
queryParams[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.router.navigate([path], {
|
||||
relativeTo: this.route,
|
||||
queryParams: queryParams,
|
||||
queryParamsHandling: 'merge',
|
||||
skipLocationChange: false,
|
||||
replaceUrl: false
|
||||
}).then();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -0,0 +1,13 @@
|
||||
@if (loading()) {
|
||||
Loading...
|
||||
} @else {
|
||||
|
||||
@if (items().length === 0) {
|
||||
<span>List is empty :(</span>
|
||||
}
|
||||
@else {
|
||||
@for (item of items(); track item.id) {
|
||||
<ng-container *ngTemplateOutlet="itemTemplate(); context: { $implicit: item, metadata: metadata() }"></ng-container>
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ListComponent } from './list.component';
|
||||
|
||||
describe('ListComponent', () => {
|
||||
let component: ListComponent;
|
||||
let fixture: ComponentFixture<ListComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [ListComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ListComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,25 @@
|
||||
import {Component, input, TemplateRef} from '@angular/core';
|
||||
import {PanelComponent} from '../panel/panel.component';
|
||||
import {NgTemplateOutlet} from '@angular/common';
|
||||
|
||||
export interface Identifiable {
|
||||
id: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-list',
|
||||
imports: [
|
||||
PanelComponent,
|
||||
NgTemplateOutlet
|
||||
],
|
||||
templateUrl: './list.component.html',
|
||||
styleUrl: './list.component.scss',
|
||||
})
|
||||
export class ListComponent {
|
||||
|
||||
public items = input.required<Identifiable[]>();
|
||||
public itemTemplate = input.required<TemplateRef<any>>();
|
||||
public loading = input<boolean>(false);
|
||||
public metadata = input<any>();
|
||||
|
||||
}
|
||||
@ -21,7 +21,7 @@ export class LoginComponent {
|
||||
|
||||
protected loginFormGroup = new FormGroup({
|
||||
usernameOrEmail: new FormControl('housemaster'),
|
||||
password: new FormControl('kR0pNCspBKx8lOzAIch5'),
|
||||
password: new FormControl('W33PWO6IIRpkc6VSBIY0'),
|
||||
keepSignedIn: new FormControl(false),
|
||||
});
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import {Component, computed, OnInit, signal} from '@angular/core';
|
||||
import {NavProfileComponent} from './nav-profile/nav-profile.component';
|
||||
import {ActivatedRoute, NavigationEnd, Route, Router, RouterLink} from '@angular/router';
|
||||
import {ActivatedRoute, NavigationEnd, Route, Router} from '@angular/router';
|
||||
import {IconComponent} from '../icon/icon.component';
|
||||
import {DrawerComponent} from './drawer/drawer.component';
|
||||
import {filter} from 'rxjs';
|
||||
@ -13,7 +13,6 @@ import {LinkComponent} from '../link/link.component';
|
||||
NavProfileComponent,
|
||||
IconComponent,
|
||||
DrawerComponent,
|
||||
RouterLink,
|
||||
NgClass,
|
||||
LinkComponent
|
||||
],
|
||||
@ -86,14 +85,27 @@ export class ShellComponent implements OnInit {
|
||||
const child = children[0];
|
||||
|
||||
const routeURL = child.snapshot.url.map(segment => segment.path).join('/');
|
||||
if (routeURL) {
|
||||
const overrideUrl = child.snapshot.data['breadcrumbUrl'];
|
||||
|
||||
if (overrideUrl) {
|
||||
|
||||
if (overrideUrl.startsWith('?')) {
|
||||
url += overrideUrl;
|
||||
} else {
|
||||
url += !url.endsWith('/') ? `/${overrideUrl}` : `${overrideUrl}`;
|
||||
}
|
||||
|
||||
}
|
||||
else if (routeURL) {
|
||||
url += !url.endsWith('/') ? `/${routeURL}` : `${routeURL}`;
|
||||
}
|
||||
|
||||
const label =
|
||||
child.snapshot.data['title'] ||
|
||||
child.snapshot.routeConfig?.title ||
|
||||
'';
|
||||
child.snapshot.routeConfig?.title
|
||||
|| child.snapshot.data['title']
|
||||
|| '';
|
||||
|
||||
|
||||
|
||||
if (label) {
|
||||
breadcrumbs.push({ label, url });
|
||||
|
||||
@ -9,15 +9,30 @@
|
||||
</ng-template>
|
||||
|
||||
<ng-template tabId="groups" let-tab>
|
||||
{{tab.name}} - content
|
||||
<app-list [itemTemplate]="authorityItem" [items]="groups()" [loading]="groupsLoading()" metadata="group"></app-list>
|
||||
</ng-template>
|
||||
|
||||
<ng-template tabId="roles" let-tab>
|
||||
{{tab.name}} - content
|
||||
<app-list [itemTemplate]="authorityItem" [items]="roles()" [loading]="rolesLoading()" metadata="role"></app-list>
|
||||
</ng-template>
|
||||
|
||||
<ng-template tabId="authorities" let-tab>
|
||||
{{tab.name}} - content
|
||||
<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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,15 @@
|
||||
import {Component, inject, signal} from '@angular/core';
|
||||
import {TabGroup, TabGroupComponent} from '../../tab-group/tab-group.component';
|
||||
import {ActiveTabDirective} from '../../tab-group/active-tab.directive';
|
||||
import {ActivatedRoute, Router} from '@angular/router';
|
||||
import {ActivatedRoute, Router, RouterLink} from '@angular/router';
|
||||
import {SubjectListComponent} from '../../subject/subject-list/subject-list.component';
|
||||
import {AppListComponent} from '../../app/app-list/app-list.component';
|
||||
import {ListComponent} from '../../list/list.component';
|
||||
import {PanelComponent} from '../../panel/panel.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 {TenantService} from '../../../clients/gandalf/mithrandir/tenant/tenant.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-tenant-detail',
|
||||
@ -11,7 +17,10 @@ import {AppListComponent} from '../../app/app-list/app-list.component';
|
||||
TabGroupComponent,
|
||||
ActiveTabDirective,
|
||||
SubjectListComponent,
|
||||
AppListComponent
|
||||
AppListComponent,
|
||||
ListComponent,
|
||||
PanelComponent,
|
||||
RouterLink
|
||||
],
|
||||
templateUrl: './tenant-detail.component.html',
|
||||
styleUrl: './tenant-detail.component.scss',
|
||||
@ -44,8 +53,16 @@ export class TenantDetailComponent {
|
||||
protected activeTabId = signal<string | null>(null)
|
||||
protected tenantId = 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 tenantService = inject(TenantService);
|
||||
|
||||
constructor() {
|
||||
const routeSnapshot = this.route.snapshot;
|
||||
@ -65,5 +82,35 @@ export class TenantDetailComponent {
|
||||
skipLocationChange: false,
|
||||
replaceUrl: true
|
||||
});
|
||||
|
||||
const tenantId = this.tenantId();
|
||||
|
||||
if (!tenantId) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (tab.id) {
|
||||
case 'groups':
|
||||
if (this.groupsLoading()) {
|
||||
const groups = await this.tenantService.getTenantGroupsAsync(tenantId);
|
||||
this.groups.set(groups);
|
||||
this.groupsLoading.set(false);
|
||||
}
|
||||
break;
|
||||
case 'roles':
|
||||
if (this.rolesLoading()) {
|
||||
const roles = await this.tenantService.getTenantRolesAsync(tenantId);
|
||||
this.roles.set(roles);
|
||||
this.rolesLoading.set(false);
|
||||
}
|
||||
break;
|
||||
case 'authorities':
|
||||
if (this.authoritiesLoading()) {
|
||||
const authorities = await this.tenantService.getTenantAuthoritiesAsync(tenantId);
|
||||
this.authorities.set(authorities);
|
||||
this.authoritiesLoading.set(false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
6
src/dotnet/.idea/.idea.Suspectus.Gandalf/.idea/statistic.xml
generated
Normal file
6
src/dotnet/.idea/.idea.Suspectus.Gandalf/.idea/statistic.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Statistic">
|
||||
<option name="fileTypesIncluded" value="html;ts;cs;scss;css;" />
|
||||
</component>
|
||||
</project>
|
||||
@ -5,8 +5,11 @@ 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;
|
||||
|
||||
@ -121,4 +124,130 @@ public class TenantController : ControllerBase
|
||||
|
||||
return Ok(dtos);
|
||||
}
|
||||
|
||||
[HttpGet("{tenantIdHash}/groups")]
|
||||
public async Task<IActionResult> GetTenantGroups(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 groups = await _context.Groups
|
||||
.Where(x => x.TenantId == tenantId && x.Type == AuthorityType.Tenant)
|
||||
.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),
|
||||
CategoryPath = x.CategoryPath,
|
||||
Description = x.Description,
|
||||
Type = x.Type
|
||||
});
|
||||
|
||||
return Ok(dtos);
|
||||
}
|
||||
|
||||
[HttpGet("{tenantIdHash}/roles")]
|
||||
public async Task<IActionResult> GetTenantRoles(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 groups = await _context.Roles
|
||||
.Where(x => x.TenantId == tenantId && x.Type == AuthorityType.Tenant)
|
||||
.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),
|
||||
CategoryPath = x.CategoryPath,
|
||||
Description = x.Description,
|
||||
Type = x.Type
|
||||
});
|
||||
|
||||
return Ok(dtos);
|
||||
}
|
||||
|
||||
[HttpGet("{tenantIdHash}/authorities")]
|
||||
public async Task<IActionResult> GetTenantAuthorities(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 groups = await _context.Authorities
|
||||
.Where(x => x.TenantId == tenantId && x.Type == AuthorityType.Tenant)
|
||||
.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),
|
||||
CategoryPath = x.CategoryPath,
|
||||
Description = x.Description,
|
||||
Type = x.Type
|
||||
});
|
||||
|
||||
return Ok(dtos);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
using Suspectus.Gandalf.Palantir.Data.Entities.Base;
|
||||
using Suspectus.Gandalf.Palantir.Data.Entities.Security.Permission.Data;
|
||||
|
||||
namespace Suspectus.Gandalf.Palantir.Data.Dto.Authority;
|
||||
|
||||
public class AuthorityDto
|
||||
{
|
||||
public required string Id { get; set; }
|
||||
public required string TenantId { get; set; }
|
||||
public required string Name { get; set; }
|
||||
public required string CategoryPath { get; set; }
|
||||
public required string? Description { get; set; }
|
||||
public required AuthorityType Type { get; set; }
|
||||
public required EntityVisibility Visibility { get; set; }
|
||||
public string? AppId { get; set; }
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
using Suspectus.Gandalf.Palantir.Data.Dto.Authority;
|
||||
|
||||
namespace Suspectus.Gandalf.Palantir.Data.Dto.Group;
|
||||
|
||||
public class GroupDto : AuthorityDto;
|
||||
@ -0,0 +1,6 @@
|
||||
using Suspectus.Gandalf.Palantir.Data.Dto.Authority;
|
||||
using Suspectus.Gandalf.Palantir.Data.Dto.Group;
|
||||
|
||||
namespace Suspectus.Gandalf.Palantir.Data.Dto.Role;
|
||||
|
||||
public class RoleDto : AuthorityDto;
|
||||
Loading…
x
Reference in New Issue
Block a user