add tenant subject list
This commit is contained in:
parent
b1519f4307
commit
8a0dbf71dc
@ -0,0 +1,6 @@
|
|||||||
|
export interface SubjectListDto {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
isOwner: boolean;
|
||||||
|
visibility: string;
|
||||||
|
}
|
||||||
@ -1,6 +1,7 @@
|
|||||||
import {Injectable} from "@angular/core";
|
import {Injectable} from "@angular/core";
|
||||||
import {lastValueFrom, Observable} from "rxjs";
|
import {lastValueFrom, Observable} from "rxjs";
|
||||||
import {PalantirService} from "./palantir.service";
|
import {PalantirService} from '../palantir.service';
|
||||||
|
import {SubjectListDto} from './dtos/subject-list-dto';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
@ -16,4 +17,12 @@ export class SubjectService extends PalantirService {
|
|||||||
meAsync(onError?: (error: any) => void): Promise<{ "name": string }> {
|
meAsync(onError?: (error: any) => void): Promise<{ "name": string }> {
|
||||||
return lastValueFrom(this.me$(onError));
|
return lastValueFrom(this.me$(onError));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
forTenant$(tenantId: string, onError?: (error: any) => void): Observable<SubjectListDto[]> {
|
||||||
|
return this.handleRequest(this.http.get<SubjectListDto[]>(this.baseUrl + '/for-tenant/' + tenantId), onError);
|
||||||
|
}
|
||||||
|
|
||||||
|
forTenantAsync(tenantId: string, onError?: (error: any) => void): Promise<SubjectListDto[]> {
|
||||||
|
return lastValueFrom(this.forTenant$(tenantId, onError));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
@if (loading()) {
|
||||||
|
Loading...
|
||||||
|
} @else {
|
||||||
|
@for (subject of subjects(); track subject.id) {
|
||||||
|
<app-panel class="neutral-80">
|
||||||
|
<span class="title" [ngClass]="{'owner': subject.isOwner}">{{subject.name}}</span>
|
||||||
|
<span class="actions">
|
||||||
|
<button [routerLink]="'subject/' + subject.id" class="primary outline">View Details</button>
|
||||||
|
</span>
|
||||||
|
</app-panel>
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
:host {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: .5rem;
|
||||||
|
|
||||||
|
app-panel {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&.owner {
|
||||||
|
&::before {
|
||||||
|
background-color: var(--primary-30);
|
||||||
|
color: var(--neutral-90);
|
||||||
|
font-size: 0.7rem;
|
||||||
|
line-height: 0.7rem;
|
||||||
|
padding: 0.15rem 0.3rem;
|
||||||
|
font-weight: bold;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
margin-right: 1ch;
|
||||||
|
content: 'Owner';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { SubjectListComponent } from './subject-list.component';
|
||||||
|
|
||||||
|
describe('SubjectListComponent', () => {
|
||||||
|
let component: SubjectListComponent;
|
||||||
|
let fixture: ComponentFixture<SubjectListComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [SubjectListComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(SubjectListComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
import {Component, inject, input, OnInit, signal} from '@angular/core';
|
||||||
|
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';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-subject-list',
|
||||||
|
imports: [
|
||||||
|
PanelComponent,
|
||||||
|
RouterLink,
|
||||||
|
NgClass
|
||||||
|
],
|
||||||
|
templateUrl: './subject-list.component.html',
|
||||||
|
styleUrl: './subject-list.component.scss',
|
||||||
|
})
|
||||||
|
export class SubjectListComponent implements OnInit {
|
||||||
|
|
||||||
|
public tenantId = input<string>()
|
||||||
|
|
||||||
|
protected loading = signal<boolean>(true);
|
||||||
|
protected subjects = signal<SubjectListDto[]>([]);
|
||||||
|
|
||||||
|
private subjectService = inject(SubjectService);
|
||||||
|
|
||||||
|
async ngOnInit(): Promise<void> {
|
||||||
|
|
||||||
|
const tenantId = this.tenantId();
|
||||||
|
|
||||||
|
if (tenantId) {
|
||||||
|
const subjects = await this.subjectService.forTenantAsync(tenantId);
|
||||||
|
this.subjects.set(subjects);
|
||||||
|
this.loading.set(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
<app-tab-group [tabs]="tabs" (tabChanged)="tabChanged($event)" [activeTabId]="activeTabId()">
|
<app-tab-group [tabs]="tabs" (tabChanged)="tabChanged($event)" [activeTabId]="activeTabId()">
|
||||||
|
|
||||||
<ng-template tabId="subjects" let-tab>
|
<ng-template tabId="subjects" let-tab>
|
||||||
{{tab.name}} - content
|
<app-subject-list [tenantId]="tenantId()!"></app-subject-list>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template tabId="apps" let-tab>
|
<ng-template tabId="apps" let-tab>
|
||||||
|
|||||||
@ -2,12 +2,14 @@ import {Component, inject, signal} from '@angular/core';
|
|||||||
import {TabGroup, TabGroupComponent} from '../../tab-group/tab-group.component';
|
import {TabGroup, TabGroupComponent} from '../../tab-group/tab-group.component';
|
||||||
import {ActiveTabDirective} from '../../tab-group/active-tab.directive';
|
import {ActiveTabDirective} from '../../tab-group/active-tab.directive';
|
||||||
import {ActivatedRoute, Router} from '@angular/router';
|
import {ActivatedRoute, Router} from '@angular/router';
|
||||||
|
import {SubjectListComponent} from '../../subject/subject-list/subject-list.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-tenant-detail',
|
selector: 'app-tenant-detail',
|
||||||
imports: [
|
imports: [
|
||||||
TabGroupComponent,
|
TabGroupComponent,
|
||||||
ActiveTabDirective
|
ActiveTabDirective,
|
||||||
|
SubjectListComponent
|
||||||
],
|
],
|
||||||
templateUrl: './tenant-detail.component.html',
|
templateUrl: './tenant-detail.component.html',
|
||||||
styleUrl: './tenant-detail.component.scss',
|
styleUrl: './tenant-detail.component.scss',
|
||||||
@ -38,13 +40,17 @@ export class TenantDetailComponent {
|
|||||||
]
|
]
|
||||||
|
|
||||||
protected activeTabId = signal<string | null>(null)
|
protected activeTabId = signal<string | null>(null)
|
||||||
|
protected tenantId = signal<string | null>(null)
|
||||||
|
|
||||||
private route = inject(ActivatedRoute);
|
private route = inject(ActivatedRoute);
|
||||||
private router = inject(Router)
|
private router = inject(Router)
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
const tabId = this.route.snapshot.queryParamMap.get('tab');
|
const routeSnapshot = this.route.snapshot;
|
||||||
|
const tenantId = routeSnapshot.paramMap.get('id');
|
||||||
|
const tabId = routeSnapshot.queryParamMap.get('tab');
|
||||||
this.activeTabId.set(tabId)
|
this.activeTabId.set(tabId)
|
||||||
|
this.tenantId.set(tenantId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async tabChanged(tab: TabGroup) {
|
async tabChanged(tab: TabGroup) {
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { ResolveFn } from '@angular/router';
|
|||||||
import { subjectResolver } from './subject.resolver';
|
import { subjectResolver } from './subject.resolver';
|
||||||
|
|
||||||
describe('subjectResolver', () => {
|
describe('subjectResolver', () => {
|
||||||
const executeResolver: ResolveFn<boolean> = (...resolverParameters) =>
|
const executeResolver: ResolveFn<boolean> = (...resolverParameters) =>
|
||||||
TestBed.runInInjectionContext(() => subjectResolver(...resolverParameters));
|
TestBed.runInInjectionContext(() => subjectResolver(...resolverParameters));
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import {ResolveFn} from '@angular/router';
|
import {ResolveFn} from '@angular/router';
|
||||||
import {inject} from '@angular/core';
|
import {inject} from '@angular/core';
|
||||||
import {map} from 'rxjs';
|
import {map} from 'rxjs';
|
||||||
import {SubjectService} from '../clients/gandalf/mithrandir/subject.service';
|
import {SubjectService} from '../clients/gandalf/mithrandir/subject/subject.service';
|
||||||
|
|
||||||
export const subjectResolver: ResolveFn<string> = (route, state) => {
|
export const subjectResolver: ResolveFn<string> = (route, state) => {
|
||||||
const subjectService = inject(SubjectService);
|
const subjectService = inject(SubjectService);
|
||||||
|
|||||||
@ -1,13 +1,16 @@
|
|||||||
|
using HashidsNet;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Suspectus.Gandalf.Palantir.Abstractions;
|
using Suspectus.Gandalf.Palantir.Abstractions;
|
||||||
using Suspectus.Gandalf.Palantir.Data.Database;
|
using Suspectus.Gandalf.Palantir.Data.Database;
|
||||||
|
using Suspectus.Gandalf.Palantir.Data.Dto.Subject;
|
||||||
|
using Suspectus.Gandalf.Palantir.Data.Entities.Base;
|
||||||
|
|
||||||
namespace Suspectus.Gandalf.Palantir.Api.Controllers;
|
namespace Suspectus.Gandalf.Palantir.Api.Controllers;
|
||||||
|
|
||||||
[ApiController]
|
[ApiController]
|
||||||
[Route("api/[controller]")]
|
[Route("api/[controller]")]
|
||||||
public class SubjectController(InvokerContext invokerContext, ApplicationContext context) : ControllerBase
|
public class SubjectController(InvokerContext invokerContext, ApplicationContext context, IHashids hashids) : ControllerBase
|
||||||
{
|
{
|
||||||
[HttpGet("me")]
|
[HttpGet("me")]
|
||||||
public async Task<IActionResult> GetSubject()
|
public async Task<IActionResult> GetSubject()
|
||||||
@ -15,4 +18,33 @@ public class SubjectController(InvokerContext invokerContext, ApplicationContext
|
|||||||
var subject = await context.Subjects.Where(x => x.Id == invokerContext.Invoker!.SubjectId).Select(x => x.Name).SingleAsync();
|
var subject = await context.Subjects.Where(x => x.Id == invokerContext.Invoker!.SubjectId).Select(x => x.Name).SingleAsync();
|
||||||
return Ok(new { Name = subject });
|
return Ok(new { Name = subject });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet("for-tenant/{tenantIdHash}")]
|
||||||
|
public async Task<IActionResult> GetSubjectsByTenantId(string tenantIdHash, InvokerContext invokerContext)
|
||||||
|
{
|
||||||
|
if (!hashids.TryDecodeSingleLong(tenantIdHash, out var tenantId))
|
||||||
|
{
|
||||||
|
return BadRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
var tenantOwnerId = await context.Tenants.Where(x => x.Id == tenantId).Select(x => (long?)x.OwnerId).SingleOrDefaultAsync();
|
||||||
|
|
||||||
|
if (tenantOwnerId is null)
|
||||||
|
return NotFound();
|
||||||
|
|
||||||
|
var subjects = await context.TenantSubjectRelations
|
||||||
|
.Where(x => x.TenantId == tenantId)
|
||||||
|
.Select(x => x.Subject!)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
var dtos = subjects.Select(x => new SubjectListDto
|
||||||
|
{
|
||||||
|
Id = hashids.EncodeLong(x.Id!.Value),
|
||||||
|
Name = x.Name,
|
||||||
|
IsOwner = x.Id == tenantOwnerId,
|
||||||
|
Visibility = x.Visibility
|
||||||
|
});
|
||||||
|
|
||||||
|
return Ok(dtos);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
using Suspectus.Gandalf.Palantir.Data.Entities.Base;
|
||||||
|
|
||||||
|
namespace Suspectus.Gandalf.Palantir.Data.Dto.Subject;
|
||||||
|
|
||||||
|
public class SubjectListDto
|
||||||
|
{
|
||||||
|
public required string Id { get; set; }
|
||||||
|
public required string Name { get; set; }
|
||||||
|
public required bool IsOwner { get; set; }
|
||||||
|
public required EntityVisibility Visibility { get; set; }
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user