Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Improve types #70

Merged
merged 6 commits into from
Jul 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions shared-types/routes/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { UserModel } from 'shared-types';
import { BodyPayload } from './index'

export interface AuthLoginBody extends BodyPayload {
username: string;
password: string;
}
// Bearer token
export interface AuthLoginResponse { token: string }

export interface AuthRegisterBody extends BodyPayload {
username: string;
password: string;
}
export interface AuthRegisterResponse { user: UserModel }
9 changes: 7 additions & 2 deletions shared-types/routes/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
export * from './monobank';
export * from './auth';

export type BodyPayload<T> = T
export type QueryPayload<T> = T
export type BodyPayload = {
[key: string | number]: string | number | boolean;
}
export type QueryPayload = {
[key: string]: string;
}
66 changes: 53 additions & 13 deletions shared-types/routes/monobank.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,59 @@
import { MonobankUserModel } from 'shared-types';
import { BodyPayload } from './index'
import { MonobankTrasnactionModel, MonobankUserModel, MonobankAccountModel } from 'shared-types';
import { BodyPayload, QueryPayload } from './index'

export interface UpdateMonobankTransactionBody extends BodyPayload<{
export interface UpdateMonobankTransactionBody extends BodyPayload {
id: number;
categoryId?: number;
note?: string;
}> {}
}

export interface PairMonobankAccountBody extends BodyPayload<{
export interface PairMonobankAccountBody extends BodyPayload {
token: MonobankUserModel['apiToken']
}> {}

export interface UpdateMonobankUserBody extends BodyPayload<{
apiToken?: string;
name?: string;
webHookUrl?: string;
clientId?: string;
}> {}
}

export interface UpdateMonobankUserBody extends BodyPayload {
apiToken?: MonobankUserModel['apiToken'];
name?: MonobankUserModel['name'];
webHookUrl?: MonobankUserModel['webHookUrl'];
clientId?: MonobankUserModel['clientId'];
}

export interface UpdateMonobankAccountByIdBody extends BodyPayload {
accountId: string;
name?: MonobankAccountModel['name'];
isEnabled?: boolean;
// We store array of pans as a string
maskedPan?: string;
type?: MonobankAccountModel['type'];
iban?: MonobankAccountModel['iban'];
balance?: MonobankAccountModel['balance'];
creditLimit?: MonobankAccountModel['creditLimit'];
currencyId?: number;
}

export type GetMonobankAccountsResponse = MonobankAccountModel[]

export interface LoadMonoTransactionsQuery extends QueryPayload {
from: string;
to: string;
accountId: string;
}
export interface LoadMonoTransactionsResponse {
minutesToFinish: number;
}

export interface GetMonobankTransactionsQuery extends QueryPayload {
sort?: 'asc' | 'desc';
includeUser?: string;
includeAccount?: string;
includeCategory?: string;
includeAll?: string;
nestedInclude?: string;
from?: string;
limit?: string;
}
export type GetMonobankTransactionsResponse = MonobankTrasnactionModel[]

export interface UpdateWebhookBody extends BodyPayload {
clientId: string;
}
5 changes: 5 additions & 0 deletions src/common/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const getQueryBooleanValue = (value: string): boolean => {
if (value === 'true') return true
if (value === 'false') return false
return Boolean(value)
}
15 changes: 9 additions & 6 deletions src/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@ import type { Response } from 'express';
import { Transaction } from 'sequelize/types';
import { API_RESPONSE_STATUS } from 'shared-types';

// eslint-disable-next-line @typescript-eslint/no-explicit-any
type Send<ResBody = any, T = Response<ResBody>> = (body?: ResBody) => T;
export interface CustomResponse extends Response {
json: Send<{
// Enforce res.json(object) to always have `status` field and optional `response`
// with ability to pass `response` type using res.json<Type>()
type Send<T = Response> = {
<ResBody>(body: {
response?: ResBody,
status: API_RESPONSE_STATUS,
response?: unknown,
}, this>
}): T;
};
export interface CustomResponse extends Response {
json: Send<this>;
}


Expand Down
10 changes: 5 additions & 5 deletions src/controllers/auth.controller.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { API_RESPONSE_STATUS } from 'shared-types';
import { API_RESPONSE_STATUS, endpointsTypes } from 'shared-types';
import { CustomResponse } from '@common/types';
import { errorHandler } from './helpers';

import * as authService from '@services/auth.service';

export const login = async (req, res: CustomResponse) => {
const { username, password } = req.body;
const { username, password }: endpointsTypes.AuthLoginBody = req.body;

try {
const token = await authService.login({ username, password })

return res.status(200).json({
return res.status(200).json<endpointsTypes.AuthLoginResponse>({
status: API_RESPONSE_STATUS.success,
response: token,
});
Expand All @@ -20,12 +20,12 @@ export const login = async (req, res: CustomResponse) => {
};

export const register = async (req, res: CustomResponse) => {
const { username, password } = req.body;
const { username, password }: endpointsTypes.AuthRegisterBody = req.body;

try {
const user = await authService.register({ username, password })

return res.status(201).json({
return res.status(201).json<endpointsTypes.AuthRegisterResponse>({
status: API_RESPONSE_STATUS.success,
response: { user },
});
Expand Down
58 changes: 33 additions & 25 deletions src/controllers/banks/monobank.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
ExternalMonobankTransactionResponse,
} from 'shared-types';
import { CustomResponse } from '@common/types';
import { getQueryBooleanValue } from '@common/helpers';

import * as monobankAccountsService from '@services/banks/monobank/accounts';
import * as monobankUsersService from '@services/banks/monobank/users';
Expand All @@ -42,24 +43,27 @@ const hostWebhooksCallback = config.get('hostWebhooksCallback');
const apiPrefix = config.get('apiPrefix');
const hostname = config.get('bankIntegrations.monobank.apiEndpoint');

function dateRange({ from, to }) {
function dateRange(
{ from, to }: { from: number; to: number; }
): { start: number; end: number }[] {
const difference = differenceInCalendarMonths(
new Date(Number(to)),
new Date(Number(from)),
new Date(to),
new Date(from),
);
const dates = [];

// eslint-disable-next-line no-plusplus
for (let i = 0; i <= difference; i++) {
const start = startOfMonth(addMonths(new Date(Number(from)), i));
const end = endOfMonth(addMonths(new Date(Number(from)), i));
const start = startOfMonth(addMonths(new Date(from), i));
const end = endOfMonth(addMonths(new Date(from), i));

dates.push({
start: Number((new Date(start).getTime() / 1000).toFixed(0)),
end: Number((new Date(end).getTime() / 1000).toFixed(0)),
});
}

console.log('dates', dates)

return dates;
}

Expand Down Expand Up @@ -306,15 +310,15 @@ export const updateUser = async (req, res: CustomResponse) => {
export const getTransactions = async (req, res: CustomResponse) => {
const { id } = req.user;
const {
sort = SORT_DIRECTIONS.desc,
sort = SORT_DIRECTIONS.desc as 'desc',
includeUser,
includeAccount,
includeCategory,
includeAll,
nestedInclude,
from,
limit,
} = req.query;
}: endpointsTypes.GetMonobankTransactionsQuery = req.query;

if (!Object.values(SORT_DIRECTIONS).includes(sort)) {
return res.status(400).json({
Expand All @@ -330,16 +334,16 @@ export const getTransactions = async (req, res: CustomResponse) => {
const transactions = await monobankTransactionsService.getTransactions({
systemUserId: id,
sortDirection: sort,
includeUser,
includeAccount,
includeCategory,
includeAll,
nestedInclude,
from,
limit,
includeUser: getQueryBooleanValue(includeUser),
includeAccount: getQueryBooleanValue(includeAccount),
includeCategory: getQueryBooleanValue(includeCategory),
includeAll: getQueryBooleanValue(includeAll),
nestedInclude: getQueryBooleanValue(nestedInclude),
from: Number(from),
limit: Number(limit),
});

return res.status(200).json({
return res.status(200).json<endpointsTypes.GetMonobankTransactionsResponse>({
status: API_RESPONSE_STATUS.success,
response: transactions,
});
Expand Down Expand Up @@ -401,7 +405,7 @@ export const getAccounts = async (req, res) => {
});
}

const accounts = await monobankAccountsService.getAccountsByUserId({
const accounts: endpointsTypes.GetMonobankAccountsResponse = await monobankAccountsService.getAccountsByUserId({
monoUserId: monoUser.id,
});
return res.status(200).json({
Expand All @@ -420,18 +424,21 @@ export const getAccounts = async (req, res) => {
};

export const updateAccount = async (req, res: CustomResponse) => {
const { id } = req.user;

const {
accountId,
name,
isEnabled,
} = req.body;
}: endpointsTypes.UpdateMonobankAccountByIdBody = req.body;

try {
// TODO: check user is correct. Check account is exist
const account = await monobankAccountsService.updateById({
accountId,
name,
isEnabled,
monoUserId: id,
});
return res.status(200).json({
status: API_RESPONSE_STATUS.success,
Expand Down Expand Up @@ -542,7 +549,7 @@ export const monobankWebhook = async (req, res: CustomResponse) => {

export const updateWebhook = async (req, res: CustomResponse) => {
try {
const { clientId } = req.body;
const { clientId }: endpointsTypes.UpdateWebhookBody = req.body;
const { id } = req.user;

const token = `monobank-${id}-update-webhook`;
Expand Down Expand Up @@ -583,7 +590,7 @@ export const updateWebhook = async (req, res: CustomResponse) => {

export const loadTransactions = async (req, res: CustomResponse) => {
try {
const { from, to, accountId } = req.query;
const { from, to, accountId }: endpointsTypes.LoadMonoTransactionsQuery = req.query;
const { id: systemUserId } = req.user;

const redisToken = `monobank-${systemUserId}-load-transactions`;
Expand Down Expand Up @@ -655,7 +662,7 @@ export const loadTransactions = async (req, res: CustomResponse) => {
interval: 60000,
intervalCap: 1,
});
const months = dateRange({ from, to });
const months = dateRange({ from: Number(from), to: Number(to) });

usersQuery.set(`query-${systemUserId}`, queue);

Expand Down Expand Up @@ -709,7 +716,7 @@ export const loadTransactions = async (req, res: CustomResponse) => {
logger.info(`[Monobank controller]: One of load transactions task is completed. Size: ${queue.size} Pending: ${queue.pending}`);
});

return res.status(200).json({
return res.status(200).json<endpointsTypes.LoadMonoTransactionsResponse>({
status: API_RESPONSE_STATUS.success,
response: {
minutesToFinish: months.length - 1,
Expand Down Expand Up @@ -746,7 +753,7 @@ export const refreshAccounts = async (req, res) => {
const tempToken = await req.redisClient.get(token);

if (!tempToken) {
let clientInfo;
let clientInfo: ExternalMonobankClientInfoResponse;
try {
clientInfo = (await axios({
method: 'GET',
Expand Down Expand Up @@ -787,8 +794,9 @@ export const refreshAccounts = async (req, res) => {
await Promise.all(
clientInfo.accounts.map((item) => monobankAccountsService.updateById({
accountId: item.id,
currencyCode: item.currencyCode,
cashbackType: item.cashbackType,
// We need to pass currencyId based on currencyCode
// currencyCode: item.currencyCode,
// cashbackType: item.cashbackType,
balance: item.balance,
creditLimit: item.creditLimit,
maskedPan: JSON.stringify(item.maskedPan),
Expand Down
19 changes: 5 additions & 14 deletions src/models/banks/monobank/Accounts.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
ForeignKey,
Length,
} from 'sequelize-typescript';
import { endpointsTypes } from 'shared-types';
import { GenericSequelizeModelAttributes } from '@common/types';
import Currencies from '../../Currencies.model';
import MonobankUsers from './Users.model';
Expand Down Expand Up @@ -139,26 +140,16 @@ export const getAccountsById = async (
return account;
};

export interface MonoAccountUpdatePayload {
accountId: number;
name?: string;
isEnabled?: boolean;
currencyCode?: number;
cashbackType?: string;
balance?: number;
creditLimit?: number;
maskedPan?: string;
type?: string;
iban?: string;
monoUserId?: number;
export interface MonoAccountUpdatePayload extends endpointsTypes.UpdateMonobankAccountByIdBody {
monoUserId: number;
}
export const updateById = async (
{ accountId, monoUserId, ...toUpdate }: MonoAccountUpdatePayload,
attributes: GenericSequelizeModelAttributes = {},
) => {
const where: {
accountId: number;
monoUserId?: number;
accountId: MonoAccountUpdatePayload['accountId'];
monoUserId?: MonoAccountUpdatePayload['monoUserId'];
} = { accountId };

if (monoUserId) {
Expand Down
Loading