Initial commit
This commit is contained in:
@@ -0,0 +1,400 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using RobotNet10.MapEditor.Shared.DTOs.Requests;
|
||||
using RobotNet10.MapManager.Data;
|
||||
|
||||
namespace RobotNet10.MapManager.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Service implementation for managing layouts, versions, and levels
|
||||
/// </summary>
|
||||
public class LayoutService(MapDbContext context) : ILayoutService
|
||||
{
|
||||
private readonly MapDbContext _context = context;
|
||||
|
||||
// ==========================================
|
||||
// LAYOUT OPERATIONS
|
||||
// ==========================================
|
||||
|
||||
public async Task<Layout> CreateLayoutAsync(CreateLayoutRequest request)
|
||||
{
|
||||
// Check if LayoutId already exists
|
||||
var exists = await _context.Layouts.AnyAsync(l => l.LayoutId == request.LayoutId);
|
||||
if (exists)
|
||||
{
|
||||
throw new InvalidOperationException($"Layout with ID '{request.LayoutId}' already exists");
|
||||
}
|
||||
|
||||
var layout = new Layout
|
||||
{
|
||||
LayoutId = request.LayoutId,
|
||||
LayoutName = request.LayoutName,
|
||||
Description = request.Description,
|
||||
IsActive = false,
|
||||
CreatedDate = DateTime.UtcNow,
|
||||
ModifiedDate = DateTime.UtcNow,
|
||||
CreatedBy = request.CreatedBy
|
||||
};
|
||||
|
||||
_context.Layouts.Add(layout);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return layout;
|
||||
}
|
||||
|
||||
public async Task<List<Layout>> SearchLayoutsAsync(string? searchText)
|
||||
{
|
||||
var query = _context.Layouts.Include(l => l.Versions)
|
||||
.ThenInclude(v => v.Levels)
|
||||
.ThenInclude(l => l.EditorSettings)
|
||||
.AsSplitQuery()
|
||||
.AsQueryable();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(searchText))
|
||||
{
|
||||
var search = searchText.ToLower();
|
||||
query = query.Where(l =>
|
||||
l.LayoutId.ToLower().Contains(search) ||
|
||||
l.LayoutName.ToLower().Contains(search));
|
||||
}
|
||||
|
||||
return await query
|
||||
.OrderByDescending(l => l.ModifiedDate)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<Layout?> GetLayoutByIdAsync(Guid layoutId)
|
||||
{
|
||||
return await _context.Layouts
|
||||
.Include(l => l.Versions)
|
||||
.ThenInclude(v => v.Levels)
|
||||
.ThenInclude(l => l.EditorSettings)
|
||||
.FirstOrDefaultAsync(l => l.Id == layoutId);
|
||||
}
|
||||
|
||||
public async Task<Layout?> GetLayoutByLayoutIdAsync(string layoutId)
|
||||
{
|
||||
return await _context.Layouts
|
||||
.Include(l => l.Versions)
|
||||
.ThenInclude(v => v.Levels)
|
||||
.ThenInclude(l => l.EditorSettings)
|
||||
.FirstOrDefaultAsync(l => l.LayoutId == layoutId);
|
||||
}
|
||||
|
||||
public async Task<Layout?> GetLayoutByNameAsync(string layoutName)
|
||||
{
|
||||
return await _context.Layouts
|
||||
.Include(l => l.Versions)
|
||||
.ThenInclude(v => v.Levels)
|
||||
.ThenInclude(l => l.EditorSettings)
|
||||
.FirstOrDefaultAsync(l => l.LayoutName == layoutName);
|
||||
}
|
||||
|
||||
public async Task<Layout> UpdateLayoutAsync(Guid layoutId, UpdateLayoutRequest request)
|
||||
{
|
||||
var layout = await GetLayoutByIdAsync(layoutId) ??
|
||||
throw new InvalidOperationException($"Layout with ID '{layoutId}' not found");
|
||||
|
||||
layout.LayoutName = request.LayoutName;
|
||||
layout.Description = request.Description;
|
||||
layout.ModifiedDate = DateTime.UtcNow;
|
||||
layout.ModifiedBy = request.ModifiedBy;
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return layout;
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteLayoutAsync(Guid layoutId)
|
||||
{
|
||||
var layout = await GetLayoutByIdAsync(layoutId);
|
||||
if (layout == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if layout is active
|
||||
if (layout.IsActive)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Cannot delete active layout '{layout.LayoutId}'. Deactivate it first.");
|
||||
}
|
||||
|
||||
// Hard delete - EF Core cascade will handle:
|
||||
// Layout → Versions → Levels → Nodes/Edges/Stations → Properties
|
||||
_context.Layouts.Remove(layout);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<Layout> ActivateLayoutAsync(Guid layoutId)
|
||||
{
|
||||
var layout = await GetLayoutByIdAsync(layoutId) ??
|
||||
throw new InvalidOperationException($"Layout with ID '{layoutId}' not found");
|
||||
|
||||
layout.IsActive = true;
|
||||
layout.ModifiedDate = DateTime.UtcNow;
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return layout;
|
||||
}
|
||||
|
||||
public async Task<Layout> DeactivateLayoutAsync(Guid layoutId)
|
||||
{
|
||||
var layout = await GetLayoutByIdAsync(layoutId) ??
|
||||
throw new InvalidOperationException($"Layout with ID '{layoutId}' not found");
|
||||
layout.IsActive = false;
|
||||
layout.ModifiedDate = DateTime.UtcNow;
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return layout;
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// VERSION OPERATIONS
|
||||
// ==========================================
|
||||
|
||||
public async Task<LayoutVersion> CreateVersionAsync(Guid layoutId, CreateLayoutVersionRequest request)
|
||||
{
|
||||
var layout = await GetLayoutByIdAsync(layoutId) ??
|
||||
throw new InvalidOperationException($"Layout with ID '{layoutId}' not found");
|
||||
|
||||
// Check if version already exists
|
||||
var exists = await _context.LayoutVersions
|
||||
.AnyAsync(v => v.LayoutId == layoutId && v.Version == request.Version);
|
||||
if (exists)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Version '{request.Version}' already exists for layout '{layout.LayoutId}'");
|
||||
}
|
||||
|
||||
var version = new LayoutVersion
|
||||
{
|
||||
LayoutId = layoutId,
|
||||
Version = request.Version,
|
||||
LayoutDescription = request.LayoutDescription,
|
||||
CreatedBy = request.CreatedBy,
|
||||
CreatedDate = DateTime.UtcNow,
|
||||
IsActive = false // New versions start as inactive
|
||||
};
|
||||
|
||||
_context.LayoutVersions.Add(version);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return version;
|
||||
}
|
||||
|
||||
public async Task<List<LayoutVersion>> GetVersionsAsync(Guid layoutId)
|
||||
{
|
||||
return await _context.LayoutVersions
|
||||
.Where(v => v.LayoutId == layoutId)
|
||||
.Include(v => v.Levels)
|
||||
.ThenInclude(l => l.EditorSettings)
|
||||
.OrderByDescending(v => v.CreatedDate)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<LayoutVersion?> GetVersionAsync(Guid versionId)
|
||||
{
|
||||
return await _context.LayoutVersions
|
||||
.Include(v => v.Layout)
|
||||
.Include(v => v.Levels)
|
||||
.ThenInclude(l => l.EditorSettings)
|
||||
.FirstOrDefaultAsync(v => v.Id == versionId);
|
||||
}
|
||||
|
||||
public async Task<LayoutVersion> UpdateVersionAsync(Guid versionId, UpdateLayoutRequest request)
|
||||
{
|
||||
var version = await GetVersionAsync(versionId) ??
|
||||
throw new InvalidOperationException($"Version with ID '{versionId}' not found");
|
||||
version.LayoutDescription = request.Description;
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return version;
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteVersionAsync(Guid versionId)
|
||||
{
|
||||
var version = await GetVersionAsync(versionId);
|
||||
if (version == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if parent layout is active
|
||||
if (version.Layout.IsActive)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Cannot delete version from active layout '{version.Layout.LayoutId}'. " +
|
||||
"Deactivate the layout first.");
|
||||
}
|
||||
|
||||
// Hard delete - cascade will handle levels and all nested data
|
||||
_context.LayoutVersions.Remove(version);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// LEVEL OPERATIONS
|
||||
// ==========================================
|
||||
|
||||
public async Task<LayoutLevel> CreateLevelAsync(Guid versionId, CreateLayoutLevelRequest request)
|
||||
{
|
||||
var version = await GetVersionAsync(versionId) ??
|
||||
throw new InvalidOperationException($"Version with ID '{versionId}' not found");
|
||||
|
||||
// Check if level already exists
|
||||
var exists = await _context.LayoutLevels
|
||||
.AnyAsync(l => l.VersionId == versionId && l.LayoutLevelId == request.LayoutLevelId);
|
||||
if (exists)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Level '{request.LayoutLevelId}' already exists in this version");
|
||||
}
|
||||
|
||||
var level = new LayoutLevel
|
||||
{
|
||||
VersionId = versionId,
|
||||
LayoutLevelId = request.LayoutLevelId,
|
||||
LevelOrder = request.LevelOrder
|
||||
};
|
||||
|
||||
_context.LayoutLevels.Add(level);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
// Create editor settings if coordinate system provided
|
||||
if (request.CoordinateSystem != null)
|
||||
{
|
||||
var settings = new LayoutLevelEditorSettings
|
||||
{
|
||||
LevelId = level.Id,
|
||||
OriginX = request.CoordinateSystem.OriginX,
|
||||
OriginY = request.CoordinateSystem.OriginY,
|
||||
Resolution = request.CoordinateSystem.Resolution,
|
||||
BoundsMinX = request.CoordinateSystem.BoundsMinX,
|
||||
BoundsMaxX = request.CoordinateSystem.BoundsMaxX,
|
||||
BoundsMinY = request.CoordinateSystem.BoundsMinY,
|
||||
BoundsMaxY = request.CoordinateSystem.BoundsMaxY,
|
||||
ImageWidth = request.CoordinateSystem.ImageWidth,
|
||||
ImageHeight = request.CoordinateSystem.ImageHeight,
|
||||
CreatedDate = DateTime.UtcNow,
|
||||
ModifiedDate = DateTime.UtcNow
|
||||
};
|
||||
_context.LayoutLevelEditorSettings.Add(settings);
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
return level;
|
||||
}
|
||||
|
||||
public async Task<List<LayoutLevel>> GetLevelsAsync(Guid versionId)
|
||||
{
|
||||
return await _context.LayoutLevels
|
||||
.Where(l => l.VersionId == versionId)
|
||||
.Include(l => l.EditorSettings)
|
||||
.OrderBy(l => l.LevelOrder)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<LayoutLevel?> GetLevelAsync(Guid levelId)
|
||||
{
|
||||
return await _context.LayoutLevels
|
||||
.Include(l => l.Version)
|
||||
.ThenInclude(v => v.Layout)
|
||||
.Include(l => l.EditorSettings)
|
||||
.FirstOrDefaultAsync(l => l.Id == levelId);
|
||||
}
|
||||
|
||||
public async Task<LayoutLevel> UpdateLevelAsync(Guid levelId, UpdateLayoutLevelRequest request)
|
||||
{
|
||||
var level = await GetLevelAsync(levelId) ?? throw new InvalidOperationException($"Level with ID '{levelId}' not found");
|
||||
|
||||
// Update level properties
|
||||
if (request.LayoutLevelId != null)
|
||||
level.LayoutLevelId = request.LayoutLevelId;
|
||||
|
||||
if (request.LevelOrder.HasValue)
|
||||
level.LevelOrder = request.LevelOrder.Value;
|
||||
|
||||
// Get or create editor settings
|
||||
var settings = await _context.LayoutLevelEditorSettings
|
||||
.FirstOrDefaultAsync(s => s.LevelId == levelId);
|
||||
|
||||
if (settings == null)
|
||||
{
|
||||
// Create new settings
|
||||
settings = new LayoutLevelEditorSettings
|
||||
{
|
||||
LevelId = levelId,
|
||||
CreatedDate = DateTime.UtcNow
|
||||
};
|
||||
_context.LayoutLevelEditorSettings.Add(settings);
|
||||
}
|
||||
|
||||
// Update coordinate system if provided
|
||||
if (request.CoordinateSystem != null)
|
||||
{
|
||||
settings.OriginX = request.CoordinateSystem.OriginX;
|
||||
settings.OriginY = request.CoordinateSystem.OriginY;
|
||||
settings.Resolution = request.CoordinateSystem.Resolution;
|
||||
settings.BoundsMinX = request.CoordinateSystem.BoundsMinX;
|
||||
settings.BoundsMaxX = request.CoordinateSystem.BoundsMaxX;
|
||||
settings.BoundsMinY = request.CoordinateSystem.BoundsMinY;
|
||||
settings.BoundsMaxY = request.CoordinateSystem.BoundsMaxY;
|
||||
settings.ImageWidth = request.CoordinateSystem.ImageWidth;
|
||||
settings.ImageHeight = request.CoordinateSystem.ImageHeight;
|
||||
}
|
||||
|
||||
// Update editor settings if provided
|
||||
if (request.EditorSettings != null)
|
||||
{
|
||||
if (request.EditorSettings.EdgeMinLengthCreate.HasValue)
|
||||
settings.EdgeMinLengthCreate = request.EditorSettings.EdgeMinLengthCreate.Value;
|
||||
|
||||
if (request.EditorSettings.EdgeNameAutoGenerate.HasValue)
|
||||
settings.EdgeNameAutoGenerate = request.EditorSettings.EdgeNameAutoGenerate.Value;
|
||||
|
||||
if (request.EditorSettings.NodeNameAutoGenerate.HasValue)
|
||||
settings.NodeNameAutoGenerate = request.EditorSettings.NodeNameAutoGenerate.Value;
|
||||
|
||||
if (request.EditorSettings.NodeProximityRadius.HasValue)
|
||||
settings.NodeProximityRadius = request.EditorSettings.NodeProximityRadius.Value;
|
||||
}
|
||||
|
||||
// Update modified date if any settings were changed
|
||||
if (request.CoordinateSystem != null || request.EditorSettings != null)
|
||||
{
|
||||
settings.ModifiedDate = DateTime.UtcNow;
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return level;
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteLevelAsync(Guid levelId)
|
||||
{
|
||||
var level = await GetLevelAsync(levelId);
|
||||
if (level == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if parent layout is active
|
||||
if (level.Version.Layout.IsActive)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Cannot delete level from active layout '{level.Version.Layout.LayoutId}'. " +
|
||||
"Deactivate the layout first.");
|
||||
}
|
||||
|
||||
// Hard delete - cascade will handle all nested data
|
||||
_context.LayoutLevels.Remove(level);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user