Skip to content

Commit

Permalink
feat(payments): create relations and enable permission checks
Browse files Browse the repository at this point in the history
  • Loading branch information
getlarge committed Dec 17, 2023
1 parent 33ca6fb commit 23118dd
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 11 deletions.
5 changes: 3 additions & 2 deletions apps/payments/src/app/health/health.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import {
import { Resources } from '@ticketing/shared/constants';
import { Connection } from 'mongoose';

import { AppConfigService } from '../env';
import type { EnvironmentVariables } from '../env';

@ApiTags(Resources.HEALTH)
@Controller({ path: Resources.HEALTH, version: VERSION_NEUTRAL })
Expand All @@ -40,7 +40,8 @@ export class HealthController {
readonly microserviceOptions: MicroserviceHealthIndicatorOptions;

constructor(
@Inject(ConfigService) private readonly configService: AppConfigService,
@Inject(ConfigService)
private readonly configService: ConfigService<EnvironmentVariables, true>,
@Inject(getConnectionToken())
private readonly mongooseConnection: Connection,
private readonly health: HealthCheckService,
Expand Down
40 changes: 34 additions & 6 deletions apps/payments/src/app/payments/payments.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,25 @@ import {
ApiTags,
} from '@nestjs/swagger';
import { SecurityRequirements } from '@ticketing/microservices/shared/constants';
import { CurrentUser } from '@ticketing/microservices/shared/decorators';
import { OryAuthGuard } from '@ticketing/microservices/shared/guards';
import { Actions, Resources } from '@ticketing/shared/constants';
import {
CurrentUser,
PermissionCheck,
} from '@ticketing/microservices/shared/decorators';
import {
OryAuthGuard,
OryPermissionGuard,
} from '@ticketing/microservices/shared/guards';
import { PermissionNamespaces } from '@ticketing/microservices/shared/models';
import { relationTupleToString } from '@ticketing/microservices/shared/relation-tuple-parser';
import {
Actions,
CURRENT_USER_KEY,
Resources,
} from '@ticketing/shared/constants';
import { requestValidationErrorFactory } from '@ticketing/shared/errors';
import { User } from '@ticketing/shared/models';
import type { FastifyRequest } from 'fastify/types/request';
import { get } from 'lodash-es';

import { CreatePayment, CreatePaymentDto, Payment, PaymentDto } from './models';
import { PaymentsService } from './payments.service';
Expand All @@ -30,14 +44,28 @@ import { PaymentsService } from './payments.service';
export class PaymentsController {
constructor(private readonly paymentsService: PaymentsService) {}

@UseGuards(OryAuthGuard)
@PermissionCheck((ctx) => {
const req = ctx.switchToHttp().getRequest<FastifyRequest>();
const currentUserId = get(req, `${CURRENT_USER_KEY}.id`);
const resourceId = get(req.body as CreatePayment, 'orderId');
return relationTupleToString({
namespace: PermissionNamespaces[Resources.ORDERS],
object: resourceId,
relation: 'owners',
subjectIdOrSet: {
namespace: PermissionNamespaces[Resources.USERS],
object: currentUserId,
},
});
})
@UseGuards(OryAuthGuard, OryPermissionGuard)
@UsePipes(
new ValidationPipe({
transform: true,
exceptionFactory: requestValidationErrorFactory,
forbidUnknownValues: true,
whitelist: true,
})
}),
)
@ApiBearerAuth(SecurityRequirements.Bearer)
@ApiCookieAuth(SecurityRequirements.Session)
Expand All @@ -54,7 +82,7 @@ export class PaymentsController {
@Post('')
create(
@Body() payment: CreatePayment,
@CurrentUser() currentUser: User
@CurrentUser() currentUser: User,
): Promise<Payment> {
return this.paymentsService.create(payment, currentUser);
}
Expand Down
45 changes: 44 additions & 1 deletion apps/payments/src/app/payments/payments.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,15 @@ import {
} from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';
import { InjectModel } from '@nestjs/mongoose';
import { OryPermissionsService } from '@ticketing/microservices/ory-client';
import {
Patterns,
PaymentCreatedEvent,
} from '@ticketing/microservices/shared/events';
import { PermissionNamespaces } from '@ticketing/microservices/shared/models';
import { transactionManager } from '@ticketing/microservices/shared/mongo';
import { RelationTuple } from '@ticketing/microservices/shared/relation-tuple-parser';
import { Resources } from '@ticketing/shared/constants';
import { OrderStatus, User } from '@ticketing/shared/models';
import { Model } from 'mongoose';
import { firstValueFrom } from 'rxjs';
Expand All @@ -31,10 +35,25 @@ export class PaymentsService {
@InjectModel(OrderSchema.name) private orderModel: Model<OrderDocument>,
@InjectModel(PaymentSchema.name)
private paymentModel: Model<PaymentDocument>,
@Inject(OryPermissionsService)
private readonly oryPermissionsService: OryPermissionsService,
@Inject(StripeService) private readonly stripeService: StripeService,
@Inject(ORDERS_CLIENT) private client: ClientProxy,
) {}

private async createRelationShip(
relationTuple: RelationTuple,
): Promise<void> {
const relationShipCreated =
await this.oryPermissionsService.createRelation(relationTuple);
if (!relationShipCreated) {
throw new BadRequestException(
`Could not create relation ${relationTuple}`,
);
}
this.logger.debug(`Created relation ${relationTuple.toString()}`);
}

// TODO: add safe guard to avoid double payment
async create(
paymentRequest: CreatePayment,
Expand Down Expand Up @@ -84,7 +103,31 @@ export class PaymentsService {
{ session },
);
const payment = res[0].toJSON<Payment>();
// 6. emit payment:create event
// 6. create a relation between the order and the payment
const relationTupleWithOrder = new RelationTuple(
PermissionNamespaces[Resources.PAYMENTS],
payment.id,
'parents',
{
namespace: PermissionNamespaces[Resources.ORDERS],
object: orderId,
},
);
await this.createRelationShip(relationTupleWithOrder);

// 7. create a relation between the user and the payment
const relationTupleWithUser = new RelationTuple(
PermissionNamespaces[Resources.PAYMENTS],
payment.id,
'owners',
{
namespace: PermissionNamespaces[Resources.USERS],
object: currentUser.id,
},
);
await this.createRelationShip(relationTupleWithUser);

// 8. emit payment:create event
await firstValueFrom(
this.client.emit<
PaymentCreatedEvent['name'],
Expand Down
7 changes: 5 additions & 2 deletions apps/payments/src/app/payments/stripe.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@ import { Inject, Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import Stripe from 'stripe';

import { AppConfigService } from '../env';
import type { EnvironmentVariables } from '../env';

@Injectable()
export class StripeService extends Stripe {
readonly logger = new Logger(StripeService.name);

constructor(@Inject(ConfigService) configService: AppConfigService) {
constructor(
@Inject(ConfigService)
configService: ConfigService<EnvironmentVariables, true>,
) {
super(configService.get('STRIPE_SECRET_KEY'), {
apiVersion: '2020-08-27',
});
Expand Down

0 comments on commit 23118dd

Please sign in to comment.