Project Initialization

This commit is contained in:
Muluhabt
2026-05-12 15:17:16 +03:00
parent 33fa742e8a
commit 3b8b6979db
259 changed files with 15962 additions and 0 deletions

View File

@@ -0,0 +1,27 @@
import { Passenger } from '@edr/types';
import { IsDateString, IsEnum, IsNumber, IsOptional, IsString, IsUUID, Min } from 'class-validator';
export class CreateTicketDto {
@IsString()
reference!: string;
@IsUUID()
passengerId!: string;
@IsUUID()
scheduleId!: string;
@IsUUID()
seatId!: string;
@IsNumber()
@Min(0)
pricePaid!: number;
@IsDateString()
issuedAt!: string;
@IsOptional()
@IsEnum(Passenger.TicketStatus)
status?: Passenger.TicketStatus;
}

View File

@@ -0,0 +1,29 @@
import { Passenger } from '@edr/types';
import { Type } from 'class-transformer';
import { IsEnum, IsInt, IsOptional, IsUUID, Min } from 'class-validator';
export class FilterTicketDto {
@IsOptional()
@IsEnum(Passenger.TicketStatus)
status?: Passenger.TicketStatus;
@IsOptional()
@IsUUID()
passengerId?: string;
@IsOptional()
@IsUUID()
scheduleId?: string;
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(1)
page?: number = 1;
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(1)
pageSize?: number = 20;
}

View File

@@ -0,0 +1,32 @@
import { BaseEntity } from '@edr/api-common';
import { Passenger } from '@edr/types';
import { Column, Entity } from 'typeorm';
@Entity({ name: 'tickets' })
export class Ticket extends BaseEntity {
@Column({ name: 'reference', type: 'varchar', length: 64, unique: true })
reference!: string;
@Column({ name: 'passenger_id', type: 'uuid' })
passengerId!: string;
@Column({ name: 'schedule_id', type: 'uuid' })
scheduleId!: string;
@Column({ name: 'seat_id', type: 'uuid' })
seatId!: string;
@Column({
name: 'status',
type: 'enum',
enum: Passenger.TicketStatus,
default: Passenger.TicketStatus.Reserved,
})
status!: Passenger.TicketStatus;
@Column({ name: 'price_paid', type: 'numeric', precision: 10, scale: 2 })
pricePaid!: number;
@Column({ name: 'issued_at', type: 'timestamptz' })
issuedAt!: Date;
}

View File

@@ -0,0 +1,38 @@
import { Body, Controller, Delete, Get, HttpCode, Param, ParseUUIDPipe, Post, Query } from '@nestjs/common';
import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { CreateTicketDto } from './dto/create-ticket.dto';
import { FilterTicketDto } from './dto/filter-ticket.dto';
import { TicketsService } from './tickets.service';
@ApiTags('tickets')
// @UseGuards(JwtAuthGuard) — TODO: integrate @edr/auth
@Controller('tickets')
export class TicketsController {
constructor(private readonly ticketsService: TicketsService) {}
@Post()
@ApiOperation({ summary: 'Issue a new passenger ticket' })
create(@Body() dto: CreateTicketDto) {
return this.ticketsService.create(dto);
}
@Get()
@ApiOperation({ summary: 'List passenger tickets (paginated)' })
findAll(@Query() filter: FilterTicketDto) {
return this.ticketsService.findAll(filter);
}
@Get(':id')
@ApiOperation({ summary: 'Get a ticket by ID' })
findOne(@Param('id', ParseUUIDPipe) id: string) {
return this.ticketsService.findById(id);
}
@Delete(':id')
@HttpCode(204)
@ApiOperation({ summary: 'Cancel a ticket' })
cancel(@Param('id', ParseUUIDPipe) id: string) {
return this.ticketsService.cancel(id);
}
}

View File

@@ -0,0 +1,15 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Ticket } from './entities/ticket.entity';
import { TicketsController } from './tickets.controller';
import { TicketsRepository } from './tickets.repository';
import { TicketsService } from './tickets.service';
@Module({
imports: [TypeOrmModule.forFeature([Ticket])],
controllers: [TicketsController],
providers: [TicketsService, TicketsRepository],
exports: [TicketsService],
})
export class TicketsModule {}

View File

@@ -0,0 +1,21 @@
import { BaseRepository } from '@edr/api-common';
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Ticket } from './entities/ticket.entity';
@Injectable()
export class TicketsRepository extends BaseRepository<Ticket> {
constructor(
@InjectRepository(Ticket)
repository: Repository<Ticket>,
) {
super(repository);
}
/** Find a ticket by its passenger-facing reference. */
findByReference(reference: string): Promise<Ticket | null> {
return this.repository.findOne({ where: { reference } });
}
}

View File

@@ -0,0 +1,51 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { CreateTicketDto } from './dto/create-ticket.dto';
import { FilterTicketDto } from './dto/filter-ticket.dto';
import { Ticket } from './entities/ticket.entity';
import { TicketsRepository } from './tickets.repository';
@Injectable()
export class TicketsService {
constructor(private readonly ticketsRepository: TicketsRepository) {}
/** Issue a new passenger ticket. */
create(dto: CreateTicketDto): Promise<Ticket> {
return this.ticketsRepository.create({
...dto,
issuedAt: new Date(dto.issuedAt),
});
}
/** Paginated list of tickets matching the filter. */
async findAll(filter: FilterTicketDto): Promise<{ items: Ticket[]; total: number }> {
const page = filter.page ?? 1;
const pageSize = filter.pageSize ?? 20;
const [items, total] = await this.ticketsRepository.findAndCount({
where: {
...(filter.status ? { status: filter.status } : {}),
...(filter.passengerId ? { passengerId: filter.passengerId } : {}),
...(filter.scheduleId ? { scheduleId: filter.scheduleId } : {}),
},
skip: (page - 1) * pageSize,
take: pageSize,
order: { createdAt: 'DESC' },
});
return { items, total };
}
/** Get a single ticket by ID. */
async findById(id: string): Promise<Ticket> {
const ticket = await this.ticketsRepository.findById(id);
if (!ticket) {
throw new NotFoundException(`Ticket ${id} not found`);
}
return ticket;
}
/** Cancel and soft-delete a ticket. */
async cancel(id: string): Promise<void> {
await this.findById(id);
await this.ticketsRepository.softDelete(id);
}
}