Skip to content

Commit

Permalink
2.5.10 square platform marketplace updates
Browse files Browse the repository at this point in the history
  • Loading branch information
pauljonescodes committed Nov 15, 2023
1 parent 79cd3de commit ed55fb8
Show file tree
Hide file tree
Showing 10 changed files with 158 additions and 30 deletions.
15 changes: 12 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@
"stripe": "^14.4.0",
"swagger-ui-express": "5.0.0",
"tsconfig-paths": "^4.2.0",
"typeorm": "0.3.17"
"typeorm": "0.3.17",
"typeorm-encrypted": "^0.8.0"
},
"description": "",
"devDependencies": {
Expand Down Expand Up @@ -148,5 +149,5 @@
"typeorm": "node --loader ts-node/esm -r tsconfig-paths/register ./node_modules/typeorm/cli.js"
},
"type": "module",
"version": "2.5.8"
"version": "2.5.10"
}
14 changes: 7 additions & 7 deletions src/database/data-source.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import 'reflect-metadata';
import { DataSource, DataSourceOptions } from 'typeorm';

// import fs from 'fs';
// const envConfig = fs.readFileSync('.env');
// for (const line of envConfig.toString().split('\n')) {
// const [key, value] = line.split('=');
// process.env[key] = value;
// }
import fs from 'fs';
const envConfig = fs.readFileSync('.env');
for (const line of envConfig.toString().split('\n')) {
const [key, value] = line.split('=');
process.env[key] = value;
}

export const AppDataSource = new DataSource({
type: process.env.DATABASE_TYPE,
Expand All @@ -23,7 +23,7 @@ export const AppDataSource = new DataSource({
keepConnectionAlive: true,
logging: process.env.DATABASE_LOGGING,
entities: ['/src/**/*.entity{.ts,.js}'],
migrations: ['/migrations/**/*{.ts,.js}'],
migrations: ['src/database/migrations/**/*{.ts,.js}'],
cli: {
entitiesDir: 'src',
migrationsDir: 'src/database/migrations',
Expand Down
39 changes: 39 additions & 0 deletions src/database/migrations/1700008096601-EncryptSquareTokens.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Logger } from '@nestjs/common';
import { MerchantEntity } from 'src/moa-square/entities/merchant.entity.js';
import { MigrationInterface, QueryRunner } from 'typeorm';
import { EncryptionTransformer } from 'typeorm-encrypted';
import { MoaSquareEncryptionTransformerConfig } from '../../moa-square/moa-square.config.js';

export class EncryptSquareTokens1700008096601 implements MigrationInterface {
private readonly logger = new Logger(EncryptSquareTokens1700008096601.name);

public async up(queryRunner: QueryRunner): Promise<void> {
this.logger.verbose(this.up.name);

const merchants: Array<MerchantEntity> = await queryRunner.query(
'SELECT id, squareAccessToken, squareRefreshToken FROM merchant',
);
const transformer = new EncryptionTransformer(
MoaSquareEncryptionTransformerConfig,
);

for (const merchant of merchants) {
const encryptedSquareAccessToken = transformer.to(
merchant.squareAccessToken,
);
await queryRunner.query(
`UPDATE merchant SET squareAccessToken = '${encryptedSquareAccessToken}' WHERE id = '${merchant.id}'`,
);

const encryptedSquareRefreshToken = transformer.to(
merchant.squareRefreshToken,
);
await queryRunner.query(
`UPDATE merchant SET squareRefreshToken = '${encryptedSquareRefreshToken}' WHERE id = '${merchant.id}'`,
);
}
}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
public async down(_queryRunner: QueryRunner): Promise<void> {}
}
4 changes: 2 additions & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ async function bootstrap() {
app,
new DocumentBuilder()
.setTitle('MyOrderApp Square API')
.setVersion('2.5.8')
.setVersion('2.5.10')
.addBearerAuth()
.addApiKey(
{ type: 'apiKey', name: config.headerApiKey, in: 'header' },
Expand Down Expand Up @@ -125,7 +125,7 @@ async function bootstrap() {
app,
new DocumentBuilder()
.setTitle('MyOrderApp Admin API')
.setVersion('2.5.8')
.setVersion('2.5.10')
.addBearerAuth()
.addApiKey(
{ type: 'apiKey', name: config.headerApiKey, in: 'header' },
Expand Down
23 changes: 23 additions & 0 deletions src/moa-square/controllers/merchants.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,29 @@ export class MerchantsController {
return;
}

@Get('me/square/logout')
@HttpCode(HttpStatus.OK)
@UseGuards(AuthGuard('jwt'), MerchantsGuard)
@ApiBearerAuth()
@ApiOperation({
summary: 'Delete Square Oauth',
operationId: 'getMeSquareLogout',
})
@ApiUnauthorizedResponse({
description: 'You need to be authenticated to access this endpoint.',
type: ErrorResponse,
})
@ApiOkResponse({ description: 'Square Oauth deleted' })
async getMeSquareLogout(@Req() request: any): Promise<void> {
this.logger.verbose(this.getMeSquareLogout.name);

await this.merchantsSquareService.deleteOauth({
merchantId: request.merchant.id,
});

return;
}

@Get('me/square/sync')
@HttpCode(HttpStatus.OK)
@UseGuards(AuthGuard('jwt'), MerchantsGuard)
Expand Down
40 changes: 33 additions & 7 deletions src/moa-square/entities/merchant.entity.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ApiHideProperty, ApiProperty } from '@nestjs/swagger';
import { Exclude } from 'class-transformer';
import { Exclude, Expose } from 'class-transformer';
import { nanoid } from 'nanoid';
import type { Relation } from 'typeorm';
import {
Expand All @@ -16,7 +16,9 @@ import {
UpdateDateColumn,
VersionColumn,
} from 'typeorm';
import { EncryptionTransformer } from 'typeorm-encrypted';
import { UserEntity } from '../../users/entities/user.entity.js';
import { MoaSquareEncryptionTransformerConfig } from '../moa-square.config.js';
import { AppConfigEntity } from './app-config.entity.js';
import { CatalogEntity } from './catalog.entity.js';
import { CustomerEntity } from './customer.entity.js';
Expand Down Expand Up @@ -88,30 +90,54 @@ export class MerchantEntity extends BaseEntity {

@Exclude({ toPlainOnly: true })
@ApiHideProperty()
@Column({ nullable: true })
squareAccessToken?: string;
@Column({
nullable: true,
type: String,
transformer: new EncryptionTransformer(
MoaSquareEncryptionTransformerConfig,
),
})
squareAccessToken?: string | null;

@Exclude({ toPlainOnly: true })
@ApiHideProperty()
@Column({ nullable: true })
squareRefreshToken?: string;
@Column({
nullable: true,
type: String,
transformer: new EncryptionTransformer(
MoaSquareEncryptionTransformerConfig,
),
})
squareRefreshToken?: string | null;

@Exclude({ toPlainOnly: true })
@ApiHideProperty()
@Column({ nullable: true })
squareExpiresAt?: Date;
@Column({ nullable: true, type: Date })
squareExpiresAt?: Date | null;

@ApiProperty({ required: false, nullable: true })
@Column({ nullable: true })
squareId?: string;

@Expose()
@ApiProperty({ required: false, type: Boolean, nullable: true })
get squareConnected(): boolean {
return (
this.squareAccessToken != undefined &&
this.squareRefreshToken != undefined &&
this.squareExpiresAt != undefined &&
this.squareExpiresAt > new Date()
);
}

/*
* Square merchant
*/

@ApiProperty({
required: false,
nullable: true,
type: String,
description: "The name of the merchant's overall business.",
})
@Column({ nullable: true, type: String })
Expand Down
13 changes: 13 additions & 0 deletions src/moa-square/moa-square.config.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
import { registerAs } from '@nestjs/config';
import { plainToClass } from 'class-transformer';
import { IsBoolean, IsOptional, IsString, validateSync } from 'class-validator';
import { EncryptionOptions } from 'typeorm-encrypted';
import { toBigIntOrThrow } from '../utils/to-big-int-or-throw.js';

export const MoaSquareEncryptionTransformerConfig: EncryptionOptions = {
key: '0a7c51002579544f0519682287fb9119bb98d04437cdf82df547e41826c61794',
algorithm: 'aes-256-cbc',
ivLength: 16,
};

export type MyOrderAppSquareConfigType = {
squareClientEnvironment: string;
squareOauthClientId: string;
squareOauthClientSecret: string;
squareWebhookSignatureKey: string;
squareEncryptionToken: string;

squareAppFeeBigIntDenominator: bigint;
squareTier0AppFeeBigIntNumerator: bigint;
Expand Down Expand Up @@ -46,6 +54,9 @@ export type MyOrderAppSquareConfigType = {
};

class MyOrderAppSquareConfigValidator {
@IsString()
SQUARE_ENCRYPTION_TOKEN!: string;

@IsString()
SQUARE_OAUTH_CLIENT_ID!: string;

Expand Down Expand Up @@ -186,6 +197,8 @@ export const MyOrderAppSquareConfig = registerAs('moaSquare', () => {
squareOauthClientSecret: process.env.SQUARE_OAUTH_CLIENT_SECRET!,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
squareWebhookSignatureKey: process.env.SQUARE_WEBHOOK_SIGNATURE_KEY!,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
squareEncryptionToken: process.env.SQUARE_ENCRYPTION_TOKEN!,

squareAppFeeBigIntDenominator: toBigIntOrThrow(
process.env.SQUARE_TIER_APP_FEE_BIG_INT_DENOMINATOR!,
Expand Down
11 changes: 5 additions & 6 deletions src/moa-square/services/customers.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,12 +119,11 @@ export class CustomersService extends EntityRepositoryService<CustomerEntity> {
throw new NotFoundException(translations.merchantsNotFound);
}

if (
await this.findOne({
where: { userId, merchantId: merchant.id },
})
) {
throw new BadRequestException(translations.customersExists);
const existing = await this.findOne({
where: { userId, merchantId: merchant.id },
});
if (existing) {
return existing;
}

if (!merchant?.squareAccessToken) {
Expand Down
24 changes: 21 additions & 3 deletions src/moa-square/services/merchants.square.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,24 @@ export class MerchantsSquareService {
return merchant;
}

async deleteOauth(params: { merchantId: string }) {
this.logger.verbose(this.deleteOauth.name);
const { merchantId } = params;

const merchant = await this.service.findOneOrFail({
where: { id: merchantId },
relations: { user: true },
});

merchant.squareAccessToken = null;
merchant.squareExpiresAt = null;
merchant.squareRefreshToken = null;

await this.service.save(merchant);

return merchant;
}

async sync(params: { merchantId: string }) {
this.logger.verbose(this.sync.name);
const translations = this.currentTranslations();
Expand Down Expand Up @@ -282,14 +300,14 @@ export class MerchantsSquareService {
@Cron(CronExpression.EVERY_DAY_AT_4AM)
async refreshTokensCron() {
this.logger.verbose(this.refreshTokensCron.name);
const seventyTwoHoursFromNow = new Date(
new Date().getTime() + 72 * 60 * 60 * 1000,
const threeWeeksFromNow = new Date(
new Date().getTime() + 21 * 24 * 60 * 60 * 1000, // 21 days, 24 hours per day, 60 minutes per hour, 60 seconds per minute, 1000 milliseconds per second
);
const merchantsWhereSquareTokenExpiresInLessThan72Hours =
await this.service.find({
where: {
squareExpiresAt: LessThan(
DateUtils.mixedDateToUtcDatetimeString(seventyTwoHoursFromNow),
DateUtils.mixedDateToUtcDatetimeString(threeWeeksFromNow),
),
},
});
Expand Down

0 comments on commit ed55fb8

Please sign in to comment.