Skip to content
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
2 changes: 2 additions & 0 deletions backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { ScheduleModule } from '@nestjs/schedule';
import { ThrottlerModule } from '@nestjs/throttler';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { LocationsModule } from './locations/locations.module';

@Module({
imports: [
Expand Down Expand Up @@ -33,6 +34,7 @@ import { AppService } from './app.service';
}),
inject: [ConfigService],
}),
LocationsModule,
],
controllers: [AppController],
providers: [AppService],
Expand Down
24 changes: 24 additions & 0 deletions backend/src/locations/dto/create-location.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsEnum, IsNotEmpty, IsOptional, IsString } from 'class-validator';
import { LocationType } from '../location.entity';

export class CreateLocationDto {
@ApiProperty({ example: 'Lagos HQ' })
@IsString()
@IsNotEmpty()
name: string;

@ApiProperty({ enum: LocationType })
@IsEnum(LocationType)
type: LocationType;

@ApiProperty({ required: false, example: '1 Nelson Mandela St, Lagos' })
@IsOptional()
@IsString()
address?: string;

@ApiProperty({ required: false, example: 'Main office for the company' })
@IsOptional()
@IsString()
description?: string;
}
4 changes: 4 additions & 0 deletions backend/src/locations/dto/update-location.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { PartialType } from '@nestjs/mapped-types';
import { CreateLocationDto } from './create-location.dto';

export class UpdateLocationDto extends PartialType(CreateLocationDto) {}
38 changes: 38 additions & 0 deletions backend/src/locations/location.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
} from 'typeorm';

export enum LocationType {
OFFICE = 'office',
WAREHOUSE = 'warehouse',
BRANCH = 'branch',
REMOTE = 'remote',
}

@Entity('locations')
export class Location {
@PrimaryGeneratedColumn('uuid')
id: string;

@Column({ unique: true })
name: string;

@Column({ type: 'enum', enum: LocationType })
type: LocationType;

@Column({ nullable: true })
address?: string;

@Column({ nullable: true, type: 'text' })
description?: string;

@CreateDateColumn()
createdAt: Date;

@UpdateDateColumn()
updatedAt: Date;
}
56 changes: 56 additions & 0 deletions backend/src/locations/locations.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import {
Controller,
Get,
Post,
Patch,
Delete,
Param,
Body,
UseGuards,
HttpCode,
HttpStatus,
} from '@nestjs/common';
import { ApiTags, ApiOperation, ApiBearerAuth } from '@nestjs/swagger';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
import { LocationsService } from './locations.service';
import { CreateLocationDto } from './dto/create-location.dto';
import { UpdateLocationDto } from './dto/update-location.dto';

@ApiTags('Locations')
@ApiBearerAuth('JWT-auth')
@UseGuards(JwtAuthGuard)
@Controller('locations')
export class LocationsController {
constructor(private readonly locationsService: LocationsService) {}

@Get()
@ApiOperation({ summary: 'List all tracked locations' })
findAll() {
return this.locationsService.findAll();
}

@Get(':id')
@ApiOperation({ summary: 'Get a location by ID' })
findOne(@Param('id') id: string) {
return this.locationsService.findOne(id);
}

@Post()
@ApiOperation({ summary: 'Create a new location' })
create(@Body() dto: CreateLocationDto) {
return this.locationsService.create(dto);
}

@Patch(':id')
@ApiOperation({ summary: 'Update a location' })
update(@Param('id') id: string, @Body() dto: UpdateLocationDto) {
return this.locationsService.update(id, dto);
}

@Delete(':id')
@HttpCode(HttpStatus.NO_CONTENT)
@ApiOperation({ summary: 'Delete a location' })
remove(@Param('id') id: string) {
return this.locationsService.remove(id);
}
}
13 changes: 13 additions & 0 deletions backend/src/locations/locations.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Location } from './location.entity';
import { LocationsService } from './locations.service';
import { LocationsController } from './locations.controller';

@Module({
imports: [TypeOrmModule.forFeature([Location])],
providers: [LocationsService],
controllers: [LocationsController],
exports: [LocationsService],
})
export class LocationsModule {}
57 changes: 57 additions & 0 deletions backend/src/locations/locations.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import {
ConflictException,
Injectable,
NotFoundException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Location } from './location.entity';
import { CreateLocationDto } from './dto/create-location.dto';
import { UpdateLocationDto } from './dto/update-location.dto';

@Injectable()
export class LocationsService {
constructor(
@InjectRepository(Location)
private readonly repo: Repository<Location>,
) {}

findAll(): Promise<Location[]> {
return this.repo.find({ order: { name: 'ASC' } });
}

async findOne(id: string): Promise<Location> {
const location = await this.repo.findOne({ where: { id } });
if (!location) {
throw new NotFoundException('Location not found');
}
return location;
}

async create(dto: CreateLocationDto): Promise<Location> {
await this.ensureNameUnique(dto.name);
return this.repo.save(this.repo.create(dto));
}

async update(id: string, dto: UpdateLocationDto): Promise<Location> {
const location = await this.findOne(id);
if (dto.name && dto.name !== location.name) {
await this.ensureNameUnique(dto.name);
}

Object.assign(location, dto);
return this.repo.save(location);
}

async remove(id: string): Promise<void> {
const location = await this.findOne(id);
await this.repo.remove(location);
}

private async ensureNameUnique(name: string): Promise<void> {
const existing = await this.repo.findOne({ where: { name } });
if (existing) {
throw new ConflictException('A location with this name already exists');
}
}
}
Loading