remove graph

This commit is contained in:
2025-11-22 10:49:31 +03:30
parent 2c0ab601f3
commit ee1e6049c5
42 changed files with 364 additions and 875 deletions
-9
View File
@@ -18,20 +18,11 @@ import { RestaurantsModule } from './modules/restaurants/restaurants.module';
import { FoodModule } from './modules/foods/food.module';
import { CartModule } from './modules/cart/cart.module';
import { RolesModule } from './modules/roles/roles.module';
import { GraphQLModule } from '@nestjs/graphql';
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
import { PaymentsModule } from './modules/payments/payments.module';
@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true, cache: true }),
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
autoSchemaFile: true,
playground: true,
introspection: true,
context: ({ req, reply }) => ({ request: req, reply }),
}),
MikroOrmModule.forRootAsync(dataBaseConfig),
// CacheModule.registerAsync(cacheConfig()),
JwtModule.registerAsync({
+1 -17
View File
@@ -1,25 +1,9 @@
import { ArgumentsHost, Catch, ExceptionFilter, HttpException, HttpStatus, ExecutionContext } from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { ArgumentsHost, Catch, ExceptionFilter, HttpException, HttpStatus } from '@nestjs/common';
import { FastifyReply } from 'fastify';
@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException, host: ArgumentsHost) {
// Check if this is a GraphQL context
if (host.getType<'graphql' | 'http'>() === 'graphql') {
try {
// In GraphQL context, host is actually an ExecutionContext
const gqlContext = GqlExecutionContext.create(host as ExecutionContext);
if (gqlContext && gqlContext.getInfo()) {
// For GraphQL, let the exception propagate - GraphQL will handle it automatically
// We can't use HTTP response methods in GraphQL context
return;
}
} catch {
// Not a GraphQL context, continue with HTTP handling
}
}
// Handle HTTP/REST context
try {
const ctx = host.switchToHttp();
+3 -50
View File
@@ -1,6 +1,5 @@
import { CallHandler, ExecutionContext, Injectable, NestInterceptor } from '@nestjs/common';
import { GqlExecutionContext } from '@nestjs/graphql';
import { FastifyReply, FastifyRequest } from 'fastify';
import { FastifyReply } from 'fastify';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
@@ -8,59 +7,13 @@ import { map } from 'rxjs/operators';
export class ResponseInterceptor implements NestInterceptor {
constructor() {}
private isGraphQLBody(body: unknown): boolean {
if (!body || typeof body !== 'object') {
return false;
}
const bodyObj = body as Record<string, unknown>;
return !!(bodyObj.query || bodyObj.mutation || bodyObj.operationName);
}
intercept(context: ExecutionContext, next: CallHandler<Record<string, unknown>>): Observable<unknown> {
// Check if this is a GraphQL request
// GraphQL requests should bypass this interceptor as they handle their own response format
// Method 1: Try to create GraphQL context - if it doesn't throw, it's GraphQL
try {
const gqlContext = GqlExecutionContext.create(context);
// If we can create it and it has GraphQL-specific properties, it's GraphQL
if (gqlContext && gqlContext.getInfo()) {
return next.handle();
}
} catch {
// Not a GraphQL context, continue
}
// Method 2: Check request URL and body for GraphQL endpoint
try {
const request = context.switchToHttp().getRequest<FastifyRequest>();
// Check URL path
if (request.url?.includes('/graphql')) {
return next.handle();
}
// Check if request body contains GraphQL query/mutation
if (this.isGraphQLBody(request.body)) {
return next.handle();
}
} catch {
// Not an HTTP context, might be GraphQL - skip wrapping
return next.handle();
}
// Method 3: Check if handler is a GraphQL resolver by checking the class name
const handler = context.getHandler();
const className = context.getClass()?.name || '';
const handlerName = handler?.name || '';
if (className.includes('Resolver') || handlerName.includes('Resolver')) {
return next.handle();
}
// For REST endpoints, wrap the response
try {
const ctx = context.switchToHttp().getResponse<FastifyReply>();
const statusCode = ctx.statusCode;
// If statusCode is undefined, it might be a GraphQL request - skip wrapping
// If statusCode is undefined, skip wrapping
if (statusCode === undefined) {
return next.handle();
}
@@ -87,7 +40,7 @@ export class ResponseInterceptor implements NestInterceptor {
}),
);
} catch {
// If we can't get HTTP context either, return data as-is (likely GraphQL)
// If we can't get HTTP context, return data as-is
return next.handle();
}
}
+1 -3
View File
@@ -12,8 +12,6 @@ import { MikroOrmModule } from '@mikro-orm/nestjs';
import { User } from '../users/entities/user.entity';
import { AdminAuthController } from './controllers/admin-auth.controller';
import { AdminAuthGuard } from './guards/adminAuth.guard';
import { AuthResolver } from './graphql/resolvers/auth.resolver';
import { AdminAuthResolver } from './graphql/resolvers/admin-auth.resolver';
@Module({
imports: [
@@ -34,7 +32,7 @@ import { AdminAuthResolver } from './graphql/resolvers/admin-auth.resolver';
MikroOrmModule.forFeature([User]),
],
controllers: [AuthController, AdminAuthController],
providers: [AuthService, TokensService, AdminAuthGuard, AuthResolver, AdminAuthResolver],
providers: [AuthService, TokensService, AdminAuthGuard],
exports: [AdminAuthGuard],
})
export class AuthModule {}
-16
View File
@@ -1,16 +0,0 @@
// Inputs
export * from './inputs/request-otp.input';
export * from './inputs/verify-otp.input';
export * from './inputs/refresh-token.input';
// Objects
export * from './objects/token-response.object';
export * from './objects/restaurant-info.object';
export * from './objects/admin-auth-response.object';
export * from './objects/auth-response.object';
export * from './objects/otp-response.object';
// Resolvers
export * from './resolvers/auth.resolver';
export * from './resolvers/admin-auth.resolver';
@@ -1,11 +0,0 @@
import { Field, InputType } from '@nestjs/graphql';
import { IsNotEmpty, IsString } from 'class-validator';
@InputType()
export class RefreshTokenInput {
@Field(() => String, { description: 'Refresh token' })
@IsString({ message: 'Refresh token must be a string' })
@IsNotEmpty({ message: 'Refresh token is required' })
refreshToken: string;
}
@@ -1,17 +0,0 @@
import { Field, InputType } from '@nestjs/graphql';
import { IsNotEmpty, IsString, IsMobilePhone } from 'class-validator';
@InputType()
export class RequestOtpInput {
@Field(() => String, { description: 'Mobile number' })
@IsNotEmpty()
@IsString()
@IsMobilePhone('fa-IR')
phone: string;
@Field(() => String, { description: 'Restaurant slug' })
@IsNotEmpty()
@IsString()
slug: string;
}
@@ -1,25 +0,0 @@
import { Field, InputType } from '@nestjs/graphql';
import { IsNotEmpty, IsString, Length, Matches } from 'class-validator';
@InputType()
export class VerifyOtpInput {
@Field(() => String, { description: 'Mobile number' })
@IsNotEmpty()
@IsString()
@Matches(/^09\d{9}$/, {
message: 'Mobile number must be a valid Iranian phone number (e.g., 09123456789)',
})
phone: string;
@Field(() => String, { description: 'OTP code' })
@IsNotEmpty()
@IsString()
@Length(5)
otp: string;
@Field(() => String, { description: 'Restaurant slug' })
@IsNotEmpty()
@IsString()
slug: string;
}
@@ -1,37 +0,0 @@
import { Field, ObjectType } from '@nestjs/graphql';
import { TokenResponse } from './token-response.object';
import { RestaurantInfo } from './restaurant-info.object';
@ObjectType()
export class AdminInfo {
@Field(() => String)
id: string;
@Field(() => String, { nullable: true })
firstName?: string;
@Field(() => String, { nullable: true })
lastName?: string;
@Field(() => String)
phone: string;
@Field(() => String)
role: string;
@Field(() => [String])
permissions: string[];
@Field(() => RestaurantInfo, { nullable: true })
restaurant?: RestaurantInfo;
}
@ObjectType()
export class AdminAuthResponse {
@Field(() => TokenResponse)
tokens: TokenResponse;
@Field(() => AdminInfo)
admin: AdminInfo;
}
@@ -1,40 +0,0 @@
import { Field, ObjectType } from '@nestjs/graphql';
import { TokenResponse } from './token-response.object';
import { RestaurantInfo } from './restaurant-info.object';
@ObjectType()
export class UserInfo {
@Field(() => String)
id: string;
@Field(() => String)
firstName: string;
@Field(() => String, { nullable: true })
lastName?: string;
@Field(() => String)
phone: string;
@Field(() => Number)
wallet: number;
@Field(() => Number)
points: number;
@Field(() => Boolean, { nullable: true })
isActive?: boolean;
@Field(() => RestaurantInfo)
restaurant: RestaurantInfo;
}
@ObjectType()
export class AuthResponse {
@Field(() => TokenResponse)
tokens: TokenResponse;
@Field(() => UserInfo)
user: UserInfo;
}
@@ -1,8 +0,0 @@
import { Field, ObjectType } from '@nestjs/graphql';
@ObjectType()
export class OtpResponse {
@Field(() => String, { description: 'OTP code (for development/testing purposes)' })
code: string;
}
@@ -1,14 +0,0 @@
import { Field, ObjectType } from '@nestjs/graphql';
@ObjectType()
export class RestaurantInfo {
@Field(() => String)
id: string;
@Field(() => String)
name: string;
@Field(() => String)
slug: string;
}
@@ -1,20 +0,0 @@
import { Field, ObjectType } from '@nestjs/graphql';
@ObjectType()
export class TokenInfo {
@Field(() => String)
token: string;
@Field(() => Number)
expire: number;
}
@ObjectType()
export class TokenResponse {
@Field(() => TokenInfo)
accessToken: TokenInfo;
@Field(() => TokenInfo)
refreshToken: TokenInfo;
}
@@ -1,40 +0,0 @@
import { Resolver, Mutation, Args } from '@nestjs/graphql';
import { AuthService } from '../../services/auth.service';
import { RequestOtpInput } from '../inputs/request-otp.input';
import { VerifyOtpInput } from '../inputs/verify-otp.input';
import { RefreshTokenInput } from '../inputs/refresh-token.input';
import { OtpResponse } from '../objects/otp-response.object';
import { AdminAuthResponse } from '../objects/admin-auth-response.object';
import { TokenResponse } from '../objects/token-response.object';
import { Throttle } from '@nestjs/throttler';
import { RefreshTokenRateLimit } from 'src/common/decorators/rate-limit.decorator';
@Resolver()
export class AdminAuthResolver {
constructor(private readonly authService: AuthService) {}
@Throttle({ default: { limit: 3, ttl: 180_000 } })
@Mutation(() => OtpResponse, {
description: 'Request OTP for admin login',
})
async adminRequestOtp(@Args('input') input: RequestOtpInput): Promise<OtpResponse> {
const code = await this.authService.requestOtpAdmin(input);
return { code };
}
@Mutation(() => AdminAuthResponse, {
description: 'Verify OTP code for admin login',
})
async adminVerifyOtp(@Args('input') input: VerifyOtpInput): Promise<AdminAuthResponse> {
return this.authService.verifyOtpAdmin(input.phone, input.slug, input.otp);
}
@RefreshTokenRateLimit()
@Mutation(() => TokenResponse, {
description: 'Refresh the admin access token and refresh token',
})
async adminRefreshToken(@Args('input') input: RefreshTokenInput): Promise<TokenResponse> {
return this.authService.refreshTokenAdmin(input.refreshToken);
}
}
@@ -1,45 +0,0 @@
import { Resolver, Mutation, Query, Args } from '@nestjs/graphql';
import { AuthService } from '../../services/auth.service';
import { RequestOtpInput } from '../inputs/request-otp.input';
import { VerifyOtpInput } from '../inputs/verify-otp.input';
import { RefreshTokenInput } from '../inputs/refresh-token.input';
import { OtpResponse } from '../objects/otp-response.object';
import { AuthResponse } from '../objects/auth-response.object';
import { TokenResponse } from '../objects/token-response.object';
import { Throttle } from '@nestjs/throttler';
import { RefreshTokenRateLimit } from 'src/common/decorators/rate-limit.decorator';
@Resolver()
export class AuthResolver {
constructor(private readonly authService: AuthService) {}
@Query(() => String, {
description: 'Health check query',
})
async health(): Promise<string> {
return 'OK';
}
@Throttle({ default: { limit: 3, ttl: 180_000 } })
@Mutation(() => OtpResponse, {
description: 'Request OTP for login or signup',
})
async requestOtp(@Args('input') input: RequestOtpInput): Promise<OtpResponse> {
return this.authService.requestOtp(input);
}
@Mutation(() => AuthResponse, {
description: 'Verify OTP code',
})
async verifyOtp(@Args('input') input: VerifyOtpInput): Promise<AuthResponse> {
return this.authService.verifyOtp(input.phone, input.slug, input.otp);
}
@RefreshTokenRateLimit()
@Mutation(() => TokenResponse, {
description: 'Refresh the user access token and refresh token',
})
async refreshToken(@Args('input') input: RefreshTokenInput): Promise<TokenResponse> {
return this.authService.refreshToken(input.refreshToken);
}
}
@@ -0,0 +1,68 @@
import { Controller, Get, Post, Body, Patch, Param, Delete, UseGuards } from '@nestjs/common';
import { PaymentMethodService } from '../services/payment-method.service';
import { CreatePaymentMethodDto } from '../dto/create-payment-method.dto';
import { UpdatePaymentMethodDto } from '../dto/update-payment-method.dto';
import {
ApiTags,
ApiOperation,
ApiResponse,
ApiCreatedResponse,
ApiOkResponse,
ApiNotFoundResponse,
ApiBody,
ApiParam,
ApiBearerAuth,
} from '@nestjs/swagger';
import { AdminAuthGuard } from 'src/modules/auth/guards/adminAuth.guard';
@UseGuards(AdminAuthGuard)
@ApiBearerAuth()
@ApiTags('admin/payment-methods')
@Controller('admin/payment-methods')
export class PaymentMethodController {
constructor(private readonly paymentMethodService: PaymentMethodService) {}
@Post()
@ApiOperation({ summary: 'Create a new payment method' })
@ApiBody({ type: CreatePaymentMethodDto })
@ApiCreatedResponse({ description: 'Payment method created successfully' })
create(@Body() createPaymentMethodDto: CreatePaymentMethodDto) {
return this.paymentMethodService.create(createPaymentMethodDto);
}
@Get()
@ApiOperation({ summary: 'Get all payment methods' })
@ApiOkResponse({ description: 'List of all payment methods' })
findAll() {
return this.paymentMethodService.findAll();
}
@Get(':id')
@ApiOperation({ summary: 'Get a payment method by ID' })
@ApiParam({ name: 'id', type: 'string', description: 'Payment method ID' })
@ApiOkResponse({ description: 'Payment method found' })
@ApiNotFoundResponse({ description: 'Payment method not found' })
findOne(@Param('id') id: string) {
return this.paymentMethodService.findOne(id);
}
@Patch(':id')
@ApiOperation({ summary: 'Update a payment method' })
@ApiParam({ name: 'id', type: 'string', description: 'Payment method ID' })
@ApiBody({ type: UpdatePaymentMethodDto })
@ApiOkResponse({ description: 'Payment method updated successfully' })
@ApiNotFoundResponse({ description: 'Payment method not found' })
update(@Param('id') id: string, @Body() updatePaymentMethodDto: UpdatePaymentMethodDto) {
return this.paymentMethodService.update(id, updatePaymentMethodDto);
}
@Delete(':id')
@ApiOperation({ summary: 'Delete a payment method' })
@ApiParam({ name: 'id', type: 'string', description: 'Payment method ID' })
@ApiOkResponse({ description: 'Payment method deleted successfully' })
@ApiNotFoundResponse({ description: 'Payment method not found' })
remove(@Param('id') id: string) {
return this.paymentMethodService.remove(id);
}
}
@@ -0,0 +1,68 @@
import { Controller, Get, Post, Body, Patch, Param, Delete, UseGuards } from '@nestjs/common';
import { PaymentsService } from '../services/payments.service';
import { CreatePaymentDto } from '../dto/create-payment.dto';
import { UpdatePaymentDto } from '../dto/update-payment.dto';
import {
ApiTags,
ApiOperation,
ApiResponse,
ApiCreatedResponse,
ApiOkResponse,
ApiNotFoundResponse,
ApiBody,
ApiParam,
ApiBearerAuth,
} from '@nestjs/swagger';
import { AdminAuthGuard } from 'src/modules/auth/guards/adminAuth.guard';
@UseGuards(AdminAuthGuard)
@ApiBearerAuth()
@ApiTags('admin/payments')
@Controller('admin/payments')
export class PaymentsController {
constructor(private readonly paymentsService: PaymentsService) {}
@Post()
@ApiOperation({ summary: 'Create a new payment' })
@ApiBody({ type: CreatePaymentDto })
@ApiCreatedResponse({ description: 'Payment created successfully' })
create(@Body() createPaymentDto: CreatePaymentDto) {
return this.paymentsService.create(createPaymentDto);
}
@Get()
@ApiOperation({ summary: 'Get all payments' })
@ApiOkResponse({ description: 'List of all payments' })
findAll() {
return this.paymentsService.findAll();
}
@Get(':id')
@ApiOperation({ summary: 'Get a payment by ID' })
@ApiParam({ name: 'id', type: 'number', description: 'Payment ID' })
@ApiOkResponse({ description: 'Payment found' })
@ApiNotFoundResponse({ description: 'Payment not found' })
findOne(@Param('id') id: string) {
return this.paymentsService.findOne(+id);
}
@Patch(':id')
@ApiOperation({ summary: 'Update a payment' })
@ApiParam({ name: 'id', type: 'number', description: 'Payment ID' })
@ApiBody({ type: UpdatePaymentDto })
@ApiOkResponse({ description: 'Payment updated successfully' })
@ApiNotFoundResponse({ description: 'Payment not found' })
update(@Param('id') id: string, @Body() updatePaymentDto: UpdatePaymentDto) {
return this.paymentsService.update(+id, updatePaymentDto);
}
@Delete(':id')
@ApiOperation({ summary: 'Delete a payment' })
@ApiParam({ name: 'id', type: 'number', description: 'Payment ID' })
@ApiOkResponse({ description: 'Payment deleted successfully' })
@ApiNotFoundResponse({ description: 'Payment not found' })
remove(@Param('id') id: string) {
return this.paymentsService.remove(+id);
}
}
@@ -0,0 +1,85 @@
import { Controller, Get, Post, Body, Patch, Param, Delete, Query, UseGuards } from '@nestjs/common';
import { RestaurantPaymentMethodService } from '../services/restaurant-payment-method.service';
import { CreateRestaurantPaymentMethodDto } from '../dto/create-restaurant-payment-method.dto';
import { UpdateRestaurantPaymentMethodDto } from '../dto/update-restaurant-payment-method.dto';
import {
ApiTags,
ApiOperation,
ApiResponse,
ApiCreatedResponse,
ApiOkResponse,
ApiNotFoundResponse,
ApiBody,
ApiParam,
ApiQuery,
ApiBearerAuth,
} from '@nestjs/swagger';
import { AdminAuthGuard } from 'src/modules/auth/guards/adminAuth.guard';
@UseGuards(AdminAuthGuard)
@ApiBearerAuth()
@ApiTags('admin/restaurant-payment-methods')
@Controller('admin/restaurant-payment-methods')
export class RestaurantPaymentMethodController {
constructor(private readonly restaurantPaymentMethodService: RestaurantPaymentMethodService) {}
@Post()
@ApiOperation({ summary: 'Create a new restaurant payment method' })
@ApiBody({ type: CreateRestaurantPaymentMethodDto })
@ApiCreatedResponse({ description: 'Restaurant payment method created successfully' })
create(@Body() createRestaurantPaymentMethodDto: CreateRestaurantPaymentMethodDto) {
return this.restaurantPaymentMethodService.create(createRestaurantPaymentMethodDto);
}
@Get()
@ApiOperation({ summary: 'Get all restaurant payment methods' })
@ApiOkResponse({ description: 'List of all restaurant payment methods' })
findAll() {
return this.restaurantPaymentMethodService.findAll();
}
@Get('by-restaurant/:restaurantId')
@ApiOperation({ summary: 'Get restaurant payment methods by restaurant ID' })
@ApiParam({ name: 'restaurantId', type: 'string', description: 'Restaurant ID' })
@ApiOkResponse({ description: 'List of restaurant payment methods for the restaurant' })
findByRestaurant(@Param('restaurantId') restaurantId: string) {
return this.restaurantPaymentMethodService.findByRestaurant(restaurantId);
}
@Get('by-payment-method/:paymentMethodId')
@ApiOperation({ summary: 'Get restaurant payment methods by payment method ID' })
@ApiParam({ name: 'paymentMethodId', type: 'string', description: 'Payment method ID' })
@ApiOkResponse({ description: 'List of restaurant payment methods for the payment method' })
findByPaymentMethod(@Param('paymentMethodId') paymentMethodId: string) {
return this.restaurantPaymentMethodService.findByPaymentMethod(paymentMethodId);
}
@Get(':id')
@ApiOperation({ summary: 'Get a restaurant payment method by ID' })
@ApiParam({ name: 'id', type: 'string', description: 'Restaurant payment method ID' })
@ApiOkResponse({ description: 'Restaurant payment method found' })
@ApiNotFoundResponse({ description: 'Restaurant payment method not found' })
findOne(@Param('id') id: string) {
return this.restaurantPaymentMethodService.findOne(id);
}
@Patch(':id')
@ApiOperation({ summary: 'Update a restaurant payment method' })
@ApiParam({ name: 'id', type: 'string', description: 'Restaurant payment method ID' })
@ApiBody({ type: UpdateRestaurantPaymentMethodDto })
@ApiOkResponse({ description: 'Restaurant payment method updated successfully' })
@ApiNotFoundResponse({ description: 'Restaurant payment method not found' })
update(@Param('id') id: string, @Body() updateRestaurantPaymentMethodDto: UpdateRestaurantPaymentMethodDto) {
return this.restaurantPaymentMethodService.update(id, updateRestaurantPaymentMethodDto);
}
@Delete(':id')
@ApiOperation({ summary: 'Delete a restaurant payment method' })
@ApiParam({ name: 'id', type: 'string', description: 'Restaurant payment method ID' })
@ApiOkResponse({ description: 'Restaurant payment method deleted successfully' })
@ApiNotFoundResponse({ description: 'Restaurant payment method not found' })
remove(@Param('id') id: string) {
return this.restaurantPaymentMethodService.remove(id);
}
}
@@ -0,0 +1,29 @@
import { IsString, IsOptional, IsBoolean, IsNumber } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
export class CreatePaymentMethodDto {
@ApiProperty({ description: 'Payment method name' })
@IsString()
name!: string;
@ApiProperty({ description: 'Payment method description', required: false })
@IsOptional()
@IsString()
description?: string;
@ApiProperty({ description: 'Payment method icon', required: false })
@IsOptional()
@IsString()
icon?: string;
@ApiProperty({ description: 'Is payment method active', required: false, default: true })
@IsOptional()
@IsBoolean()
isActive?: boolean;
@ApiProperty({ description: 'Display order', required: false })
@IsOptional()
@IsNumber()
order?: number;
}
@@ -1,19 +0,0 @@
import { InputType, Field, Int } from '@nestjs/graphql';
@InputType()
export class CreatePaymentMethodInput {
@Field()
name!: string;
@Field({ nullable: true })
description?: string;
@Field({ nullable: true })
icon?: string;
@Field(() => Boolean, { nullable: true, defaultValue: true })
isActive?: boolean;
@Field(() => Int, { nullable: true })
order?: number;
}
@@ -0,0 +1,9 @@
import { IsNumber } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
export class CreatePaymentDto {
@ApiProperty({ description: 'Example field (placeholder)' })
@IsNumber()
exampleField: number;
}
@@ -1,7 +0,0 @@
import { InputType, Int, Field } from '@nestjs/graphql';
@InputType()
export class CreatePaymentInput {
@Field(() => Int, { description: 'Example field (placeholder)' })
exampleField: number;
}
@@ -0,0 +1,23 @@
import { IsString, IsOptional, IsBoolean } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
export class CreateRestaurantPaymentMethodDto {
@ApiProperty({ description: 'Restaurant ID' })
@IsString()
restaurantId!: string;
@ApiProperty({ description: 'Payment method ID' })
@IsString()
paymentMethodId!: string;
@ApiProperty({ description: 'Merchant ID', required: false })
@IsOptional()
@IsString()
merchantId?: string;
@ApiProperty({ description: 'Is active', required: false, default: true })
@IsOptional()
@IsBoolean()
isActive?: boolean;
}
@@ -1,16 +0,0 @@
import { InputType, Field } from '@nestjs/graphql';
@InputType()
export class CreateRestaurantPaymentMethodInput {
@Field()
restaurantId!: string;
@Field()
paymentMethodId!: string;
@Field({ nullable: true })
merchantId?: string;
@Field({ nullable: true })
isActive?: boolean;
}
@@ -0,0 +1,11 @@
import { PartialType } from '@nestjs/mapped-types';
import { CreatePaymentMethodDto } from './create-payment-method.dto';
import { IsString } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
export class UpdatePaymentMethodDto extends PartialType(CreatePaymentMethodDto) {
@ApiProperty({ description: 'Payment method ID' })
@IsString()
id!: string;
}
@@ -1,8 +0,0 @@
import { CreatePaymentMethodInput } from './create-payment-method.input';
import { InputType, Field, PartialType } from '@nestjs/graphql';
@InputType()
export class UpdatePaymentMethodInput extends PartialType(CreatePaymentMethodInput) {
@Field()
id!: string;
}
@@ -0,0 +1,11 @@
import { PartialType } from '@nestjs/mapped-types';
import { CreatePaymentDto } from './create-payment.dto';
import { IsNumber } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
export class UpdatePaymentDto extends PartialType(CreatePaymentDto) {
@ApiProperty({ description: 'Payment ID' })
@IsNumber()
id: number;
}
@@ -1,8 +0,0 @@
import { CreatePaymentInput } from './create-payment.input';
import { InputType, Field, Int, PartialType } from '@nestjs/graphql';
@InputType()
export class UpdatePaymentInput extends PartialType(CreatePaymentInput) {
@Field(() => Int)
id: number;
}
@@ -0,0 +1,11 @@
import { PartialType } from '@nestjs/mapped-types';
import { CreateRestaurantPaymentMethodDto } from './create-restaurant-payment-method.dto';
import { IsString } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
export class UpdateRestaurantPaymentMethodDto extends PartialType(CreateRestaurantPaymentMethodDto) {
@ApiProperty({ description: 'Restaurant payment method ID' })
@IsString()
id!: string;
}
@@ -1,8 +0,0 @@
import { CreateRestaurantPaymentMethodInput } from './create-restaurant-payment-method.input';
import { InputType, Field, PartialType } from '@nestjs/graphql';
@InputType()
export class UpdateRestaurantPaymentMethodInput extends PartialType(CreateRestaurantPaymentMethodInput) {
@Field()
id!: string;
}
@@ -1,31 +1,23 @@
import { Entity, Property } from '@mikro-orm/core';
import { BaseEntity } from '../../../common/entities/base.entity';
import { ObjectType, Field, Int } from '@nestjs/graphql';
@ObjectType()
@Entity({ tableName: 'payment_methods' })
export class PaymentMethod extends BaseEntity {
@Field()
@Property()
name!: string;
@Field()
@Property()
keyName!: string;
@Field({ nullable: true })
@Property({ nullable: true })
description?: string;
@Field({ nullable: true })
@Property({ nullable: true })
icon?: string;
@Field(() => Boolean)
@Property({ default: true })
isActive: boolean = true;
@Field(() => Int, { nullable: true })
@Property({ type: 'int', nullable: true })
order?: number;
}
@@ -1,7 +1,3 @@
import { ObjectType, Field, Int } from '@nestjs/graphql';
@ObjectType()
export class Payment {
@Field(() => Int, { description: 'Example field (placeholder)' })
exampleField: number;
}
@@ -2,25 +2,20 @@ import { Entity, ManyToOne, Unique, Property } from '@mikro-orm/core';
import { BaseEntity } from '../../../common/entities/base.entity';
import { Restaurant } from '../../restaurants/entities/restaurant.entity';
import { PaymentMethod } from './payment-method.entity';
import { ObjectType, Field } from '@nestjs/graphql';
@ObjectType()
@Entity({ tableName: 'restaurant_payment_methods' })
@Unique({ properties: ['restaurant', 'paymentMethod'] })
export class RestaurantPaymentMethod extends BaseEntity {
@ManyToOne(() => Restaurant)
restaurant!: Restaurant;
@Field(() => PaymentMethod, { nullable: true })
@ManyToOne(() => PaymentMethod)
paymentMethod!: PaymentMethod;
// Add merchant ID for payment gateway providers (e.g., ZarinPal)
@Field({ nullable: true })
@Property({ nullable: true })
merchantId?: string;
@Field({ nullable: true })
@Property({ type: 'boolean', default: true })
isActive: boolean = true;
}
+7 -7
View File
@@ -1,26 +1,26 @@
import { Module } from '@nestjs/common';
import { PaymentsService } from './services/payments.service';
import { PaymentsResolver } from './resolvers/payments.resolver';
import { MikroOrmModule } from '@mikro-orm/nestjs';
import { PaymentMethod } from './entities/payment-method.entity';
import { PaymentMethodRepository } from './repositories/payment-method.repository';
import { PaymentMethodService } from './services/payment-method.service';
import { PaymentMethodResolver } from './resolvers/payment-method.resolver';
import { RestaurantPaymentMethod } from './entities/restaurant-payment-method.entity';
import { RestaurantPaymentMethodRepository } from './repositories/restaurant-payment-method.repository';
import { RestaurantPaymentMethodService } from './services/restaurant-payment-method.service';
import { RestaurantPaymentMethodResolver } from './resolvers/restaurant-payment-method.resolver';
import { Restaurant } from '../restaurants/entities/restaurant.entity';
import { PaymentsController } from './controllers/payments.controller';
import { PaymentMethodController } from './controllers/payment-method.controller';
import { RestaurantPaymentMethodController } from './controllers/restaurant-payment-method.controller';
import { AuthModule } from '../auth/auth.module';
import { JwtModule } from '@nestjs/jwt';
@Module({
imports: [MikroOrmModule.forFeature([PaymentMethod, RestaurantPaymentMethod, Restaurant])],
imports: [MikroOrmModule.forFeature([PaymentMethod, RestaurantPaymentMethod, Restaurant]), AuthModule, JwtModule],
controllers: [PaymentsController, PaymentMethodController, RestaurantPaymentMethodController],
providers: [
PaymentsResolver,
PaymentsService,
PaymentMethodResolver,
PaymentMethodService,
PaymentMethodRepository,
RestaurantPaymentMethodResolver,
RestaurantPaymentMethodService,
RestaurantPaymentMethodRepository,
],
@@ -1,35 +0,0 @@
import { Resolver, Query, Mutation, Args, ID } from '@nestjs/graphql';
import { PaymentMethodService } from '../services/payment-method.service';
import { PaymentMethod } from '../entities/payment-method.entity';
import { CreatePaymentMethodInput } from '../dto/create-payment-method.input';
import { UpdatePaymentMethodInput } from '../dto/update-payment-method.input';
@Resolver(() => PaymentMethod)
export class PaymentMethodResolver {
constructor(private readonly paymentMethodService: PaymentMethodService) {}
@Mutation(() => PaymentMethod)
createPaymentMethod(@Args('createPaymentMethodInput') createPaymentMethodInput: CreatePaymentMethodInput) {
return this.paymentMethodService.create(createPaymentMethodInput);
}
@Query(() => [PaymentMethod], { name: 'paymentMethods' })
findAll() {
return this.paymentMethodService.findAll();
}
@Query(() => PaymentMethod, { name: 'paymentMethod' })
findOne(@Args('id', { type: () => ID }) id: string) {
return this.paymentMethodService.findOne(id);
}
@Mutation(() => PaymentMethod)
updatePaymentMethod(@Args('updatePaymentMethodInput') updatePaymentMethodInput: UpdatePaymentMethodInput) {
return this.paymentMethodService.update(updatePaymentMethodInput.id, updatePaymentMethodInput);
}
@Mutation(() => PaymentMethod)
removePaymentMethod(@Args('id', { type: () => ID }) id: string) {
return this.paymentMethodService.remove(id);
}
}
@@ -1,35 +0,0 @@
import { Resolver, Query, Mutation, Args, Int } from '@nestjs/graphql';
import { PaymentsService } from '../services/payments.service';
import { Payment } from '../entities/payment.entity';
import { CreatePaymentInput } from '../dto/create-payment.input';
import { UpdatePaymentInput } from '../dto/update-payment.input';
@Resolver(() => Payment)
export class PaymentsResolver {
constructor(private readonly paymentsService: PaymentsService) {}
@Mutation(() => Payment)
createPayment(@Args('createPaymentInput') createPaymentInput: CreatePaymentInput) {
return this.paymentsService.create(createPaymentInput);
}
@Query(() => [Payment], { name: 'payments' })
findAll() {
return this.paymentsService.findAll();
}
@Query(() => Payment, { name: 'payment' })
findOne(@Args('id', { type: () => Int }) id: number) {
return this.paymentsService.findOne(id);
}
@Mutation(() => Payment)
updatePayment(@Args('updatePaymentInput') updatePaymentInput: UpdatePaymentInput) {
return this.paymentsService.update(updatePaymentInput.id, updatePaymentInput);
}
@Mutation(() => Payment)
removePayment(@Args('id', { type: () => Int }) id: number) {
return this.paymentsService.remove(id);
}
}
@@ -1,52 +0,0 @@
import { Resolver, Query, Mutation, Args, ID } from '@nestjs/graphql';
import { RestaurantPaymentMethodService } from '../services/restaurant-payment-method.service';
import { RestaurantPaymentMethod } from '../entities/restaurant-payment-method.entity';
import { CreateRestaurantPaymentMethodInput } from '../dto/create-restaurant-payment-method.input';
import { UpdateRestaurantPaymentMethodInput } from '../dto/update-restaurant-payment-method.input';
@Resolver(() => RestaurantPaymentMethod)
export class RestaurantPaymentMethodResolver {
constructor(private readonly restaurantPaymentMethodService: RestaurantPaymentMethodService) {}
@Mutation(() => RestaurantPaymentMethod)
createRestaurantPaymentMethod(
@Args('createRestaurantPaymentMethodInput') createRestaurantPaymentMethodInput: CreateRestaurantPaymentMethodInput,
) {
return this.restaurantPaymentMethodService.create(createRestaurantPaymentMethodInput);
}
@Query(() => [RestaurantPaymentMethod], { name: 'restaurantPaymentMethods' })
findAll() {
return this.restaurantPaymentMethodService.findAll();
}
@Query(() => [RestaurantPaymentMethod], { name: 'restaurantPaymentMethodsByRestaurant' })
findByRestaurant(@Args('restaurantId', { type: () => ID }) restaurantId: string) {
return this.restaurantPaymentMethodService.findByRestaurant(restaurantId);
}
@Query(() => [RestaurantPaymentMethod], { name: 'restaurantPaymentMethodsByPaymentMethod' })
findByPaymentMethod(@Args('paymentMethodId', { type: () => ID }) paymentMethodId: string) {
return this.restaurantPaymentMethodService.findByPaymentMethod(paymentMethodId);
}
@Query(() => RestaurantPaymentMethod, { name: 'restaurantPaymentMethod' })
findOne(@Args('id', { type: () => ID }) id: string) {
return this.restaurantPaymentMethodService.findOne(id);
}
@Mutation(() => RestaurantPaymentMethod)
updateRestaurantPaymentMethod(
@Args('updateRestaurantPaymentMethodInput') updateRestaurantPaymentMethodInput: UpdateRestaurantPaymentMethodInput,
) {
return this.restaurantPaymentMethodService.update(
updateRestaurantPaymentMethodInput.id,
updateRestaurantPaymentMethodInput,
);
}
@Mutation(() => RestaurantPaymentMethod)
removeRestaurantPaymentMethod(@Args('id', { type: () => ID }) id: string) {
return this.restaurantPaymentMethodService.remove(id);
}
}
@@ -2,8 +2,8 @@ import { Injectable, NotFoundException } from '@nestjs/common';
import { EntityManager } from '@mikro-orm/postgresql';
import { PaymentMethod } from '../entities/payment-method.entity';
import { PaymentMethodRepository } from '../repositories/payment-method.repository';
import { CreatePaymentMethodInput } from '../dto/create-payment-method.input';
import { UpdatePaymentMethodInput } from '../dto/update-payment-method.input';
import { CreatePaymentMethodDto } from '../dto/create-payment-method.dto';
import { UpdatePaymentMethodDto } from '../dto/update-payment-method.dto';
import { RequiredEntityData } from '@mikro-orm/core';
@Injectable()
@@ -13,9 +13,9 @@ export class PaymentMethodService {
private readonly em: EntityManager,
) {}
async create(createPaymentMethodInput: CreatePaymentMethodInput): Promise<PaymentMethod> {
async create(createPaymentMethodDto: CreatePaymentMethodDto): Promise<PaymentMethod> {
const paymentMethod = this.paymentMethodRepository.create(
createPaymentMethodInput as unknown as RequiredEntityData<PaymentMethod>,
createPaymentMethodDto as unknown as RequiredEntityData<PaymentMethod>,
);
await this.em.persistAndFlush(paymentMethod);
return paymentMethod;
@@ -33,9 +33,9 @@ export class PaymentMethodService {
return paymentMethod;
}
async update(id: string, updatePaymentMethodInput: UpdatePaymentMethodInput): Promise<PaymentMethod> {
async update(id: string, updatePaymentMethodDto: UpdatePaymentMethodDto): Promise<PaymentMethod> {
const paymentMethod = await this.findOne(id);
this.em.assign(paymentMethod, updatePaymentMethodInput);
this.em.assign(paymentMethod, updatePaymentMethodDto);
await this.em.flush();
return paymentMethod;
}
@@ -1,10 +1,10 @@
import { Injectable } from '@nestjs/common';
import { CreatePaymentInput } from '../dto/create-payment.input';
import { UpdatePaymentInput } from '../dto/update-payment.input';
import { CreatePaymentDto } from '../dto/create-payment.dto';
import { UpdatePaymentDto } from '../dto/update-payment.dto';
@Injectable()
export class PaymentsService {
create(createPaymentInput: CreatePaymentInput) {
create(createPaymentDto: CreatePaymentDto) {
return 'This action adds a new payment';
}
@@ -16,7 +16,7 @@ export class PaymentsService {
return `This action returns a #${id} payment`;
}
update(id: number, updatePaymentInput: UpdatePaymentInput) {
update(id: number, updatePaymentDto: UpdatePaymentDto) {
return `This action updates a #${id} payment`;
}
@@ -4,8 +4,8 @@ import { RestaurantPaymentMethod } from '../entities/restaurant-payment-method.e
import { RestaurantPaymentMethodRepository } from '../repositories/restaurant-payment-method.repository';
import { Restaurant } from '../../restaurants/entities/restaurant.entity';
import { PaymentMethod } from '../entities/payment-method.entity';
import { CreateRestaurantPaymentMethodInput } from '../dto/create-restaurant-payment-method.input';
import { UpdateRestaurantPaymentMethodInput } from '../dto/update-restaurant-payment-method.input';
import { CreateRestaurantPaymentMethodDto } from '../dto/create-restaurant-payment-method.dto';
import { UpdateRestaurantPaymentMethodDto } from '../dto/update-restaurant-payment-method.dto';
import { RequiredEntityData } from '@mikro-orm/core';
@Injectable()
@@ -16,24 +16,24 @@ export class RestaurantPaymentMethodService {
) {}
async create(
createRestaurantPaymentMethodInput: CreateRestaurantPaymentMethodInput,
createRestaurantPaymentMethodDto: CreateRestaurantPaymentMethodDto,
): Promise<RestaurantPaymentMethod> {
// Check if restaurant exists
const restaurant = await this.em.findOne(Restaurant, { id: createRestaurantPaymentMethodInput.restaurantId });
const restaurant = await this.em.findOne(Restaurant, { id: createRestaurantPaymentMethodDto.restaurantId });
if (!restaurant) {
throw new NotFoundException(`Restaurant with ID ${createRestaurantPaymentMethodInput.restaurantId} not found`);
throw new NotFoundException(`Restaurant with ID ${createRestaurantPaymentMethodDto.restaurantId} not found`);
}
// Check if payment method exists
const paymentMethod = await this.em.findOne(PaymentMethod, { id: createRestaurantPaymentMethodInput.paymentMethodId });
const paymentMethod = await this.em.findOne(PaymentMethod, { id: createRestaurantPaymentMethodDto.paymentMethodId });
if (!paymentMethod) {
throw new NotFoundException(`PaymentMethod with ID ${createRestaurantPaymentMethodInput.paymentMethodId} not found`);
throw new NotFoundException(`PaymentMethod with ID ${createRestaurantPaymentMethodDto.paymentMethodId} not found`);
}
// Check if the relationship already exists
const existing = await this.restaurantPaymentMethodRepository.findOne({
restaurant: { id: createRestaurantPaymentMethodInput.restaurantId },
paymentMethod: { id: createRestaurantPaymentMethodInput.paymentMethodId },
restaurant: { id: createRestaurantPaymentMethodDto.restaurantId },
paymentMethod: { id: createRestaurantPaymentMethodDto.paymentMethodId },
});
if (existing) {
@@ -43,8 +43,8 @@ export class RestaurantPaymentMethodService {
const restaurantPaymentMethod = this.restaurantPaymentMethodRepository.create({
restaurant,
paymentMethod,
merchantId: createRestaurantPaymentMethodInput.merchantId,
isActive: createRestaurantPaymentMethodInput.isActive ?? true,
merchantId: createRestaurantPaymentMethodDto.merchantId,
isActive: createRestaurantPaymentMethodDto.isActive ?? true,
} as unknown as RequiredEntityData<RestaurantPaymentMethod>);
await this.em.persistAndFlush(restaurantPaymentMethod);
@@ -82,28 +82,28 @@ export class RestaurantPaymentMethodService {
async update(
id: string,
updateRestaurantPaymentMethodInput: UpdateRestaurantPaymentMethodInput,
updateRestaurantPaymentMethodDto: UpdateRestaurantPaymentMethodDto,
): Promise<RestaurantPaymentMethod> {
const restaurantPaymentMethod = await this.findOne(id);
if (updateRestaurantPaymentMethodInput.restaurantId) {
const restaurant = await this.em.findOne(Restaurant, { id: updateRestaurantPaymentMethodInput.restaurantId });
if (updateRestaurantPaymentMethodDto.restaurantId) {
const restaurant = await this.em.findOne(Restaurant, { id: updateRestaurantPaymentMethodDto.restaurantId });
if (!restaurant) {
throw new NotFoundException(`Restaurant with ID ${updateRestaurantPaymentMethodInput.restaurantId} not found`);
throw new NotFoundException(`Restaurant with ID ${updateRestaurantPaymentMethodDto.restaurantId} not found`);
}
restaurantPaymentMethod.restaurant = restaurant;
}
if (updateRestaurantPaymentMethodInput.paymentMethodId) {
const paymentMethod = await this.em.findOne(PaymentMethod, { id: updateRestaurantPaymentMethodInput.paymentMethodId });
if (updateRestaurantPaymentMethodDto.paymentMethodId) {
const paymentMethod = await this.em.findOne(PaymentMethod, { id: updateRestaurantPaymentMethodDto.paymentMethodId });
if (!paymentMethod) {
throw new NotFoundException(`PaymentMethod with ID ${updateRestaurantPaymentMethodInput.paymentMethodId} not found`);
throw new NotFoundException(`PaymentMethod with ID ${updateRestaurantPaymentMethodDto.paymentMethodId} not found`);
}
// Check if the new relationship already exists
const existing = await this.restaurantPaymentMethodRepository.findOne({
restaurant: { id: restaurantPaymentMethod.restaurant.id },
paymentMethod: { id: updateRestaurantPaymentMethodInput.paymentMethodId },
paymentMethod: { id: updateRestaurantPaymentMethodDto.paymentMethodId },
});
if (existing && existing.id !== id) {
@@ -114,12 +114,12 @@ export class RestaurantPaymentMethodService {
}
// Update merchantId and isActive if provided
if (updateRestaurantPaymentMethodInput.merchantId !== undefined) {
restaurantPaymentMethod.merchantId = updateRestaurantPaymentMethodInput.merchantId;
if (updateRestaurantPaymentMethodDto.merchantId !== undefined) {
restaurantPaymentMethod.merchantId = updateRestaurantPaymentMethodDto.merchantId;
}
if (updateRestaurantPaymentMethodInput.isActive !== undefined) {
restaurantPaymentMethod.isActive = updateRestaurantPaymentMethodInput.isActive;
if (updateRestaurantPaymentMethodDto.isActive !== undefined) {
restaurantPaymentMethod.isActive = updateRestaurantPaymentMethodDto.isActive;
}
await this.em.flush();