The NTD Calculator Record service is a backend application built with NestJS, designed to manage balance and record operations for users. It includes token validation from an external authentication service and provides endpoints for CRUD operations and pagination.
- Node.js (version 16 or later)
- Docker
- NestJS CLI
-
Clone the repository:
git clone https://github.com/PullStackDeveloper/ntd_calculator-records.git cd ntd-calculator-record -
Install dependencies:
yarn install
-
Configure environment variables: Create a
.envfile in the root directory with the following content:DB_HOST=localhost DB_PORT=3306 DB_USERNAME=ntd_user DB_PASSWORD=set-your-password DB_NAME=ntd_calculator AUTH_API_URL=http://localhost:4000/api/v1/auth/validate-token -
Run MySQL using Docker: Pull the Docker image:
docker pull fernando0988/ntd_calculator_image:latest
Run the Docker container:
docker run --name ntd_calculator-container -e MYSQL_ROOT_PASSWORD=set-your-password -e MYSQL_DATABASE=ntd_calculator -e MYSQL_USER=ntd_user -e MYSQL_PASSWORD=set-your-password -p 3306:3306 -d fernando0988/ntd_calculator_image:latest
-
Run the application:
yarn start:dev
srcbalancedtoupdate-balance.dto.ts: Data Transfer Object for updating balance.
balance.controller.ts: Controller handling balance-related routes.balance.entity.ts: Entity representing the balance table.balance.module.ts: Module encapsulating balance-related components.balance.service.ts: Service handling business logic for balance operations.
commoninterceptorsauth.interceptor.ts: Interceptor for token validation.
recorddtocreate-record.dto.ts: Data Transfer Object for creating records.
interfacepagination.interface.ts: Interface for pagination options.
record.controller.ts: Controller handling record-related routes.record.entity.ts: Entity representing the record table.record.module.ts: Module encapsulating record-related components.record.service.ts: Service handling business logic for record operations.
app.controller.ts: Main application controller.app.module.ts: Main application module.main.ts: Entry point of the application.
The application uses an AuthInterceptor to validate tokens provided in the Authorization header of incoming requests. The token is validated against an external authentication service specified by the AUTH_API_URL environment variable.
- getBalance: Retrieves the balance of the authenticated user.
- updateBalance: Updates the balance of the authenticated user.
@Controller('balance')
export class BalanceController {
constructor(private readonly balanceService: BalanceService) {}
@Get()
@Version('1')
async getBalance(@Req() req: Request) {
return this.balanceService.getBalance(req['user'].id);
}
@Patch()
@Version('1')
async updateBalance(
@Req() req: Request,
@Body() updateBalanceDto: UpdateBalanceDto,
) {
return this.balanceService.updateBalance(
req['user'].id,
updateBalanceDto.amount,
);
}
}- getBalance: Retrieves the balance for a given user ID.
- updateBalance: Updates the balance for a given user ID.
@Injectable()
export class BalanceService {
constructor(
@InjectRepository(Balance)
private balanceRepository: Repository<Balance>,
) {}
async getBalance(userId: number): Promise<Balance> {
return this.balanceRepository.findOne({ where: { userId } });
}
async updateBalance(userId: number, amount: number): Promise<Balance> {
const balance = await this.getBalance(userId);
balance.amount = amount;
return this.balanceRepository.save(balance);
}
}- create: Creates a new record.
- findOne: Retrieves a specific record by its ID.
- findAll: Retrieves all records for the authenticated user with pagination.
- remove: Marks a record as deleted.
@Controller('records')
export class RecordController {
constructor(private readonly recordService: RecordService) {}
@Post()
@Version('1')
async create(
@Body() createRecordDto: CreateRecordDto,
@Req() req: Request,
): Promise<Record> {
return this.recordService.create(createRecordDto);
}
@Get(':id')
@Version('1')
async findOne(@Param('id') id: number, @Req() req: Request): Promise<Record> {
return this.recordService.findOne(id, req['user'].id);
}
@Get()
@Version('1')
async findAll(
@Req() req: Request,
@Query('page') page: number = 1,
@Query('limit') limit: number = 10,
): Promise<{ data: Record[]; count: number }> {
return this.recordService.findAll(req['user'].id, { page, limit });
}
@Delete(':id')
@Version('1')
async remove(@Param('id') id: number, @Req() req: Request): Promise<void> {
return this.recordService.remove(id, req['user'].id);
}
}- create: Creates and saves a new record.
- findOne: Retrieves a specific record by its ID and user ID.
- findAll: Retrieves all records for a user with pagination options.
- remove: Marks a record as deleted.
@Injectable()
export class RecordService {
constructor(
@InjectRepository(Record)
private recordRepository: Repository<Record>,
) {}
async create(createRecordDto: CreateRecordDto): Promise<Record> {
const record = this.recordRepository.create(createRecordDto);
return this.recordRepository.save(record);
}
async findOne(id: number, userId: number): Promise<Record> {
return this.recordRepository.findOne({
where: { id, userId, isDeleted: false },
});
}
async findAll(
userId: number,
options: PaginationOptions,
): Promise<{ data: Record[]; count: number }> {
const { page, limit } = options;
const [data, count] = await this.recordRepository.findAndCount({
where: { userId, isDeleted: false },
skip: (page - 1) * limit,
take: limit,
order: { date: 'DESC' },
});
return { data, count };
}
async remove(id: number, userId: number): Promise<void> {
const record = await this.recordRepository.findOne({
where: { id, userId, isDeleted: false },
});
if (record) {
await this.recordRepository.update(id, { isDeleted: true });
}
}
}Defines the structure of data required to update a balance.
export class UpdateBalanceDto {
@IsNumber()
@IsNotEmpty()
amount: number;
}Defines the structure of data required to create a record.
export class CreateRecordDto {
@IsString()
@IsNotEmpty()
operationType: string;
@IsNumber()
@IsNotEmpty()
amount: number;
@IsNumber()
@IsNotEmpty()
userBalance: number;
@IsString()
@IsNotEmpty()
operationResponse: string;
@IsNumber()
@IsNotEmpty()
userId: number;
}The PaginationOptions interface defines the structure for pagination parameters.
export interface PaginationOptions {
page: number;
limit: number;
}The RecordService uses these options to paginate the results in the findAll method.
Entities represent the database tables. They are defined using TypeORM decorators.
Represents the balance table.
@Entity()
@Unique(['userId'])
export class Balance {
@PrimaryGeneratedColumn()
id: number;
@Column('double')
amount: number;
@Column()
userId: number;
}Represents the record table.
@Entity()
export class Record {
@PrimaryGeneratedColumn()
id: number;
@Column()
operationType: string;
@Column('decimal')
amount: number;
@Column('decimal')
userBalance: number;
@Column()
operationResponse: string;
@CreateDateColumn()
date: Date;
@Column()
userId: number;
@Column({ default: false })
isDeleted: boolean;
}