Skip to content

Commit

Permalink
add modules and link with nest
Browse files Browse the repository at this point in the history
  • Loading branch information
mirexdoors committed Jun 9, 2021
1 parent 650ee5b commit 7395a67
Show file tree
Hide file tree
Showing 17 changed files with 1,091 additions and 39 deletions.
Binary file added data/data.sqlite
Binary file not shown.
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,14 @@
"@nestjs/common": "^7.6.15",
"@nestjs/core": "^7.6.15",
"@nestjs/platform-express": "^7.6.15",
"@nestjs/typeorm": "^7.1.5",
"bignumber.js": "^9.0.1",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^6.6.6",
"ts-mockito": "^2.6.1"
"sqlite3": "^5.0.2",
"ts-mockito": "^2.6.1",
"typeorm": "^0.2.34"
},
"devDependencies": {
"@nestjs/cli": "^7.6.0",
Expand Down
16 changes: 15 additions & 1 deletion src/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { join } from 'path';

import { AccountPersistenceModule } from './modules/account-persistence/account-persistence.module';
import { AccountWebModule } from './modules/account-web/account-web.module';

@Module({
imports: [],
imports: [
TypeOrmModule.forRoot({
type: 'sqlite',
database: join(__dirname, '..', 'data', 'data.sqlite'),
logging: true,
autoLoadEntities: true,
}),
AccountPersistenceModule,
AccountWebModule,
],
})
export class AppModule {}
2 changes: 2 additions & 0 deletions src/domains/entities/account.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export class AccountEntity {
}

const withdrawal: ActivityEntity = new ActivityEntity(
this.id,
this.id,
targetAccountId,
new Date(),
Expand All @@ -48,6 +49,7 @@ export class AccountEntity {

public deposite(money: MoneyEntity, sourceAccountId: AccountId): boolean {
const deposite: ActivityEntity = new ActivityEntity(
this.id,
sourceAccountId,
this.id,
new Date(),
Expand Down
12 changes: 12 additions & 0 deletions src/domains/entities/activity.entity.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,26 @@
import { AccountId } from './account.entity';
import { MoneyEntity } from './money.entity';

export type ActivityId = number | null;

export class ActivityEntity {
constructor(
private readonly _ownerAccountId: AccountId,
private readonly _sourceAccountId: AccountId,
private readonly _targetAccountId: AccountId,
private readonly _timestamp: Date,
private readonly _money: MoneyEntity,
private readonly _id?: ActivityId,
) {}

get ownerAccountId(): string {
return this._ownerAccountId;
}

get id(): number | null {
return this._id === undefined ? null : this._id;
}

get sourceAccountId(): string {
return this._sourceAccountId;
}
Expand Down
4 changes: 3 additions & 1 deletion src/domains/ports/in/send-money.use-case.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { SendMoneyCommand } from './send-money.command';

export const SendMoneyUseCaseSymbol = Symbol('SendMoneyUseCase');

export interface SendMoneyUseCase {
sendMoney(command: SendMoneyCommand);
sendMoney(command: SendMoneyCommand): Promise<boolean>;
}
2 changes: 1 addition & 1 deletion src/domains/ports/out/load-account.port.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AccountEntity, AccountId } from '../../entities/account.entity';

export interface LoadAccountPort {
loadAccount(accountId: AccountId): AccountEntity;
loadAccount(accountId: AccountId): Promise<AccountEntity>;
}
5 changes: 3 additions & 2 deletions src/domains/servicies/get-account-balance.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { LoadAccountPort } from '../ports/out/load-account.port';
export class GetAccountBalanceService implements GetAccountBalanceQuery {
constructor(private readonly _loadAccountPort: LoadAccountPort) {}

getAccountBalance(accountId: AccountId) {
this._loadAccountPort.loadAccount(accountId).calculateBalance();
async getAccountBalance(accountId: AccountId) {
const account = await this._loadAccountPort.loadAccount(accountId);
return account.calculateBalance();
}
}
12 changes: 5 additions & 7 deletions src/domains/servicies/send-money.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,12 @@ export class SendMoneyService implements SendMoneyUseCase {
private readonly _updateAccountStatePort: UpdateAccountStatePort,
) {}

sendMoney(command: SendMoneyCommand) {
const sourceAccount: AccountEntity = this._loadAccountPort.loadAccount(
command.sourceAccountId,
);
async sendMoney(command: SendMoneyCommand) {
const sourceAccount: AccountEntity =
await this._loadAccountPort.loadAccount(command.sourceAccountId);

const targetAccount: AccountEntity = this._loadAccountPort.loadAccount(
command.targetAccountId,
);
const targetAccount: AccountEntity =
await this._loadAccountPort.loadAccount(command.targetAccountId);

if (!sourceAccount.withdraw(command.money, targetAccount.id)) {
return false;
Expand Down
51 changes: 51 additions & 0 deletions src/modules/account-persistence/account-persistence.adapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Injectable } from '@nestjs/common';
import { LoadAccountPort } from '../../domains/ports/out/load-account.port';
import { UpdateAccountStatePort } from '../../domains/ports/out/update-account-state.port';
import {
AccountEntity,
AccountId,
} from '../../domains/entities/account.entity';
import { InjectRepository } from '@nestjs/typeorm';
import { AccountOrmEntity } from './account.orm-entity';
import { Repository } from 'typeorm';
import { ActivityOrmEntity } from './activity.orm-entity';
import { AccountMapper } from './account.mapper';

@Injectable()
export class AccountPersistenceAdapter
implements LoadAccountPort, UpdateAccountStatePort
{
constructor(
@InjectRepository(AccountOrmEntity)
private readonly _accountRepository: Repository<AccountOrmEntity>,

@InjectRepository(ActivityOrmEntity)
private readonly _activityRepository: Repository<ActivityOrmEntity>,
) {}

async loadAccount(accountId: AccountId): Promise<AccountEntity> {
const account = await this._accountRepository.findOne({
userId: accountId,
});

if (account === undefined) {
throw new Error('Account not found');
}

const activities = await this._activityRepository.find({
ownerAccountId: accountId,
});

return AccountMapper.mapToDomain(account, activities);
}

updateActivities(account: AccountEntity) {
account.activityWindow.activities.forEach((activity) => {
if (activity.id === null) {
this._activityRepository.save(
AccountMapper.mapToActivityOrmEntity(activity),
);
}
});
}
}
27 changes: 27 additions & 0 deletions src/modules/account-persistence/account-persistence.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Global, Module } from '@nestjs/common';
import { AccountPersistenceAdapter } from './account-persistence.adapter';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AccountOrmEntity } from './account.orm-entity';
import { ActivityOrmEntity } from './activity.orm-entity';
import { SendMoneyUseCaseSymbol } from '../../domains/ports/in/send-money.use-case';
import { SendMoneyService } from '../../domains/servicies/send-money.service';

@Global()
@Module({
imports: [TypeOrmModule.forFeature([AccountOrmEntity, ActivityOrmEntity])],
providers: [
AccountPersistenceAdapter,
{
provide: SendMoneyUseCaseSymbol,
useFactory: (AccountPersistenceAdapter) => {
return new SendMoneyService(
AccountPersistenceAdapter,
AccountPersistenceAdapter,
);
},
inject: [AccountPersistenceAdapter],
},
],
exports: [SendMoneyUseCaseSymbol],
})
export class AccountPersistenceModule {}
51 changes: 51 additions & 0 deletions src/modules/account-persistence/account.mapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { AccountOrmEntity } from './account.orm-entity';
import { ActivityOrmEntity } from './activity.orm-entity';
import { AccountEntity } from '../../domains/entities/account.entity';
import { ActivityWindowEntity } from '../../domains/entities/activity-window.entity';
import { ActivityEntity } from '../../domains/entities/activity.entity';
import { MoneyEntity } from '../../domains/entities/money.entity';

export class AccountMapper {
static mapToDomain(
account: AccountOrmEntity,
activities: ActivityOrmEntity[],
) {
const activityWindow = this.mapToActivityWindow(activities);
const balance = activityWindow.calculateBalance(account.userId);

return new AccountEntity(account.userId, balance, activityWindow);
}

static mapToActivityWindow(
activities: ActivityOrmEntity[],
): ActivityWindowEntity {
const activityWindowEntity = new ActivityWindowEntity();

activities.forEach((activity) => {
const activityEntity = new ActivityEntity(
activity.ownerAccountId,
activity.sourceAccountId,
activity.targetAccountId,
new Date(activity.timestamp),
MoneyEntity.of(activity.amount),
activity.id,
);
activityWindowEntity.addActivity(activityEntity);
});
return activityWindowEntity;
}

static mapToActivityOrmEntity(activity: ActivityEntity): ActivityOrmEntity {
const activityOrmEntity = new ActivityOrmEntity();

activityOrmEntity.timestamp = activity.timestamp.getTime();
activityOrmEntity.ownerAccountId = activity.ownerAccountId;
activityOrmEntity.sourceAccountId = activity.sourceAccountId;
activityOrmEntity.targetAccountId = activity.targetAccountId;
activityOrmEntity.amount = activity.money.amount.toNumber();
if (activity.id !== null) {
activityOrmEntity.id = activity.id;
}
return activityOrmEntity;
}
}
9 changes: 9 additions & 0 deletions src/modules/account-persistence/account.orm-entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Column, PrimaryGeneratedColumn, Entity } from 'typeorm';

@Entity('Account', {})
export class AccountOrmEntity {
@PrimaryGeneratedColumn()
id: number;
@Column()
userId: string;
}
22 changes: 22 additions & 0 deletions src/modules/account-persistence/activity.orm-entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';

@Entity('Activity', {})
export class ActivityOrmEntity {
@PrimaryGeneratedColumn()
id: number;

@Column()
timestamp: number;

@Column()
ownerAccountId: string;

@Column()
sourceAccountId: string;

@Column()
targetAccountId: string;

@Column()
amount: number;
}
7 changes: 7 additions & 0 deletions src/modules/account-web/account-web.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Module } from '@nestjs/common';
import { SendMoneyController } from './send-money.controller';

@Module({
controllers: [SendMoneyController],
})
export class AccountWebModule {}
31 changes: 31 additions & 0 deletions src/modules/account-web/send-money.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Controller, Get, Inject, Query } from '@nestjs/common';
import {
SendMoneyUseCase,
SendMoneyUseCaseSymbol,
} from '../../domains/ports/in/send-money.use-case';
import { SendMoneyCommand } from '../../domains/ports/in/send-money.command';
import { MoneyEntity } from '../../domains/entities/money.entity';

@Controller('/account/send')
export class SendMoneyController {
constructor(
@Inject(SendMoneyUseCaseSymbol)
private readonly _sendMoneyUseCase: SendMoneyUseCase,
) {}

@Get('/')
async sendMoney(
@Query('sourceAccountId') sourceAccountId: string,
@Query('targetAccountId') targetAccountId: string,
@Query('amount') amount: number,
) {
const command = new SendMoneyCommand(
sourceAccountId,
targetAccountId,
MoneyEntity.of(amount),
);
const result = await this._sendMoneyUseCase.sendMoney(command);

return { result };
}
}
Loading

0 comments on commit 7395a67

Please sign in to comment.