Skip to content

Commit

Permalink
feat: Refactor Accounts table
Browse files Browse the repository at this point in the history
- delete `AccountTypes` table
- change `accountTypeId` to `accountCategory`
- update Accounts model, controller, service, and tests, to reflect changes
  • Loading branch information
letehaha committed Jan 28, 2024
1 parent a3ab0a4 commit a80f359
Show file tree
Hide file tree
Showing 8 changed files with 179 additions and 18 deletions.
15 changes: 15 additions & 0 deletions shared-types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,21 @@ export enum ACCOUNT_TYPES {
monobank = 'monobank',
}

export enum ACCOUNT_CATEGORIES {
general = 'general',
cash = 'cash',
currentAccount = 'current-account',
creditCard = 'credit-card',
saving = 'saving',
bonus = 'bonus',
insurance = 'insurance',
investment = 'investment',
loan = 'loan',
mortgage = 'mortgage',
overdraft = 'overdraft',
crypto = 'crypto',
}

export enum PAYMENT_TYPES {
bankTransfer = 'bankTransfer',
voucher = 'voucher',
Expand Down
3 changes: 2 additions & 1 deletion shared-types/models/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
TRANSACTION_TYPES,
PAYMENT_TYPES,
TRANSACTION_TRANSFER_NATURE,
ACCOUNT_CATEGORIES,
} from 'shared-types';
export * from './external-services';

Expand Down Expand Up @@ -40,7 +41,7 @@ export interface AccountModel {
refCurrentBalance: number;
creditLimit: number;
refCreditLimit: number;
accountTypeId: number;
accountCategory: ACCOUNT_CATEGORIES;
currencyId: number;
userId: number;
externalId?: string;
Expand Down
4 changes: 2 additions & 2 deletions shared-types/routes/accounts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { AccountModel } from 'shared-types';
import { BodyPayload } from './index';

export interface CreateAccountBody extends BodyPayload {
accountTypeId: AccountModel['accountTypeId'];
accountCategory: AccountModel['accountCategory'];
currencyId: AccountModel['currencyId'];
name: AccountModel['name'];
initialBalance: AccountModel['initialBalance'];
Expand All @@ -12,7 +12,7 @@ export interface CreateAccountBody extends BodyPayload {
}

export interface UpdateAccountBody extends BodyPayload {
accountTypeId?: AccountModel['accountTypeId'];
accountCategory?: AccountModel['accountCategory'];
name?: AccountModel['name'];
currentBalance?: AccountModel['currentBalance'];
creditLimit?: AccountModel['creditLimit'];
Expand Down
9 changes: 5 additions & 4 deletions src/controllers/accounts.controller.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
ACCOUNT_CATEGORIES,
ACCOUNT_TYPES,
API_RESPONSE_STATUS,
endpointsTypes,
Expand Down Expand Up @@ -43,7 +44,7 @@ export const getAccountById = async (req, res: CustomResponse) => {

export const createAccount = async (req, res) => {
const {
accountTypeId,
accountCategory = ACCOUNT_CATEGORIES.general,
currencyId,
name,
type = ACCOUNT_TYPES.system,
Expand All @@ -63,7 +64,7 @@ export const createAccount = async (req, res) => {
}

const account = await accountsService.createAccount({
accountTypeId,
accountCategory,
currencyId,
name,
type,
Expand All @@ -85,7 +86,7 @@ export const updateAccount = async (req, res) => {
const { id } = req.params;
const { id: userId } = req.user;
const {
accountTypeId,
accountCategory,
name,
creditLimit,
isEnabled,
Expand Down Expand Up @@ -116,7 +117,7 @@ export const updateAccount = async (req, res) => {
userId,
...removeUndefinedKeys({
isEnabled,
accountTypeId: Number(accountTypeId),
accountCategory,
currentBalance: Number(currentBalance),
name,
creditLimit: Number(creditLimit),
Expand Down
141 changes: 141 additions & 0 deletions src/migrations/1706433927547-account-type-as-enum.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
module.exports = {
up: async (queryInterface, Sequelize) => {
const transaction = await queryInterface.sequelize.transaction();

try {
// Define the ENUM type for account types
const ACCOUNT_TYPES = [
'general', 'cash', 'current-account', 'credit-card', 'saving',
'bonus', 'insurance', 'investment', 'loan', 'mortgage',
'overdraft', 'crypto',
];

// Add a temporary ENUM column
await queryInterface.addColumn(
'Accounts',
'accountCategory',
{
type: Sequelize.ENUM(...ACCOUNT_TYPES),
allowNull: false,
defaultValue: 'general',
},
{ transaction }
);

// Map numeric IDs to ENUM values
for (let i = 0; i < ACCOUNT_TYPES.length; i++) {
await queryInterface.sequelize.query(
`UPDATE "Accounts" SET "accountCategory" = '${ACCOUNT_TYPES[i]}' WHERE "accountTypeId" = ${i + 1}`,
{ transaction }
);
}

// Remove the old accountTypeId column
await queryInterface.removeColumn('Accounts', 'accountTypeId', { transaction });

// Drop the AccountTypes table
await queryInterface.dropTable('AccountTypes', { transaction });

await transaction.commit();
} catch (err) {
await transaction.rollback();
throw err;
}
},
down: async (queryInterface, Sequelize) => {
const transaction = await queryInterface.sequelize.transaction();

try {
// Strict data for recovering, do not edit until you sure
const ACCOUNT_TYPES = {
general: "General",
cash: "Cash",
'current-account': "Current account",
'credit-card': "Credit card",
saving: "Saving account",
bonus: "Bonus",
insurance: "Insurance",
investment: "Investment",
loan: "Loan",
mortgage: "Mortgage",
overdraft: "Account with overdraft",
crypto: "Crypto",
}

// Recreate the AccountTypes table
await queryInterface.createTable('AccountTypes', {
id: {
type: Sequelize.INTEGER,
unique: true,
allowNull: false,
autoIncrement: true,
primaryKey: true,
},
name: {
type: Sequelize.STRING,
allowNull: false,
},
}, { transaction });

await queryInterface.bulkInsert(
'AccountTypes',
Object.values(ACCOUNT_TYPES).map((name, index) => ({ id: index + 1, name })),
{ transaction },
);

// Firstly create column with allowNull: true
// then fill it
// then set allowNull: fakse
await queryInterface.addColumn(
'Accounts',
'accountTypeId',
{
type: Sequelize.INTEGER,
allowNull: true,
references: {
model: 'AccountTypes',
key: 'id',
},
onUpdate: 'CASCADE',
onDelete: 'SET NULL',
},
{ transaction },
);

// Map ENUM values back to numeric IDs
for (let i = 0; i < Object.values(ACCOUNT_TYPES).length; i++) {
await queryInterface.sequelize.query(
`UPDATE "Accounts" SET "accountTypeId" = ${i + 1} WHERE "accountCategory" = '${Object.keys(ACCOUNT_TYPES)[i]}'`,
{ transaction }
);
}

await queryInterface.changeColumn(
'Accounts',
'accountTypeId',
{
type: Sequelize.INTEGER,
allowNull: false,
references: {
model: 'AccountTypes',
key: 'id',
},
onUpdate: 'CASCADE',
onDelete: 'SET NULL',
},
{ transaction },
);

// Remove the ENUM accountTypeId column
await queryInterface.removeColumn('Accounts', 'accountCategory', { transaction });

// Drop the ENUM type
await queryInterface.sequelize.query('DROP TYPE "enum_Accounts_accountCategory"', { transaction });

await transaction.commit();
} catch (err) {
await transaction.rollback();
throw err;
}
},
};
19 changes: 10 additions & 9 deletions src/models/Accounts.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,10 @@ import {
HasMany,
} from 'sequelize-typescript';
import { Op } from 'sequelize';
import { ACCOUNT_TYPES } from 'shared-types';
import { ACCOUNT_CATEGORIES, ACCOUNT_TYPES } from 'shared-types';
import { GenericSequelizeModelAttributes } from '@common/types';
import Users from '@models/Users.model';
import Currencies from '@models/Currencies.model';
import AccountTypes from '@models/AccountTypes.model';
import Balances from '@models/Balances.model';
import Transactions from '@models/Transactions.model';

Expand All @@ -28,8 +27,7 @@ export interface AccountsAttributes {
creditLimit: number;
refCreditLimit: number;
type: ACCOUNT_TYPES;
// general, creditCard, etc. TODO: delete it
accountTypeId: number;
accountCategory: ACCOUNT_CATEGORIES;
currencyId: number;
userId: number;
externalId: string; // represents id from the original external system if exists
Expand Down Expand Up @@ -112,9 +110,12 @@ export default class Accounts extends Model<AccountsAttributes> {
})
type: ACCOUNT_TYPES;

@ForeignKey(() => AccountTypes)
@Column
accountTypeId: number;
@Column({
allowNull: false,
defaultValue: ACCOUNT_CATEGORIES.general,
type: DataType.ENUM({ values: Object.values(ACCOUNT_CATEGORIES) }),
})
accountCategory: ACCOUNT_CATEGORIES;

@ForeignKey(() => Currencies)
@Column
Expand Down Expand Up @@ -229,7 +230,7 @@ export interface CreateAccountPayload {
externalId?: AccountsAttributes['externalId'];
externalData?: AccountsAttributes['externalData'];
isEnabled?: AccountsAttributes['isEnabled'];
accountTypeId: AccountsAttributes['accountTypeId'];
accountCategory: AccountsAttributes['accountCategory'];
currencyId: AccountsAttributes['currencyId'];
name: AccountsAttributes['name'];
initialBalance: AccountsAttributes['initialBalance'];
Expand Down Expand Up @@ -276,7 +277,7 @@ export interface UpdateAccountByIdPayload {
id: AccountsAttributes['id'];
userId: AccountsAttributes['userId'];
externalId?: AccountsAttributes['externalId'];
accountTypeId?: AccountsAttributes['accountTypeId'];
accountCategory?: AccountsAttributes['accountCategory'];
// currency updating is disabled
// currencyId?: AccountsAttributes['currencyId'];
name?: AccountsAttributes['name'];
Expand Down
3 changes: 2 additions & 1 deletion src/services/accounts.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
ExternalMonobankClientInfoResponse,
MonobankUserModel,
ACCOUNT_TYPES,
ACCOUNT_CATEGORIES,
} from 'shared-types';
import { Transaction } from 'sequelize/types';
import * as Accounts from '@models/Accounts.model';
Expand Down Expand Up @@ -79,7 +80,7 @@ export const createSystemAccountsFromMonobankAccounts = async (
{
userId,
currencyId: accountCurrencyCodes[account.currencyCode],
accountTypeId: 4,
accountCategory: ACCOUNT_CATEGORIES.creditCard,
name: account.maskedPan[0] || account.iban,
externalId: account.id,
initialBalance: account.balance,
Expand Down
3 changes: 2 additions & 1 deletion src/tests/helpers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
TRANSACTION_TRANSFER_NATURE,
TransactionModel,
PAYMENT_TYPES,
ACCOUNT_CATEGORIES,
} from 'shared-types';
import { app } from '@root/app';
import Accounts from '@models/Accounts.model';
Expand Down Expand Up @@ -76,7 +77,7 @@ export const randomDate = (
export const buildAccountPayload = (
overrides: Partial<endpointsTypes.CreateAccountBody> = {},
): endpointsTypes.CreateAccountBody => ({
accountTypeId: 1,
accountCategory: ACCOUNT_CATEGORIES.general,
currencyId: global.BASE_CURRENCY.id,
name: 'test',
type: ACCOUNT_TYPES.system,
Expand Down

0 comments on commit a80f359

Please sign in to comment.