Skip to content
This repository has been archived by the owner on May 11, 2021. It is now read-only.

Commit

Permalink
feat(stats): add endpoint implementation for sources ans categories
Browse files Browse the repository at this point in the history
  • Loading branch information
igorkamyshev committed Mar 7, 2019
1 parent b14a934 commit 439d78d
Show file tree
Hide file tree
Showing 13 changed files with 149 additions and 28 deletions.
111 changes: 93 additions & 18 deletions back/src/money/application/Statistician.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
import { Injectable } from '@nestjs/common'
import { groupBy } from 'lodash'
import { Assign, Intersection } from 'utility-types'

import { CategoryGroupOutcomeModel } from '@shared/models/money/CategoryGroupOutcomeModel'
import { SourceGroupIncomeModel } from '@shared/models/money/SourceGroupIncomeModel'
import { createGroups } from '@back/utils/infrastructure/dateGroups/createGroups'
import { DateRange } from '@back/utils/infrastructure/dto/DateRange'
import { Currency } from '@shared/enum/Currency'
import { GroupBy } from '@shared/enum/GroupBy'

import { AbstractTransaction } from '../domain/dto/AbstarctTransaction'
import { Transaction } from '../domain/dto/Transaction'
import { IncomeRepository } from '../domain/IncomeRepository'
import { TransactionRepository } from '../domain/interfaces/TransactionRepository'
import { AbstractTransaction } from '../domain/interfaces/AbstarctTransaction'
import { OutcomeRepository } from '../domain/OutcomeRepository'
import { IncomeRepository } from '../domain/IncomeRepository'
import { CurrencyConverter } from './CurrencyConverter'
import { Transaction } from '../domain/dto/Transaction'
import { amountMapper } from './helpers/amountMapper'
import { rangeFilter } from './helpers/rangeFilter'
import { sumReducer } from './helpers/sumReducer'
import { SummedGroup } from './types/SummedGroup'

@Injectable()
export class Statistician {
Expand All @@ -25,21 +31,21 @@ export class Statistician {
public async showDateRangeStats(
userLogin: string,
dateRange: DateRange,
groupBy: GroupBy,
statsGroupBy: GroupBy,
currency: Currency,
) {
const [incomes, outcomes] = await Promise.all([
this.incomeRepo.findByRangeForUser(userLogin, dateRange),
this.outcomeRepo.findByRangeForUser(userLogin, dateRange),
])

const groups = createGroups(groupBy)(dateRange)

const [convertedIncomes, convertedOutcomes] = await Promise.all([
Promise.all(incomes.map(this.convertItem(currency))),
Promise.all(outcomes.map(this.convertItem(currency))),
this.convertItems(currency)(incomes),
this.convertItems(currency)(outcomes),
])

const groups = createGroups(statsGroupBy)(dateRange)

return groups.map(group => ({
currency,
start: group.from,
Expand All @@ -55,16 +61,85 @@ export class Statistician {
}))
}

private convertItem(targetCurrency: Currency) {
return async (item: AbstractTransaction): Promise<Transaction> => {
const newAmount = await this.converter.convert(
item.currency,
targetCurrency,
item.amount,
item.date,
)
public async showCategories(
userLogin: string,
dateRange: DateRange,
currency: Currency,
): Promise<CategoryGroupOutcomeModel[]> {
return this.showGrouped(
userLogin,
dateRange,
currency,
this.outcomeRepo,
'category',
'outcome',
)
}

return new Transaction(newAmount, targetCurrency, item.date)
}
public async showSources(
userLogin: string,
dateRange: DateRange,
currency: Currency,
): Promise<SourceGroupIncomeModel[]> {
return this.showGrouped(
userLogin,
dateRange,
currency,
this.incomeRepo,
'source',
'income',
)
}

private async showGrouped<T extends string, K extends string>(
userLogin: string,
dateRange: DateRange,
currency: Currency,
repo: TransactionRepository,
groupKey: T,
sumKey: K,
): Promise<SummedGroup<T, K>[]> {
const rawTransactions = await repo.findByRangeForUser(userLogin, dateRange)

const groups = Object.entries(groupBy(rawTransactions, groupKey)).map(
([key, transactions]) => ({
key,
transactions,
}),
)

const convertedGroups = await Promise.all(
groups.map(async ({ key, transactions }) => ({
key,
transactions: await this.convertItems(currency)(transactions),
})),
)

const summedGroups = convertedGroups.map(
({ key, transactions }) =>
({
[groupKey]: key,
[sumKey]: transactions.map(amountMapper).reduce(sumReducer, 0),
currency,
} as SummedGroup<T, K>),
)

return summedGroups
}

private convertItems(targetCurrency: Currency) {
return (items: AbstractTransaction[]): Promise<Transaction[]> =>
Promise.all(
items.map(async item => {
const newAmount = await this.converter.convert(
item.currency,
targetCurrency,
item.amount,
item.date,
)

return new Transaction(newAmount, targetCurrency, item.date)
}),
)
}
}
2 changes: 1 addition & 1 deletion back/src/money/application/helpers/amountMapper.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
import { AbstractTransaction } from '@back/money/domain/dto/AbstarctTransaction'
import { AbstractTransaction } from '@back/money/domain/interfaces/AbstarctTransaction'

export const amountMapper = (item: AbstractTransaction) => item.amount
2 changes: 1 addition & 1 deletion back/src/money/application/helpers/rangeFilter.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AbstractTransaction } from '@back/money/domain/dto/AbstarctTransaction'
import { AbstractTransaction } from '@back/money/domain/interfaces/AbstarctTransaction'
import { DateGroup } from '@back/utils/infrastructure/dateGroups/DateGroup'

export const rangeFilter = ({ from, to }: DateGroup) => ({
Expand Down
11 changes: 11 additions & 0 deletions back/src/money/application/types/SummedGroup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Intersection } from 'utility-types'

import { CategoryGroupOutcomeModel } from '@shared/models/money/CategoryGroupOutcomeModel'
import { SourceGroupIncomeModel } from '@shared/models/money/SourceGroupIncomeModel'

export type SummedGroup<T extends string, K extends string> = Intersection<
CategoryGroupOutcomeModel,
SourceGroupIncomeModel
> &
{ [key in T]: string } &
{ [key in K]: number }
2 changes: 1 addition & 1 deletion back/src/money/domain/Income.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Column, Entity, ManyToOne, PrimaryColumn } from 'typeorm'
import { Currency } from '@shared/enum/Currency'

import { User } from '@back/user/domain/User.entity'
import { AbstractTransaction } from './dto/AbstarctTransaction'
import { AbstractTransaction } from './interfaces/AbstarctTransaction'

@Entity()
export class Income implements AbstractTransaction {
Expand Down
3 changes: 2 additions & 1 deletion back/src/money/domain/IncomeRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import { Repository } from 'typeorm'
import { DateRange } from '@back/utils/infrastructure/dto/DateRange'

import { Income } from './Income.entity'
import { TransactionRepository } from './interfaces/TransactionRepository'

@Injectable()
class IncomeRepo {
class IncomeRepo implements TransactionRepository {
public constructor(
@InjectRepository(Income)
private readonly incomeRepo: Repository<Income>,
Expand Down
2 changes: 1 addition & 1 deletion back/src/money/domain/Outcome.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Column, Entity, ManyToOne, PrimaryColumn } from 'typeorm'
import { Currency } from '@shared/enum/Currency'

import { User } from '@back/user/domain/User.entity'
import { AbstractTransaction } from './dto/AbstarctTransaction'
import { AbstractTransaction } from './interfaces/AbstarctTransaction'

@Entity()
export class Outcome implements AbstractTransaction {
Expand Down
3 changes: 2 additions & 1 deletion back/src/money/domain/OutcomeRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import { Repository } from 'typeorm'
import { DateRange } from '@back/utils/infrastructure/dto/DateRange'

import { Outcome } from './Outcome.entity'
import { TransactionRepository } from './interfaces/TransactionRepository'

@Injectable()
class OutomeRepo {
class OutomeRepo implements TransactionRepository {
public constructor(
@InjectRepository(Outcome)
private readonly outcomeRepo: Repository<Outcome>,
Expand Down
2 changes: 1 addition & 1 deletion back/src/money/domain/dto/Transaction.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Currency } from '@shared/enum/Currency'

import { AbstractTransaction } from './AbstarctTransaction'
import { AbstractTransaction } from '../interfaces/AbstarctTransaction'

export class Transaction implements AbstractTransaction {
public constructor(
Expand Down
19 changes: 19 additions & 0 deletions back/src/money/domain/interfaces/TransactionRepository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Option } from 'tsoption'

import { DateRange } from '@back/utils/infrastructure/dto/DateRange'

import { AbstractTransaction } from './AbstarctTransaction'

export interface TransactionRepository {
findForUser(
id: string,
userLogin: string,
): Promise<Option<AbstractTransaction>>

findEarliest(userLogin: string): Promise<Option<AbstractTransaction>>

findByRangeForUser(
userLogin: string,
range: DateRange,
): Promise<AbstractTransaction[]>
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
import { reverse, sortBy } from 'lodash'

import { Historian } from '@back/money/application/Historian'
import { AbstractTransaction } from '@back/money/domain/dto/AbstarctTransaction'
import { AbstractTransaction } from '@back/money/domain/interfaces/AbstarctTransaction'
import { TokenPayload } from '@back/user/application/dto/TokenPayload'
import { CurrentUser } from '@back/user/presentation/http/decorator/CurrentUser'
import { OnlyForUsers } from '@back/user/presentation/http/security/OnlyForUsers'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,13 @@ export class StatisticsController {
@ApiQueryDateRange()
public async showIncomeSourcesStats(
@Query(ParseDateRangePipe) range: DateRange,
@Query('currency', createEnumValidationPipe(Currency))
currency: Currency = Currency.USD,
@CurrentUser() { login }: TokenPayload,
): Promise<SourceGroupIncomeResponse[]> {
throw Error('Not implemented')
const sources = await this.statistician.showSources(login, range, currency)

return sources
}

@Get('outcome-categories')
Expand All @@ -78,7 +83,16 @@ export class StatisticsController {
@ApiQueryDateRange()
public async showOutcomeCategoriesStats(
@Query(ParseDateRangePipe) range: DateRange,
@Query('currency', createEnumValidationPipe(Currency))
currency: Currency = Currency.USD,
@CurrentUser() { login }: TokenPayload,
): Promise<CategoryGroupOutcomeResponse[]> {
throw Error('Not implemented')
const categories = await this.statistician.showCategories(
login,
range,
currency,
)

return categories
}
}

0 comments on commit 439d78d

Please sign in to comment.