зеркало из https://github.com/mozilla/fxa.git
feat(payments): add cart library and factories
Because: * Need a library to handle the cart db table and related functions This commit: * Add cart DB model * Initializes the Cart library and creates factories and types * Adds CartManager library * Adds Cart related resolvers with basic Cart DB queries Closes: #FXA-7508 #FXA-7505
This commit is contained in:
Родитель
2c2299f64f
Коммит
868a723be6
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"extends": ["../../../.eslintrc.json"],
|
||||
"ignorePatterns": ["!**/*"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.ts", "*.tsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
# payments-cart
|
||||
|
||||
This library was generated with [Nx](https://nx.dev).
|
||||
|
||||
## Building
|
||||
|
||||
Run `nx build payments-cart` to build the library.
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `nx test payments-cart` to execute the unit tests via [Jest](https://jestjs.io).
|
|
@ -0,0 +1,11 @@
|
|||
/* eslint-disable */
|
||||
export default {
|
||||
displayName: 'payments-cart',
|
||||
preset: '../../../jest.preset.js',
|
||||
testEnvironment: 'node',
|
||||
transform: {
|
||||
'^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }],
|
||||
},
|
||||
moduleFileExtensions: ['ts', 'js', 'html'],
|
||||
coverageDirectory: '../../../coverage/libs/payments/cart',
|
||||
};
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"name": "@fxa/payments/cart",
|
||||
"version": "0.0.1",
|
||||
"type": "commonjs"
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"name": "payments-cart",
|
||||
"$schema": "../../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "libs/payments/cart/src",
|
||||
"projectType": "library",
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@nx/js:tsc",
|
||||
"outputs": ["{options.outputPath}"],
|
||||
"options": {
|
||||
"outputPath": "dist/libs/payments/cart",
|
||||
"main": "libs/payments/cart/src/index.ts",
|
||||
"tsConfig": "libs/payments/cart/tsconfig.lib.json",
|
||||
"assets": ["libs/payments/cart/*.md"]
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/linter:eslint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": ["libs/payments/cart/**/*.ts"]
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"executor": "@nx/jest:jest",
|
||||
"outputs": ["{workspaceRoot}/coverage/{projectRoot}"],
|
||||
"options": {
|
||||
"jestConfig": "libs/payments/cart/jest.config.ts",
|
||||
"passWithNoTests": true
|
||||
},
|
||||
"configurations": {
|
||||
"ci": {
|
||||
"ci": true,
|
||||
"codeCoverage": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": []
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
|
||||
@InputType()
|
||||
export class CartIdInput {
|
||||
@Field({
|
||||
description: 'Cart ID',
|
||||
})
|
||||
public id!: string;
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
import { Field, ID, ObjectType, registerEnumType } from '@nestjs/graphql';
|
||||
import { CartState } from '../lib/types';
|
||||
import { TaxAddress } from './';
|
||||
import { Invoice } from './invoice.model';
|
||||
|
||||
registerEnumType(CartState, {
|
||||
name: 'CartState',
|
||||
});
|
||||
|
||||
@ObjectType({
|
||||
description:
|
||||
'The Cart associated with a customer Subscription Platform checkout',
|
||||
})
|
||||
export class Cart {
|
||||
@Field((type) => ID, { description: 'Cart unique identifier' })
|
||||
public id!: string;
|
||||
|
||||
@Field((type) => ID, {
|
||||
nullable: true,
|
||||
description: 'Firefox Account User ID',
|
||||
})
|
||||
public uid?: string;
|
||||
|
||||
@Field((type) => CartState, { description: 'State of the cart' })
|
||||
public state!: CartState;
|
||||
|
||||
@Field({ nullable: true, description: 'Error reason ID' })
|
||||
public errorReasonId?: string;
|
||||
|
||||
@Field({ description: 'Offering ID configured in the CMS' })
|
||||
public offeringConfigId!: string;
|
||||
|
||||
@Field({ description: 'Interval' })
|
||||
public interval!: string;
|
||||
|
||||
@Field({
|
||||
nullable: true,
|
||||
description: 'Experiment associated with the cart',
|
||||
})
|
||||
public experiment?: string;
|
||||
|
||||
@Field((type) => TaxAddress, {
|
||||
nullable: true,
|
||||
description: 'Tax address',
|
||||
})
|
||||
public taxAddress?: TaxAddress;
|
||||
|
||||
@Field((type) => Invoice, { description: 'The previous invoice' })
|
||||
public previousInvoice?: Invoice;
|
||||
|
||||
@Field((type) => Invoice, {
|
||||
description: 'The next, also known as upcoming, invoice',
|
||||
})
|
||||
public nextInvoice!: Invoice;
|
||||
|
||||
@Field({ description: 'Timestamp when the cart was created' })
|
||||
public createdAt!: number;
|
||||
|
||||
@Field({ description: 'Timestamp the cart was last updated' })
|
||||
public updatedAt!: number;
|
||||
|
||||
@Field({ nullable: true, description: 'Applied coupon code' })
|
||||
public couponCode?: string;
|
||||
|
||||
@Field({
|
||||
nullable: true,
|
||||
description: 'Stripe customer ID of cart customer',
|
||||
})
|
||||
public stripeCustomerId?: string;
|
||||
|
||||
@Field({ nullable: true, description: 'Email set by customer' })
|
||||
public email?: string;
|
||||
|
||||
@Field({ description: 'Amount of plan at checkout' })
|
||||
public amount!: number;
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
import { Provider } from '@nestjs/common';
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { MozLoggerService } from 'fxa-shared/nestjs/logger/logger.service';
|
||||
import { Logger } from '../../../../shared/log/src';
|
||||
import {
|
||||
CartIdInputFactory,
|
||||
SetupCartInputFactory,
|
||||
UpdateCartInputFactory,
|
||||
} from '../lib/factories';
|
||||
import { CartManager } from '../lib/manager';
|
||||
import { CartResolver } from './cart.resolver';
|
||||
const fakeSetupCart = jest.fn();
|
||||
const fakeRestartCart = jest.fn();
|
||||
const fakeCheckoutCart = jest.fn();
|
||||
const fakeUpdateCart = jest.fn();
|
||||
|
||||
jest.mock('../lib/manager', () => {
|
||||
// Works and lets you check for constructor calls:
|
||||
return {
|
||||
CartManager: jest.fn().mockImplementation(() => {
|
||||
return {
|
||||
setupCart: fakeSetupCart,
|
||||
restartCart: fakeRestartCart,
|
||||
checkoutCart: fakeCheckoutCart,
|
||||
updateCart: fakeUpdateCart,
|
||||
};
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
describe('#payments-cart - resolvers', () => {
|
||||
let resolver: CartResolver;
|
||||
const mockLogger: Logger = {
|
||||
debug: jest.fn(),
|
||||
error: jest.fn(),
|
||||
info: jest.fn(),
|
||||
trace: jest.fn(),
|
||||
warn: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(async () => {
|
||||
(CartManager as jest.Mock).mockClear();
|
||||
|
||||
const MockMozLogger: Provider = {
|
||||
provide: MozLoggerService,
|
||||
useValue: mockLogger,
|
||||
};
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [CartResolver, MockMozLogger],
|
||||
}).compile();
|
||||
|
||||
resolver = module.get<CartResolver>(CartResolver);
|
||||
});
|
||||
describe('setupCart', () => {
|
||||
it('should successfully create a new cart', async () => {
|
||||
const input = SetupCartInputFactory({
|
||||
interval: 'annually',
|
||||
});
|
||||
await resolver.setupCart(input);
|
||||
expect(fakeSetupCart).toBeCalledWith(expect.objectContaining(input));
|
||||
});
|
||||
});
|
||||
|
||||
describe('restartCart', () => {
|
||||
it('should successfully set cart state to "START"', async () => {
|
||||
const input = CartIdInputFactory();
|
||||
await resolver.restartCart(input);
|
||||
expect(fakeRestartCart).toBeCalledWith(input.id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('checkoutCart', () => {
|
||||
it('should successfully set cart state to "PROCESSING"', async () => {
|
||||
const input = CartIdInputFactory();
|
||||
await resolver.checkoutCart(input);
|
||||
expect(fakeCheckoutCart).toBeCalledWith(input.id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateCart', () => {
|
||||
it('should successfully update an existing cart', async () => {
|
||||
const input = UpdateCartInputFactory();
|
||||
await resolver.updateCart(input);
|
||||
expect(fakeUpdateCart).toBeCalledWith(input);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,71 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
|
||||
import { MozLoggerService } from 'fxa-shared/nestjs/logger/logger.service';
|
||||
import { Cart } from '../../../../shared/db/mysql/account/src';
|
||||
import { InvoiceFactory } from '../lib/factories';
|
||||
import { CartManager } from '../lib/manager';
|
||||
import {
|
||||
CartIdInput,
|
||||
Cart as CartType,
|
||||
SetupCartInput,
|
||||
UpdateCartInput,
|
||||
} from './index';
|
||||
|
||||
@Resolver((of: any) => CartType)
|
||||
export class CartResolver {
|
||||
private cartManager: CartManager;
|
||||
constructor(private log: MozLoggerService) {
|
||||
this.cartManager = new CartManager(this.log);
|
||||
}
|
||||
|
||||
@Query((returns) => CartType, { nullable: true })
|
||||
public async cart(): Promise<CartType | null> {
|
||||
// Query just for testing purposes
|
||||
// TODO - To be done in FXA-7521
|
||||
const cart = await Cart.query().first();
|
||||
if (!cart) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
...cart,
|
||||
nextInvoice: InvoiceFactory(), // Temporary
|
||||
};
|
||||
}
|
||||
|
||||
@Mutation((returns) => CartType, { nullable: true })
|
||||
public async setupCart(
|
||||
@Args('input', { type: () => SetupCartInput })
|
||||
input: SetupCartInput
|
||||
): Promise<CartType | null> {
|
||||
return this.cartManager.setupCart({
|
||||
...input,
|
||||
});
|
||||
}
|
||||
|
||||
@Mutation((returns) => CartType, { nullable: true })
|
||||
public async restartCart(
|
||||
@Args('input', { type: () => CartIdInput })
|
||||
input: CartIdInput
|
||||
): Promise<CartType | null> {
|
||||
return this.cartManager.restartCart(input.id);
|
||||
}
|
||||
|
||||
@Mutation((returns) => CartType, { nullable: true })
|
||||
public async checkoutCart(
|
||||
@Args('input', { type: () => CartIdInput })
|
||||
input: CartIdInput
|
||||
): Promise<CartType | null> {
|
||||
return this.cartManager.checkoutCart(input.id);
|
||||
}
|
||||
|
||||
@Mutation((returns) => CartType, { nullable: true })
|
||||
public async updateCart(
|
||||
@Args('input', { type: () => UpdateCartInput })
|
||||
input: UpdateCartInput
|
||||
): Promise<CartType | null> {
|
||||
return this.cartManager.updateCart(input);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
export { CartIdInput } from './cart-id.input';
|
||||
export { Cart } from './cart.model';
|
||||
export { CartResolver } from './cart.resolver';
|
||||
export { Invoice } from './invoice.model';
|
||||
export { SetupCartInput } from './setup-cart.input';
|
||||
export { Subscription } from './subscription.model';
|
||||
export { TaxAddress } from './tax-address.model';
|
||||
export { TaxAmount } from './tax-amount.model';
|
||||
export { UpdateCartInput } from './update-cart.input';
|
|
@ -0,0 +1,16 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
import { TaxAmount } from './tax-amount.model';
|
||||
|
||||
@ObjectType()
|
||||
export class Invoice {
|
||||
@Field({ description: 'Total of invoice' })
|
||||
public totalAmount!: number;
|
||||
|
||||
@Field((type) => [TaxAmount], {
|
||||
description: 'Tax amounts of the invoice',
|
||||
})
|
||||
public taxAmounts!: TaxAmount[];
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
|
||||
@InputType()
|
||||
export class SetupCartInput {
|
||||
@Field()
|
||||
public offeringConfigId!: string;
|
||||
|
||||
@Field({ nullable: true })
|
||||
public interval?: string;
|
||||
|
||||
@Field({
|
||||
nullable: true,
|
||||
description: 'Cart ID',
|
||||
})
|
||||
public id?: string;
|
||||
|
||||
@Field({
|
||||
nullable: true,
|
||||
description: 'FxA UID',
|
||||
})
|
||||
public uid?: string;
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
import { Invoice } from './invoice.model';
|
||||
|
||||
@ObjectType({
|
||||
description: 'Subscription representation used within the Cart context',
|
||||
})
|
||||
export class Subscription {
|
||||
@Field()
|
||||
public pageConfigId!: string;
|
||||
|
||||
@Field((type) => Invoice)
|
||||
public previousInvoice!: Invoice;
|
||||
|
||||
@Field((type) => Invoice)
|
||||
public nextInvoice!: Invoice;
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
@ObjectType({
|
||||
description: 'The Tax Address associated with the Cart',
|
||||
})
|
||||
export class TaxAddress {
|
||||
@Field({
|
||||
nullable: true,
|
||||
description: 'Country code for tax',
|
||||
})
|
||||
public countryCode!: string;
|
||||
|
||||
@Field({ description: 'Postal code for tax' })
|
||||
public postalCode!: string;
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
import { Field, ObjectType } from '@nestjs/graphql';
|
||||
|
||||
@ObjectType({
|
||||
description: 'Tax amounts used within the Cart',
|
||||
})
|
||||
export class TaxAmount {
|
||||
@Field()
|
||||
public title!: string;
|
||||
|
||||
@Field()
|
||||
public amount!: number;
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
import { SetupCartInput } from './setup-cart.input';
|
||||
|
||||
@InputType()
|
||||
export class UpdateCartInput extends SetupCartInput {
|
||||
@Field({
|
||||
description: 'Cart ID',
|
||||
})
|
||||
public id!: string;
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
export * from './lib/types';
|
||||
export * from './lib/factories';
|
||||
export * from './gql';
|
|
@ -0,0 +1,96 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
import { faker } from '@faker-js/faker';
|
||||
import {
|
||||
Cart,
|
||||
CartIdInput,
|
||||
Invoice,
|
||||
SetupCartInput,
|
||||
Subscription,
|
||||
TaxAddress,
|
||||
TaxAmount,
|
||||
UpdateCartInput,
|
||||
} from '../gql';
|
||||
import { CartState, SetupCart } from './types';
|
||||
|
||||
const OFFERING_CONFIG_IDS = [
|
||||
'vpn',
|
||||
'relay-phone',
|
||||
'relay-email',
|
||||
'hubs',
|
||||
'mdnplus',
|
||||
];
|
||||
|
||||
export const CartFactory = (override?: Partial<Cart>): Cart => ({
|
||||
id: faker.string.uuid(),
|
||||
state: CartState.START,
|
||||
offeringConfigId: faker.helpers.arrayElement(OFFERING_CONFIG_IDS),
|
||||
interval: faker.helpers.arrayElement([
|
||||
'daily',
|
||||
'monthly',
|
||||
'semiannually',
|
||||
'annually',
|
||||
]),
|
||||
nextInvoice: InvoiceFactory(),
|
||||
createdAt: faker.date.recent().getTime(),
|
||||
updatedAt: faker.date.recent().getTime(),
|
||||
amount: faker.number.int(10000),
|
||||
...override,
|
||||
});
|
||||
|
||||
export const TaxAmountFactory = (override?: Partial<TaxAmount>): TaxAmount => ({
|
||||
title: faker.location.state({ abbreviated: true }),
|
||||
amount: faker.number.int(10000),
|
||||
...override,
|
||||
});
|
||||
|
||||
export const InvoiceFactory = (override?: Partial<Invoice>): Invoice => ({
|
||||
totalAmount: faker.number.int(10000),
|
||||
taxAmounts: [TaxAmountFactory()],
|
||||
...override,
|
||||
});
|
||||
|
||||
export const SubscriptionFactory = (
|
||||
override?: Partial<Subscription>
|
||||
): Subscription => ({
|
||||
pageConfigId: faker.helpers.arrayElement(['default', 'alternate-pricing']),
|
||||
previousInvoice: InvoiceFactory(),
|
||||
nextInvoice: InvoiceFactory(),
|
||||
...override,
|
||||
});
|
||||
|
||||
export const TaxAddressFactory = (
|
||||
override?: Partial<TaxAddress>
|
||||
): TaxAddress => ({
|
||||
countryCode: faker.location.countryCode(),
|
||||
postalCode: faker.location.zipCode(),
|
||||
...override,
|
||||
});
|
||||
|
||||
export const CartIdInputFactory = (
|
||||
override?: Partial<CartIdInput>
|
||||
): CartIdInput => ({
|
||||
id: faker.string.uuid(),
|
||||
...override,
|
||||
});
|
||||
|
||||
export const SetupCartInputFactory = (
|
||||
override?: Partial<SetupCartInput>
|
||||
): SetupCartInput => ({
|
||||
offeringConfigId: faker.helpers.arrayElement(OFFERING_CONFIG_IDS),
|
||||
...override,
|
||||
});
|
||||
|
||||
export const UpdateCartInputFactory = (
|
||||
override?: Partial<UpdateCartInput>
|
||||
): UpdateCartInput => ({
|
||||
id: faker.string.uuid(),
|
||||
offeringConfigId: faker.helpers.arrayElement(OFFERING_CONFIG_IDS),
|
||||
...override,
|
||||
});
|
||||
|
||||
export const SetupCartFactory = (override?: Partial<SetupCart>): SetupCart => ({
|
||||
offeringConfigId: faker.helpers.arrayElement(OFFERING_CONFIG_IDS),
|
||||
...override,
|
||||
});
|
|
@ -0,0 +1,113 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
import { Knex } from 'knex';
|
||||
import { NotFoundError } from 'objection';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { Cart, CartFactory } from '../../../../shared/db/mysql/account/src';
|
||||
import { Logger } from '../../../../shared/log/src';
|
||||
import { SetupCartFactory, UpdateCartInputFactory } from './factories';
|
||||
import { CartManager, ERRORS } from './manager';
|
||||
import { testCartDatabaseSetup } from './tests';
|
||||
import { CartState } from './types';
|
||||
|
||||
const CART_ID = '8730e0d5939c450286e6e6cc1bbeeab2';
|
||||
const RANDOM_ID = uuidv4({}, Buffer.alloc(16)).toString('hex');
|
||||
|
||||
describe('#payments-cart - manager', () => {
|
||||
let knex: Knex;
|
||||
let cartManager: CartManager;
|
||||
const mockLogger: Logger = {
|
||||
debug: jest.fn(),
|
||||
error: jest.fn(),
|
||||
info: jest.fn(),
|
||||
trace: jest.fn(),
|
||||
warn: jest.fn(),
|
||||
};
|
||||
|
||||
beforeAll(async () => {
|
||||
cartManager = new CartManager(mockLogger);
|
||||
knex = await testCartDatabaseSetup();
|
||||
await Cart.query().insert({
|
||||
...CartFactory(),
|
||||
id: CART_ID,
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await knex.destroy();
|
||||
});
|
||||
|
||||
describe('setupCart', () => {
|
||||
it('should successfully create a new cart', async () => {
|
||||
const setupCart = SetupCartFactory({
|
||||
interval: 'annually',
|
||||
});
|
||||
const cart = await cartManager.setupCart(setupCart);
|
||||
expect(cart).toEqual(expect.objectContaining(setupCart));
|
||||
});
|
||||
});
|
||||
|
||||
describe('restartCart', () => {
|
||||
it('should successfully set cart state to "START"', async () => {
|
||||
await Cart.query().update({ state: CartState.PROCESSING });
|
||||
const cart = await cartManager.restartCart(CART_ID);
|
||||
expect(cart?.state).toBe(CartState.START);
|
||||
});
|
||||
|
||||
it('should return null if no cart with provided ID is found', async () => {
|
||||
try {
|
||||
await cartManager.restartCart(RANDOM_ID);
|
||||
} catch (error) {
|
||||
expect(error).toStrictEqual(
|
||||
new NotFoundError({ message: ERRORS.CART_NOT_FOUND })
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('checkoutCart', () => {
|
||||
it('should successfully set cart state to "PROCESSING"', async () => {
|
||||
await Cart.query().update({ state: CartState.START });
|
||||
const cart = await cartManager.checkoutCart(CART_ID);
|
||||
expect(cart?.state).toBe(CartState.PROCESSING);
|
||||
});
|
||||
|
||||
it('should return null if no cart with provided ID is found', async () => {
|
||||
try {
|
||||
await cartManager.checkoutCart(RANDOM_ID);
|
||||
} catch (error) {
|
||||
expect(error).toStrictEqual(
|
||||
new NotFoundError({ message: ERRORS.CART_NOT_FOUND })
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('updateCart', () => {
|
||||
it('should successfully update an existing cart', async () => {
|
||||
const updateCart = UpdateCartInputFactory({
|
||||
id: CART_ID,
|
||||
interval: 'monthly',
|
||||
offeringConfigId: 'randomproduct',
|
||||
});
|
||||
const cart = await cartManager.updateCart(updateCart);
|
||||
expect(cart).toEqual(expect.objectContaining(updateCart));
|
||||
});
|
||||
|
||||
it('should return null if no cart with provided ID is found', async () => {
|
||||
const updateCart = UpdateCartInputFactory({
|
||||
id: RANDOM_ID,
|
||||
interval: 'monthly',
|
||||
offeringConfigId: 'randomproduct',
|
||||
});
|
||||
try {
|
||||
await cartManager.updateCart(updateCart);
|
||||
} catch (error) {
|
||||
expect(error).toStrictEqual(
|
||||
new NotFoundError({ message: ERRORS.CART_NOT_FOUND })
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,115 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
import { Cart } from '../../../../shared/db/mysql/account/src';
|
||||
import {
|
||||
generateFxAUuid,
|
||||
uuidTransformer,
|
||||
} from '../../../../shared/db/mysql/core/src';
|
||||
import { Logger } from '../../../../shared/log/src';
|
||||
import { Cart as CartType } from '../gql';
|
||||
import { UpdateCartInput } from '../gql/update-cart.input';
|
||||
import { InvoiceFactory } from './factories';
|
||||
import { CartState, SetupCart } from './types';
|
||||
|
||||
const DEFAULT_INTERVAL = 'monthly';
|
||||
|
||||
// TODO - Adopt error library developed as part of FXA-7656
|
||||
export enum ERRORS {
|
||||
CART_NOT_FOUND = 'Cart not found for id',
|
||||
}
|
||||
|
||||
export class CartManager {
|
||||
private log: Logger;
|
||||
constructor(log: Logger) {
|
||||
this.log = log;
|
||||
}
|
||||
|
||||
public async setupCart(input: SetupCart): Promise<CartType | null> {
|
||||
const currentDate = Date.now();
|
||||
const cart = await Cart.query().insert({
|
||||
...input,
|
||||
id: generateFxAUuid(),
|
||||
state: CartState.START,
|
||||
interval: input.interval || DEFAULT_INTERVAL,
|
||||
amount: 0, // Hardcoded to 0 for now. TODO - Actual amount to be added in FXA-7521
|
||||
createdAt: currentDate,
|
||||
updatedAt: currentDate,
|
||||
});
|
||||
|
||||
return {
|
||||
...cart,
|
||||
nextInvoice: InvoiceFactory(), // Temporary
|
||||
};
|
||||
}
|
||||
|
||||
public async restartCart(cartId: string): Promise<CartType | null> {
|
||||
const id = uuidTransformer.to(cartId);
|
||||
const cart = await Cart.query()
|
||||
.where('id', id)
|
||||
.first()
|
||||
.throwIfNotFound({ message: ERRORS.CART_NOT_FOUND });
|
||||
|
||||
// Patch and fetch instance
|
||||
// Use update if you update the whole row with all its columns. Otherwise, using the patch method is recommended.
|
||||
// https://vincit.github.io/objection.js/api/query-builder/mutate-methods.html#update
|
||||
await cart
|
||||
.$query()
|
||||
.patchAndFetch({ state: CartState.START, updatedAt: Date.now() });
|
||||
|
||||
// Patch changes to the DB
|
||||
await Cart.query().patch(cart).where('id', id);
|
||||
|
||||
return {
|
||||
...cart,
|
||||
nextInvoice: InvoiceFactory(), // Temporary
|
||||
};
|
||||
}
|
||||
|
||||
public async checkoutCart(cartId: string): Promise<CartType | null> {
|
||||
const id = uuidTransformer.to(cartId);
|
||||
const cart = await Cart.query()
|
||||
.where('id', id)
|
||||
.first()
|
||||
.throwIfNotFound({ message: ERRORS.CART_NOT_FOUND });
|
||||
// Patch and fetch instance
|
||||
// Use update if you update the whole row with all its columns. Otherwise, using the patch method is recommended.
|
||||
// https://vincit.github.io/objection.js/api/query-builder/mutate-methods.html#update
|
||||
await cart
|
||||
.$query()
|
||||
.patchAndFetch({ state: CartState.PROCESSING, updatedAt: Date.now() });
|
||||
|
||||
// Patch changes to the DB
|
||||
await Cart.query().patch(cart).where('id', id);
|
||||
|
||||
return {
|
||||
...cart,
|
||||
nextInvoice: InvoiceFactory(), // Temporary
|
||||
};
|
||||
}
|
||||
|
||||
public async updateCart(input: UpdateCartInput): Promise<CartType | null> {
|
||||
const { id: cartId, ...rest } = input;
|
||||
const id = uuidTransformer.to(cartId);
|
||||
|
||||
const cart = await Cart.query()
|
||||
.where('id', id)
|
||||
.first()
|
||||
.throwIfNotFound({ message: ERRORS.CART_NOT_FOUND });
|
||||
// Patch and fetch instance
|
||||
// Use update if you update the whole row with all its columns. Otherwise, using the patch method is recommended.
|
||||
// https://vincit.github.io/objection.js/api/query-builder/mutate-methods.html#update
|
||||
await cart.$query().patchAndFetch({
|
||||
...rest,
|
||||
updatedAt: Date.now(),
|
||||
});
|
||||
|
||||
// Patch changes to the DB
|
||||
await Cart.query().patch(cart).where('id', id);
|
||||
|
||||
return {
|
||||
...cart,
|
||||
nextInvoice: InvoiceFactory(), // Temporary
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
import knex, { Knex } from 'knex';
|
||||
import {
|
||||
runSql,
|
||||
setupAccountDatabase,
|
||||
} from '../../../../shared/db/mysql/core/src';
|
||||
|
||||
const CART_TEST_DB = 'testCart';
|
||||
const SQL_FILE_LOCATION = '../../../account/src/test';
|
||||
|
||||
export async function testCartDatabaseSetup(): Promise<Knex> {
|
||||
// Create the db if it doesn't exist
|
||||
let instance = knex({
|
||||
client: 'mysql',
|
||||
connection: {
|
||||
charset: 'UTF8MB4_BIN',
|
||||
host: 'localhost',
|
||||
password: '',
|
||||
port: 3306,
|
||||
user: 'root',
|
||||
},
|
||||
});
|
||||
|
||||
await instance.raw(`DROP DATABASE IF EXISTS ${CART_TEST_DB}`);
|
||||
await instance.raw(`CREATE DATABASE ${CART_TEST_DB}`);
|
||||
await instance.destroy();
|
||||
|
||||
instance = setupAccountDatabase({
|
||||
database: CART_TEST_DB,
|
||||
host: 'localhost',
|
||||
password: '',
|
||||
port: 3306,
|
||||
user: 'root',
|
||||
});
|
||||
|
||||
await runSql([`${SQL_FILE_LOCATION}/accounts.sql`], instance);
|
||||
await runSql([`${SQL_FILE_LOCATION}/carts.sql`], instance);
|
||||
|
||||
return instance;
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
import { Cart } from '../../../../shared/db/mysql/account/src';
|
||||
|
||||
export enum CartState {
|
||||
START = 'start',
|
||||
PROCESSING = 'processing',
|
||||
SUCCESS = 'success',
|
||||
FAIL = 'fail',
|
||||
}
|
||||
|
||||
export type SetupCart = Pick<
|
||||
Cart,
|
||||
| 'uid'
|
||||
| 'errorReasonId'
|
||||
| 'offeringConfigId'
|
||||
| 'experiment'
|
||||
| 'taxAddress'
|
||||
| 'couponCode'
|
||||
| 'stripeCustomerId'
|
||||
| 'email'
|
||||
> & { interval?: string };
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"extends": "../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"module": "commonjs"
|
||||
},
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../../dist/out-tsc",
|
||||
"declaration": true,
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"]
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../../dist/out-tsc",
|
||||
"module": "commonjs",
|
||||
"types": ["jest", "node"]
|
||||
},
|
||||
"include": [
|
||||
"jest.config.ts",
|
||||
"src/**/*.test.ts",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.d.ts"
|
||||
]
|
||||
}
|
|
@ -1,12 +1,15 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
export { AccountCustomers } from './lib/account-customers';
|
||||
export { Account } from './lib/account';
|
||||
export { AccountCustomers } from './lib/account-customers';
|
||||
export { BaseModel } from './lib/base';
|
||||
export { Cart } from './lib/cart';
|
||||
export { Device } from './lib/device';
|
||||
export { EmailBounce } from './lib/email-bounce';
|
||||
export { Email } from './lib/email';
|
||||
export { EmailBounce } from './lib/email-bounce';
|
||||
export { EmailType } from './lib/email-type';
|
||||
export { CartFactory } from './lib/factories';
|
||||
export { LinkedAccount } from './lib/linked-account';
|
||||
export { MetaData } from './lib/metadata';
|
||||
export { PayPalBillingAgreements } from './lib/paypal-ba';
|
||||
|
@ -15,4 +18,5 @@ export { RelyingParty } from './lib/relying-party';
|
|||
export { SecurityEvent } from './lib/security-event';
|
||||
export { SentEmail } from './lib/sent-email';
|
||||
export { SignInCodes } from './lib/sign-in-codes';
|
||||
export { CartFields } from './lib/types';
|
||||
export { UnblockCodes } from './lib/unblock-codes';
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
import { Model } from 'objection';
|
||||
|
||||
import { intBoolTransformer, uuidTransformer } from '@fxa/shared/db/mysql/core';
|
||||
import { intBoolTransformer, uuidTransformer } from '../../../core/src';
|
||||
|
||||
/**
|
||||
* Base Model for helpers that should be present on all models.
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
import { BaseModel } from './base';
|
||||
import { Account } from './account';
|
||||
import { CartState, TaxAddress } from '@fxa/payments/cart';
|
||||
|
||||
export class Cart extends BaseModel {
|
||||
static tableName = 'carts';
|
||||
|
||||
protected $uuidFields = ['id', 'uid'];
|
||||
|
||||
// table fields
|
||||
id!: string;
|
||||
uid?: string;
|
||||
state!: CartState;
|
||||
errorReasonId?: string;
|
||||
offeringConfigId!: string;
|
||||
interval!: string;
|
||||
experiment?: string;
|
||||
taxAddress?: TaxAddress;
|
||||
createdAt!: number;
|
||||
updatedAt!: number;
|
||||
couponCode?: string;
|
||||
stripeCustomerId?: string;
|
||||
email?: string;
|
||||
amount!: number;
|
||||
|
||||
static relationMappings = {
|
||||
account: {
|
||||
join: {
|
||||
from: 'carts.uid',
|
||||
to: 'accounts.uid',
|
||||
},
|
||||
modelClass: Account,
|
||||
relation: BaseModel.BelongsToOneRelation,
|
||||
},
|
||||
};
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { CartState } from '../../../../../../payments/cart/src';
|
||||
import { Cart } from './cart';
|
||||
import { CartFields } from './types';
|
||||
|
||||
export const CartFactory = (override?: Partial<Cart>): CartFields => ({
|
||||
id: faker.string.uuid(),
|
||||
state: CartState.START,
|
||||
offeringConfigId: faker.helpers.arrayElement([
|
||||
'vpn',
|
||||
'relay-phone',
|
||||
'relay-email',
|
||||
'hubs',
|
||||
'mdnplus',
|
||||
]),
|
||||
interval: faker.helpers.arrayElement([
|
||||
'daily',
|
||||
'monthly',
|
||||
'semiannually',
|
||||
'annually',
|
||||
]),
|
||||
createdAt: faker.date.recent().getTime(),
|
||||
updatedAt: faker.date.recent().getTime(),
|
||||
amount: faker.number.int(10000),
|
||||
...override,
|
||||
});
|
|
@ -0,0 +1,22 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
import { Cart } from './cart';
|
||||
|
||||
export type CartFields = Pick<
|
||||
Cart,
|
||||
| 'id'
|
||||
| 'uid'
|
||||
| 'state'
|
||||
| 'errorReasonId'
|
||||
| 'offeringConfigId'
|
||||
| 'interval'
|
||||
| 'experiment'
|
||||
| 'taxAddress'
|
||||
| 'createdAt'
|
||||
| 'updatedAt'
|
||||
| 'couponCode'
|
||||
| 'stripeCustomerId'
|
||||
| 'email'
|
||||
| 'amount'
|
||||
>;
|
|
@ -0,0 +1,23 @@
|
|||
CREATE TABLE `accounts` (
|
||||
`uid` binary(16) NOT NULL,
|
||||
`normalizedEmail` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
|
||||
`email` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
|
||||
`emailCode` binary(16) NOT NULL,
|
||||
`emailVerified` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`kA` binary(32) NOT NULL,
|
||||
`wrapWrapKb` binary(32) NOT NULL,
|
||||
`authSalt` binary(32) NOT NULL,
|
||||
`verifyHash` binary(32) NOT NULL,
|
||||
`verifierVersion` tinyint(3) unsigned NOT NULL,
|
||||
`verifierSetAt` bigint(20) unsigned NOT NULL,
|
||||
`createdAt` bigint(20) unsigned NOT NULL,
|
||||
`locale` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
|
||||
`lockedAt` bigint(20) unsigned DEFAULT NULL,
|
||||
`profileChangedAt` bigint(20) unsigned DEFAULT NULL,
|
||||
`keysChangedAt` bigint(20) unsigned DEFAULT NULL,
|
||||
`ecosystemAnonId` text CHARACTER SET ascii COLLATE ascii_bin,
|
||||
`disabledAt` bigint(20) unsigned DEFAULT NULL,
|
||||
`metricsOptOutAt` bigint(20) unsigned DEFAULT NULL,
|
||||
PRIMARY KEY (`uid`),
|
||||
UNIQUE KEY `normalizedEmail` (`normalizedEmail`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
|
|
@ -0,0 +1,19 @@
|
|||
CREATE TABLE `carts` (
|
||||
`id` binary(16) NOT NULL,
|
||||
`uid` binary(16) DEFAULT NULL,
|
||||
`state` enum('start','processing','success','fail') COLLATE utf8mb4_bin NOT NULL,
|
||||
`errorReasonId` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
|
||||
`offeringConfigId` varchar(255) COLLATE utf8mb4_bin NOT NULL,
|
||||
`interval` varchar(255) COLLATE utf8mb4_bin NOT NULL,
|
||||
`experiment` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
|
||||
`taxAddress` json DEFAULT NULL,
|
||||
`createdAt` bigint unsigned NOT NULL,
|
||||
`updatedAt` bigint unsigned NOT NULL,
|
||||
`couponCode` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
|
||||
`stripeCustomerId` varchar(32) COLLATE utf8mb4_bin DEFAULT NULL,
|
||||
`email` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
|
||||
`amount` int unsigned NOT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `uid` (`uid`),
|
||||
CONSTRAINT `carts_ibfk_1` FOREIGN KEY (`uid`) REFERENCES `accounts` (`uid`) ON DELETE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;
|
|
@ -3,8 +3,14 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
export {
|
||||
MySQLConfig,
|
||||
makeValidatedMySQLConfig,
|
||||
makeConvictMySQLConfig,
|
||||
makeValidatedMySQLConfig,
|
||||
} from './lib/config';
|
||||
export { createKnex, monitorKnexConnectionPool } from './lib/core';
|
||||
export { uuidTransformer, intBoolTransformer } from './lib/transformers';
|
||||
export {
|
||||
createKnex,
|
||||
generateFxAUuid,
|
||||
monitorKnexConnectionPool,
|
||||
} from './lib/core';
|
||||
export { setupAccountDatabase } from './lib/setup';
|
||||
export { runSql } from './lib/tests';
|
||||
export { intBoolTransformer, uuidTransformer } from './lib/transformers';
|
||||
|
|
|
@ -4,11 +4,13 @@
|
|||
import { knex, Knex } from 'knex';
|
||||
import { promisify } from 'util';
|
||||
|
||||
import { ConsoleLogger, Logger } from '@fxa/shared/log';
|
||||
import { localStatsD, StatsD } from '@fxa/shared/metrics/statsd';
|
||||
import { ConsoleLogger, Logger } from '../../../../../log/src';
|
||||
import { localStatsD, StatsD } from '../../../../../metrics/statsd/src';
|
||||
|
||||
import { MySQLConfig } from './config';
|
||||
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
const REQUIRED_SQL_MODES = ['STRICT_ALL_TABLES', 'NO_ENGINE_SUBSTITUTION'];
|
||||
|
||||
/**
|
||||
|
@ -102,3 +104,7 @@ export function createKnex(
|
|||
|
||||
return db;
|
||||
}
|
||||
|
||||
export function generateFxAUuid() {
|
||||
return uuidv4({}, Buffer.alloc(16)).toString('hex');
|
||||
}
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
import { BaseModel as AccountBaseModel } from '../../../account/src';
|
||||
import { Logger } from '../../../../../log/src';
|
||||
import { StatsD } from '../../../../../metrics/statsd/src';
|
||||
import { MySQLConfig } from './config';
|
||||
import { createKnex } from './core';
|
||||
|
||||
export function setupAccountDatabase(
|
||||
opts: MySQLConfig,
|
||||
log?: Logger,
|
||||
metrics?: StatsD
|
||||
) {
|
||||
const knex = createKnex(opts, log, metrics);
|
||||
AccountBaseModel.knex(knex);
|
||||
return knex;
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
import fs from 'fs';
|
||||
import { Knex } from 'knex';
|
||||
import path from 'path';
|
||||
|
||||
export const runSql = async (filePaths: string[], instance: Knex) =>
|
||||
Promise.all(
|
||||
filePaths
|
||||
.map((x) => path.join(__dirname, x))
|
||||
.map((x) => fs.readFileSync(x, 'utf8'))
|
||||
.map((x) => instance.raw.bind(instance)(x))
|
||||
);
|
|
@ -4,6 +4,7 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { setupAuthDatabase, setupProfileDatabase } from 'fxa-shared/db';
|
||||
import { setupAccountDatabase } from '../../../../libs/shared/db/mysql/core/src';
|
||||
import { Account } from 'fxa-shared/db/models/auth';
|
||||
import { StatsD } from 'hot-shots';
|
||||
import { MozLoggerService } from 'fxa-shared/nestjs/logger/logger.service';
|
||||
|
@ -15,6 +16,7 @@ import { AppConfig } from '../config';
|
|||
export class DatabaseService {
|
||||
public authKnex: Knex;
|
||||
public profileKnex: Knex;
|
||||
public accountKnex: Knex;
|
||||
|
||||
constructor(
|
||||
configService: ConfigService<AppConfig>,
|
||||
|
@ -28,6 +30,11 @@ export class DatabaseService {
|
|||
logger,
|
||||
metrics
|
||||
);
|
||||
this.accountKnex = setupAccountDatabase(
|
||||
dbConfig.mysql.auth,
|
||||
logger,
|
||||
metrics
|
||||
);
|
||||
}
|
||||
|
||||
async dbHealthCheck(): Promise<Record<string, any>> {
|
||||
|
|
|
@ -25,6 +25,7 @@ import Config, { AppConfig } from '../config';
|
|||
import { AccountResolver } from './account.resolver';
|
||||
import { SessionResolver } from './session.resolver';
|
||||
import { LegalResolver } from './legal.resolver';
|
||||
import { CartResolver } from '../../../../libs/payments/cart/src';
|
||||
import { Request, Response } from 'express';
|
||||
|
||||
const config = Config.getProperties();
|
||||
|
@ -66,7 +67,13 @@ export const GraphQLConfigFactory = async (
|
|||
|
||||
@Module({
|
||||
imports: [BackendModule, CustomsModule],
|
||||
providers: [AccountResolver, CustomsService, SessionResolver, LegalResolver],
|
||||
providers: [
|
||||
AccountResolver,
|
||||
CustomsService,
|
||||
SessionResolver,
|
||||
LegalResolver,
|
||||
CartResolver,
|
||||
],
|
||||
})
|
||||
export class GqlModule implements NestModule {
|
||||
configure(consumer: MiddlewareConsumer) {
|
||||
|
|
|
@ -30,7 +30,8 @@
|
|||
],
|
||||
"@fxa/shared/db/mysql/core": ["libs/shared/db/mysql/core/src/index.ts"],
|
||||
"@fxa/shared/log": ["libs/shared/log/src/index.ts"],
|
||||
"@fxa/shared/metrics/statsd": ["libs/shared/metrics/statsd/src/index.ts"]
|
||||
"@fxa/shared/metrics/statsd": ["libs/shared/metrics/statsd/src/index.ts"],
|
||||
"@fxa/payments/cart": ["libs/payments/cart/src/index.ts"],
|
||||
},
|
||||
"typeRoots": [
|
||||
"./types",
|
||||
|
|
Загрузка…
Ссылка в новой задаче