Feature #99 + BugFixes

This commit is contained in:
2024-09-14 19:46:18 +02:00
parent 8dd13d5472
commit 8595e70ceb
16 changed files with 626 additions and 24 deletions

View File

@@ -13,7 +13,7 @@ export class AuthController {
}
@UseGuards(AdminAuthGuard)
@Get('users')
@Get('user/all')
getUsers(): any {
return this.authService.getUsers();
}

View File

@@ -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;
}

View File

@@ -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<Stripe.Customer[]> {
return this.paymentService.getAllStripeCustomer();
}
@UseGuards(AdminAuthGuard)
@Get('subscription/all')
async getAllStripeSubscriptions(): Promise<Stripe.Subscription[]> {
return this.paymentService.getAllStripeSubscriptions();
}
@UseGuards(AdminAuthGuard)
@Get('paymentmethod/:email')
async getStripePaymentMethods(@Param('email') email: string): Promise<Stripe.PaymentMethod[]> {
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<any> {
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<void> {
await this.paymentService.deleteCustomerCompletely(customerId);
}
}

View File

@@ -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<Stripe.Customer[]> {
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<Stripe.Subscription[]> {
const allSubscriptions: Stripe.Subscription[] = [];
const response = await this.stripe.subscriptions.list({
limit: 100,
});
allSubscriptions.push(...response.data);
return allSubscriptions;
}
async getStripePaymentMethod(email: string): Promise<Stripe.PaymentMethod[]> {
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<void> {
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.');
}
}
}

View File

@@ -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<User> {
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<User> {
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<User[]> {
return await this.userService.getAllUser();
}
@Post()
async save(@Body() user: any): Promise<User> {
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;
}

View File

@@ -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<User> {
try {
user.updated = new Date();