fix: Add missing maps module and scope gitignore rules
All checks were successful
Test Asgard Runner / test (push) Successful in 3s

maps/ gitignore rule was catching backend-nest/src/modules/maps/.
Scoped to /maps/ (root only) so runtime data is still ignored
but source code isn't.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Vantz Stockwell
2026-02-15 21:35:47 -05:00
parent 50848fd0e8
commit 2ad6a658ca
6 changed files with 172 additions and 3 deletions

View File

@@ -0,0 +1,22 @@
import { IsArray, ValidateNested, IsUUID, IsInt, Min } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer';
export class RotationEntryDto {
@ApiProperty({ example: '550e8400-e29b-41d4-a716-446655440000' })
@IsUUID()
map_id: string;
@ApiProperty({ example: 1 })
@IsInt()
@Min(0)
rotation_order: number;
}
export class UpdateRotationDto {
@ApiProperty({ type: [RotationEntryDto] })
@IsArray()
@ValidateNested({ each: true })
@Type(() => RotationEntryDto)
maps: RotationEntryDto[];
}

View File

@@ -0,0 +1,25 @@
import { IsString, IsOptional, IsEnum, IsInt, MaxLength, Min, Max } from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
export class UploadMapDto {
@ApiProperty({ example: 'Custom PvP Map', maxLength: 255 })
@IsString()
@MaxLength(255)
display_name: string;
@ApiProperty({ example: 'custom', enum: ['custom', 'procedural'] })
@IsEnum(['custom', 'procedural'])
map_type: 'custom' | 'procedural';
@ApiPropertyOptional({ example: 123456, description: 'Procedural map seed' })
@IsOptional()
@IsInt()
seed?: number;
@ApiPropertyOptional({ example: 4000, description: 'World size in meters' })
@IsOptional()
@IsInt()
@Min(1000)
@Max(6000)
world_size?: number;
}

View File

@@ -0,0 +1,45 @@
import { Controller, Get, Delete, Put, Body, Param, UseGuards } from '@nestjs/common';
import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger';
import { MapsService } from './maps.service';
import { UpdateRotationDto } from './dto/update-rotation.dto';
import { CurrentTenant } from '../../common/decorators/current-tenant.decorator';
import { RequirePermission } from '../../common/decorators/require-permission.decorator';
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
import { PermissionsGuard } from '../../common/guards/permissions.guard';
@ApiTags('maps')
@ApiBearerAuth()
@Controller('maps')
@UseGuards(JwtAuthGuard, PermissionsGuard)
export class MapsController {
constructor(private readonly mapsService: MapsService) {}
@Get()
@RequirePermission('map.view')
@ApiOperation({ summary: 'Get all maps for tenant' })
getMaps(@CurrentTenant() licenseId: string) {
return this.mapsService.getMaps(licenseId);
}
@Delete(':id')
@RequirePermission('map.manage')
@ApiOperation({ summary: 'Delete map from library' })
async deleteMap(@CurrentTenant() licenseId: string, @Param('id') mapId: string) {
await this.mapsService.deleteMap(licenseId, mapId);
return { deleted: true };
}
@Get('rotation')
@RequirePermission('map.view')
@ApiOperation({ summary: 'Get current map rotation' })
getRotation(@CurrentTenant() licenseId: string) {
return this.mapsService.getRotation(licenseId);
}
@Put('rotation')
@RequirePermission('map.manage')
@ApiOperation({ summary: 'Update map rotation order' })
updateRotation(@CurrentTenant() licenseId: string, @Body() dto: UpdateRotationDto) {
return this.mapsService.updateRotation(licenseId, dto);
}
}

View File

@@ -0,0 +1,14 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { MapsController } from './maps.controller';
import { MapsService } from './maps.service';
import { MapLibrary } from '../../entities/map-library.entity';
import { MapRotation } from '../../entities/map-rotation.entity';
@Module({
imports: [TypeOrmModule.forFeature([MapLibrary, MapRotation])],
controllers: [MapsController],
providers: [MapsService],
exports: [MapsService],
})
export class MapsModule {}

View File

@@ -0,0 +1,63 @@
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { MapLibrary } from '../../entities/map-library.entity';
import { MapRotation } from '../../entities/map-rotation.entity';
import { UpdateRotationDto } from './dto/update-rotation.dto';
@Injectable()
export class MapsService {
constructor(
@InjectRepository(MapLibrary)
private readonly mapLibraryRepo: Repository<MapLibrary>,
@InjectRepository(MapRotation)
private readonly mapRotationRepo: Repository<MapRotation>,
) {}
async getMaps(licenseId: string): Promise<{ maps: MapLibrary[] }> {
const maps = await this.mapLibraryRepo.find({
where: { license_id: licenseId },
order: { uploaded_at: 'DESC' },
});
return { maps };
}
async deleteMap(licenseId: string, mapId: string): Promise<void> {
const result = await this.mapLibraryRepo.delete({
id: mapId,
license_id: licenseId,
});
if (result.affected === 0) {
throw new NotFoundException(`Map ${mapId} not found`);
}
}
async getRotation(licenseId: string): Promise<MapRotation[]> {
return this.mapRotationRepo.find({
where: { license_id: licenseId },
relations: ['map'],
order: { rotation_order: 'ASC' },
});
}
async updateRotation(licenseId: string, dto: UpdateRotationDto): Promise<MapRotation[]> {
// Delete existing rotation entries for this license
await this.mapRotationRepo.delete({ license_id: licenseId });
// Create new rotation entries
const rotationEntries = dto.maps.map((entry) =>
this.mapRotationRepo.create({
license_id: licenseId,
map_id: entry.map_id,
rotation_order: entry.rotation_order,
is_active: true,
}),
);
await this.mapRotationRepo.save(rotationEntries);
// Return updated rotation with map data
return this.getRotation(licenseId);
}
}