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/nats_data/
|
||||
|
||||
# Maps and backups (runtime data)
|
||||
maps/
|
||||
backups/
|
||||
# Maps and backups (runtime data, top-level only)
|
||||
/maps/
|
||||
/backups/
|
||||
|
||||
# Logs
|
||||
*.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