finalize migration (yay)
This commit is contained in:
+14
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"tabWidth": 4,
|
||||||
|
"arrowParens": "avoid",
|
||||||
|
"bracketSameLine": true,
|
||||||
|
"semi": true,
|
||||||
|
"overrides": [
|
||||||
|
{
|
||||||
|
"files": "*.yml",
|
||||||
|
"options": {
|
||||||
|
"tabWidth": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
+4
-2
@@ -4,13 +4,15 @@
|
|||||||
"description": "",
|
"description": "",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"generate": "tsoa spec-and-routes"
|
"generate": "tsoa spec-and-routes",
|
||||||
|
"dev": "pwsh ./scripts/dev.ps1"
|
||||||
},
|
},
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "UNLICENSED",
|
"license": "UNLICENSED",
|
||||||
"packageManager": "pnpm@10.20.0",
|
"packageManager": "pnpm@10.24.0+sha512.01ff8ae71b4419903b65c60fb2dc9d34cf8bb6e06d03bde112ef38f7a34d6904c424ba66bea5cdcf12890230bf39f9580473140ed9c946fef328b6e5238a345a",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"nodemon": "^3.1.11",
|
||||||
"tsoa": "^6.6.0"
|
"tsoa": "^6.6.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+11
-6
@@ -8,13 +8,9 @@
|
|||||||
"type": "node",
|
"type": "node",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"name": "Launch Program",
|
"name": "Launch Program",
|
||||||
"skipFiles": [
|
"skipFiles": ["<node_internals>/**"],
|
||||||
"<node_internals>/**"
|
|
||||||
],
|
|
||||||
"program": "${workspaceFolder}\\dist\\index.js",
|
"program": "${workspaceFolder}\\dist\\index.js",
|
||||||
"outFiles": [
|
"outFiles": ["${workspaceFolder}/**/*.js"],
|
||||||
"${workspaceFolder}/**/*.js"
|
|
||||||
],
|
|
||||||
"env": {
|
"env": {
|
||||||
"TKD_DB_USER": "webapi",
|
"TKD_DB_USER": "webapi",
|
||||||
"TKD_DB_PASSWORD": "devpassword",
|
"TKD_DB_PASSWORD": "devpassword",
|
||||||
@@ -22,6 +18,15 @@
|
|||||||
"TKD_COOKIE_SECRET": "devsecret"
|
"TKD_COOKIE_SECRET": "devsecret"
|
||||||
},
|
},
|
||||||
"runtimeExecutable": "C:\\Users\\Toni\\AppData\\Roaming\\fnm\\aliases\\default\\node"
|
"runtimeExecutable": "C:\\Users\\Toni\\AppData\\Roaming\\fnm\\aliases\\default\\node"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Attach to Gandalf",
|
||||||
|
"type": "node",
|
||||||
|
"request": "attach",
|
||||||
|
"address": "192.168.2.250",
|
||||||
|
"port": 9229,
|
||||||
|
"localRoot": "${workspaceFolder}",
|
||||||
|
"remoteRoot": "/var/www/api"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,15 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node dist/index.js",
|
"start": "node dist/index.js",
|
||||||
"build": "tsc -p ."
|
"build": "tsc -p .",
|
||||||
|
"dev": "pnpm -w run dev"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"packageManager": "pnpm@10.24.0+sha512.01ff8ae71b4419903b65c60fb2dc9d34cf8bb6e06d03bde112ef38f7a34d6904c424ba66bea5cdcf12890230bf39f9580473140ed9c946fef328b6e5238a345a",
|
"packageManager": "pnpm@10.24.0+sha512.01ff8ae71b4419903b65c60fb2dc9d34cf8bb6e06d03bde112ef38f7a34d6904c424ba66bea5cdcf12890230bf39f9580473140ed9c946fef328b6e5238a345a",
|
||||||
"private": true,
|
"private": true,
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/cors": "^2.8.19",
|
|
||||||
"@types/connect-pg-simple": "^7.0.3",
|
"@types/connect-pg-simple": "^7.0.3",
|
||||||
|
"@types/cors": "^2.8.19",
|
||||||
"@types/express": "^5.0.5",
|
"@types/express": "^5.0.5",
|
||||||
"@types/express-session": "^1.18.2",
|
"@types/express-session": "^1.18.2",
|
||||||
"@types/node": "^24.9.2",
|
"@types/node": "^24.9.2",
|
||||||
@@ -20,17 +21,19 @@
|
|||||||
"typescript": "~5.9.3"
|
"typescript": "~5.9.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cors": "^2.8.5",
|
|
||||||
"rxjs": "~7.8.2",
|
|
||||||
"@keycloak/keycloak-admin-client": "^26.4.4",
|
"@keycloak/keycloak-admin-client": "^26.4.4",
|
||||||
"@prisma/adapter-pg": "^7.0.1",
|
"@prisma/adapter-pg": "^7.0.1",
|
||||||
"@prisma/client": "^7.0.1",
|
"@prisma/client": "^7.0.1",
|
||||||
"@tsoa/runtime": "^6.6.0",
|
"@tsoa/runtime": "^6.6.0",
|
||||||
"connect-pg-simple": "^10.0.0",
|
"connect-pg-simple": "^10.0.0",
|
||||||
|
"cors": "^2.8.5",
|
||||||
"dotenv": "^17.2.3",
|
"dotenv": "^17.2.3",
|
||||||
"express": "^5.1.0",
|
"express": "^5.1.0",
|
||||||
"express-session": "^1.18.2",
|
"express-session": "^1.18.2",
|
||||||
|
"helmet": "^8.1.0",
|
||||||
"keycloak-connect": "^26.1.1",
|
"keycloak-connect": "^26.1.1",
|
||||||
"pg": "^8.16.3"
|
"pg": "^8.16.3",
|
||||||
|
"rxjs": "~7.8.2",
|
||||||
|
"tsoa": "^6.6.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- You are about to drop the column `uvertrag` on the `Member` table. All the data in the column will be lost.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Member" DROP COLUMN "uvertrag",
|
||||||
|
ADD COLUMN "marker" BOOLEAN;
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "session" (
|
||||||
|
"sid" VARCHAR NOT NULL,
|
||||||
|
"sess" JSON NOT NULL,
|
||||||
|
"expire" TIMESTAMP(6) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "session_pkey" PRIMARY KEY ("sid")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE INDEX "IDX_session_expire" ON "session"("expire");
|
||||||
@@ -15,12 +15,12 @@ model Member {
|
|||||||
geburtsort String? @db.VarChar
|
geburtsort String? @db.VarChar
|
||||||
geschlecht String? @db.VarChar
|
geschlecht String? @db.VarChar
|
||||||
passnummer String? @db.VarChar
|
passnummer String? @db.VarChar
|
||||||
uvertrag Boolean?
|
|
||||||
verein Boolean?
|
verein Boolean?
|
||||||
graduierung String? @db.VarChar
|
graduierung String? @db.VarChar
|
||||||
letztePruefung DateTime? @db.Date
|
letztePruefung DateTime? @db.Date
|
||||||
kontakt String? @db.VarChar
|
kontakt String? @db.VarChar
|
||||||
kuendigungzum DateTime? @db.Date
|
kuendigungzum DateTime? @db.Date
|
||||||
|
marker Boolean?
|
||||||
}
|
}
|
||||||
|
|
||||||
model session {
|
model session {
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
echo "Starting development environment..."
|
|
||||||
|
|
||||||
pnpm -v
|
|
||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
Path,
|
Path,
|
||||||
Post,
|
Post,
|
||||||
Put,
|
Put,
|
||||||
|
Query,
|
||||||
Request,
|
Request,
|
||||||
Response,
|
Response,
|
||||||
Route,
|
Route,
|
||||||
@@ -14,7 +15,7 @@ import {
|
|||||||
} from "@tsoa/runtime";
|
} from "@tsoa/runtime";
|
||||||
import express from "express";
|
import express from "express";
|
||||||
import { Claim } from "../dtos/claim.js";
|
import { Claim } from "../dtos/claim.js";
|
||||||
import type { Member, MemberCreateArgs } from "../dtos/member.js";
|
import type { MemberCreateArgs, MemberDto } from "../dtos/member.js";
|
||||||
import { inject } from "../infrastructure/di/index.js";
|
import { inject } from "../infrastructure/di/index.js";
|
||||||
import { ErrorStates } from "../infrastructure/errorStates.js";
|
import { ErrorStates } from "../infrastructure/errorStates.js";
|
||||||
import { KC_SECURITY_NAME } from "../services/keycloak/user/index.js";
|
import { KC_SECURITY_NAME } from "../services/keycloak/user/index.js";
|
||||||
@@ -28,7 +29,7 @@ export class MembersContoller extends Controller {
|
|||||||
@Get()
|
@Get()
|
||||||
public async getMembers(
|
public async getMembers(
|
||||||
@Request() request: express.Request,
|
@Request() request: express.Request,
|
||||||
): Promise<Member[]> {
|
): Promise<MemberDto[]> {
|
||||||
return await this.membersService.getMembers(request);
|
return await this.membersService.getMembers(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,7 +40,7 @@ export class MembersContoller extends Controller {
|
|||||||
public async getMember(
|
public async getMember(
|
||||||
@Path() memberId: string,
|
@Path() memberId: string,
|
||||||
@Request() request: express.Request,
|
@Request() request: express.Request,
|
||||||
): Promise<Member> {
|
): Promise<MemberDto> {
|
||||||
try {
|
try {
|
||||||
const foundMember = await this.membersService.getMemberById(
|
const foundMember = await this.membersService.getMemberById(
|
||||||
memberId,
|
memberId,
|
||||||
@@ -64,7 +65,7 @@ export class MembersContoller extends Controller {
|
|||||||
public async createMember(
|
public async createMember(
|
||||||
@Body() createArgs: MemberCreateArgs,
|
@Body() createArgs: MemberCreateArgs,
|
||||||
@Request() request: express.Request,
|
@Request() request: express.Request,
|
||||||
): Promise<Member> {
|
): Promise<MemberDto> {
|
||||||
return await this.membersService.createMember(createArgs, request);
|
return await this.membersService.createMember(createArgs, request);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +75,7 @@ export class MembersContoller extends Controller {
|
|||||||
@Response(404, "Member not found")
|
@Response(404, "Member not found")
|
||||||
@Put()
|
@Put()
|
||||||
public async updateMember(
|
public async updateMember(
|
||||||
@Body() member: Member,
|
@Body() member: MemberDto,
|
||||||
@Request() request: express.Request,
|
@Request() request: express.Request,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
try {
|
try {
|
||||||
@@ -97,7 +98,7 @@ export class MembersContoller extends Controller {
|
|||||||
@Delete("{memberId}")
|
@Delete("{memberId}")
|
||||||
public async scheduleDeletion(
|
public async scheduleDeletion(
|
||||||
@Path() memberId: string,
|
@Path() memberId: string,
|
||||||
@Body() cancelDate: Date,
|
@Query() cancelDate: Date,
|
||||||
@Request() request: express.Request,
|
@Request() request: express.Request,
|
||||||
) {
|
) {
|
||||||
await this.membersService.scheduleDeletion(
|
await this.membersService.scheduleDeletion(
|
||||||
|
|||||||
@@ -1,27 +1,39 @@
|
|||||||
import { type MemberModel as DbMember } from "../generated/prisma/models.js";
|
import { type MemberModel as DbMember } from "../generated/prisma/models.js";
|
||||||
|
|
||||||
export interface Member {
|
export interface MemberDto {
|
||||||
id: string | null;
|
id: string | null;
|
||||||
vorname: string | null;
|
vorname: string | null;
|
||||||
nachname: string | null;
|
nachname: string | null;
|
||||||
geburtsdatum: Date | null;
|
geburtsdatum: string | null;
|
||||||
geburtsort: string | null;
|
geburtsort: string | null;
|
||||||
geschlecht: string | null;
|
geschlecht: string | null;
|
||||||
passnummer: string | null;
|
passnummer: string | null;
|
||||||
uvertrag: boolean | null;
|
|
||||||
verein: boolean | null;
|
verein: boolean | null;
|
||||||
graduierung: string | null;
|
graduierung: string | null;
|
||||||
letztePruefung: Date | null;
|
letztePruefung: string | null;
|
||||||
kontakt: string | null;
|
kontakt: string | null;
|
||||||
kuendigungzum: Date | null;
|
kuendigungzum: string | null;
|
||||||
loeschenam: Date | null;
|
marker: boolean | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MemberCreateArgs = Omit<Member, "id" | "loeschenam">;
|
export class MemberCreateArgs implements Omit<MemberDto, "id"> {
|
||||||
|
vorname!: string | null;
|
||||||
|
nachname!: string | null;
|
||||||
|
geburtsdatum!: string | null;
|
||||||
|
geburtsort!: string | null;
|
||||||
|
geschlecht!: string | null;
|
||||||
|
passnummer!: string | null;
|
||||||
|
verein!: boolean | null;
|
||||||
|
graduierung!: string | null;
|
||||||
|
letztePruefung!: string | null;
|
||||||
|
kontakt!: string | null;
|
||||||
|
kuendigungzum!: string | null;
|
||||||
|
marker!: boolean | null;
|
||||||
|
}
|
||||||
|
|
||||||
export function mapDbMemberToMember(dbMember: DbMember): Member {
|
export function mapDbMemberToMember(dbMember: DbMember): MemberDto {
|
||||||
return {
|
return {
|
||||||
geburtsdatum: dbMember.geburtsdatum,
|
geburtsdatum: dbMember.geburtsdatum?.toDateString() ?? null,
|
||||||
id: dbMember.id,
|
id: dbMember.id,
|
||||||
nachname: dbMember.nachname,
|
nachname: dbMember.nachname,
|
||||||
geburtsort: dbMember.geburtsort,
|
geburtsort: dbMember.geburtsort,
|
||||||
@@ -29,11 +41,34 @@ export function mapDbMemberToMember(dbMember: DbMember): Member {
|
|||||||
graduierung: dbMember.graduierung,
|
graduierung: dbMember.graduierung,
|
||||||
vorname: dbMember.vorname,
|
vorname: dbMember.vorname,
|
||||||
kontakt: dbMember.kontakt,
|
kontakt: dbMember.kontakt,
|
||||||
kuendigungzum: dbMember.kuendigungzum,
|
kuendigungzum: dbMember.kuendigungzum?.toDateString() ?? null,
|
||||||
letztePruefung: dbMember.letztePruefung,
|
letztePruefung: dbMember.letztePruefung?.toDateString() ?? null,
|
||||||
passnummer: dbMember.passnummer,
|
passnummer: dbMember.passnummer,
|
||||||
uvertrag: dbMember.uvertrag,
|
|
||||||
verein: dbMember.verein,
|
verein: dbMember.verein,
|
||||||
loeschenam: new Date(), //TODO
|
marker: dbMember.marker,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mapMemberDtoToDbMember(member: MemberDto): DbMember {
|
||||||
|
return {
|
||||||
|
id: member.id!,
|
||||||
|
vorname: member.vorname,
|
||||||
|
nachname: member.nachname,
|
||||||
|
geburtsdatum: member.geburtsdatum
|
||||||
|
? new Date(member.geburtsdatum)
|
||||||
|
: null,
|
||||||
|
geburtsort: member.geburtsort,
|
||||||
|
geschlecht: member.geschlecht,
|
||||||
|
passnummer: member.passnummer,
|
||||||
|
verein: member.verein,
|
||||||
|
graduierung: member.graduierung,
|
||||||
|
letztePruefung: member.letztePruefung
|
||||||
|
? new Date(member.letztePruefung)
|
||||||
|
: null,
|
||||||
|
kontakt: member.kontakt,
|
||||||
|
kuendigungzum: member.kuendigungzum
|
||||||
|
? new Date(member.kuendigungzum)
|
||||||
|
: null,
|
||||||
|
marker: member.marker,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
+57
-11
@@ -1,22 +1,25 @@
|
|||||||
|
import constants from "constants";
|
||||||
import cors from "cors";
|
import cors from "cors";
|
||||||
import express, {
|
import express, {
|
||||||
type NextFunction,
|
type NextFunction,
|
||||||
type Request,
|
type Request,
|
||||||
type Response,
|
type Response,
|
||||||
} from "express";
|
} from "express";
|
||||||
|
import fs from "fs";
|
||||||
|
import https from "https";
|
||||||
import { RegisterRoutes } from "./generated/routes.js";
|
import { RegisterRoutes } from "./generated/routes.js";
|
||||||
import { inject } from "./infrastructure/di/injector.js";
|
import { inject } from "./infrastructure/di/injector.js";
|
||||||
import { sessionHandler } from "./infrastructure/sessionHandler.js";
|
import { sessionHandler } from "./infrastructure/sessionHandler.js";
|
||||||
import { EnvironmentService } from "./services/environmentService.js";
|
import { EnvironmentService } from "./services/environmentService.js";
|
||||||
import { keycloak } from "./services/keycloak/user/index.js";
|
import { KeycloakUser } from "./services/keycloak/user/keycloak-user.js";
|
||||||
|
// import helmet from "helmet";
|
||||||
|
// TODO import http2 from 'http2';
|
||||||
|
|
||||||
|
async function run() {
|
||||||
const app = express();
|
const app = express();
|
||||||
|
const keycloakUser = inject(KeycloakUser);
|
||||||
|
|
||||||
app.use(sessionHandler);
|
//TODO app.use(helmet.hsts()); this and other helmets
|
||||||
app.use(keycloak.middleware());
|
|
||||||
|
|
||||||
app.use(express.urlencoded({ extended: true }));
|
|
||||||
app.use(express.json());
|
|
||||||
app.use(
|
app.use(
|
||||||
cors({
|
cors({
|
||||||
origin: [
|
origin: [
|
||||||
@@ -24,12 +27,17 @@ app.use(
|
|||||||
"https://tcc-1-ev-intern.web.app",
|
"https://tcc-1-ev-intern.web.app",
|
||||||
"http://localhost:4200",
|
"http://localhost:4200",
|
||||||
"https://localhost:4200",
|
"https://localhost:4200",
|
||||||
"http://localhost:3000",
|
|
||||||
"http://localhost:8080",
|
"http://localhost:8080",
|
||||||
],
|
],
|
||||||
|
methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
|
||||||
//TODO reevaluate
|
//TODO reevaluate
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
app.use(sessionHandler);
|
||||||
|
app.use(await keycloakUser.middleware());
|
||||||
|
|
||||||
|
app.use(express.urlencoded({ extended: true }));
|
||||||
|
app.use(express.json());
|
||||||
|
|
||||||
// const dbService = inject(DatabaseService);
|
// const dbService = inject(DatabaseService);
|
||||||
// app.use(async (req, res, next) => {
|
// app.use(async (req, res, next) => {
|
||||||
@@ -48,11 +56,15 @@ RegisterRoutes(app);
|
|||||||
|
|
||||||
const environment = inject(EnvironmentService);
|
const environment = inject(EnvironmentService);
|
||||||
// wants to be last
|
// wants to be last
|
||||||
app.use((err: unknown, _req: Request, res: Response, _next: NextFunction) => {
|
app.use(
|
||||||
res.status(500);
|
(err: unknown, _req: Request, res: Response, _next: NextFunction) => {
|
||||||
res.send(JSON.stringify(err));
|
console.dir(err);
|
||||||
|
const ex = JSON.stringify(err);
|
||||||
|
res.header("Content-Length", `${ex.length}`);
|
||||||
|
res.status(500).send(ex);
|
||||||
throw err;
|
throw err;
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
if (environment.isDev()) {
|
if (environment.isDev()) {
|
||||||
//TODO create self-signed for localhost
|
//TODO create self-signed for localhost
|
||||||
@@ -62,6 +74,40 @@ if (environment.isDev()) {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
//TODO start with HTTPS in production
|
//TODO start with HTTPS in production
|
||||||
|
const port = 5443;
|
||||||
|
const SSL_CERT_PATH = process.env["SSL_CERT_PATH"];
|
||||||
|
const SSL_KEY_PATH = process.env["SSL_KEY_PATH"];
|
||||||
|
const SSL_KEY_PASSWORD = process.env["SSL_KEY_PASSWORD"];
|
||||||
|
if (!SSL_CERT_PATH || !SSL_KEY_PATH || !SSL_KEY_PASSWORD) {
|
||||||
|
throw new Error("Missing SSL configuration");
|
||||||
|
}
|
||||||
|
|
||||||
|
const sslOptions: https.ServerOptions = {
|
||||||
|
cert: fs.readFileSync(SSL_CERT_PATH),
|
||||||
|
key: fs.readFileSync(SSL_KEY_PATH),
|
||||||
|
passphrase: SSL_KEY_PASSWORD,
|
||||||
|
minVersion: "TLSv1.3",
|
||||||
|
maxVersion: "TLSv1.3",
|
||||||
|
ecdhCurve: "X25519:prime256v1:secp384r1",
|
||||||
|
honorCipherOrder: false,
|
||||||
|
secureOptions:
|
||||||
|
constants.SSL_OP_NO_TLSv1 |
|
||||||
|
constants.SSL_OP_NO_TLSv1_1 |
|
||||||
|
constants.SSL_OP_NO_TLSv1_2 |
|
||||||
|
constants.SSL_OP_NO_SSLv2 |
|
||||||
|
constants.SSL_OP_NO_SSLv3,
|
||||||
|
};
|
||||||
|
https.createServer(sslOptions, app).listen(port, () => {
|
||||||
|
console.log(`Server is running on port ${port}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// const h2SslOptions={};
|
||||||
|
// http2.createServer(h2SslOptions, app).listen(port, ()=>{
|
||||||
|
// console.log(`HTTP/2 server is running on port ${port}`);
|
||||||
|
// })
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("done");
|
console.log("done");
|
||||||
|
}
|
||||||
|
|
||||||
|
run();
|
||||||
|
|||||||
@@ -1,40 +1,31 @@
|
|||||||
import { type Request, type RequestHandler } from "express";
|
import { type Request, type Response } from "express";
|
||||||
|
|
||||||
import { KC_SECURITY_NAME, keycloak } from "../services/keycloak/user/index.js";
|
import { inject } from "../infrastructure/di/index.js";
|
||||||
|
import {
|
||||||
|
KC_SECURITY_NAME,
|
||||||
|
KeycloakUser,
|
||||||
|
} from "../services/keycloak/user/index.js";
|
||||||
|
|
||||||
function promisifyMiddleware(
|
const keycloak = inject(KeycloakUser);
|
||||||
request: Request,
|
|
||||||
middleware: RequestHandler,
|
|
||||||
response?: Response,
|
|
||||||
): Promise<any> {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
middleware(request, response || ({} as any), (next_result: any) => {
|
|
||||||
console.dir(next_result);
|
|
||||||
next_result instanceof Error
|
|
||||||
? reject(next_result)
|
|
||||||
: resolve(next_result);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function doKeycloakAuth(
|
async function doKeycloakAuth(
|
||||||
request: Request,
|
request: Request,
|
||||||
roles?: string[],
|
roles?: string[],
|
||||||
response?: Response,
|
response?: Response,
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
if (roles && roles.length > 0) {
|
if (!roles || roles.length < 1) {
|
||||||
const grant = await keycloak.getGrant(request, response ?? ({} as any));
|
roles = ["default-roles-taekwondo"];
|
||||||
const allowed = roles.every(role =>
|
}
|
||||||
grant.access_token?.hasRealmRole(role),
|
const allowed = await keycloak.checkRoles(
|
||||||
|
roles,
|
||||||
|
request,
|
||||||
|
response ?? ({} as any),
|
||||||
);
|
);
|
||||||
if (allowed) {
|
if (allowed) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
} else {
|
} else {
|
||||||
return Promise.reject(httpError(403, "Forbidden"));
|
return Promise.reject(httpError(403, "Forbidden"));
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return await promisifyMiddleware(request, keycloak.protect(), response);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function expressAuthentication(
|
export async function expressAuthentication(
|
||||||
|
|||||||
@@ -44,9 +44,9 @@ export class DatabaseService {
|
|||||||
private readonly prismaClient: ExtendedClient;
|
private readonly prismaClient: ExtendedClient;
|
||||||
|
|
||||||
public constructor() {
|
public constructor() {
|
||||||
const dbUser = process.env["TKD_DB_USER"];
|
const dbUser = process.env["TKD_DB_USER"]!;
|
||||||
const dbPassword = process.env["TKD_DB_PASSWORD"];
|
const dbPassword = process.env["TKD_DB_PASSWORD"]!;
|
||||||
const dbHost = process.env["TKD_DB_HOST"];
|
const dbHost = process.env["TKD_DB_HOST"]!;
|
||||||
|
|
||||||
const connectionString = `postgresql://${dbUser}:${dbPassword}@${dbHost}:5432/taekwondo?schema=public`;
|
const connectionString = `postgresql://${dbUser}:${dbPassword}@${dbHost}:5432/taekwondo?schema=public`;
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,43 @@
|
|||||||
import { Injectable } from "../../../infrastructure/di/injectable-decorator.js";
|
import { Injectable } from "../../../infrastructure/di/injectable-decorator.js";
|
||||||
import kcClientConfig from "./nodejs-api.json" with { type: "json" };
|
|
||||||
|
|
||||||
import KeycloakAdminClient from "@keycloak/keycloak-admin-client";
|
import KeycloakAdminClient from "@keycloak/keycloak-admin-client";
|
||||||
import type RoleRepresentation from "@keycloak/keycloak-admin-client/lib/defs/roleRepresentation.js";
|
import type RoleRepresentation from "@keycloak/keycloak-admin-client/lib/defs/roleRepresentation.js";
|
||||||
import { Claim } from "../../../dtos/claim.js";
|
import { Claim } from "../../../dtos/claim.js";
|
||||||
import { type User } from "../../../dtos/user.js";
|
import { type User } from "../../../dtos/user.js";
|
||||||
|
import { inject } from "../../../infrastructure/di/index.js";
|
||||||
|
import { EnvironmentService } from "../../environmentService.js";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class KeycloakAdmin {
|
export class KeycloakAdmin {
|
||||||
private readonly kcAdminClient: Promise<KeycloakAdminClient>;
|
private readonly kcAdminClient: Promise<KeycloakAdminClient>;
|
||||||
|
private readonly environment = inject(EnvironmentService);
|
||||||
|
private readonly config = this.getConfig();
|
||||||
|
|
||||||
|
private async getConfig() {
|
||||||
|
if (this.environment.isDev()) {
|
||||||
|
const config = await import("./nodejs-api.dev.json", {
|
||||||
|
with: { type: "json" },
|
||||||
|
});
|
||||||
|
return config.default;
|
||||||
|
} else {
|
||||||
|
const config = await import("./nodejs-api.prod.json", {
|
||||||
|
with: { type: "json" },
|
||||||
|
});
|
||||||
|
return config.default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.kcAdminClient = new Promise(async resolve => {
|
this.kcAdminClient = new Promise(async resolve => {
|
||||||
|
const config = await this.config;
|
||||||
const client = new KeycloakAdminClient({
|
const client = new KeycloakAdminClient({
|
||||||
baseUrl: kcClientConfig.baseUrl,
|
baseUrl: config["auth-server-url"],
|
||||||
realmName: "taekwondo",
|
realmName: "taekwondo",
|
||||||
});
|
});
|
||||||
|
|
||||||
await client.auth({
|
await client.auth({
|
||||||
...kcClientConfig,
|
clientSecret: config.credentials.secret,
|
||||||
|
clientId: config.resource,
|
||||||
grantType: "client_credentials",
|
grantType: "client_credentials",
|
||||||
});
|
});
|
||||||
resolve(client);
|
resolve(client);
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"realm": "taekwondo",
|
||||||
|
"auth-server-url": "http://localhost:8080/",
|
||||||
|
"ssl-required": "external",
|
||||||
|
"resource": "nodejs-api",
|
||||||
|
"credentials": {
|
||||||
|
"secret": "devsecret"
|
||||||
|
},
|
||||||
|
"confidential-port": 0
|
||||||
|
}
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
{
|
|
||||||
"clientId": "nodejs-api",
|
|
||||||
"name": "NodejsApi",
|
|
||||||
"description": "",
|
|
||||||
"rootUrl": "http://localhost:3000",
|
|
||||||
"adminUrl": "http://localhost:3000",
|
|
||||||
"baseUrl": "http://localhost:3000",
|
|
||||||
"surrogateAuthRequired": false,
|
|
||||||
"enabled": true,
|
|
||||||
"alwaysDisplayInConsole": false,
|
|
||||||
"clientAuthenticatorType": "client-secret",
|
|
||||||
"secret": "devsecret",
|
|
||||||
"redirectUris": ["http://localhost:3000/*"],
|
|
||||||
"webOrigins": ["http://localhost:3000"],
|
|
||||||
"notBefore": 0,
|
|
||||||
"bearerOnly": false,
|
|
||||||
"consentRequired": false,
|
|
||||||
"standardFlowEnabled": false,
|
|
||||||
"implicitFlowEnabled": false,
|
|
||||||
"directAccessGrantsEnabled": false,
|
|
||||||
"serviceAccountsEnabled": true,
|
|
||||||
"publicClient": false,
|
|
||||||
"frontchannelLogout": true,
|
|
||||||
"protocol": "openid-connect",
|
|
||||||
"attributes": {
|
|
||||||
"realm_client": "false",
|
|
||||||
"oidc.ciba.grant.enabled": "false",
|
|
||||||
"client.secret.creation.time": "1762463686",
|
|
||||||
"backchannel.logout.session.required": "true",
|
|
||||||
"standard.token.exchange.enabled": "true",
|
|
||||||
"oauth2.device.authorization.grant.enabled": "false",
|
|
||||||
"pkce.code.challenge.method": "S256",
|
|
||||||
"backchannel.logout.revoke.offline.tokens": "false",
|
|
||||||
"dpop.bound.access.tokens": "false"
|
|
||||||
},
|
|
||||||
"authenticationFlowBindingOverrides": {},
|
|
||||||
"fullScopeAllowed": true,
|
|
||||||
"nodeReRegistrationTimeout": -1,
|
|
||||||
"defaultClientScopes": [
|
|
||||||
"service_account",
|
|
||||||
"web-origins",
|
|
||||||
"acr",
|
|
||||||
"roles",
|
|
||||||
"profile",
|
|
||||||
"basic",
|
|
||||||
"email"
|
|
||||||
],
|
|
||||||
"optionalClientScopes": [
|
|
||||||
"address",
|
|
||||||
"phone",
|
|
||||||
"organization",
|
|
||||||
"offline_access",
|
|
||||||
"microprofile-jwt"
|
|
||||||
],
|
|
||||||
"access": {
|
|
||||||
"view": true,
|
|
||||||
"configure": true,
|
|
||||||
"manage": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,11 +1,52 @@
|
|||||||
|
import { type Request, type RequestHandler, type Response } from "express";
|
||||||
import KeycloakConnect from "keycloak-connect";
|
import KeycloakConnect from "keycloak-connect";
|
||||||
|
import { inject } from "../../../infrastructure/di/index.js";
|
||||||
|
import { Injectable } from "../../../infrastructure/di/injectable-decorator.js";
|
||||||
import { sessionStore } from "../../../infrastructure/sessionHandler.js";
|
import { sessionStore } from "../../../infrastructure/sessionHandler.js";
|
||||||
import keycloakConfig from "./keycloak.json" with { type: "json" };
|
import { EnvironmentService } from "../../environmentService.js";
|
||||||
|
//TODO transform into service and load config based on environment (see keycloak-admin)
|
||||||
|
|
||||||
export const KC_SECURITY_NAME = "bearerAuth";
|
export const KC_SECURITY_NAME = "bearerAuth";
|
||||||
|
|
||||||
export const keycloak = new KeycloakConnect(
|
@Injectable()
|
||||||
{ store: sessionStore },
|
export class KeycloakUser {
|
||||||
keycloakConfig,
|
private readonly environment = inject(EnvironmentService);
|
||||||
);
|
private readonly keycloakConnect: Promise<KeycloakConnect.Keycloak>;
|
||||||
export const kcUserConfig = keycloakConfig;
|
|
||||||
|
private async getConfig() {
|
||||||
|
if (this.environment.isDev()) {
|
||||||
|
const config = await import("./keycloak.dev.json", {
|
||||||
|
with: { type: "json" },
|
||||||
|
});
|
||||||
|
return config.default;
|
||||||
|
} else {
|
||||||
|
const config = await import("./keycloak.prod.json", {
|
||||||
|
with: { type: "json" },
|
||||||
|
});
|
||||||
|
return config.default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public constructor() {
|
||||||
|
this.keycloakConnect = new Promise(async resolve => {
|
||||||
|
const config = await this.getConfig();
|
||||||
|
const client = new KeycloakConnect({ store: sessionStore }, config);
|
||||||
|
resolve(client);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async checkRoles(
|
||||||
|
roles: string[],
|
||||||
|
request: Request,
|
||||||
|
response: Response,
|
||||||
|
) {
|
||||||
|
const client = await this.keycloakConnect;
|
||||||
|
const grant = await client.getGrant(request, response ?? ({} as any));
|
||||||
|
return roles.every(role => grant.access_token?.hasRealmRole(role));
|
||||||
|
}
|
||||||
|
|
||||||
|
public async middleware(): Promise<RequestHandler[]> {
|
||||||
|
const client = await this.keycloakConnect;
|
||||||
|
return client.middleware();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import express from "express";
|
import express from "express";
|
||||||
import {
|
import {
|
||||||
type Member,
|
|
||||||
type MemberCreateArgs,
|
type MemberCreateArgs,
|
||||||
|
type MemberDto,
|
||||||
mapDbMemberToMember,
|
mapDbMemberToMember,
|
||||||
} from "../dtos/member.js";
|
} from "../dtos/member.js";
|
||||||
import { inject, Injectable } from "../infrastructure/di/index.js";
|
import { inject, Injectable } from "../infrastructure/di/index.js";
|
||||||
@@ -12,7 +12,7 @@ import { DatabaseService } from "./db/prisma.js";
|
|||||||
export class MembersService {
|
export class MembersService {
|
||||||
private readonly database = inject(DatabaseService);
|
private readonly database = inject(DatabaseService);
|
||||||
|
|
||||||
public async getMembers(request: express.Request): Promise<Member[]> {
|
public async getMembers(request: express.Request): Promise<MemberDto[]> {
|
||||||
const dbMembers = await this.database.doRequest(
|
const dbMembers = await this.database.doRequest(
|
||||||
async prisma => await prisma.member.findMany(),
|
async prisma => await prisma.member.findMany(),
|
||||||
request,
|
request,
|
||||||
@@ -23,7 +23,7 @@ export class MembersService {
|
|||||||
public async getMemberById(
|
public async getMemberById(
|
||||||
memberId: string,
|
memberId: string,
|
||||||
request: express.Request,
|
request: express.Request,
|
||||||
): Promise<Member> {
|
): Promise<MemberDto> {
|
||||||
const member = await this.database.doRequest(
|
const member = await this.database.doRequest(
|
||||||
async prisma =>
|
async prisma =>
|
||||||
await prisma.member.findFirst({ where: { id: memberId } }),
|
await prisma.member.findFirst({ where: { id: memberId } }),
|
||||||
@@ -42,11 +42,11 @@ export class MembersService {
|
|||||||
public async createMember(
|
public async createMember(
|
||||||
createArgs: MemberCreateArgs,
|
createArgs: MemberCreateArgs,
|
||||||
request: express.Request,
|
request: express.Request,
|
||||||
): Promise<Member> {
|
): Promise<MemberDto> {
|
||||||
const x: MemberCreateArgs = {
|
// const x: MemberCreateArgs = {
|
||||||
...createArgs,
|
// ...createArgs,
|
||||||
geburtsdatum: createArgs.geburtsdatum ?? null,
|
// geburtsdatum: createArgs.geburtsdatum ?? null,
|
||||||
};
|
// };
|
||||||
const newMember = await this.database.doRequest(
|
const newMember = await this.database.doRequest(
|
||||||
async prisma => await prisma.member.create({ data: createArgs }),
|
async prisma => await prisma.member.create({ data: createArgs }),
|
||||||
request,
|
request,
|
||||||
@@ -55,15 +55,16 @@ export class MembersService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async updateMember(
|
public async updateMember(
|
||||||
member: Member,
|
member: MemberDto,
|
||||||
request: express.Request,
|
request: express.Request,
|
||||||
): Promise<Member> {
|
): Promise<MemberDto> {
|
||||||
const id = member.id;
|
const id = member.id;
|
||||||
if (!id) {
|
if (!id) {
|
||||||
throw new Error("Member id is required for update", {
|
throw new Error("Member id is required for update", {
|
||||||
cause: ErrorStates.BAD_REQUEST,
|
cause: ErrorStates.BAD_REQUEST,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const modifiedMember = await this.database.doRequest(
|
const modifiedMember = await this.database.doRequest(
|
||||||
async prisma =>
|
async prisma =>
|
||||||
await prisma.member.update({
|
await prisma.member.update({
|
||||||
|
|||||||
@@ -56,5 +56,6 @@
|
|||||||
"alwaysStrict": true,
|
"alwaysStrict": true,
|
||||||
"forceConsistentCasingInFileNames": true
|
"forceConsistentCasingInFileNames": true
|
||||||
},
|
},
|
||||||
"files": ["./src/index.ts"]
|
"files": ["./src/index.ts"],
|
||||||
|
"include": ["src"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,8 @@
|
|||||||
"build": "ng build",
|
"build": "ng build",
|
||||||
"watch": "ng build --watch --configuration development",
|
"watch": "ng build --watch --configuration development",
|
||||||
"test": "ng test",
|
"test": "ng test",
|
||||||
"generate": "oazapfts --optimistic --useEnumType ./src/app/generated-api/swagger.json ./src/app/generated-api/api.ts"
|
"generate": "oazapfts --optimistic --useEnumType ./src/app/generated-api/swagger.json ./src/app/generated-api/api.ts",
|
||||||
|
"dev": "pnpm -w run dev"
|
||||||
},
|
},
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"printWidth": 100,
|
"printWidth": 100,
|
||||||
@@ -29,7 +30,7 @@
|
|||||||
"@angular/core": "^20.3.15",
|
"@angular/core": "^20.3.15",
|
||||||
"@angular/forms": "^20.3.15",
|
"@angular/forms": "^20.3.15",
|
||||||
"@angular/material": "20.2.11",
|
"@angular/material": "20.2.11",
|
||||||
"@angular/material-date-fns-adapter": "^21.0.2",
|
"@angular/material-date-fns-adapter": "^20.2.14",
|
||||||
"@angular/platform-browser": "^20.3.15",
|
"@angular/platform-browser": "^20.3.15",
|
||||||
"@angular/router": "^20.3.15",
|
"@angular/router": "^20.3.15",
|
||||||
"@date-fns/tz": "^1.4.1",
|
"@date-fns/tz": "^1.4.1",
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import {
|
import {
|
||||||
ApplicationConfig,
|
ApplicationConfig,
|
||||||
|
isDevMode,
|
||||||
LOCALE_ID,
|
LOCALE_ID,
|
||||||
|
provideAppInitializer,
|
||||||
provideBrowserGlobalErrorListeners,
|
provideBrowserGlobalErrorListeners,
|
||||||
provideZoneChangeDetection,
|
provideZoneChangeDetection,
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
@@ -33,9 +35,11 @@ import {
|
|||||||
import { MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field';
|
import { MAT_FORM_FIELD_DEFAULT_OPTIONS } from '@angular/material/form-field';
|
||||||
import { de } from 'date-fns/locale';
|
import { de } from 'date-fns/locale';
|
||||||
import { routes } from './app.routes';
|
import { routes } from './app.routes';
|
||||||
|
import { initializeApi } from './util/api';
|
||||||
|
|
||||||
export const appConfig: ApplicationConfig = {
|
export const appConfig: ApplicationConfig = {
|
||||||
providers: [
|
providers: [
|
||||||
|
provideAppInitializer(initializeApi),
|
||||||
provideRouter(
|
provideRouter(
|
||||||
routes,
|
routes,
|
||||||
withPreloading(PreloadAllModules),
|
withPreloading(PreloadAllModules),
|
||||||
@@ -45,7 +49,7 @@ export const appConfig: ApplicationConfig = {
|
|||||||
provideZoneChangeDetection({ eventCoalescing: true }),
|
provideZoneChangeDetection({ eventCoalescing: true }),
|
||||||
provideKeycloak({
|
provideKeycloak({
|
||||||
config: {
|
config: {
|
||||||
url: true
|
url: isDevMode()
|
||||||
? 'http://localhost:8080/'
|
? 'http://localhost:8080/'
|
||||||
: 'https://keycloak.toni714.de:8443/',
|
: 'https://keycloak.toni714.de:8443/',
|
||||||
realm: 'taekwondo',
|
realm: 'taekwondo',
|
||||||
@@ -55,6 +59,9 @@ export const appConfig: ApplicationConfig = {
|
|||||||
onLoad: 'check-sso',
|
onLoad: 'check-sso',
|
||||||
silentCheckSsoRedirectUri:
|
silentCheckSsoRedirectUri:
|
||||||
window.location.origin + '/silent-check-sso.html',
|
window.location.origin + '/silent-check-sso.html',
|
||||||
|
pkceMethod: 'S256',
|
||||||
|
flow: 'standard',
|
||||||
|
locale: 'de',
|
||||||
},
|
},
|
||||||
features: [withAutoRefreshToken({ sessionTimeout: 600000 })],
|
features: [withAutoRefreshToken({ sessionTimeout: 600000 })],
|
||||||
providers: [AutoRefreshTokenService, UserActivityService],
|
providers: [AutoRefreshTokenService, UserActivityService],
|
||||||
@@ -63,7 +70,7 @@ export const appConfig: ApplicationConfig = {
|
|||||||
provide: INCLUDE_BEARER_TOKEN_INTERCEPTOR_CONFIG,
|
provide: INCLUDE_BEARER_TOKEN_INTERCEPTOR_CONFIG,
|
||||||
useValue: [
|
useValue: [
|
||||||
createInterceptorCondition<IncludeBearerTokenCondition>({
|
createInterceptorCondition<IncludeBearerTokenCondition>({
|
||||||
urlPattern: true
|
urlPattern: isDevMode()
|
||||||
? /^http:\/\/localhost:(3000|8080)\/.*$/
|
? /^http:\/\/localhost:(3000|8080)\/.*$/
|
||||||
: /^https:\/\/tkd-api\.toni714\.de\/.*$/,
|
: /^https:\/\/tkd-api\.toni714\.de\/.*$/,
|
||||||
}),
|
}),
|
||||||
|
|||||||
+15
-7
@@ -14,9 +14,13 @@ import { MatFormFieldModule } from '@angular/material/form-field';
|
|||||||
import { MatInputModule } from '@angular/material/input';
|
import { MatInputModule } from '@angular/material/input';
|
||||||
import { MatSelectModule } from '@angular/material/select';
|
import { MatSelectModule } from '@angular/material/select';
|
||||||
import * as api from '../../generated-api/api';
|
import * as api from '../../generated-api/api';
|
||||||
import { DatePipePipe } from '../../pipes/date-pipe.pipe.js';
|
import { DatePipePipe, dateToLocal } from '../../pipes/date-pipe.pipe.js';
|
||||||
import { EditMemberDialogComponent } from '../edit-member-dialog/edit-member-dialog.component.js';
|
import { EditMemberDialogComponent } from '../edit-member-dialog/edit-member-dialog.component.js';
|
||||||
|
|
||||||
|
type DeletionForm = {
|
||||||
|
kuendigungzum: FormControl<Date | null>;
|
||||||
|
};
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-delete-member-dialog',
|
selector: 'app-delete-member-dialog',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
@@ -35,23 +39,27 @@ import { EditMemberDialogComponent } from '../edit-member-dialog/edit-member-dia
|
|||||||
styleUrl: './delete-member-dialog.component.scss',
|
styleUrl: './delete-member-dialog.component.scss',
|
||||||
})
|
})
|
||||||
export class DeleteMemberDialogComponent {
|
export class DeleteMemberDialogComponent {
|
||||||
form: FormGroup;
|
form: FormGroup<DeletionForm>;
|
||||||
member: api.Member;
|
member: api.MemberDto;
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(MAT_DIALOG_DATA) public data: { member: api.Member },
|
@Inject(MAT_DIALOG_DATA) public data: { member: api.MemberDto },
|
||||||
private dialogRef: MatDialogRef<EditMemberDialogComponent>,
|
private dialogRef: MatDialogRef<EditMemberDialogComponent>,
|
||||||
) {
|
) {
|
||||||
this.member = data.member;
|
this.member = data.member;
|
||||||
this.form = new FormGroup({
|
this.form = new FormGroup<DeletionForm>({
|
||||||
kuendigungzum: new FormControl('', [Validators.required]),
|
kuendigungzum: new FormControl(null, [Validators.required]),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async submit() {
|
async submit() {
|
||||||
|
const date = this.form.controls.kuendigungzum.value;
|
||||||
|
if (date) {
|
||||||
await api.scheduleDeletion(
|
await api.scheduleDeletion(
|
||||||
this.member.id!,
|
this.member.id!,
|
||||||
this.form.controls['kuendigungzum'].value,
|
dateToLocal(date).toISOString(),
|
||||||
);
|
);
|
||||||
|
|
||||||
this.dialogRef.close();
|
this.dialogRef.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
+2
-2
@@ -30,7 +30,7 @@ import { MemberDtoHelper } from '../../util/member.js';
|
|||||||
export class EditMemberDialogComponent {
|
export class EditMemberDialogComponent {
|
||||||
form: FormGroup;
|
form: FormGroup;
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(MAT_DIALOG_DATA) public data: { member: api.Member },
|
@Inject(MAT_DIALOG_DATA) public data: { member: api.MemberDto },
|
||||||
private memberDtoHelper: MemberDtoHelper,
|
private memberDtoHelper: MemberDtoHelper,
|
||||||
private dialogRef: MatDialogRef<EditMemberDialogComponent>,
|
private dialogRef: MatDialogRef<EditMemberDialogComponent>,
|
||||||
) {
|
) {
|
||||||
@@ -38,7 +38,7 @@ export class EditMemberDialogComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
submit() {
|
submit() {
|
||||||
const member: api.Member = this.memberDtoHelper.memberFromForm(
|
const member: api.MemberDto = this.memberDtoHelper.memberFromForm(
|
||||||
this.form,
|
this.form,
|
||||||
);
|
);
|
||||||
api.updateMember(member);
|
api.updateMember(member);
|
||||||
|
|||||||
@@ -14,19 +14,23 @@
|
|||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<mat-label>Geburtsdatum</mat-label>
|
<mat-label>Geburtsdatum</mat-label>
|
||||||
<input matInput [matDatepicker]="picker" formControlName="geburtsdatum" />
|
<input
|
||||||
|
matInput
|
||||||
|
[matDatepicker]="picker"
|
||||||
|
formControlName="geburtsdatum" />
|
||||||
<mat-hint>DD.MM.YYYY</mat-hint>
|
<mat-hint>DD.MM.YYYY</mat-hint>
|
||||||
<mat-datepicker-toggle matIconSuffix [for]="picker"></mat-datepicker-toggle>
|
<mat-datepicker-toggle
|
||||||
|
matIconSuffix
|
||||||
|
[for]="picker"></mat-datepicker-toggle>
|
||||||
<mat-datepicker #picker></mat-datepicker>
|
<mat-datepicker #picker></mat-datepicker>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<mat-label>Geburtsort</mat-label>
|
<mat-label>Geburtsort</mat-label>
|
||||||
<input matInput formControlName="geburtsort" />
|
<input matInput formControlName="geburtsort" />
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
<mat-checkbox formControlName="uvertrag"
|
<mat-checkbox [formControl]="memberForm.controls.verein"
|
||||||
>Unterrichtsvertrag Abgegeben?</mat-checkbox
|
>Verein Beigetreten?</mat-checkbox
|
||||||
>
|
>
|
||||||
<mat-checkbox formControlName="uvertrag">Verein Beigetreten?</mat-checkbox>
|
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<mat-label>Passnummer (falls vorhanden)</mat-label>
|
<mat-label>Passnummer (falls vorhanden)</mat-label>
|
||||||
<input matInput formControlName="passnummer" />
|
<input matInput formControlName="passnummer" />
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export class NewMemberComponent {
|
|||||||
);
|
);
|
||||||
|
|
||||||
onSubmit() {
|
onSubmit() {
|
||||||
const newMember: api.PickMemberExcludeKeyofMemberIdOrLoeschenam =
|
const newMember: api.MemberCreateArgs =
|
||||||
this.memberDtoHelper.newMemberFromForm(this.memberForm);
|
this.memberDtoHelper.newMemberFromForm(this.memberForm);
|
||||||
|
|
||||||
api.createMember(newMember)
|
api.createMember(newMember)
|
||||||
|
|||||||
@@ -5,25 +5,57 @@
|
|||||||
<div class="filter-container mat-elevation-z8">
|
<div class="filter-container mat-elevation-z8">
|
||||||
<div class="filter-header">
|
<div class="filter-header">
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
|
<label>Filter:</label>
|
||||||
<input
|
<input
|
||||||
matInput
|
matInput
|
||||||
(keyup)="applyFilter($event)"
|
[formControl]="filterControl"
|
||||||
[value]="filter$ | async"
|
placeholder="Filter" />
|
||||||
placeholder="Filter"
|
</mat-form-field>
|
||||||
/>
|
<div class="status-filters">
|
||||||
|
<mat-radio-group [formControl]="statusControl">
|
||||||
|
<mat-radio-button [value]="MemberStatusFilter.ACTIVE"
|
||||||
|
>Aktiv</mat-radio-button
|
||||||
|
>
|
||||||
|
<mat-radio-button [value]="MemberStatusFilter.INACTIVE"
|
||||||
|
>Inaktiv</mat-radio-button
|
||||||
|
>
|
||||||
|
<mat-radio-button [value]="MemberStatusFilter.ALL"
|
||||||
|
>Alle</mat-radio-button
|
||||||
|
>
|
||||||
|
</mat-radio-group>
|
||||||
|
<mat-form-field
|
||||||
|
matTooltip="Zeigt nur Mitglieder, die zum angegebenen Datum den ausgewählten status hatten"
|
||||||
|
matTooltipPosition="above">
|
||||||
|
<label>Zum Datum:</label>
|
||||||
|
<input
|
||||||
|
matInput
|
||||||
|
[matDatepicker]="picker"
|
||||||
|
[formControl]="statusDateControl"
|
||||||
|
[disabled]="statusControl.value===MemberStatusFilter.ALL"
|
||||||
|
placeholder="Aktiv bis" />
|
||||||
|
<mat-hint>DD.MM.YYYY</mat-hint>
|
||||||
|
<mat-datepicker-toggle
|
||||||
|
matIconSuffix
|
||||||
|
[disabled]="statusControl.value===MemberStatusFilter.ALL"
|
||||||
|
[for]="picker"></mat-datepicker-toggle>
|
||||||
|
<mat-datepicker #picker></mat-datepicker>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
|
<mat-checkbox [formControl]="onlyWithID"
|
||||||
|
>Nur mit Passnummer</mat-checkbox
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<ng-container *ngIf="dataSource$ | async as dataSource">
|
</div>
|
||||||
<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
|
|
||||||
|
<!--TODO Sorting doesnt work anymore-->
|
||||||
|
<table mat-table matSort [dataSource]="dataSource" class="mat-elevation-z8">
|
||||||
<ng-container matColumnDef="edit">
|
<ng-container matColumnDef="edit">
|
||||||
<th mat-header-cell *matHeaderCellDef>Bearb.</th>
|
<th mat-header-cell *matHeaderCellDef>Bearb.</th>
|
||||||
<td mat-cell *matCellDef="let element">
|
<td mat-cell *matCellDef="let element">
|
||||||
<button
|
<button
|
||||||
mat-mini-fab-button
|
mat-mini-fab-button
|
||||||
(click)="editMember(element.id)"
|
(click)="editMember(element.id)"
|
||||||
aria-label="Edit Member"
|
aria-label="Edit Member">
|
||||||
>
|
|
||||||
<mat-icon fontIcon="edit"></mat-icon>
|
<mat-icon fontIcon="edit"></mat-icon>
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
@@ -34,76 +66,60 @@
|
|||||||
<button
|
<button
|
||||||
mat-mini-fab-button
|
mat-mini-fab-button
|
||||||
(click)="deleteMember(element.id)"
|
(click)="deleteMember(element.id)"
|
||||||
aria-label="Delete Member"
|
aria-label="Delete Member">
|
||||||
>
|
|
||||||
<mat-icon fontIcon="delete"></mat-icon>
|
<mat-icon fontIcon="delete"></mat-icon>
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container matColumnDef="vorname">
|
<ng-container matColumnDef="vorname">
|
||||||
<th mat-header-cell *matHeaderCellDef>Vorname</th>
|
<th mat-header-cell mat-sort-header *matHeaderCellDef>Vorname</th>
|
||||||
<td mat-cell *matCellDef="let element">
|
<td mat-cell *matCellDef="let element">
|
||||||
<span [innerHTML]="element.vorname | stylizeNames"></span>
|
<span [innerHTML]="element.vorname | stylizeNames"></span>
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container matColumnDef="nachname">
|
<ng-container matColumnDef="nachname">
|
||||||
<th mat-header-cell *matHeaderCellDef>Nachname</th>
|
<th mat-header-cell mat-sort-header *matHeaderCellDef>Nachname</th>
|
||||||
<td mat-cell *matCellDef="let element">
|
<td mat-cell *matCellDef="let element">
|
||||||
<span [innerHTML]="element.nachname | stylizeNames"></span>
|
<span [innerHTML]="element.nachname | stylizeNames"></span>
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container matColumnDef="geschlecht">
|
<ng-container matColumnDef="geschlecht">
|
||||||
<th mat-header-cell *matHeaderCellDef>Geschlecht</th>
|
<th mat-header-cell mat-sort-header *matHeaderCellDef>Geschlecht</th>
|
||||||
<td mat-cell *matCellDef="let element">{{ element.geschlecht }}</td>
|
<td mat-cell *matCellDef="let element">{{ element.geschlecht }}</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container matColumnDef="passnummer">
|
<ng-container matColumnDef="passnummer">
|
||||||
<th mat-header-cell *matHeaderCellDef>Passnummer</th>
|
<th mat-header-cell mat-sort-header *matHeaderCellDef>Passnummer</th>
|
||||||
<td mat-cell *matCellDef="let element">
|
<td mat-cell *matCellDef="let element">
|
||||||
{{ element.passnummer | nobreakHyphen }}
|
{{ element.passnummer | nobreakHyphen }}
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container matColumnDef="geburtsdatum">
|
<ng-container matColumnDef="geburtsdatum">
|
||||||
<th mat-header-cell *matHeaderCellDef>Geburtsdatum</th>
|
<th mat-header-cell mat-sort-header *matHeaderCellDef>Geburtsdatum</th>
|
||||||
<td mat-cell *matCellDef="let element">
|
<td mat-cell *matCellDef="let element">
|
||||||
{{ element.geburtsdatum | datePipe }}
|
{{ element.geburtsdatum | datePipe }}
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container matColumnDef="geburtsort">
|
<ng-container matColumnDef="geburtsort">
|
||||||
<th mat-header-cell *matHeaderCellDef>Geburtsort</th>
|
<th mat-header-cell mat-sort-header *matHeaderCellDef>Geburtsort</th>
|
||||||
<td mat-cell *matCellDef="let element">{{ element.geburtsort }}</td>
|
<td mat-cell *matCellDef="let element">{{ element.geburtsort }}</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container matColumnDef="uvertrag">
|
|
||||||
<th mat-header-cell *matHeaderCellDef>Unterrichtsvertrag</th>
|
|
||||||
<td mat-cell *matCellDef="let element">
|
|
||||||
<mat-icon
|
|
||||||
*ngIf="element.uvertrag"
|
|
||||||
class="positive"
|
|
||||||
aria-label="Unterrichtsvertrag Check"
|
|
||||||
fontIcon="check"
|
|
||||||
></mat-icon>
|
|
||||||
<mat-icon
|
|
||||||
*ngIf="!element.uvertrag"
|
|
||||||
class="negative"
|
|
||||||
aria-label="Unterrichtsvertrag Cross"
|
|
||||||
fontIcon="close"
|
|
||||||
></mat-icon>
|
|
||||||
</td>
|
|
||||||
</ng-container>
|
|
||||||
<ng-container matColumnDef="verein">
|
<ng-container matColumnDef="verein">
|
||||||
<th mat-header-cell *matHeaderCellDef>Vereinsmitgliedschaft</th>
|
<th mat-header-cell mat-sort-header *matHeaderCellDef>
|
||||||
|
Vereinsmitgliedschaft
|
||||||
|
</th>
|
||||||
<td mat-cell *matCellDef="let element">
|
<td mat-cell *matCellDef="let element">
|
||||||
|
@if(element.verein){
|
||||||
<mat-icon
|
<mat-icon
|
||||||
*ngIf="element.verein"
|
|
||||||
class="positive"
|
class="positive"
|
||||||
aria-label="Verein Check"
|
aria-label="Verein Check"
|
||||||
fontIcon="check"
|
fontIcon="check"></mat-icon>
|
||||||
></mat-icon>
|
}@else {
|
||||||
|
|
||||||
<mat-icon
|
<mat-icon
|
||||||
*ngIf="!element.verein"
|
|
||||||
class="negative"
|
class="negative"
|
||||||
aria-label="Verein Cross"
|
aria-label="Verein Cross"
|
||||||
fontIcon="close"
|
fontIcon="close"></mat-icon>
|
||||||
></mat-icon>
|
}
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
@@ -113,7 +129,9 @@
|
|||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ng-container matColumnDef="kuendigungzum">
|
<ng-container matColumnDef="kuendigungzum">
|
||||||
<th mat-header-cell *matHeaderCellDef>Kündigung zum</th>
|
<th mat-header-cell mat-sort-header *matHeaderCellDef>
|
||||||
|
Kündigung zum
|
||||||
|
</th>
|
||||||
<td mat-cell *matCellDef="let element">
|
<td mat-cell *matCellDef="let element">
|
||||||
{{ (element.kuendigungzum | datePipe) ?? "-" }}
|
{{ (element.kuendigungzum | datePipe) ?? "-" }}
|
||||||
</td>
|
</td>
|
||||||
@@ -122,4 +140,3 @@
|
|||||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||||
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
|
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
|
||||||
</table>
|
</table>
|
||||||
</ng-container>
|
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
mat-radio-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-header {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-evenly;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-form-field {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-filters {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
import { AsyncPipe, NgIf } from '@angular/common';
|
import { AfterViewInit, Component, viewChild } from '@angular/core';
|
||||||
import { Component } from '@angular/core';
|
import { FormControl, ReactiveFormsModule } from '@angular/forms';
|
||||||
import { MatButtonModule } from '@angular/material/button';
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
import { MatCheckbox } from '@angular/material/checkbox';
|
||||||
|
import { MatDatepickerModule } from '@angular/material/datepicker';
|
||||||
import { MatDialog } from '@angular/material/dialog';
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
import { MatIconModule } from '@angular/material/icon';
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
import { MatInputModule } from '@angular/material/input';
|
import { MatInputModule } from '@angular/material/input';
|
||||||
|
import { MatRadioModule } from '@angular/material/radio';
|
||||||
|
import { MatSort, MatSortModule } from '@angular/material/sort';
|
||||||
import { MatTableDataSource, MatTableModule } from '@angular/material/table';
|
import { MatTableDataSource, MatTableModule } from '@angular/material/table';
|
||||||
|
import { MatTooltipModule } from '@angular/material/tooltip';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
import { BehaviorSubject, firstValueFrom, from, Observable } from 'rxjs';
|
import { combineLatest, startWith } from 'rxjs';
|
||||||
import * as api from '../../generated-api/api';
|
import * as api from '../../generated-api/api';
|
||||||
import { DatePipePipe } from '../../pipes/date-pipe.pipe.js';
|
import { DatePipePipe } from '../../pipes/date-pipe.pipe.js';
|
||||||
import { NobreakHyphenPipe } from '../../pipes/nobreak-hyphen.pipe.js';
|
import { NobreakHyphenPipe } from '../../pipes/nobreak-hyphen.pipe.js';
|
||||||
@@ -16,6 +21,12 @@ import { DeleteMemberDialogComponent } from '../delete-member-dialog/delete-memb
|
|||||||
import { EditMemberDialogComponent } from '../edit-member-dialog/edit-member-dialog.component.js';
|
import { EditMemberDialogComponent } from '../edit-member-dialog/edit-member-dialog.component.js';
|
||||||
import { NewMemberComponent } from '../new-member/new-member.component';
|
import { NewMemberComponent } from '../new-member/new-member.component';
|
||||||
|
|
||||||
|
enum MemberStatusFilter {
|
||||||
|
ACTIVE = 'ACTIVE',
|
||||||
|
INACTIVE = 'INACTIVE',
|
||||||
|
ALL = 'ALL',
|
||||||
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-view-members',
|
selector: 'app-view-members',
|
||||||
imports: [
|
imports: [
|
||||||
@@ -23,60 +34,33 @@ import { NewMemberComponent } from '../new-member/new-member.component';
|
|||||||
MatFormFieldModule,
|
MatFormFieldModule,
|
||||||
MatInputModule,
|
MatInputModule,
|
||||||
MatIconModule,
|
MatIconModule,
|
||||||
NgIf,
|
|
||||||
StylizeNamesPipe,
|
StylizeNamesPipe,
|
||||||
NobreakHyphenPipe,
|
NobreakHyphenPipe,
|
||||||
RouterModule,
|
RouterModule,
|
||||||
MatButtonModule,
|
MatButtonModule,
|
||||||
AsyncPipe,
|
|
||||||
DatePipePipe,
|
DatePipePipe,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
MatDatepickerModule,
|
||||||
|
MatTooltipModule,
|
||||||
|
MatCheckbox,
|
||||||
|
MatRadioModule,
|
||||||
|
MatSortModule,
|
||||||
],
|
],
|
||||||
templateUrl: './view-members.html',
|
templateUrl: './view-members.html',
|
||||||
styleUrl: './view-members.scss',
|
styleUrl: './view-members.scss',
|
||||||
})
|
})
|
||||||
export class ViewMembers {
|
export class ViewMembers implements AfterViewInit {
|
||||||
async deleteMember(memberId: any) {
|
protected MemberStatusFilter = MemberStatusFilter;
|
||||||
const dataSource = await firstValueFrom(this.dataSource$);
|
protected readonly filterControl = new FormControl<string>('');
|
||||||
|
protected readonly statusControl = new FormControl<MemberStatusFilter>(
|
||||||
|
MemberStatusFilter.ACTIVE,
|
||||||
|
);
|
||||||
|
protected readonly statusDateControl = new FormControl<Date | null>(null);
|
||||||
|
protected readonly onlyWithID = new FormControl<boolean>(false);
|
||||||
|
protected dataSource = new MatTableDataSource<api.MemberDto>();
|
||||||
|
private readonly datePipe = new DatePipePipe();
|
||||||
|
|
||||||
const dialogRef = this.dialog.open(DeleteMemberDialogComponent, {
|
protected readonly displayedColumns: string[] = [
|
||||||
data: {
|
|
||||||
member: dataSource.data.find(member => member.id === memberId),
|
|
||||||
},
|
|
||||||
hasBackdrop: true,
|
|
||||||
width: '90%',
|
|
||||||
height: '90%',
|
|
||||||
});
|
|
||||||
|
|
||||||
dialogRef.afterClosed().subscribe(async result => {
|
|
||||||
this.dataSource$ = this.updateData();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
newMember() {
|
|
||||||
const dialogRef = this.dialog.open(NewMemberComponent, {});
|
|
||||||
dialogRef.afterClosed().subscribe(() => {
|
|
||||||
this.dataSource$ = this.updateData();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
async editMember(memberId: any) {
|
|
||||||
const dataSource = await firstValueFrom(this.dataSource$);
|
|
||||||
|
|
||||||
const dialogRef = this.dialog.open(EditMemberDialogComponent, {
|
|
||||||
data: {
|
|
||||||
member: dataSource.data.find(member => member.id === memberId),
|
|
||||||
},
|
|
||||||
hasBackdrop: true,
|
|
||||||
width: '90%',
|
|
||||||
height: '90%',
|
|
||||||
});
|
|
||||||
|
|
||||||
dialogRef.afterClosed().subscribe(async result => {
|
|
||||||
this.dataSource$ = this.updateData();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
dataSource$: Observable<MatTableDataSource<api.Member>> =
|
|
||||||
new BehaviorSubject(new MatTableDataSource([] as api.Member[]));
|
|
||||||
filter$: BehaviorSubject<string> = new BehaviorSubject('');
|
|
||||||
displayedColumns: string[] = [
|
|
||||||
'edit',
|
'edit',
|
||||||
'delete',
|
'delete',
|
||||||
'vorname',
|
'vorname',
|
||||||
@@ -85,31 +69,141 @@ export class ViewMembers {
|
|||||||
'passnummer',
|
'passnummer',
|
||||||
'geburtsdatum',
|
'geburtsdatum',
|
||||||
'geburtsort',
|
'geburtsort',
|
||||||
'uvertrag',
|
|
||||||
'verein',
|
'verein',
|
||||||
'kontakt',
|
'kontakt',
|
||||||
'kuendigungzum',
|
'kuendigungzum',
|
||||||
];
|
];
|
||||||
|
|
||||||
applyFilter(event: KeyboardEvent) {
|
protected deleteMember(memberId: any) {
|
||||||
let filterValue = (event.target as HTMLInputElement).value ?? '';
|
const dialogRef = this.dialog.open(DeleteMemberDialogComponent, {
|
||||||
filterValue = filterValue.trim();
|
data: {
|
||||||
filterValue = filterValue.toLowerCase();
|
member: this.dataSource.data.find(
|
||||||
this.filter$.next(filterValue);
|
member => member.id === memberId,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
hasBackdrop: true,
|
||||||
|
width: '90%',
|
||||||
|
height: '90%',
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogRef.afterClosed().subscribe(async result => {
|
||||||
|
this.updateData();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
constructor(private dialog: MatDialog) {
|
protected newMember() {
|
||||||
this.dataSource$ = this.updateData();
|
const dialogRef = this.dialog.open(NewMemberComponent, {});
|
||||||
|
dialogRef.afterClosed().subscribe(() => {
|
||||||
|
this.updateData();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
protected editMember(memberId: any) {
|
||||||
|
const dialogRef = this.dialog.open(EditMemberDialogComponent, {
|
||||||
|
data: {
|
||||||
|
member: this.dataSource.data.find(
|
||||||
|
member => member.id === memberId,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
hasBackdrop: true,
|
||||||
|
width: '90%',
|
||||||
|
height: '90%',
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogRef.afterClosed().subscribe(async result => {
|
||||||
|
//TODO do upsert instead
|
||||||
|
this.updateData();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
updateData() {
|
public constructor(private dialog: MatDialog) {
|
||||||
return from(
|
const originalFilterPredicate = this.dataSource.filterPredicate;
|
||||||
api.getMembers().then(members => {
|
this.dataSource.filterPredicate = (
|
||||||
let dataSource = new MatTableDataSource<api.Member>(members);
|
data: api.MemberDto,
|
||||||
this.filter$.subscribe(filter => {
|
encodedFilter: string,
|
||||||
dataSource.filter = filter;
|
) => {
|
||||||
});
|
const parts = encodedFilter.split(';');
|
||||||
return dataSource;
|
const filter = parts[0];
|
||||||
}),
|
const status = parts[1] as MemberStatusFilter;
|
||||||
|
const statusDateStr = parts[2];
|
||||||
|
const onlyWithID = parts[3] === 'true';
|
||||||
|
|
||||||
|
const currentDate =
|
||||||
|
statusDateStr && statusDateStr != ''
|
||||||
|
? new Date(statusDateStr)
|
||||||
|
: null;
|
||||||
|
const cancellationDate = data.kuendigungzum
|
||||||
|
? new Date(data.kuendigungzum)
|
||||||
|
: null;
|
||||||
|
if (
|
||||||
|
currentDate &&
|
||||||
|
cancellationDate &&
|
||||||
|
((status === MemberStatusFilter.ACTIVE &&
|
||||||
|
cancellationDate <= currentDate) ||
|
||||||
|
(status === MemberStatusFilter.INACTIVE &&
|
||||||
|
cancellationDate > currentDate))
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (status === MemberStatusFilter.INACTIVE && !cancellationDate) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
onlyWithID &&
|
||||||
|
(!data.passnummer || data.passnummer.trim() === '')
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return originalFilterPredicate(
|
||||||
|
{
|
||||||
|
...data,
|
||||||
|
geburtsdatum: this.datePipe.transform(
|
||||||
|
data.geburtsdatum ?? undefined,
|
||||||
|
),
|
||||||
|
kuendigungzum: this.datePipe.transform(
|
||||||
|
data.kuendigungzum ?? undefined,
|
||||||
|
),
|
||||||
|
letztePruefung: this.datePipe.transform(
|
||||||
|
data.letztePruefung ?? undefined,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
filter,
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
combineLatest([
|
||||||
|
this.filterControl.valueChanges.pipe(
|
||||||
|
startWith(this.filterControl.value),
|
||||||
|
),
|
||||||
|
this.statusControl.valueChanges.pipe(
|
||||||
|
startWith(this.statusControl.value),
|
||||||
|
),
|
||||||
|
this.statusDateControl.valueChanges.pipe(
|
||||||
|
startWith(this.statusDateControl.value),
|
||||||
|
),
|
||||||
|
this.onlyWithID.valueChanges.pipe(startWith(this.onlyWithID.value)),
|
||||||
|
])
|
||||||
|
// .pipe(takeUntilDestroyed())
|
||||||
|
.subscribe(combined => {
|
||||||
|
const filter = combined[0];
|
||||||
|
const status = combined[1];
|
||||||
|
const date = combined[2];
|
||||||
|
const onlyWithID = combined[3];
|
||||||
|
this.dataSource.filter = `${filter?.replaceAll(';', '') ?? ''};${status};${date?.toISOString()};${onlyWithID}`;
|
||||||
|
console.log('filter', this.dataSource.filter);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.updateData();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected readonly sort = viewChild.required(MatSort);
|
||||||
|
public ngAfterViewInit(): void {
|
||||||
|
this.dataSource.sort = this.sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async updateData(): Promise<void> {
|
||||||
|
const members = await api.getMembers();
|
||||||
|
console.log('Members loaded');
|
||||||
|
console.dir(members);
|
||||||
|
this.dataSource.data = members;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,28 @@
|
|||||||
import { Inject, Pipe, PipeTransform } from '@angular/core';
|
import { inject, Pipe, PipeTransform } from '@angular/core';
|
||||||
import { MAT_DATE_LOCALE } from '@angular/material/core';
|
import { MAT_DATE_LOCALE } from '@angular/material/core';
|
||||||
import * as tz from '@date-fns/tz';
|
import * as tz from '@date-fns/tz';
|
||||||
import { format, Locale } from 'date-fns';
|
import { format, Locale } from 'date-fns';
|
||||||
|
|
||||||
@Pipe({
|
@Pipe({
|
||||||
name: 'datePipe',
|
name: 'datePipe',
|
||||||
standalone: true
|
standalone: true,
|
||||||
})
|
})
|
||||||
export class DatePipePipe implements PipeTransform {
|
export class DatePipePipe implements PipeTransform {
|
||||||
constructor(@Inject(MAT_DATE_LOCALE) private locale: Locale){}
|
private readonly locale = inject<Locale>(MAT_DATE_LOCALE);
|
||||||
|
|
||||||
transform(value?: string): string {
|
public transform(value?: string): string {
|
||||||
if(!value) return "-";
|
if (!value) return '-';
|
||||||
const date=new tz.TZDate(value!, "UTC");
|
const zonedDate = dateToUTC(value);
|
||||||
const timeZone=Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
||||||
const zonedDate=date.withTimeZone(timeZone);
|
|
||||||
return format(zonedDate, 'P', { locale: this.locale });
|
return format(zonedDate, 'P', { locale: this.locale });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function dateToLocal(date: Date): Date {
|
||||||
|
return new Date(date.getTime() - date.getTimezoneOffset() * 60000);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function dateToUTC(value: string): Date {
|
||||||
|
const date = new tz.TZDate(value!, 'UTC');
|
||||||
|
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||||
|
return date.withTimeZone(timeZone);
|
||||||
|
}
|
||||||
|
|||||||
@@ -116,6 +116,7 @@ export class Authentication {
|
|||||||
return await this.keycloak.login({
|
return await this.keycloak.login({
|
||||||
redirectUri: redirectUri,
|
redirectUri: redirectUri,
|
||||||
locale: 'de-DE',
|
locale: 'de-DE',
|
||||||
|
//TODO loginHint:''
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { isDevMode } from '@angular/core';
|
||||||
|
|
||||||
|
import * as api from '../generated-api/api';
|
||||||
|
|
||||||
|
export function initializeApi() {
|
||||||
|
if (isDevMode()) {
|
||||||
|
// override api url in dev mode
|
||||||
|
api.defaults.baseUrl = 'http://localhost:3000';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,10 +2,8 @@ import { Inject, Injectable } from '@angular/core';
|
|||||||
import { FormControl, FormGroup } from '@angular/forms';
|
import { FormControl, FormGroup } from '@angular/forms';
|
||||||
import { MAT_DATE_LOCALE } from '@angular/material/core';
|
import { MAT_DATE_LOCALE } from '@angular/material/core';
|
||||||
import { Locale, parse } from 'date-fns';
|
import { Locale, parse } from 'date-fns';
|
||||||
import {
|
import { MemberCreateArgs, MemberDto } from '../generated-api/api';
|
||||||
Member,
|
import { dateToLocal, dateToUTC } from '../pipes/date-pipe.pipe';
|
||||||
PickMemberExcludeKeyofMemberIdOrLoeschenam,
|
|
||||||
} from '../generated-api/api';
|
|
||||||
|
|
||||||
interface MemberForm {
|
interface MemberForm {
|
||||||
id: FormControl<string | null | undefined>;
|
id: FormControl<string | null | undefined>;
|
||||||
@@ -15,12 +13,12 @@ interface MemberForm {
|
|||||||
geburtsdatum: FormControl<Date | null>;
|
geburtsdatum: FormControl<Date | null>;
|
||||||
geburtsort: FormControl<string | null | undefined>;
|
geburtsort: FormControl<string | null | undefined>;
|
||||||
passnummer: FormControl<string | null | undefined>;
|
passnummer: FormControl<string | null | undefined>;
|
||||||
uvertrag: FormControl<boolean | null | undefined>;
|
|
||||||
verein: FormControl<boolean | null | undefined>;
|
verein: FormControl<boolean | null | undefined>;
|
||||||
kontakt: FormControl<string | null | undefined>;
|
kontakt: FormControl<string | null | undefined>;
|
||||||
graduierung: FormControl<string | null | undefined>;
|
graduierung: FormControl<string | null | undefined>;
|
||||||
kuendigungzum: FormControl<string | null | undefined>;
|
kuendigungzum: FormControl<Date | null | undefined>;
|
||||||
letztePruefung: FormControl<string | null | undefined>;
|
letztePruefung: FormControl<Date | null | undefined>;
|
||||||
|
marker: FormControl<boolean | null | undefined>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
@@ -28,7 +26,7 @@ interface MemberForm {
|
|||||||
})
|
})
|
||||||
export class MemberDtoHelper {
|
export class MemberDtoHelper {
|
||||||
constructor(@Inject(MAT_DATE_LOCALE) private locale: Locale) {}
|
constructor(@Inject(MAT_DATE_LOCALE) private locale: Locale) {}
|
||||||
public newMemberForm(member?: Member): FormGroup<MemberForm> {
|
public newMemberForm(member?: MemberDto): FormGroup<MemberForm> {
|
||||||
const geburtsdatum = member?.geburtsdatum;
|
const geburtsdatum = member?.geburtsdatum;
|
||||||
let enDate: Date | null = null;
|
let enDate: Date | null = null;
|
||||||
if (geburtsdatum) {
|
if (geburtsdatum) {
|
||||||
@@ -38,62 +36,71 @@ export class MemberDtoHelper {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return new FormGroup({
|
return new FormGroup<MemberForm>({
|
||||||
id: new FormControl(member?.id),
|
id: new FormControl(member?.id),
|
||||||
vorname: new FormControl(member?.vorname),
|
vorname: new FormControl(member?.vorname),
|
||||||
nachname: new FormControl(member?.nachname),
|
nachname: new FormControl(member?.nachname),
|
||||||
geschlecht: new FormControl(member?.geschlecht),
|
geschlecht: new FormControl(member?.geschlecht),
|
||||||
geburtsdatum: new FormControl(enDate),
|
geburtsdatum: new FormControl(
|
||||||
|
member?.geburtsdatum ? dateToUTC(member?.geburtsdatum) : null,
|
||||||
|
),
|
||||||
geburtsort: new FormControl(member?.geburtsort),
|
geburtsort: new FormControl(member?.geburtsort),
|
||||||
passnummer: new FormControl(member?.passnummer),
|
passnummer: new FormControl(member?.passnummer),
|
||||||
uvertrag: new FormControl(member?.uvertrag),
|
|
||||||
verein: new FormControl(member?.verein),
|
verein: new FormControl(member?.verein),
|
||||||
kontakt: new FormControl(member?.kontakt),
|
kontakt: new FormControl(member?.kontakt),
|
||||||
graduierung: new FormControl(member?.graduierung),
|
graduierung: new FormControl(member?.graduierung),
|
||||||
kuendigungzum: new FormControl(member?.kuendigungzum),
|
kuendigungzum: new FormControl(
|
||||||
letztePruefung: new FormControl(member?.letztePruefung),
|
member?.kuendigungzum ? dateToUTC(member?.kuendigungzum) : null,
|
||||||
|
),
|
||||||
|
letztePruefung: new FormControl(
|
||||||
|
member?.letztePruefung
|
||||||
|
? dateToUTC(member?.letztePruefung)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
marker: new FormControl(false),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
memberFromForm(form: FormGroup<MemberForm>): Member {
|
memberFromForm(form: FormGroup<MemberForm>): MemberDto {
|
||||||
return {
|
return {
|
||||||
id: form.controls.id.value ?? null,
|
id: form.controls.id.value ?? null,
|
||||||
vorname: form.controls.vorname.value ?? null,
|
vorname: form.controls.vorname.value ?? null,
|
||||||
nachname: form.controls.nachname.value ?? null,
|
nachname: form.controls.nachname.value ?? null,
|
||||||
geschlecht: form.controls.geschlecht.value ?? null,
|
geschlecht: form.controls.geschlecht.value ?? null,
|
||||||
geburtsdatum: form.controls.geburtsdatum.value
|
geburtsdatum: form.controls.geburtsdatum.value
|
||||||
? JSON.stringify(form.controls.geburtsdatum.value)
|
? dateToLocal(form.controls.geburtsdatum.value).toISOString()
|
||||||
: null,
|
: null,
|
||||||
geburtsort: form.controls.geburtsort.value ?? null,
|
geburtsort: form.controls.geburtsort.value ?? null,
|
||||||
passnummer: form.controls.passnummer.value ?? null,
|
passnummer: form.controls.passnummer.value ?? null,
|
||||||
uvertrag: form.controls.uvertrag.value ?? null,
|
|
||||||
verein: form.controls.verein.value ?? null,
|
verein: form.controls.verein.value ?? null,
|
||||||
kontakt: form.controls.kontakt.value ?? null,
|
kontakt: form.controls.kontakt.value ?? null,
|
||||||
graduierung: form.controls.graduierung?.value ?? null,
|
graduierung: form.controls.graduierung?.value ?? null,
|
||||||
kuendigungzum: form.controls.kuendigungzum.value ?? null, //TODO not for new member
|
kuendigungzum: form.controls.kuendigungzum.value
|
||||||
letztePruefung: form.controls.letztePruefung?.value ?? null, //TODO not for new member
|
? dateToLocal(form.controls.kuendigungzum.value).toISOString()
|
||||||
loeschenam: null, //TODO? not client settable
|
: null,
|
||||||
|
letztePruefung: form.controls.letztePruefung.value
|
||||||
|
? dateToLocal(form.controls.letztePruefung.value).toISOString()
|
||||||
|
: null,
|
||||||
|
marker: form.controls.marker.value ?? false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
newMemberFromForm(
|
newMemberFromForm(form: FormGroup<MemberForm>): MemberCreateArgs {
|
||||||
form: FormGroup<MemberForm>,
|
|
||||||
): PickMemberExcludeKeyofMemberIdOrLoeschenam {
|
|
||||||
return {
|
return {
|
||||||
vorname: form.controls.vorname.value ?? null,
|
vorname: form.controls.vorname.value ?? null,
|
||||||
nachname: form.controls.nachname.value ?? null,
|
nachname: form.controls.nachname.value ?? null,
|
||||||
geschlecht: form.controls.geschlecht.value ?? null,
|
geschlecht: form.controls.geschlecht.value ?? null,
|
||||||
geburtsdatum: form.controls.geburtsdatum.value
|
geburtsdatum: form.controls.geburtsdatum.value
|
||||||
? JSON.stringify(form.controls.geburtsdatum.value)
|
? dateToLocal(form.controls.geburtsdatum.value).toISOString()
|
||||||
: null,
|
: null,
|
||||||
geburtsort: form.controls.geburtsort.value ?? null,
|
geburtsort: form.controls.geburtsort.value ?? null,
|
||||||
passnummer: form.controls.passnummer.value ?? null,
|
passnummer: form.controls.passnummer.value ?? null,
|
||||||
uvertrag: form.controls.uvertrag.value ?? null,
|
|
||||||
verein: form.controls.verein.value ?? null,
|
verein: form.controls.verein.value ?? null,
|
||||||
kontakt: form.controls.kontakt.value ?? null,
|
kontakt: form.controls.kontakt.value ?? null,
|
||||||
graduierung: form.controls.graduierung?.value ?? null,
|
graduierung: form.controls.graduierung?.value ?? null,
|
||||||
kuendigungzum: null, //TODO not for new member
|
kuendigungzum: null, //TODO not for new member
|
||||||
letztePruefung: null, //TODO not for new member
|
letztePruefung: null, //TODO not for new member
|
||||||
|
marker: form.controls.marker.value ?? false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+112
-53
@@ -8,6 +8,9 @@ importers:
|
|||||||
|
|
||||||
.:
|
.:
|
||||||
devDependencies:
|
devDependencies:
|
||||||
|
nodemon:
|
||||||
|
specifier: ^3.1.11
|
||||||
|
version: 3.1.11
|
||||||
tsoa:
|
tsoa:
|
||||||
specifier: ^6.6.0
|
specifier: ^6.6.0
|
||||||
version: 6.6.0
|
version: 6.6.0
|
||||||
@@ -41,6 +44,9 @@ importers:
|
|||||||
express-session:
|
express-session:
|
||||||
specifier: ^1.18.2
|
specifier: ^1.18.2
|
||||||
version: 1.18.2
|
version: 1.18.2
|
||||||
|
helmet:
|
||||||
|
specifier: ^8.1.0
|
||||||
|
version: 8.1.0
|
||||||
keycloak-connect:
|
keycloak-connect:
|
||||||
specifier: ^26.1.1
|
specifier: ^26.1.1
|
||||||
version: 26.1.1
|
version: 26.1.1
|
||||||
@@ -50,6 +56,9 @@ importers:
|
|||||||
rxjs:
|
rxjs:
|
||||||
specifier: ~7.8.2
|
specifier: ~7.8.2
|
||||||
version: 7.8.2
|
version: 7.8.2
|
||||||
|
tsoa:
|
||||||
|
specifier: ^6.6.0
|
||||||
|
version: 6.6.0
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@types/connect-pg-simple':
|
'@types/connect-pg-simple':
|
||||||
specifier: ^7.0.3
|
specifier: ^7.0.3
|
||||||
@@ -100,8 +109,8 @@ importers:
|
|||||||
specifier: 20.2.11
|
specifier: 20.2.11
|
||||||
version: 20.2.11(437fd31793958c779574d25f524a2c50)
|
version: 20.2.11(437fd31793958c779574d25f524a2c50)
|
||||||
'@angular/material-date-fns-adapter':
|
'@angular/material-date-fns-adapter':
|
||||||
specifier: ^21.0.2
|
specifier: ^20.2.14
|
||||||
version: 21.0.2(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/material@20.2.11(437fd31793958c779574d25f524a2c50))(date-fns@4.1.0)
|
version: 20.2.14(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/material@20.2.11(437fd31793958c779574d25f524a2c50))(date-fns@4.1.0)
|
||||||
'@angular/platform-browser':
|
'@angular/platform-browser':
|
||||||
specifier: ^20.3.15
|
specifier: ^20.3.15
|
||||||
version: 20.3.15(@angular/common@20.3.15(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))
|
version: 20.3.15(@angular/common@20.3.15(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(rxjs@7.8.2))(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))
|
||||||
@@ -414,11 +423,11 @@ packages:
|
|||||||
'@angular/platform-browser': 20.3.15
|
'@angular/platform-browser': 20.3.15
|
||||||
rxjs: ^6.5.3 || ^7.4.0
|
rxjs: ^6.5.3 || ^7.4.0
|
||||||
|
|
||||||
'@angular/material-date-fns-adapter@21.0.2':
|
'@angular/material-date-fns-adapter@20.2.14':
|
||||||
resolution: {integrity: sha512-vNJSiC6Ik6jmtdLUPWm514yJjoRP6blpw0DsACLcEDZJuIujAtvN/YmE3e+azk3Psr3nJgR3p029nQmKfHm7pw==}
|
resolution: {integrity: sha512-/LRtKSP1WOSgbahbeQlagCZoMOQc9VYZqSDSzmhV0xfiNtT6oQD9a5KMbr5LkozJZQnZUoY7QYveTXe4BD8r2Q==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@angular/core': ^21.0.0 || ^22.0.0
|
'@angular/core': ^20.0.0 || ^21.0.0
|
||||||
'@angular/material': 21.0.2
|
'@angular/material': 20.2.14
|
||||||
date-fns: '>2.20.0 <5.0'
|
date-fns: '>2.20.0 <5.0'
|
||||||
|
|
||||||
'@angular/material@20.2.11':
|
'@angular/material@20.2.11':
|
||||||
@@ -3418,10 +3427,6 @@ packages:
|
|||||||
glob-to-regexp@0.4.1:
|
glob-to-regexp@0.4.1:
|
||||||
resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==}
|
resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==}
|
||||||
|
|
||||||
glob@10.4.5:
|
|
||||||
resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==}
|
|
||||||
hasBin: true
|
|
||||||
|
|
||||||
glob@10.5.0:
|
glob@10.5.0:
|
||||||
resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==}
|
resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@@ -3448,6 +3453,10 @@ packages:
|
|||||||
engines: {node: '>=0.4.7'}
|
engines: {node: '>=0.4.7'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
has-flag@3.0.0:
|
||||||
|
resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
|
||||||
|
engines: {node: '>=4'}
|
||||||
|
|
||||||
has-flag@4.0.0:
|
has-flag@4.0.0:
|
||||||
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
|
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -3467,6 +3476,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
helmet@8.1.0:
|
||||||
|
resolution: {integrity: sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==}
|
||||||
|
engines: {node: '>=18.0.0'}
|
||||||
|
|
||||||
hmac-drbg@1.0.1:
|
hmac-drbg@1.0.1:
|
||||||
resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==}
|
resolution: {integrity: sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==}
|
||||||
|
|
||||||
@@ -3565,6 +3578,9 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
postcss: ^8.1.0
|
postcss: ^8.1.0
|
||||||
|
|
||||||
|
ignore-by-default@1.0.1:
|
||||||
|
resolution: {integrity: sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==}
|
||||||
|
|
||||||
ignore-walk@8.0.0:
|
ignore-walk@8.0.0:
|
||||||
resolution: {integrity: sha512-FCeMZT4NiRQGh+YkeKMtWrOmBgWjHjMJ26WQWrRQyoyzqevdaGSakUaJW5xQYmjLlUVk2qUnCjYVBax9EKKg8A==}
|
resolution: {integrity: sha512-FCeMZT4NiRQGh+YkeKMtWrOmBgWjHjMJ26WQWrRQyoyzqevdaGSakUaJW5xQYmjLlUVk2qUnCjYVBax9EKKg8A==}
|
||||||
engines: {node: ^20.17.0 || >=22.9.0}
|
engines: {node: ^20.17.0 || >=22.9.0}
|
||||||
@@ -4239,6 +4255,11 @@ packages:
|
|||||||
node-releases@2.0.27:
|
node-releases@2.0.27:
|
||||||
resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==}
|
resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==}
|
||||||
|
|
||||||
|
nodemon@3.1.11:
|
||||||
|
resolution: {integrity: sha512-is96t8F/1//UHAjNPHpbsNY46ELPpftGUoSVNXwUfMk/qdjSylYrWSu1XavVTBOn526kFiOR733ATgNBCQyH0g==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
nopt@8.1.0:
|
nopt@8.1.0:
|
||||||
resolution: {integrity: sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==}
|
resolution: {integrity: sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==}
|
||||||
engines: {node: ^18.17.0 || >=20.5.0}
|
engines: {node: ^18.17.0 || >=20.5.0}
|
||||||
@@ -4632,6 +4653,9 @@ packages:
|
|||||||
prr@1.0.1:
|
prr@1.0.1:
|
||||||
resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==}
|
resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==}
|
||||||
|
|
||||||
|
pstree.remy@1.1.8:
|
||||||
|
resolution: {integrity: sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==}
|
||||||
|
|
||||||
pump@3.0.3:
|
pump@3.0.3:
|
||||||
resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==}
|
resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==}
|
||||||
|
|
||||||
@@ -4977,6 +5001,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-ZpzWAFHIFqyFE56dXqgX/DkDRZdz+rRcjoIk/RQU4IX0wiCv1l8S7ZrXDHcCc+uaf+6o7w3h2l3g6GYG5TKN9Q==}
|
resolution: {integrity: sha512-ZpzWAFHIFqyFE56dXqgX/DkDRZdz+rRcjoIk/RQU4IX0wiCv1l8S7ZrXDHcCc+uaf+6o7w3h2l3g6GYG5TKN9Q==}
|
||||||
engines: {node: ^18.17.0 || >=20.5.0}
|
engines: {node: ^18.17.0 || >=20.5.0}
|
||||||
|
|
||||||
|
simple-update-notifier@2.0.0:
|
||||||
|
resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
slice-ansi@5.0.0:
|
slice-ansi@5.0.0:
|
||||||
resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==}
|
resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -5112,6 +5140,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==}
|
resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
supports-color@5.5.0:
|
||||||
|
resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
|
||||||
|
engines: {node: '>=4'}
|
||||||
|
|
||||||
supports-color@7.2.0:
|
supports-color@7.2.0:
|
||||||
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
|
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -5202,6 +5234,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
|
resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==}
|
||||||
engines: {node: '>=0.6'}
|
engines: {node: '>=0.6'}
|
||||||
|
|
||||||
|
touch@3.1.1:
|
||||||
|
resolution: {integrity: sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
tr46@0.0.3:
|
tr46@0.0.3:
|
||||||
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
|
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
|
||||||
|
|
||||||
@@ -5269,6 +5305,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==}
|
resolution: {integrity: sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
|
|
||||||
|
undefsafe@2.0.5:
|
||||||
|
resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==}
|
||||||
|
|
||||||
undici-types@7.16.0:
|
undici-types@7.16.0:
|
||||||
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
|
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
|
||||||
|
|
||||||
@@ -5344,10 +5383,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-IUoow1YUtvoBBC06dXs8bR8B9vuA3aJfmQNKMoaPG/OFsPmoQvw8xh+6Ye25Gx9DQhoEom3Pcu9MKHerm/NpUQ==}
|
resolution: {integrity: sha512-IUoow1YUtvoBBC06dXs8bR8B9vuA3aJfmQNKMoaPG/OFsPmoQvw8xh+6Ye25Gx9DQhoEom3Pcu9MKHerm/NpUQ==}
|
||||||
engines: {node: ^18.17.0 || >=20.5.0}
|
engines: {node: ^18.17.0 || >=20.5.0}
|
||||||
|
|
||||||
validator@13.15.20:
|
|
||||||
resolution: {integrity: sha512-KxPOq3V2LmfQPP4eqf3Mq/zrT0Dqp2Vmx2Bn285LwVahLc+CsxOM0crBHczm8ijlcjZ0Q5Xd6LW3z3odTPnlrw==}
|
|
||||||
engines: {node: '>= 0.10'}
|
|
||||||
|
|
||||||
validator@13.15.23:
|
validator@13.15.23:
|
||||||
resolution: {integrity: sha512-4yoz1kEWqUjzi5zsPbAS/903QXSYp0UOtHsPpp7p9rHAw/W+dkInskAE386Fat3oKRROwO98d9ZB0G4cObgUyw==}
|
resolution: {integrity: sha512-4yoz1kEWqUjzi5zsPbAS/903QXSYp0UOtHsPpp7p9rHAw/W+dkInskAE386Fat3oKRROwO98d9ZB0G4cObgUyw==}
|
||||||
engines: {node: '>= 0.10'}
|
engines: {node: '>= 0.10'}
|
||||||
@@ -6011,7 +6046,7 @@ snapshots:
|
|||||||
rxjs: 7.8.2
|
rxjs: 7.8.2
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@angular/material-date-fns-adapter@21.0.2(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/material@20.2.11(437fd31793958c779574d25f524a2c50))(date-fns@4.1.0)':
|
'@angular/material-date-fns-adapter@20.2.14(@angular/core@20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1))(@angular/material@20.2.11(437fd31793958c779574d25f524a2c50))(date-fns@4.1.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@angular/core': 20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1)
|
'@angular/core': 20.3.15(@angular/compiler@20.3.15)(rxjs@7.8.2)(zone.js@0.15.1)
|
||||||
'@angular/material': 20.2.11(437fd31793958c779574d25f524a2c50)
|
'@angular/material': 20.2.11(437fd31793958c779574d25f524a2c50)
|
||||||
@@ -6082,7 +6117,7 @@ snapshots:
|
|||||||
'@babel/traverse': 7.28.5
|
'@babel/traverse': 7.28.5
|
||||||
'@babel/types': 7.28.5
|
'@babel/types': 7.28.5
|
||||||
convert-source-map: 2.0.0
|
convert-source-map: 2.0.0
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@5.5.0)
|
||||||
gensync: 1.0.0-beta.2
|
gensync: 1.0.0-beta.2
|
||||||
json5: 2.2.3
|
json5: 2.2.3
|
||||||
semver: 6.3.1
|
semver: 6.3.1
|
||||||
@@ -6102,7 +6137,7 @@ snapshots:
|
|||||||
'@babel/types': 7.28.5
|
'@babel/types': 7.28.5
|
||||||
'@jridgewell/remapping': 2.3.5
|
'@jridgewell/remapping': 2.3.5
|
||||||
convert-source-map: 2.0.0
|
convert-source-map: 2.0.0
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@5.5.0)
|
||||||
gensync: 1.0.0-beta.2
|
gensync: 1.0.0-beta.2
|
||||||
json5: 2.2.3
|
json5: 2.2.3
|
||||||
semver: 6.3.1
|
semver: 6.3.1
|
||||||
@@ -6162,7 +6197,7 @@ snapshots:
|
|||||||
'@babel/core': 7.28.3
|
'@babel/core': 7.28.3
|
||||||
'@babel/helper-compilation-targets': 7.27.2
|
'@babel/helper-compilation-targets': 7.27.2
|
||||||
'@babel/helper-plugin-utils': 7.27.1
|
'@babel/helper-plugin-utils': 7.27.1
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@5.5.0)
|
||||||
lodash.debounce: 4.0.8
|
lodash.debounce: 4.0.8
|
||||||
resolve: 1.22.11
|
resolve: 1.22.11
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@@ -6759,7 +6794,7 @@ snapshots:
|
|||||||
'@babel/parser': 7.28.5
|
'@babel/parser': 7.28.5
|
||||||
'@babel/template': 7.27.2
|
'@babel/template': 7.27.2
|
||||||
'@babel/types': 7.28.5
|
'@babel/types': 7.28.5
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@5.5.0)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@@ -7840,13 +7875,13 @@ snapshots:
|
|||||||
'@tsoa/runtime': 6.6.0
|
'@tsoa/runtime': 6.6.0
|
||||||
'@types/multer': 1.4.13
|
'@types/multer': 1.4.13
|
||||||
fs-extra: 11.3.2
|
fs-extra: 11.3.2
|
||||||
glob: 10.4.5
|
glob: 10.5.0
|
||||||
handlebars: 4.7.8
|
handlebars: 4.7.8
|
||||||
merge-anything: 5.1.7
|
merge-anything: 5.1.7
|
||||||
minimatch: 9.0.5
|
minimatch: 9.0.5
|
||||||
ts-deepmerge: 7.0.3
|
ts-deepmerge: 7.0.3
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
validator: 13.15.20
|
validator: 13.15.23
|
||||||
yaml: 2.8.1
|
yaml: 2.8.1
|
||||||
yargs: 17.7.2
|
yargs: 17.7.2
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@@ -8345,7 +8380,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
bytes: 3.1.2
|
bytes: 3.1.2
|
||||||
content-type: 1.0.5
|
content-type: 1.0.5
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@5.5.0)
|
||||||
http-errors: 2.0.1
|
http-errors: 2.0.1
|
||||||
iconv-lite: 0.7.0
|
iconv-lite: 0.7.0
|
||||||
on-finished: 2.4.1
|
on-finished: 2.4.1
|
||||||
@@ -8697,9 +8732,11 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ms: 2.1.3
|
ms: 2.1.3
|
||||||
|
|
||||||
debug@4.4.3:
|
debug@4.4.3(supports-color@5.5.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
ms: 2.1.3
|
ms: 2.1.3
|
||||||
|
optionalDependencies:
|
||||||
|
supports-color: 5.5.0
|
||||||
|
|
||||||
deep-is@0.1.4:
|
deep-is@0.1.4:
|
||||||
optional: true
|
optional: true
|
||||||
@@ -9068,7 +9105,7 @@ snapshots:
|
|||||||
content-type: 1.0.5
|
content-type: 1.0.5
|
||||||
cookie: 0.7.2
|
cookie: 0.7.2
|
||||||
cookie-signature: 1.2.2
|
cookie-signature: 1.2.2
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@5.5.0)
|
||||||
depd: 2.0.0
|
depd: 2.0.0
|
||||||
encodeurl: 2.0.0
|
encodeurl: 2.0.0
|
||||||
escape-html: 1.0.3
|
escape-html: 1.0.3
|
||||||
@@ -9099,7 +9136,7 @@ snapshots:
|
|||||||
|
|
||||||
extract-zip@2.0.1:
|
extract-zip@2.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@5.5.0)
|
||||||
get-stream: 5.2.0
|
get-stream: 5.2.0
|
||||||
yauzl: 2.10.0
|
yauzl: 2.10.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
@@ -9173,7 +9210,7 @@ snapshots:
|
|||||||
|
|
||||||
finalhandler@2.1.1:
|
finalhandler@2.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@5.5.0)
|
||||||
encodeurl: 2.0.0
|
encodeurl: 2.0.0
|
||||||
escape-html: 1.0.3
|
escape-html: 1.0.3
|
||||||
on-finished: 2.4.1
|
on-finished: 2.4.1
|
||||||
@@ -9193,7 +9230,7 @@ snapshots:
|
|||||||
|
|
||||||
follow-redirects@1.15.11(debug@4.4.3):
|
follow-redirects@1.15.11(debug@4.4.3):
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@5.5.0)
|
||||||
|
|
||||||
foreground-child@3.3.1:
|
foreground-child@3.3.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -9287,7 +9324,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
basic-ftp: 5.0.5
|
basic-ftp: 5.0.5
|
||||||
data-uri-to-buffer: 6.0.2
|
data-uri-to-buffer: 6.0.2
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@5.5.0)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
optional: true
|
optional: true
|
||||||
@@ -9315,15 +9352,6 @@ snapshots:
|
|||||||
|
|
||||||
glob-to-regexp@0.4.1: {}
|
glob-to-regexp@0.4.1: {}
|
||||||
|
|
||||||
glob@10.4.5:
|
|
||||||
dependencies:
|
|
||||||
foreground-child: 3.3.1
|
|
||||||
jackspeak: 3.4.3
|
|
||||||
minimatch: 9.0.5
|
|
||||||
minipass: 7.1.2
|
|
||||||
package-json-from-dist: 1.0.1
|
|
||||||
path-scurry: 1.11.1
|
|
||||||
|
|
||||||
glob@10.5.0:
|
glob@10.5.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
foreground-child: 3.3.1
|
foreground-child: 3.3.1
|
||||||
@@ -9359,6 +9387,8 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
uglify-js: 3.19.3
|
uglify-js: 3.19.3
|
||||||
|
|
||||||
|
has-flag@3.0.0: {}
|
||||||
|
|
||||||
has-flag@4.0.0: {}
|
has-flag@4.0.0: {}
|
||||||
|
|
||||||
has-symbols@1.1.0: {}
|
has-symbols@1.1.0: {}
|
||||||
@@ -9376,6 +9406,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
function-bind: 1.1.2
|
function-bind: 1.1.2
|
||||||
|
|
||||||
|
helmet@8.1.0: {}
|
||||||
|
|
||||||
hmac-drbg@1.0.1:
|
hmac-drbg@1.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
hash.js: 1.1.7
|
hash.js: 1.1.7
|
||||||
@@ -9440,7 +9472,7 @@ snapshots:
|
|||||||
http-proxy-agent@7.0.2:
|
http-proxy-agent@7.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
agent-base: 7.1.4
|
agent-base: 7.1.4
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@5.5.0)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@@ -9459,7 +9491,7 @@ snapshots:
|
|||||||
http-proxy-middleware@3.0.5:
|
http-proxy-middleware@3.0.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/http-proxy': 1.17.17
|
'@types/http-proxy': 1.17.17
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@5.5.0)
|
||||||
http-proxy: 1.18.1(debug@4.4.3)
|
http-proxy: 1.18.1(debug@4.4.3)
|
||||||
is-glob: 4.0.3
|
is-glob: 4.0.3
|
||||||
is-plain-object: 5.0.0
|
is-plain-object: 5.0.0
|
||||||
@@ -9482,7 +9514,7 @@ snapshots:
|
|||||||
https-proxy-agent@7.0.6:
|
https-proxy-agent@7.0.6:
|
||||||
dependencies:
|
dependencies:
|
||||||
agent-base: 7.1.4
|
agent-base: 7.1.4
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@5.5.0)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@@ -9504,6 +9536,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
postcss: 8.5.6
|
postcss: 8.5.6
|
||||||
|
|
||||||
|
ignore-by-default@1.0.1: {}
|
||||||
|
|
||||||
ignore-walk@8.0.0:
|
ignore-walk@8.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
minimatch: 10.1.1
|
minimatch: 10.1.1
|
||||||
@@ -9657,7 +9691,7 @@ snapshots:
|
|||||||
|
|
||||||
istanbul-lib-source-maps@4.0.1:
|
istanbul-lib-source-maps@4.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@5.5.0)
|
||||||
istanbul-lib-coverage: 3.2.2
|
istanbul-lib-coverage: 3.2.2
|
||||||
source-map: 0.6.1
|
source-map: 0.6.1
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@@ -9905,7 +9939,7 @@ snapshots:
|
|||||||
log4js@6.9.1:
|
log4js@6.9.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
date-format: 4.0.14
|
date-format: 4.0.14
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@5.5.0)
|
||||||
flatted: 3.3.3
|
flatted: 3.3.3
|
||||||
rfdc: 1.4.1
|
rfdc: 1.4.1
|
||||||
streamroller: 3.1.5
|
streamroller: 3.1.5
|
||||||
@@ -10193,6 +10227,19 @@ snapshots:
|
|||||||
|
|
||||||
node-releases@2.0.27: {}
|
node-releases@2.0.27: {}
|
||||||
|
|
||||||
|
nodemon@3.1.11:
|
||||||
|
dependencies:
|
||||||
|
chokidar: 3.6.0
|
||||||
|
debug: 4.4.3(supports-color@5.5.0)
|
||||||
|
ignore-by-default: 1.0.1
|
||||||
|
minimatch: 3.1.2
|
||||||
|
pstree.remy: 1.1.8
|
||||||
|
semver: 7.7.3
|
||||||
|
simple-update-notifier: 2.0.0
|
||||||
|
supports-color: 5.5.0
|
||||||
|
touch: 3.1.1
|
||||||
|
undefsafe: 2.0.5
|
||||||
|
|
||||||
nopt@8.1.0:
|
nopt@8.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
abbrev: 3.0.1
|
abbrev: 3.0.1
|
||||||
@@ -10376,7 +10423,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@tootallnate/quickjs-emscripten': 0.23.0
|
'@tootallnate/quickjs-emscripten': 0.23.0
|
||||||
agent-base: 7.1.4
|
agent-base: 7.1.4
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@5.5.0)
|
||||||
get-uri: 6.0.5
|
get-uri: 6.0.5
|
||||||
http-proxy-agent: 7.0.2
|
http-proxy-agent: 7.0.2
|
||||||
https-proxy-agent: 7.0.6
|
https-proxy-agent: 7.0.6
|
||||||
@@ -10627,7 +10674,7 @@ snapshots:
|
|||||||
proxy-agent@6.5.0:
|
proxy-agent@6.5.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
agent-base: 7.1.4
|
agent-base: 7.1.4
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@5.5.0)
|
||||||
http-proxy-agent: 7.0.2
|
http-proxy-agent: 7.0.2
|
||||||
https-proxy-agent: 7.0.6
|
https-proxy-agent: 7.0.6
|
||||||
lru-cache: 7.18.3
|
lru-cache: 7.18.3
|
||||||
@@ -10644,6 +10691,8 @@ snapshots:
|
|||||||
prr@1.0.1:
|
prr@1.0.1:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
pstree.remy@1.1.8: {}
|
||||||
|
|
||||||
pump@3.0.3:
|
pump@3.0.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
end-of-stream: 1.4.5
|
end-of-stream: 1.4.5
|
||||||
@@ -10828,7 +10877,7 @@ snapshots:
|
|||||||
|
|
||||||
router@2.2.0:
|
router@2.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@5.5.0)
|
||||||
depd: 2.0.0
|
depd: 2.0.0
|
||||||
is-promise: 4.0.0
|
is-promise: 4.0.0
|
||||||
parseurl: 1.3.3
|
parseurl: 1.3.3
|
||||||
@@ -10939,7 +10988,7 @@ snapshots:
|
|||||||
|
|
||||||
send@1.2.0:
|
send@1.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@5.5.0)
|
||||||
encodeurl: 2.0.0
|
encodeurl: 2.0.0
|
||||||
escape-html: 1.0.3
|
escape-html: 1.0.3
|
||||||
etag: 1.8.1
|
etag: 1.8.1
|
||||||
@@ -11074,6 +11123,10 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
|
simple-update-notifier@2.0.0:
|
||||||
|
dependencies:
|
||||||
|
semver: 7.7.3
|
||||||
|
|
||||||
slice-ansi@5.0.0:
|
slice-ansi@5.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
ansi-styles: 6.2.3
|
ansi-styles: 6.2.3
|
||||||
@@ -11125,7 +11178,7 @@ snapshots:
|
|||||||
socks-proxy-agent@8.0.5:
|
socks-proxy-agent@8.0.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
agent-base: 7.1.4
|
agent-base: 7.1.4
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@5.5.0)
|
||||||
socks: 2.8.7
|
socks: 2.8.7
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
@@ -11168,7 +11221,7 @@ snapshots:
|
|||||||
|
|
||||||
spdy-transport@3.0.0:
|
spdy-transport@3.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@5.5.0)
|
||||||
detect-node: 2.1.0
|
detect-node: 2.1.0
|
||||||
hpack.js: 2.1.6
|
hpack.js: 2.1.6
|
||||||
obuf: 1.1.2
|
obuf: 1.1.2
|
||||||
@@ -11179,7 +11232,7 @@ snapshots:
|
|||||||
|
|
||||||
spdy@4.0.2:
|
spdy@4.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@5.5.0)
|
||||||
handle-thing: 2.0.1
|
handle-thing: 2.0.1
|
||||||
http-deceiver: 1.2.7
|
http-deceiver: 1.2.7
|
||||||
select-hose: 2.0.0
|
select-hose: 2.0.0
|
||||||
@@ -11208,7 +11261,7 @@ snapshots:
|
|||||||
streamroller@3.1.5:
|
streamroller@3.1.5:
|
||||||
dependencies:
|
dependencies:
|
||||||
date-format: 4.0.14
|
date-format: 4.0.14
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@5.5.0)
|
||||||
fs-extra: 8.1.0
|
fs-extra: 8.1.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
@@ -11247,6 +11300,10 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ansi-regex: 6.2.2
|
ansi-regex: 6.2.2
|
||||||
|
|
||||||
|
supports-color@5.5.0:
|
||||||
|
dependencies:
|
||||||
|
has-flag: 3.0.0
|
||||||
|
|
||||||
supports-color@7.2.0:
|
supports-color@7.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
has-flag: 4.0.0
|
has-flag: 4.0.0
|
||||||
@@ -11352,6 +11409,8 @@ snapshots:
|
|||||||
|
|
||||||
toidentifier@1.0.1: {}
|
toidentifier@1.0.1: {}
|
||||||
|
|
||||||
|
touch@3.1.1: {}
|
||||||
|
|
||||||
tr46@0.0.3: {}
|
tr46@0.0.3: {}
|
||||||
|
|
||||||
tree-dump@1.1.0(tslib@2.8.1):
|
tree-dump@1.1.0(tslib@2.8.1):
|
||||||
@@ -11381,7 +11440,7 @@ snapshots:
|
|||||||
tuf-js@3.1.0:
|
tuf-js@3.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tufjs/models': 3.0.1
|
'@tufjs/models': 3.0.1
|
||||||
debug: 4.4.3
|
debug: 4.4.3(supports-color@5.5.0)
|
||||||
make-fetch-happen: 14.0.3
|
make-fetch-happen: 14.0.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
@@ -11412,6 +11471,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
random-bytes: 1.0.0
|
random-bytes: 1.0.0
|
||||||
|
|
||||||
|
undefsafe@2.0.5: {}
|
||||||
|
|
||||||
undici-types@7.16.0: {}
|
undici-types@7.16.0: {}
|
||||||
|
|
||||||
unicode-canonical-property-names-ecmascript@2.0.1: {}
|
unicode-canonical-property-names-ecmascript@2.0.1: {}
|
||||||
@@ -11464,8 +11525,6 @@ snapshots:
|
|||||||
|
|
||||||
validate-npm-package-name@6.0.2: {}
|
validate-npm-package-name@6.0.2: {}
|
||||||
|
|
||||||
validator@13.15.20: {}
|
|
||||||
|
|
||||||
validator@13.15.23: {}
|
validator@13.15.23: {}
|
||||||
|
|
||||||
vary@1.1.2: {}
|
vary@1.1.2: {}
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ packages:
|
|||||||
|
|
||||||
onlyBuiltDependencies:
|
onlyBuiltDependencies:
|
||||||
- '@parcel/watcher'
|
- '@parcel/watcher'
|
||||||
|
- '@prisma/engines'
|
||||||
- chromedriver
|
- chromedriver
|
||||||
- esbuild
|
- esbuild
|
||||||
- lmdb
|
- lmdb
|
||||||
- msgpackr-extract
|
- msgpackr-extract
|
||||||
|
- prisma
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
Set-Location -Path $PSScriptRoot/../packages/frontend
|
||||||
|
pnpm ng serve
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
Set-Location -Path $PSScriptRoot/../packages/api
|
||||||
|
$env:TKD_DB_HOST="localhost"
|
||||||
|
$env:TKD_DB_USER="webapi"
|
||||||
|
$env:TKD_DB_PASSWORD="devpassword"
|
||||||
|
$env:TKD_COOKIE_SECRET="devsecret"
|
||||||
|
pnpx tsx --watch --tsconfig .\tsconfig.json .\src\index.ts
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
Write-Host "Generating initial Apidoc"
|
||||||
|
pnpx tsoa spec-and-routes
|
||||||
|
Write-Host "Generating initial Frontend API client"
|
||||||
|
pnpx oazapfts --optimistic --useEnumType packages/frontend/src/app/generated-api/swagger.json packages/frontend/src/app/generated-api/api.ts
|
||||||
|
|
||||||
|
Write-Host "Starting watchers for API doc generation and Frontend client generation"
|
||||||
|
wt @(
|
||||||
|
"-w",
|
||||||
|
"0",
|
||||||
|
"-d",
|
||||||
|
".",
|
||||||
|
"pwsh",
|
||||||
|
"-c",
|
||||||
|
"pnpm",
|
||||||
|
"nodemon",
|
||||||
|
"--watch",
|
||||||
|
"./packages/api/src/controllers",
|
||||||
|
"--watch",
|
||||||
|
"./packages/api/src/dtos",
|
||||||
|
"-e",
|
||||||
|
"ts",
|
||||||
|
"-x",
|
||||||
|
"pnpm",
|
||||||
|
"run",
|
||||||
|
"generate",
|
||||||
|
";",
|
||||||
|
|
||||||
|
"sp",
|
||||||
|
"--title",
|
||||||
|
"API Doc Gen",
|
||||||
|
"-d",
|
||||||
|
".",
|
||||||
|
"pwsh",
|
||||||
|
"-c",
|
||||||
|
"pnpm",
|
||||||
|
"nodemon",
|
||||||
|
"--watch",
|
||||||
|
"./packages/frontend/src/app/generated-api/swagger.json",
|
||||||
|
"-e",
|
||||||
|
"json",
|
||||||
|
"-x",
|
||||||
|
"pnpm",
|
||||||
|
"--filter",
|
||||||
|
"frontend",
|
||||||
|
"run",
|
||||||
|
"generate"
|
||||||
|
)
|
||||||
|
Exit 0
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
$ErrorActionPreference = "Inquire"
|
||||||
|
|
||||||
|
wt @(
|
||||||
|
"nt",
|
||||||
|
"-d",
|
||||||
|
".",
|
||||||
|
"--title",
|
||||||
|
"Angular",
|
||||||
|
"pwsh",
|
||||||
|
"-NoExit",
|
||||||
|
"-File",
|
||||||
|
".\scripts\angular.ps1",
|
||||||
|
";",
|
||||||
|
|
||||||
|
"nt",
|
||||||
|
"-d",
|
||||||
|
".",
|
||||||
|
"--title",
|
||||||
|
"API TS",
|
||||||
|
"pwsh",
|
||||||
|
"-NoExit",
|
||||||
|
"-File",
|
||||||
|
".\scripts\api-ts.ps1",
|
||||||
|
";",
|
||||||
|
|
||||||
|
"nt",
|
||||||
|
"-d",
|
||||||
|
".",
|
||||||
|
"--title",
|
||||||
|
"Docker",
|
||||||
|
"pwsh",
|
||||||
|
"-NoExit",
|
||||||
|
"-File",
|
||||||
|
".\scripts\docker.ps1",
|
||||||
|
";",
|
||||||
|
|
||||||
|
"nt",
|
||||||
|
"-d",
|
||||||
|
".",
|
||||||
|
"--title",
|
||||||
|
"API Gen",
|
||||||
|
"pwsh",
|
||||||
|
"-File",
|
||||||
|
".\scripts\apigen.ps1")
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
Set-Location -Path $PSScriptRoot\..\packages\api\dev\
|
||||||
|
docker compose up
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"basePath": "",
|
"basePath": "",
|
||||||
"servers": ["localhost:3000", "tkd-api.toni714.de"],
|
"servers": ["tkd-api.toni714.de:443", "localhost:3000"],
|
||||||
"info": {
|
"info": {
|
||||||
"title": "Tcc Member API",
|
"title": "Tcc Member API",
|
||||||
"description": "API for the TCC Member Database",
|
"description": "API for the TCC Member Database",
|
||||||
|
|||||||
Reference in New Issue
Block a user