From 268eca39c048d328a3ae6fd71cee17c323da1bb8 Mon Sep 17 00:00:00 2001 From: hz Date: Tue, 20 Jan 2026 17:25:35 -0600 Subject: [PATCH] Adding coordinate to Location model --- .../Controllers/LocationsController.cs | 18 ++++- microservices/LocationsApi/DOCUMENTS.md | 11 +++- .../Models/CreateLocationRequest.cs | 2 + microservices/LocationsApi/Models/Location.cs | 2 + .../Models/UpdateLocationRequest.cs | 2 + microservices/LocationsApi/README.md | 2 +- .../LocationsApi/Services/LocationStore.cs | 66 ++++++++++++++++++- 7 files changed, 99 insertions(+), 4 deletions(-) diff --git a/microservices/LocationsApi/Controllers/LocationsController.cs b/microservices/LocationsApi/Controllers/LocationsController.cs index e01a772..75a11d3 100644 --- a/microservices/LocationsApi/Controllers/LocationsController.cs +++ b/microservices/LocationsApi/Controllers/LocationsController.cs @@ -2,6 +2,7 @@ using LocationsApi.Models; using LocationsApi.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using MongoDB.Driver; namespace LocationsApi.Controllers; @@ -23,13 +24,25 @@ public class LocationsController : ControllerBase if (string.IsNullOrWhiteSpace(req.Name)) return BadRequest("Name required"); + if (req.Coord is null) + return BadRequest("Coord required"); + var location = new Location { Name = req.Name.Trim(), + Coord = req.Coord, CreatedUtc = DateTime.UtcNow }; - await _locations.CreateAsync(location); + try + { + await _locations.CreateAsync(location); + } + catch (MongoWriteException ex) when (ex.WriteError.Category == ServerErrorCategory.DuplicateKey) + { + return Conflict("Coord must be unique"); + } + return Ok(location); } @@ -59,6 +72,9 @@ public class LocationsController : ControllerBase if (string.IsNullOrWhiteSpace(req.Name)) return BadRequest("Name required"); + if (req.Coord is not null) + return BadRequest("Coord cannot be updated"); + var updated = await _locations.UpdateNameAsync(id, req.Name.Trim()); if (!updated) return NotFound(); diff --git a/microservices/LocationsApi/DOCUMENTS.md b/microservices/LocationsApi/DOCUMENTS.md index 3172622..0df5336 100644 --- a/microservices/LocationsApi/DOCUMENTS.md +++ b/microservices/LocationsApi/DOCUMENTS.md @@ -7,7 +7,11 @@ Inbound JSON documents - CreateLocationRequest (`POST /api/locations`) ```json { - "name": "string" + "name": "string", + "coord": { + "x": 0, + "y": 0 + } } ``` - UpdateLocationRequest (`PUT /api/locations/{id}`) @@ -16,6 +20,7 @@ Inbound JSON documents "name": "string" } ``` + `coord` cannot be updated. Stored documents (MongoDB) - Location @@ -23,6 +28,10 @@ Stored documents (MongoDB) { "id": "string (ObjectId)", "name": "string", + "coord": { + "x": 0, + "y": 0 + }, "createdUtc": "string (ISO-8601 datetime)" } ``` diff --git a/microservices/LocationsApi/Models/CreateLocationRequest.cs b/microservices/LocationsApi/Models/CreateLocationRequest.cs index 6237da9..cc6989f 100644 --- a/microservices/LocationsApi/Models/CreateLocationRequest.cs +++ b/microservices/LocationsApi/Models/CreateLocationRequest.cs @@ -3,4 +3,6 @@ namespace LocationsApi.Models; public class CreateLocationRequest { public string Name { get; set; } = string.Empty; + + public required Coord Coord { get; set; } } diff --git a/microservices/LocationsApi/Models/Location.cs b/microservices/LocationsApi/Models/Location.cs index 8d7828e..81db813 100644 --- a/microservices/LocationsApi/Models/Location.cs +++ b/microservices/LocationsApi/Models/Location.cs @@ -11,5 +11,7 @@ public class Location public string Name { get; set; } = string.Empty; + public required Coord Coord { get; set; } + public DateTime CreatedUtc { get; set; } = DateTime.UtcNow; } diff --git a/microservices/LocationsApi/Models/UpdateLocationRequest.cs b/microservices/LocationsApi/Models/UpdateLocationRequest.cs index 49c8179..bfbea7a 100644 --- a/microservices/LocationsApi/Models/UpdateLocationRequest.cs +++ b/microservices/LocationsApi/Models/UpdateLocationRequest.cs @@ -3,4 +3,6 @@ namespace LocationsApi.Models; public class UpdateLocationRequest { public string Name { get; set; } = string.Empty; + + public Coord? Coord { get; set; } } diff --git a/microservices/LocationsApi/README.md b/microservices/LocationsApi/README.md index c5e459d..cdbde9a 100644 --- a/microservices/LocationsApi/README.md +++ b/microservices/LocationsApi/README.md @@ -4,7 +4,7 @@ See `DOCUMENTS.md` for request payloads and stored document shapes. ## Endpoints -- `POST /api/locations` Create a location (SUPER only). +- `POST /api/locations` Create a location with a unique coord pair (SUPER only). - `GET /api/locations` List all locations (SUPER only). - `DELETE /api/locations/{id}` Delete a location (SUPER only). - `PUT /api/locations/{id}` Update a location name (SUPER only). diff --git a/microservices/LocationsApi/Services/LocationStore.cs b/microservices/LocationsApi/Services/LocationStore.cs index df973bb..21f1676 100644 --- a/microservices/LocationsApi/Services/LocationStore.cs +++ b/microservices/LocationsApi/Services/LocationStore.cs @@ -1,4 +1,5 @@ using LocationsApi.Models; +using MongoDB.Bson; using MongoDB.Driver; namespace LocationsApi.Services; @@ -13,8 +14,71 @@ public class LocationStore var dbName = cfg["MongoDB:DatabaseName"] ?? "GameDb"; var client = new MongoClient(cs); var db = client.GetDatabase(dbName); - _col = db.GetCollection("Locations"); + var collectionName = "Locations"; + EnsureLocationSchema(db, collectionName); + _col = db.GetCollection(collectionName); + var coordIndex = Builders.IndexKeys + .Ascending(l => l.Coord.X) + .Ascending(l => l.Coord.Y); + var coordIndexOptions = new CreateIndexOptions { Unique = true }; + _col.Indexes.CreateOne(new CreateIndexModel(coordIndex, coordIndexOptions)); + } + + private static void EnsureLocationSchema(IMongoDatabase db, string collectionName) + { + var validator = new BsonDocument + { + { + "$jsonSchema", new BsonDocument + { + { "bsonType", "object" }, + { "required", new BsonArray { "name", "coord", "createdUtc" } }, + { + "properties", new BsonDocument + { + { "name", new BsonDocument { { "bsonType", "string" } } }, + { + "coord", new BsonDocument + { + { "bsonType", "object" }, + { "required", new BsonArray { "x", "y" } }, + { + "properties", new BsonDocument + { + { "x", new BsonDocument { { "bsonType", "int" } } }, + { "y", new BsonDocument { { "bsonType", "int" } } } + } + } + } + }, + { "createdUtc", new BsonDocument { { "bsonType", "date" } } } + } + } + } + } + }; + + var options = new CreateCollectionOptions + { + Validator = new BsonDocumentFilterDefinition(validator), + ValidationAction = DocumentValidationAction.Error + }; + + var collections = db.ListCollectionNames().ToList(); + if (!collections.Contains(collectionName)) + { + db.CreateCollection(collectionName, options); + return; + } + + var command = new BsonDocument + { + { "collMod", collectionName }, + { "validator", validator }, + { "validationAction", "error" } + }; + db.RunCommand(command); } public Task CreateAsync(Location location) => _col.InsertOneAsync(location);