From 8595e70cebfe49627009eed5481d6c8971bb8f07 Mon Sep 17 00:00:00 2001 From: Andreas Knuth Date: Sat, 14 Sep 2024 19:46:18 +0200 Subject: [PATCH] Feature #99 + BugFixes --- bizmatch-server/src/auth/auth.controller.ts | 2 +- bizmatch-server/src/models/main.model.ts | 7 + .../src/payment/payment.controller.ts | 29 +++- .../src/payment/payment.service.ts | 87 +++++++++- bizmatch-server/src/user/user.controller.ts | 18 +- bizmatch-server/src/user/user.service.ts | 6 +- bizmatch/src/app/app.routes.ts | 6 + .../components/header/header.component.html | 7 + .../admin/user-list/user-list.component.html | 156 ++++++++++++++++++ .../admin/user-list/user-list.component.scss | 64 +++++++ .../admin/user-list/user-list.component.ts | 138 ++++++++++++++++ ...s-commercial-property-listing.component.ts | 4 +- .../details-user/details-user.component.html | 14 +- .../src/app/pages/home/home.component.html | 2 +- .../account/account.component.html | 2 +- bizmatch/src/app/services/user.service.ts | 108 +++++++++++- 16 files changed, 626 insertions(+), 24 deletions(-) create mode 100644 bizmatch/src/app/pages/admin/user-list/user-list.component.html create mode 100644 bizmatch/src/app/pages/admin/user-list/user-list.component.scss create mode 100644 bizmatch/src/app/pages/admin/user-list/user-list.component.ts diff --git a/bizmatch-server/src/auth/auth.controller.ts b/bizmatch-server/src/auth/auth.controller.ts index 379c79a..73f3ca6 100644 --- a/bizmatch-server/src/auth/auth.controller.ts +++ b/bizmatch-server/src/auth/auth.controller.ts @@ -13,7 +13,7 @@ export class AuthController { } @UseGuards(AdminAuthGuard) - @Get('users') + @Get('user/all') getUsers(): any { return this.authService.getUsers(); } diff --git a/bizmatch-server/src/models/main.model.ts b/bizmatch-server/src/models/main.model.ts index dc6ade4..83c094f 100644 --- a/bizmatch-server/src/models/main.model.ts +++ b/bizmatch-server/src/models/main.model.ts @@ -385,6 +385,7 @@ export function createDefaultBusinessListing(): BusinessListing { }; } export type StripeSubscription = Stripe.Subscription; +export type StripeUser = Stripe.Customer; export type IpInfo = { ip: string; city: string; @@ -395,3 +396,9 @@ export type IpInfo = { postal: string; timezone: string; }; +export interface CombinedUser { + keycloakUser?: KeycloakUser; + appUser?: User; + stripeUser?: StripeUser; + stripeSubscription?: StripeSubscription; +} diff --git a/bizmatch-server/src/payment/payment.controller.ts b/bizmatch-server/src/payment/payment.controller.ts index c7abb37..47ba6a4 100644 --- a/bizmatch-server/src/payment/payment.controller.ts +++ b/bizmatch-server/src/payment/payment.controller.ts @@ -1,5 +1,6 @@ -import { Body, Controller, Get, HttpException, HttpStatus, Param, Post, Req, Res } from '@nestjs/common'; +import { Body, Controller, Delete, Get, HttpCode, HttpException, HttpStatus, Param, Post, Req, Res, UseGuards } from '@nestjs/common'; import { Request, Response } from 'express'; +import { AdminAuthGuard } from 'src/jwt-auth/admin-auth.guard'; import { Checkout } from 'src/models/main.model'; import Stripe from 'stripe'; import { PaymentService } from './payment.service'; @@ -12,6 +13,22 @@ export class PaymentController { // async createSubscription(@Body() subscriptionData: any) { // return this.paymentService.createSubscription(subscriptionData); // } + + @UseGuards(AdminAuthGuard) + @Get('user/all') + async getAllStripeCustomer(): Promise { + return this.paymentService.getAllStripeCustomer(); + } + @UseGuards(AdminAuthGuard) + @Get('subscription/all') + async getAllStripeSubscriptions(): Promise { + return this.paymentService.getAllStripeSubscriptions(); + } + @UseGuards(AdminAuthGuard) + @Get('paymentmethod/:email') + async getStripePaymentMethods(@Param('email') email: string): Promise { + return this.paymentService.getStripePaymentMethod(email); + } @Post('create-checkout-session') async createCheckoutSession(@Body() checkout: Checkout) { return this.paymentService.createCheckoutSession(checkout); @@ -40,4 +57,14 @@ export class PaymentController { async findSubscriptionsById(@Param('email') email: string): Promise { return await this.paymentService.getSubscription(email); } + /** + * Endpoint zum Löschen eines Stripe-Kunden. + * Beispiel: DELETE /stripe/customer/cus_12345 + */ + @UseGuards(AdminAuthGuard) + @Delete('customer/:id') + @HttpCode(HttpStatus.NO_CONTENT) + async deleteCustomer(@Param('id') customerId: string): Promise { + await this.paymentService.deleteCustomerCompletely(customerId); + } } diff --git a/bizmatch-server/src/payment/payment.service.ts b/bizmatch-server/src/payment/payment.service.ts index d81e85f..fd92e55 100644 --- a/bizmatch-server/src/payment/payment.service.ts +++ b/bizmatch-server/src/payment/payment.service.ts @@ -1,4 +1,4 @@ -import { BadRequestException, Inject, Injectable } from '@nestjs/common'; +import { BadRequestException, Inject, Injectable, InternalServerErrorException } from '@nestjs/common'; import { NodePgDatabase } from 'drizzle-orm/node-postgres/driver'; import { WINSTON_MODULE_PROVIDER } from 'nest-winston'; import Stripe from 'stripe'; @@ -131,4 +131,89 @@ export class PaymentService { return []; } } + /** + * Ruft alle Stripe-Kunden ab, indem die Paginierung gehandhabt wird. + * @returns Ein Array von Stripe.Customer Objekten. + */ + async getAllStripeCustomer(): Promise { + const allCustomers: Stripe.Customer[] = []; + let hasMore = true; + let startingAfter: string | undefined = undefined; + + try { + while (hasMore) { + const response = await this.stripe.customers.list({ + limit: 100, // Maximale Anzahl pro Anfrage + starting_after: startingAfter, + }); + + allCustomers.push(...response.data); + hasMore = response.has_more; + + if (hasMore && response.data.length > 0) { + startingAfter = response.data[response.data.length - 1].id; + } + } + + return allCustomers; + } catch (error) { + console.error('Fehler beim Abrufen der Stripe-Kunden:', error); + throw new Error('Kunden konnten nicht abgerufen werden.'); + } + } + async getAllStripeSubscriptions(): Promise { + const allSubscriptions: Stripe.Subscription[] = []; + const response = await this.stripe.subscriptions.list({ + limit: 100, + }); + allSubscriptions.push(...response.data); + return allSubscriptions; + } + async getStripePaymentMethod(email: string): Promise { + const existingCustomers = await this.stripe.customers.list({ + email: email, + limit: 1, + }); + const allPayments: Stripe.PaymentMethod[] = []; + if (existingCustomers.data.length > 0) { + const response = await this.stripe.paymentMethods.list({ + customer: existingCustomers.data[0].id, + limit: 10, + }); + allPayments.push(...response.data); + } + return allPayments; + } + async deleteCustomerCompletely(customerId: string): Promise { + try { + // 1. Abonnements kündigen und löschen + const subscriptions = await this.stripe.subscriptions.list({ + customer: customerId, + limit: 100, + }); + + for (const subscription of subscriptions.data) { + await this.stripe.subscriptions.cancel(subscription.id); + this.logger.info(`Abonnement ${subscription.id} gelöscht.`); + } + + // 2. Zahlungsmethoden entfernen + const paymentMethods = await this.stripe.paymentMethods.list({ + customer: customerId, + type: 'card', + }); + + for (const paymentMethod of paymentMethods.data) { + await this.stripe.paymentMethods.detach(paymentMethod.id); + this.logger.info(`Zahlungsmethode ${paymentMethod.id} entfernt.`); + } + + // 4. Kunden löschen + await this.stripe.customers.del(customerId); + this.logger.info(`Kunde ${customerId} erfolgreich gelöscht.`); + } catch (error) { + this.logger.error(`Fehler beim Löschen des Kunden ${customerId}:`, error); + throw new InternalServerErrorException('Fehler beim Löschen des Stripe-Kunden.'); + } + } } diff --git a/bizmatch-server/src/user/user.controller.ts b/bizmatch-server/src/user/user.controller.ts index c68b3ce..787de3c 100644 --- a/bizmatch-server/src/user/user.controller.ts +++ b/bizmatch-server/src/user/user.controller.ts @@ -1,5 +1,6 @@ import { BadRequestException, Body, Controller, Get, Inject, Param, Post, Query, Request, UseGuards } from '@nestjs/common'; import { WINSTON_MODULE_PROVIDER } from 'nest-winston'; +import { AdminAuthGuard } from 'src/jwt-auth/admin-auth.guard'; import { Logger } from 'winston'; import { ZodError } from 'zod'; import { FileService } from '../file/file.service'; @@ -18,20 +19,25 @@ export class UserController { ) {} @UseGuards(OptionalJwtAuthGuard) @Get() - findByMail(@Request() req, @Query('mail') mail: string): any { + async findByMail(@Request() req, @Query('mail') mail: string): Promise { this.logger.info(`Searching for user with EMail: ${mail}`); - const user = this.userService.getUserByMail(mail, req.user as JwtUser); + const user = await this.userService.getUserByMail(mail, req.user as JwtUser); this.logger.info(`Found user: ${JSON.stringify(user)}`); return user; } @Get(':id') - findById(@Param('id') id: string): any { + async findById(@Param('id') id: string): Promise { this.logger.info(`Searching for user with ID: ${id}`); - const user = this.userService.getUserById(id); + const user = await this.userService.getUserById(id); this.logger.info(`Found user: ${JSON.stringify(user)}`); return user; } + @UseGuards(AdminAuthGuard) + @Get('user/all') + async getAllUser(): Promise { + return await this.userService.getAllUser(); + } @Post() async save(@Body() user: any): Promise { this.logger.info(`Saving user: ${JSON.stringify(user)}`); @@ -60,9 +66,9 @@ export class UserController { return savedUser; } @Post('search') - find(@Body() criteria: UserListingCriteria): any { + async find(@Body() criteria: UserListingCriteria): Promise<{ results: User[]; totalCount: number }> { this.logger.info(`Searching for users with criteria: ${JSON.stringify(criteria)}`); - const foundUsers = this.userService.searchUserListings(criteria); + const foundUsers = await this.userService.searchUserListings(criteria); this.logger.info(`Found users: ${JSON.stringify(foundUsers)}`); return foundUsers; } diff --git a/bizmatch-server/src/user/user.service.ts b/bizmatch-server/src/user/user.service.ts index dcb465f..2d797ba 100644 --- a/bizmatch-server/src/user/user.service.ts +++ b/bizmatch-server/src/user/user.service.ts @@ -54,7 +54,7 @@ export class UserService { } return whereConditions; } - async searchUserListings(criteria: UserListingCriteria) { + async searchUserListings(criteria: UserListingCriteria): Promise<{ results: User[]; totalCount: number }> { const start = criteria.start ? criteria.start : 0; const length = criteria.length ? criteria.length : 12; const query = this.conn.select().from(schema.users); @@ -127,6 +127,10 @@ export class UserService { user.hasProfile = this.fileService.hasProfile(emailToDirName(user.email)); return user; } + async getAllUser() { + const users = await this.conn.select().from(schema.users); + return users; + } async saveUser(user: User, processValidation = true): Promise { try { user.updated = new Date(); diff --git a/bizmatch/src/app/app.routes.ts b/bizmatch/src/app/app.routes.ts index fce362a..4abd1b0 100644 --- a/bizmatch/src/app/app.routes.ts +++ b/bizmatch/src/app/app.routes.ts @@ -5,6 +5,7 @@ import { NotFoundComponent } from './components/not-found/not-found.component'; import { PaymentComponent } from './components/payment/payment.component'; import { AuthGuard } from './guards/auth.guard'; import { ListingCategoryGuard } from './guards/listing-category.guard'; +import { UserListComponent } from './pages/admin/user-list/user-list.component'; import { DetailsBusinessListingComponent } from './pages/details/details-business-listing/details-business-listing.component'; import { DetailsCommercialPropertyListingComponent } from './pages/details/details-commercial-property-listing/details-commercial-property-listing.component'; import { DetailsUserComponent } from './pages/details/details-user/details-user.component'; @@ -158,5 +159,10 @@ export const routes: Routes = [ path: 'success', component: SuccessComponent, }, + { + path: 'admin/users', + component: UserListComponent, + canActivate: [AuthGuard], + }, { path: '**', redirectTo: 'home' }, ]; diff --git a/bizmatch/src/app/components/header/header.component.html b/bizmatch/src/app/components/header/header.component.html index 0efc192..ee22834 100644 --- a/bizmatch/src/app/components/header/header.component.html +++ b/bizmatch/src/app/components/header/header.component.html @@ -94,6 +94,13 @@ Logout + @if(isAdmin()){ + + }