fix: Add missing maps module and scope gitignore rules
All checks were successful
Test Asgard Runner / test (push) Successful in 3s
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:
6
.gitignore
vendored
6
.gitignore
vendored
@@ -25,9 +25,9 @@ Thumbs.db
|
|||||||
docker/pg_data/
|
docker/pg_data/
|
||||||
docker/nats_data/
|
docker/nats_data/
|
||||||
|
|
||||||
# Maps and backups (runtime data)
|
# Maps and backups (runtime data, top-level only)
|
||||||
maps/
|
/maps/
|
||||||
backups/
|
/backups/
|
||||||
|
|
||||||
# Logs
|
# Logs
|
||||||
*.log
|
*.log
|
||||||
|
|||||||
22
backend-nest/src/modules/maps/dto/update-rotation.dto.ts
Normal file
22
backend-nest/src/modules/maps/dto/update-rotation.dto.ts
Normal 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[];
|
||||||
|
}
|
||||||
25
backend-nest/src/modules/maps/dto/upload-map.dto.ts
Normal file
25
backend-nest/src/modules/maps/dto/upload-map.dto.ts
Normal 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;
|
||||||
|
}
|
||||||
45
backend-nest/src/modules/maps/maps.controller.ts
Normal file
45
backend-nest/src/modules/maps/maps.controller.ts
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
14
backend-nest/src/modules/maps/maps.module.ts
Normal file
14
backend-nest/src/modules/maps/maps.module.ts
Normal 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 {}
|
||||||
63
backend-nest/src/modules/maps/maps.service.ts
Normal file
63
backend-nest/src/modules/maps/maps.service.ts
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user