ui
This commit is contained in:
@@ -1,10 +1,20 @@
|
|||||||
import { Body, Controller, Delete, Get, Param, Post, Req, UseGuards } from '@nestjs/common';
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Delete,
|
||||||
|
Get,
|
||||||
|
Param,
|
||||||
|
Post,
|
||||||
|
Req,
|
||||||
|
UseGuards,
|
||||||
|
} from '@nestjs/common';
|
||||||
import { UserService } from './user.service';
|
import { UserService } from './user.service';
|
||||||
import { AuthGuard, Roles, RolesGuard } from 'src/core/secure/guards';
|
import { AuthGuard, Roles, RolesGuard } from 'src/core/secure/guards';
|
||||||
import { Client } from 'src/model/client.entity';
|
import { Client } from 'src/model/client.entity';
|
||||||
import { AuthenticatedRequest } from 'src/model';
|
import { AuthenticatedRequest } from 'src/model';
|
||||||
import { CreateClientDto } from 'src/model/dto/create-client.dto';
|
import { CreateClientDto } from 'src/model/dto/create-client.dto';
|
||||||
import { ClientService } from 'src/client/client.service';
|
import { ClientService } from 'src/client/client.service';
|
||||||
|
import { RedirectUri } from 'src/model/redirect-uri.entity';
|
||||||
|
|
||||||
@UseGuards(AuthGuard, RolesGuard)
|
@UseGuards(AuthGuard, RolesGuard)
|
||||||
@Controller('app/user')
|
@Controller('app/user')
|
||||||
@@ -42,4 +52,13 @@ export class UserController {
|
|||||||
deleteClient(@Req() req: AuthenticatedRequest, @Param('id') id) {
|
deleteClient(@Req() req: AuthenticatedRequest, @Param('id') id) {
|
||||||
return this.clientService.deleteClient(req.user, id);
|
return this.clientService.deleteClient(req.user, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Post('clients/:id/redirect')
|
||||||
|
saveUris(
|
||||||
|
@Req() req: AuthenticatedRequest,
|
||||||
|
@Param('id') id,
|
||||||
|
@Body() uris: RedirectUri[],
|
||||||
|
): Promise<RedirectUri[]> {
|
||||||
|
return this.clientService.saveRedirectUris(req.user, id, uris);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ import { Module } from '@nestjs/common';
|
|||||||
import { UserController } from './user.controller';
|
import { UserController } from './user.controller';
|
||||||
import { SecureModule } from 'src/core/secure/secure.module';
|
import { SecureModule } from 'src/core/secure/secure.module';
|
||||||
import { UserService } from './user.service';
|
import { UserService } from './user.service';
|
||||||
import { ClientRepository } from 'src/model/client.entity';
|
|
||||||
import { ClientService } from 'src/client/client.service';
|
import { ClientService } from 'src/client/client.service';
|
||||||
|
import { DatabaseModule } from 'src/core/database/database.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
controllers: [UserController],
|
controllers: [UserController],
|
||||||
imports: [SecureModule],
|
imports: [SecureModule, DatabaseModule],
|
||||||
providers: [UserService, ClientRepository, ClientService],
|
providers: [UserService, ClientService],
|
||||||
})
|
})
|
||||||
export class UserModule {}
|
export class UserModule {}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ export class UserService {
|
|||||||
return this.clientRepository.find({
|
return this.clientRepository.find({
|
||||||
where: { admins: { id: user.id } },
|
where: { admins: { id: user.id } },
|
||||||
relations: ['admins'],
|
relations: ['admins'],
|
||||||
|
order: { createdAt: 'ASC' },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { LoggerModule } from 'src/core/logger.module';
|
|||||||
import { SessionKey, SessionKeyRepository } from 'src/model/session-key.entity';
|
import { SessionKey, SessionKeyRepository } from 'src/model/session-key.entity';
|
||||||
import { ConfigModule, ConfigService } from '@nestjs/config';
|
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||||
import { Role, RoleRepository } from 'src/model/role.entity';
|
import { Role, RoleRepository } from 'src/model/role.entity';
|
||||||
|
import { DatabaseModule } from 'src/core/database/database.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
providers: [
|
providers: [
|
||||||
@@ -47,6 +48,7 @@ import { Role, RoleRepository } from 'src/model/role.entity';
|
|||||||
Role,
|
Role,
|
||||||
]),
|
]),
|
||||||
LoggerModule,
|
LoggerModule,
|
||||||
|
DatabaseModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class AuthModule {}
|
export class AuthModule {}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { NestjsFormDataModule } from 'nestjs-form-data';
|
import { NestjsFormDataModule } from 'nestjs-form-data';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
|
||||||
import { LoggerModule } from 'src/core/logger.module';
|
import { LoggerModule } from 'src/core/logger.module';
|
||||||
import { ClientController } from './client.controller';
|
import { ClientController } from './client.controller';
|
||||||
|
import { DatabaseModule } from 'src/core/database/database.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
providers: [],
|
providers: [],
|
||||||
controllers: [ClientController],
|
controllers: [ClientController],
|
||||||
imports: [NestjsFormDataModule, TypeOrmModule.forFeature([]), LoggerModule],
|
imports: [NestjsFormDataModule, DatabaseModule, LoggerModule],
|
||||||
})
|
})
|
||||||
export class ClientModule {}
|
export class ClientModule {}
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
import { Client, ClientRepository } from 'src/model/client.entity';
|
import { Client, ClientRepository } from 'src/model/client.entity';
|
||||||
import { RedirectUri } from 'src/model/redirect-uri.entity';
|
import { RedirectRepository, RedirectUri } from 'src/model/redirect-uri.entity';
|
||||||
import { User } from 'src/model/user.entity';
|
import { User } from 'src/model/user.entity';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ClientService {
|
export class ClientService {
|
||||||
constructor(private clientRepo: ClientRepository) {}
|
constructor(
|
||||||
|
private clientRepo: ClientRepository,
|
||||||
|
private uriRepo: RedirectRepository,
|
||||||
|
) {}
|
||||||
|
|
||||||
async createClient(
|
async createClient(
|
||||||
user: User,
|
user: User,
|
||||||
@@ -71,9 +74,36 @@ export class ClientService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (client) {
|
if (client) {
|
||||||
|
await this.uriRepo.remove(client.redirectUris);
|
||||||
|
|
||||||
return this.clientRepo.remove(client);
|
return this.clientRepo.remove(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new HttpException('client not found', HttpStatus.BAD_REQUEST);
|
throw new HttpException('client not found', HttpStatus.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async saveRedirectUris(
|
||||||
|
user: User,
|
||||||
|
clientId: string,
|
||||||
|
uris: RedirectUri[],
|
||||||
|
): Promise<RedirectUri[]> {
|
||||||
|
const client = await this.clientRepo.findOne({
|
||||||
|
where: { id: clientId, admins: { id: user.id } },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!client) {
|
||||||
|
throw new HttpException('client not found', HttpStatus.NOT_FOUND);
|
||||||
|
}
|
||||||
|
console.log(uris);
|
||||||
|
uris.map((u) => {
|
||||||
|
u.client = client;
|
||||||
|
});
|
||||||
|
const redirects = await this.uriRepo.save(uris);
|
||||||
|
client.redirectUris = redirects;
|
||||||
|
await this.clientRepo.save(client);
|
||||||
|
return this.uriRepo.find({
|
||||||
|
where: { client: { id: clientId } },
|
||||||
|
order: { createdAt: 'ASC' },
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
AuthorizationCodeRepository,
|
AuthorizationCodeRepository,
|
||||||
} from 'src/model/auth-code.entity';
|
} from 'src/model/auth-code.entity';
|
||||||
import { Client, ClientRepository } from 'src/model/client.entity';
|
import { Client, ClientRepository } from 'src/model/client.entity';
|
||||||
import { RedirectUri } from 'src/model/redirect-uri.entity';
|
import { RedirectRepository, RedirectUri } from 'src/model/redirect-uri.entity';
|
||||||
import { SessionKey, SessionKeyRepository } from 'src/model/session-key.entity';
|
import { SessionKey, SessionKeyRepository } from 'src/model/session-key.entity';
|
||||||
import { User, UserRepository } from 'src/model/user.entity';
|
import { User, UserRepository } from 'src/model/user.entity';
|
||||||
|
|
||||||
@@ -15,6 +15,7 @@ const REPOSITORIES = [
|
|||||||
ClientRepository,
|
ClientRepository,
|
||||||
AuthorizationCodeRepository,
|
AuthorizationCodeRepository,
|
||||||
SessionKeyRepository,
|
SessionKeyRepository,
|
||||||
|
RedirectRepository,
|
||||||
];
|
];
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
OneToMany,
|
OneToMany,
|
||||||
ManyToMany,
|
ManyToMany,
|
||||||
JoinTable,
|
JoinTable,
|
||||||
|
CreateDateColumn,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
import { RedirectUri } from './redirect-uri.entity';
|
import { RedirectUri } from './redirect-uri.entity';
|
||||||
import { AuthorizationCode } from './auth-code.entity';
|
import { AuthorizationCode } from './auth-code.entity';
|
||||||
@@ -19,6 +20,9 @@ export class Client {
|
|||||||
@PrimaryGeneratedColumn('uuid')
|
@PrimaryGeneratedColumn('uuid')
|
||||||
id: string;
|
id: string;
|
||||||
|
|
||||||
|
@CreateDateColumn({ name: 'created_at' })
|
||||||
|
createdAt?: Date;
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
clientName: string;
|
clientName: string;
|
||||||
|
|
||||||
@@ -30,7 +34,6 @@ export class Client {
|
|||||||
description: string;
|
description: string;
|
||||||
|
|
||||||
@OneToMany(() => RedirectUri, (uri) => uri.client, {
|
@OneToMany(() => RedirectUri, (uri) => uri.client, {
|
||||||
cascade: true,
|
|
||||||
eager: true,
|
eager: true,
|
||||||
})
|
})
|
||||||
redirectUris: RedirectUri[];
|
redirectUris: RedirectUri[];
|
||||||
|
|||||||
@@ -1,14 +1,33 @@
|
|||||||
import { Entity, PrimaryGeneratedColumn, ManyToOne, Column } from 'typeorm';
|
import {
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
ManyToOne,
|
||||||
|
Column,
|
||||||
|
DataSource,
|
||||||
|
Repository,
|
||||||
|
CreateDateColumn,
|
||||||
|
} from 'typeorm';
|
||||||
import { Client } from './client.entity';
|
import { Client } from './client.entity';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
export class RedirectUri {
|
export class RedirectUri {
|
||||||
@PrimaryGeneratedColumn('uuid')
|
@PrimaryGeneratedColumn('uuid')
|
||||||
id: string;
|
id: string;
|
||||||
|
|
||||||
|
@CreateDateColumn({ name: 'created_at' })
|
||||||
|
createdAt?: Date;
|
||||||
|
|
||||||
@ManyToOne(() => Client, (client) => client.redirectUris)
|
@ManyToOne(() => Client, (client) => client.redirectUris)
|
||||||
client: Client;
|
client: Client;
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
uri: string;
|
uri: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class RedirectRepository extends Repository<RedirectUri> {
|
||||||
|
constructor(dataSource: DataSource) {
|
||||||
|
super(RedirectUri, dataSource.createEntityManager());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,18 @@
|
|||||||
<div class="header">{{ client.clientName }}</div>
|
<div class="header flex-row">
|
||||||
|
<span>{{ client.clientName }}</span>
|
||||||
|
<div class="flex-row">
|
||||||
|
<div class="flex-row" style=" gap: 0;"><mat-icon>shield_person</mat-icon><div style="line-height: 8px;">{{ client.admins.length }}</div></div>
|
||||||
|
<div class="flex-row" style=" gap: 0;"><mat-icon>link</mat-icon><div style="line-height: 8px;">{{ client.redirectUris.length }}</div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="body">
|
<div class="body">
|
||||||
{{ client.description }}
|
{{ client.description }}
|
||||||
</div>
|
</div>
|
||||||
<div class="footer">
|
<div class="flex-row footer">
|
||||||
<button mat-button >Admins</button>
|
<div>{{ client.createdAt | date }}</div>
|
||||||
<button mat-button >Redirect URIs</button>
|
<div class="flex-row">
|
||||||
<button mat-flat-button color="warn" (click)="deleteClient()">Löschen</button>
|
<button mat-button (click)="showAdmins()" >Admins</button>
|
||||||
|
<button mat-button (click)="showUris()" >Redirect URIs</button>
|
||||||
|
<button mat-flat-button color="warn" (click)="deleteClient()">Löschen</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -6,12 +6,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.footer {
|
.footer {
|
||||||
display: flex;
|
margin-top: 12px;
|
||||||
flex-direction: row;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body {
|
||||||
|
margin-top: 12px;
|
||||||
}
|
}
|
||||||
@@ -1,18 +1,18 @@
|
|||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { CardComponent } from './card.component';
|
import { ClientCardComponent } from './card.component';
|
||||||
|
|
||||||
describe('CardComponent', () => {
|
describe('CardComponent', () => {
|
||||||
let component: CardComponent;
|
let component: ClientCardComponent;
|
||||||
let fixture: ComponentFixture<CardComponent>;
|
let fixture: ComponentFixture<ClientCardComponent>;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await TestBed.configureTestingModule({
|
await TestBed.configureTestingModule({
|
||||||
imports: [CardComponent]
|
imports: [ClientCardComponent]
|
||||||
})
|
})
|
||||||
.compileComponents();
|
.compileComponents();
|
||||||
|
|
||||||
fixture = TestBed.createComponent(CardComponent);
|
fixture = TestBed.createComponent(ClientCardComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,25 +3,47 @@ import { MatButtonModule } from '@angular/material/button';
|
|||||||
import { Client } from '../../../model/client.interface';
|
import { Client } from '../../../model/client.interface';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { HotToastService } from '@ngxpert/hot-toast';
|
import { HotToastService } from '@ngxpert/hot-toast';
|
||||||
|
import { MatDialog, MatDialogModule } from '@angular/material/dialog';
|
||||||
|
import { ClientAdminsComponent } from '../client-admins/client-admins.component';
|
||||||
|
import { ClientRedirectUrisComponent } from '../client-redirect-uris/client-redirect-uris.component';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-card',
|
selector: 'app-card',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [MatButtonModule],
|
imports: [MatButtonModule, MatDialogModule, MatIconModule, CommonModule],
|
||||||
templateUrl: './card.component.html',
|
templateUrl: './card.component.html',
|
||||||
styleUrl: './card.component.scss'
|
styleUrl: './card.component.scss'
|
||||||
})
|
})
|
||||||
export class CardComponent {
|
export class ClientCardComponent {
|
||||||
|
|
||||||
@Input() client: Client;
|
@Input() client: Client;
|
||||||
@Output() onDelete = new EventEmitter<Client>();
|
@Output() onDelete = new EventEmitter<Client>();
|
||||||
|
|
||||||
private http = inject(HttpClient);
|
private http = inject(HttpClient);
|
||||||
private toast = inject(HotToastService);
|
private toast = inject(HotToastService);
|
||||||
|
private dialog = inject(MatDialog);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
deleteClient() {
|
deleteClient() {
|
||||||
this.onDelete.emit(this.client);
|
this.onDelete.emit(this.client);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showAdmins() {
|
||||||
|
const dialog = this.dialog.open(ClientAdminsComponent, {
|
||||||
|
autoFocus: false
|
||||||
|
});
|
||||||
|
dialog.componentInstance.client = this.client;
|
||||||
|
dialog.componentInstance.isDialog = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
showUris() {
|
||||||
|
const dialog = this.dialog.open(ClientRedirectUrisComponent, {
|
||||||
|
autoFocus: false
|
||||||
|
});
|
||||||
|
dialog.componentInstance.client = this.client;
|
||||||
|
dialog.componentInstance.isDialog = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
<h3 matDialogTitle>Client Admins</h3>
|
||||||
|
<div mat-dialog-content class="content">
|
||||||
|
@for (admin of client.admins; track $index) {
|
||||||
|
<div class="admin__item flex-row">
|
||||||
|
<span class="admin__name">{{ admin.firstName }} {{ admin.lastName }}</span>
|
||||||
|
@if($index!= 0) {
|
||||||
|
<button mat-raised-button color="warn" class="remove_btn" ><mat-icon>delete</mat-icon></button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (isDialog) {
|
||||||
|
<div mat-dialog-actions>
|
||||||
|
<button mat-stroked-button color="warn" mat-dialog-close >close</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
.admin__item {
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-icon {
|
||||||
|
margin: 0 !important;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remove_btn {
|
||||||
|
min-width: 0;
|
||||||
|
padding: 0 14px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ClientAdminsComponent } from './client-admins.component';
|
||||||
|
|
||||||
|
describe('ClientAdminsComponent', () => {
|
||||||
|
let component: ClientAdminsComponent;
|
||||||
|
let fixture: ComponentFixture<ClientAdminsComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [ClientAdminsComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(ClientAdminsComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { AfterViewInit, Component, Input } from '@angular/core';
|
||||||
|
import { Client } from '../../../model/client.interface';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
import { MatDialogModule, MatDialogRef } from '@angular/material/dialog';
|
||||||
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-client-admins',
|
||||||
|
standalone: true,
|
||||||
|
imports: [MatIconModule, MatDialogModule, MatButtonModule],
|
||||||
|
templateUrl: './client-admins.component.html',
|
||||||
|
styleUrl: './client-admins.component.scss'
|
||||||
|
})
|
||||||
|
export class ClientAdminsComponent {
|
||||||
|
private _client: Client;
|
||||||
|
|
||||||
|
isDialog = false;
|
||||||
|
|
||||||
|
constructor(private dialogRef: MatDialogRef<ClientAdminsComponent>) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
set client(client: Client) {
|
||||||
|
if (!client) { throw new Error('Client not set')}
|
||||||
|
this._client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
get client(): Client {
|
||||||
|
return this._client;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
<h3 matDialogTitle>Client Redirect-URIs</h3>
|
||||||
|
<div mat-dialog-content class="content">
|
||||||
|
@for (uri of client.redirectUris; track $index) {
|
||||||
|
<div class="admin__item flex-row">
|
||||||
|
<span class="admin__name">{{ uri.uri }}</span>
|
||||||
|
@if(client.redirectUris.length > 1) {
|
||||||
|
<button mat-raised-button color="warn" class="remove_btn" (click)="removeUri(uri)" ><mat-icon>delete</mat-icon></button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="add_entry flex-row">
|
||||||
|
<input type="text" id="addURIInput" placeholder="URL" >
|
||||||
|
<button (click)="addUri()" mat-button>Neu</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (isDialog) {
|
||||||
|
<div mat-dialog-actions>
|
||||||
|
<button mat-stroked-button color="warn" mat-dialog-close >close</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (isLoading) {
|
||||||
|
<div class="global-spinner" >
|
||||||
|
<mat-spinner></mat-spinner>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
.admin__item {
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content{
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-icon {
|
||||||
|
margin: 0 !important;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remove_btn {
|
||||||
|
min-width: 0;
|
||||||
|
padding: 0 14px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { ClientRedirectUrisComponent } from './client-redirect-uris.component';
|
||||||
|
|
||||||
|
describe('ClientRedirectUrisComponent', () => {
|
||||||
|
let component: ClientRedirectUrisComponent;
|
||||||
|
let fixture: ComponentFixture<ClientRedirectUrisComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [ClientRedirectUrisComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(ClientRedirectUrisComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
import { Component, inject, Input } from '@angular/core';
|
||||||
|
import { MatDialogModule, MatDialogRef } from '@angular/material/dialog';
|
||||||
|
import { Client } from '../../../model/client.interface';
|
||||||
|
import { ClientAdminsComponent } from '../client-admins/client-admins.component';
|
||||||
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { RedirectUri } from '../../../model/redirect-uri.interface';
|
||||||
|
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-client-redirect-uris',
|
||||||
|
standalone: true,
|
||||||
|
imports: [MatIconModule, MatDialogModule, MatButtonModule, MatProgressSpinnerModule],
|
||||||
|
templateUrl: './client-redirect-uris.component.html',
|
||||||
|
styleUrl: './client-redirect-uris.component.scss'
|
||||||
|
})
|
||||||
|
export class ClientRedirectUrisComponent {
|
||||||
|
private _client: Client;
|
||||||
|
private http: HttpClient = inject(HttpClient);
|
||||||
|
|
||||||
|
isDialog = false;
|
||||||
|
isLoading = false;
|
||||||
|
|
||||||
|
constructor(private dialogRef: MatDialogRef<ClientAdminsComponent>) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
set client(client: Client) {
|
||||||
|
if (!client) { throw new Error('Client not set')}
|
||||||
|
this._client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
get client(): Client {
|
||||||
|
return this._client;
|
||||||
|
}
|
||||||
|
|
||||||
|
addUri() {
|
||||||
|
const val: HTMLInputElement = document.getElementById('addURIInput') as any;
|
||||||
|
|
||||||
|
console.log(val.value)
|
||||||
|
|
||||||
|
this.client.redirectUris.push({
|
||||||
|
uri: val.value
|
||||||
|
});
|
||||||
|
|
||||||
|
val.value = null;
|
||||||
|
this.saveClient();
|
||||||
|
};
|
||||||
|
removeUri(u: RedirectUri) {
|
||||||
|
this.client.redirectUris = this.client.redirectUris.filter(r => r != u);
|
||||||
|
this.saveClient()
|
||||||
|
}
|
||||||
|
|
||||||
|
saveClient() {
|
||||||
|
this.isLoading = true;
|
||||||
|
this.http.post<RedirectUri[]>(`api/app/user/clients/${this.client.id}/redirect`, this.client.redirectUris).subscribe(res => {
|
||||||
|
this.client.redirectUris = res;
|
||||||
|
this.isLoading = false;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -40,13 +40,12 @@
|
|||||||
</mat-step>
|
</mat-step>
|
||||||
<mat-step [editable]="true">
|
<mat-step [editable]="true">
|
||||||
<ng-template matStepLabel>Admins und Redirect URLs</ng-template>
|
<ng-template matStepLabel>Admins und Redirect URLs</ng-template>
|
||||||
<div style="flex: 1 1 auto">space</div>
|
<div class="admin_container">
|
||||||
<div> {{ client?.clientName }}</div>
|
@if (client && client.admins) {
|
||||||
|
<app-client-admins [client]="client" ></app-client-admins>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
</mat-step>
|
</mat-step>
|
||||||
</mat-stepper>
|
</mat-stepper>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</mat-dialog-content>
|
</mat-dialog-content>
|
||||||
|
|
||||||
|
|||||||
@@ -11,11 +11,13 @@ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
|||||||
import {MatStepper, MatStepperModule} from '@angular/material/stepper';
|
import {MatStepper, MatStepperModule} from '@angular/material/stepper';
|
||||||
import { HotToastService } from '@ngxpert/hot-toast';
|
import { HotToastService } from '@ngxpert/hot-toast';
|
||||||
import { Client } from '../../../model/client.interface';
|
import { Client } from '../../../model/client.interface';
|
||||||
|
import { User } from '../../../model/user.interface';
|
||||||
|
import { ClientAdminsComponent } from '../client-admins/client-admins.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-create-client',
|
selector: 'app-create-client',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [ReactiveFormsModule, MatFormFieldModule, MatInputModule, MatIconModule, MatDialogModule, MatButtonModule, MatProgressSpinnerModule, CommonModule, MatStepperModule],
|
imports: [ReactiveFormsModule, MatFormFieldModule, MatInputModule, MatIconModule, MatDialogModule, MatButtonModule, MatProgressSpinnerModule, CommonModule, MatStepperModule, ClientAdminsComponent],
|
||||||
templateUrl: './create-client.component.html',
|
templateUrl: './create-client.component.html',
|
||||||
styleUrl: './create-client.component.scss'
|
styleUrl: './create-client.component.scss'
|
||||||
})
|
})
|
||||||
@@ -36,6 +38,7 @@ export class CreateClientComponent {
|
|||||||
client: Client;
|
client: Client;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
create() {
|
create() {
|
||||||
if (this.createClient.invalid) { return; }
|
if (this.createClient.invalid) { return; }
|
||||||
|
|
||||||
@@ -52,6 +55,7 @@ export class CreateClientComponent {
|
|||||||
)
|
)
|
||||||
.subscribe({
|
.subscribe({
|
||||||
next: data => {
|
next: data => {
|
||||||
|
console.log(this.stepper)
|
||||||
this.client = data;
|
this.client = data;
|
||||||
this.createClient.enable();
|
this.createClient.enable();
|
||||||
this.stepper.next();
|
this.stepper.next();
|
||||||
|
|||||||
@@ -22,8 +22,11 @@
|
|||||||
|
|
||||||
<div class="card-container">
|
<div class="card-container">
|
||||||
<div class="card-container__list">
|
<div class="card-container__list">
|
||||||
@for (client of clients; track $index) {
|
<ng-container #listSection>
|
||||||
|
|
||||||
|
</ng-container>
|
||||||
|
<!-- @for (client of clients; track $index) {
|
||||||
<app-card [client]="client" (onDelete)="openDeleteDialog($event)" ></app-card>
|
<app-card [client]="client" (onDelete)="openDeleteDialog($event)" ></app-card>
|
||||||
}
|
} -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { Component, inject, OnInit } from '@angular/core';
|
import { Component, inject, Injector, OnInit, ViewChild, ViewContainerRef } from '@angular/core';
|
||||||
import { UserService } from '../auth/user.service';
|
import { UserService } from '../auth/user.service';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { HttpClient } from '@angular/common/http';
|
import { HttpClient } from '@angular/common/http';
|
||||||
import { CardComponent } from './components/card/card.component';
|
import { ClientCardComponent } from './components/card/card.component';
|
||||||
import { Client } from '../model/client.interface';
|
import { Client } from '../model/client.interface';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
@@ -10,11 +10,12 @@ import { MatDialog, MatDialogModule } from '@angular/material/dialog';
|
|||||||
import { CreateClientComponent } from './components/create-client/create-client.component';
|
import { CreateClientComponent } from './components/create-client/create-client.component';
|
||||||
import { CreateHotToastRef, HotToastService } from '@ngxpert/hot-toast';
|
import { CreateHotToastRef, HotToastService } from '@ngxpert/hot-toast';
|
||||||
import {MatBottomSheet, MatBottomSheetModule, MatBottomSheetRef} from '@angular/material/bottom-sheet';
|
import {MatBottomSheet, MatBottomSheetModule, MatBottomSheetRef} from '@angular/material/bottom-sheet';
|
||||||
|
import { ClientAdminsComponent } from './components/client-admins/client-admins.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-dashboard',
|
selector: 'app-dashboard',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [CardComponent, MatButtonModule, MatIconModule, MatDialogModule, MatBottomSheetModule],
|
imports: [ClientCardComponent, MatButtonModule, MatIconModule, MatDialogModule, MatBottomSheetModule],
|
||||||
templateUrl: './dashboard.component.html',
|
templateUrl: './dashboard.component.html',
|
||||||
styleUrl: './dashboard.component.scss'
|
styleUrl: './dashboard.component.scss'
|
||||||
})
|
})
|
||||||
@@ -26,7 +27,9 @@ export class DashboardComponent implements OnInit {
|
|||||||
private dialog: MatDialog = inject(MatDialog);
|
private dialog: MatDialog = inject(MatDialog);
|
||||||
private toast: HotToastService = inject(HotToastService);
|
private toast: HotToastService = inject(HotToastService);
|
||||||
private bottomSheet = inject(MatBottomSheet);
|
private bottomSheet = inject(MatBottomSheet);
|
||||||
|
private injector: Injector = inject(Injector);
|
||||||
|
|
||||||
|
@ViewChild('listSection', { read: ViewContainerRef, static: true }) clientList!: ViewContainerRef;
|
||||||
clients: Client[] = [];
|
clients: Client[] = [];
|
||||||
|
|
||||||
|
|
||||||
@@ -42,6 +45,18 @@ export class DashboardComponent implements OnInit {
|
|||||||
load() {
|
load() {
|
||||||
this.http.get<Client[]>('api/app/user/clients').subscribe(res => {
|
this.http.get<Client[]>('api/app/user/clients').subscribe(res => {
|
||||||
this.clients = res;
|
this.clients = res;
|
||||||
|
this.createList();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
createList() {
|
||||||
|
this.clientList.clear();
|
||||||
|
this.clients.forEach(c => {
|
||||||
|
const ref = this.clientList.createComponent(ClientCardComponent);
|
||||||
|
ref.instance.client = c;
|
||||||
|
ref.instance.onDelete.subscribe(() => {
|
||||||
|
this.openDeleteDialog(c)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,6 +117,7 @@ export class DashboardComponent implements OnInit {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ import { RedirectUri } from "./redirect-uri.interface";
|
|||||||
import { User } from "./user.interface";
|
import { User } from "./user.interface";
|
||||||
|
|
||||||
export interface Client {
|
export interface Client {
|
||||||
admins: User;
|
|
||||||
clientName: string;
|
clientName: string;
|
||||||
id: string;
|
id: string;
|
||||||
description: string;
|
description: string;
|
||||||
redirectUris: RedirectUri[];
|
redirectUris: RedirectUri[];
|
||||||
|
admins: User[];
|
||||||
|
createdAt?: string;
|
||||||
}
|
}
|
||||||
@@ -21,6 +21,7 @@ html, body {
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
|
|
||||||
.create-client__dialog{
|
.create-client__dialog{
|
||||||
|
|||||||
Reference in New Issue
Block a user