using LocationsApi.Models; using MongoDB.Driver; namespace LocationsApi.Services; public class LocationService { private readonly IMongoCollection _locations; public LocationService(IConfiguration cfg) { var connectionString = cfg["MongoDB:ConnectionString"] ?? "mongodb://127.0.0.1:27017"; var databaseName = cfg["MongoDB:DatabaseName"] ?? "territory-autobattler"; var client = new MongoClient(connectionString); var database = client.GetDatabase(databaseName); _locations = database.GetCollection("Locations"); var coordinateIndex = Builders.IndexKeys .Ascending(x => x.Q) .Ascending(x => x.R); _locations.Indexes.CreateOne(new CreateIndexModel( coordinateIndex, new CreateIndexOptions { Unique = true })); } public Task> GetAllAsync() => _locations.Find(FilterDefinition.Empty) .SortBy(x => x.Q) .ThenBy(x => x.R) .ToListAsync(); public Task> GetRangeAsync(int minQ, int maxQ, int minR, int maxR) => _locations.Find(x => x.Q >= minQ && x.Q <= maxQ && x.R >= minR && x.R <= maxR) .SortBy(x => x.Q) .ThenBy(x => x.R) .ToListAsync(); public async Task GetAsync(int q, int r) => await _locations.Find(x => x.Q == q && x.R == r).FirstOrDefaultAsync(); public async Task UpsertAsync(UpsertLocationRequest request, string? ownerUsername = null) { var now = DateTime.UtcNow; var update = Builders.Update .SetOnInsert(x => x.CreatedAtUtc, now) .Set(x => x.Terrain, NormalizeTerrain(request.Terrain)) .Set(x => x.Label, request.Label) .Set(x => x.Level, Math.Max(1, request.Level)) .Set(x => x.UpdatedAtUtc, now); if (!string.IsNullOrWhiteSpace(ownerUsername)) { update = update.Set(x => x.OwnerUsername, ownerUsername); } var options = new FindOneAndUpdateOptions { IsUpsert = true, ReturnDocument = ReturnDocument.After }; return await _locations.FindOneAndUpdateAsync( x => x.Q == request.Q && x.R == request.R, update, options); } public async Task ClaimAsync(int q, int r, string username) { var existing = await GetAsync(q, r); if (existing is not null) { existing.OwnerUsername = username; existing.UpdatedAtUtc = DateTime.UtcNow; await _locations.ReplaceOneAsync(x => x.Id == existing.Id, existing); return existing; } return await UpsertAsync(new UpsertLocationRequest { Q = q, R = r, Terrain = "Field" }, username); } private static string NormalizeTerrain(string terrain) { var trimmed = terrain.Trim(); return trimmed switch { "Command" or "Field" or "Forest" or "Ore" or "Highland" => trimmed, _ => "Field" }; } }