diff --git a/Api/Controllers/GeoLocationsController.cs b/Api/Controllers/GeoLocationsController.cs
index 7e44b94..8d4c5a9 100644
--- a/Api/Controllers/GeoLocationsController.cs
+++ b/Api/Controllers/GeoLocationsController.cs
@@ -1,6 +1,9 @@
-using Application.Services;
+using Api.Utilities;
+using Application.Services;
using Core.Dto.Request;
+using Core.Dto.Response;
using Core.Entities;
+using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Net;
@@ -8,6 +11,7 @@ namespace Api.Controllers
{
[Route("api/v1/[controller]")]
[ApiController]
+ [Authorize(Policy = "AdminAndProLexicographers")]
public class GeoLocationsController : ControllerBase
{
private readonly GeoLocationsService _geoLocationsService;
@@ -23,11 +27,32 @@ public GeoLocationsController(GeoLocationsService geoLocationsService)
/// An representing the response containing the list of objects.
///
[HttpGet]
+ [AllowAnonymous]
[ProducesResponseType(typeof(GeoLocationDto[]), (int)HttpStatusCode.OK)]
public async Task ListGeoLocations()
{
- var result = (await _geoLocationsService.GetAll()).Select(g => new GeoLocationDto(g.Place, g.Region));
+ var result = (await _geoLocationsService.GetAll()).Select(g => new GeoLocationDto(g.Id, g.Place, g.Region));
return Ok(result);
}
+
+ [HttpPost]
+ [ProducesResponseType(typeof(GeoLocationDto), (int)HttpStatusCode.OK)]
+ public async Task Create(CreateGeoLocationDto geo)
+ {
+ var geoLocation = new GeoLocation(geo.Place, geo.Region)
+ {
+ CreatedBy = User!.Identity!.Name!
+ };
+ await _geoLocationsService.Create(geoLocation);
+ return StatusCode((int)HttpStatusCode.Created, ResponseHelper.GetResponseDict("Geolocation successfully added"));
+ }
+
+ [HttpDelete("{id}/{place}")]
+ [ProducesResponseType(typeof(GeoLocationDto), (int)HttpStatusCode.OK)]
+ public async Task Delete(string id, string place)
+ {
+ await _geoLocationsService.Delete(id, place);
+ return Ok(ResponseHelper.GetResponseDict($"Geolocation '{place}' successfully deleted"));
+ }
}
}
diff --git a/Api/Controllers/SuggestedNameController.cs b/Api/Controllers/SuggestedNameController.cs
index f3271f8..ffb56af 100644
--- a/Api/Controllers/SuggestedNameController.cs
+++ b/Api/Controllers/SuggestedNameController.cs
@@ -7,6 +7,7 @@
using System.Net;
using Application.Mappers;
using Application.Validation;
+using FluentValidation;
namespace Api.Controllers;
@@ -16,9 +17,9 @@ namespace Api.Controllers;
public class SuggestedNameController : ControllerBase
{
private readonly SuggestedNameService _suggestedNameService;
- private readonly CreateSuggestedNameValidator _suggestedNameValidator;
+ private readonly IValidator _suggestedNameValidator;
- public SuggestedNameController(SuggestedNameService suggestedNameService, CreateSuggestedNameValidator suggestedNameValidator)
+ public SuggestedNameController(SuggestedNameService suggestedNameService, IValidator suggestedNameValidator)
{
_suggestedNameService = suggestedNameService;
_suggestedNameValidator = suggestedNameValidator;
diff --git a/Api/ExceptionHandler/GlobalExceptionHandling.cs b/Api/ExceptionHandler/GlobalExceptionHandling.cs
index 8a9d13f..3f18d1c 100644
--- a/Api/ExceptionHandler/GlobalExceptionHandling.cs
+++ b/Api/ExceptionHandler/GlobalExceptionHandling.cs
@@ -1,5 +1,6 @@
namespace Api.ExceptionHandler
{
+ using Api.Utilities;
using Application.Exceptions;
using System.Net;
using System.Text.Json;
@@ -32,21 +33,17 @@ private async Task HandleExceptionAsync(HttpContext context, Exception exception
context.Response.ContentType = "application/json";
var response = context.Response;
- var errorResponse = new ErrorResponse
- {
- Success = false
- };
+ Dictionary errorResponse;
switch (exception)
{
case ClientException ex:
response.StatusCode = (int)HttpStatusCode.BadRequest;
- errorResponse.Message = ex.Message;
+ errorResponse = ResponseHelper.GetResponseDict(ex.Message);
break;
-
default:
response.StatusCode = (int)HttpStatusCode.InternalServerError;
- errorResponse.Message = "Internal server error!";
+ errorResponse = ResponseHelper.GetResponseDict("Internal server error!");
break;
}
_logger.LogError(exception, "Unhandled Application Exception");
diff --git a/Application/Mappers/NameEntryMapper.cs b/Application/Mappers/NameEntryMapper.cs
index 7a00260..57f9200 100644
--- a/Application/Mappers/NameEntryMapper.cs
+++ b/Application/Mappers/NameEntryMapper.cs
@@ -29,7 +29,7 @@ public static NameEntry MapToEntity(this NameDto request)
{
return new NameEntry
{
- Name = request.Name,
+ Name = request.Name.Trim(),
Pronunciation = request.Pronunciation?.Trim(),
Meaning = request.Meaning.Trim(),
ExtendedMeaning = request.ExtendedMeaning?.Trim(),
@@ -59,7 +59,7 @@ public static NameEntryDto MapToDto(this NameEntry nameEntry)
Meaning = nameEntry.Meaning,
ExtendedMeaning = nameEntry.ExtendedMeaning,
Morphology = (CommaSeparatedString)nameEntry.Morphology,
- GeoLocation = nameEntry.GeoLocation.Select(ge => new GeoLocationDto(ge.Place, ge.Region)).ToList(),
+ GeoLocation = nameEntry.GeoLocation.Select(ge => new GeoLocationDto(ge.Id, ge.Place, ge.Region)).ToList(),
FamousPeople = (CommaSeparatedString)nameEntry.FamousPeople,
Media = (CommaSeparatedString)nameEntry.Media,
SubmittedBy = nameEntry.CreatedBy,
diff --git a/Application/Mappers/SuggestedNameMapper.cs b/Application/Mappers/SuggestedNameMapper.cs
index 8f9422d..b26dc52 100644
--- a/Application/Mappers/SuggestedNameMapper.cs
+++ b/Application/Mappers/SuggestedNameMapper.cs
@@ -16,9 +16,9 @@ public static SuggestedName MapToEntity(this CreateSuggestedNameDto request)
return new SuggestedName
{
Id = ObjectId.GenerateNewId().ToString(),
- Name = request.Name,
- Email = request.Email,
- Details = request.Details,
+ Name = request.Name.Trim(),
+ Email = request.Email?.Trim(),
+ Details = request.Details?.Trim(),
GeoLocation = request.GeoLocation.Select(x => new GeoLocation
{
Place = x.Place,
@@ -36,7 +36,7 @@ public static SuggestedNameDto MapToDto(this SuggestedName request)
Name = request.Name,
Email = request.Email,
Details = request.Details,
- GeoLocation = request.GeoLocation.Select(ge => new GeoLocationDto(ge.Place, ge.Region)).ToList(),
+ GeoLocation = request.GeoLocation.Select(ge => new GeoLocationDto(ge.Id, ge.Place, ge.Region)).ToList(),
};
}
}
diff --git a/Application/Services/GeoLocationsService.cs b/Application/Services/GeoLocationsService.cs
index 1fad5b8..e09a01f 100644
--- a/Application/Services/GeoLocationsService.cs
+++ b/Application/Services/GeoLocationsService.cs
@@ -1,6 +1,6 @@
-using Core.Entities;
+using Application.Exceptions;
+using Core.Entities;
using Core.Repositories;
-using Application.Exceptions;
namespace Application.Services
{
@@ -14,7 +14,36 @@ public GeoLocationsService(IGeoLocationsRepository geoLocationsRepository)
}
public async Task> GetAll()
{
- return await _geoLocationsRepository.GetAll();
+ return await _geoLocationsRepository.GetAll();
+ }
+ public async Task Create(GeoLocation geoLocation)
+ {
+ var match = await _geoLocationsRepository.FindByPlace(geoLocation.Place);
+ if (match != null)
+ {
+ throw new ClientException("This location already exists.");
+ }
+
+ await _geoLocationsRepository.Create(new GeoLocation
+ {
+ Place = geoLocation.Place.Trim().ToUpper(),
+ Region = geoLocation.Region.Trim().ToUpper(),
+ CreatedBy = geoLocation.CreatedBy,
+ });
+ }
+
+ public async Task Delete(string id, string place)
+ {
+ if (string.IsNullOrWhiteSpace(place) || string.IsNullOrWhiteSpace(id))
+ {
+ throw new ClientException("One or more input parameters are not valid.");
+ }
+
+ var deleteCount = await _geoLocationsRepository.Delete(id, place);
+ if (deleteCount == 0)
+ {
+ throw new ClientException("No matching records were found to delete.");
+ }
}
}
}
diff --git a/Application/Services/NameEntryService.cs b/Application/Services/NameEntryService.cs
index 8f22bd0..b1c8c12 100644
--- a/Application/Services/NameEntryService.cs
+++ b/Application/Services/NameEntryService.cs
@@ -34,7 +34,7 @@ public async Task Create(NameEntry entry)
{
existingName.Duplicates.Add(entry);
await UpdateName(existingName);
- _logger.LogWarning($"Someone attempted to create a new name over existing name: {name}.");
+ _logger.LogWarning("Someone attempted to create a new name over existing name: {name}.", name);
return;
}
diff --git a/Application/Validation/CreateSuggestedNameValidator.cs b/Application/Validation/CreateSuggestedNameValidator.cs
index 6c52f1d..2b44837 100644
--- a/Application/Validation/CreateSuggestedNameValidator.cs
+++ b/Application/Validation/CreateSuggestedNameValidator.cs
@@ -1,6 +1,4 @@
using Core.Dto.Request;
-using Core.Dto.Response;
-using Core.Enums;
using FluentValidation;
namespace Application.Validation
diff --git a/Application/Validation/GeoLocationValidator.cs b/Application/Validation/GeoLocationValidator.cs
index 8dbb1bd..233e46e 100644
--- a/Application/Validation/GeoLocationValidator.cs
+++ b/Application/Validation/GeoLocationValidator.cs
@@ -1,15 +1,10 @@
using Core.Dto.Request;
using Core.Repositories;
using FluentValidation;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
namespace Application.Validation
{
- public class GeoLocationValidator : AbstractValidator
+ public class GeoLocationValidator : AbstractValidator
{
private readonly IGeoLocationsRepository _geoLocationsRepository;
diff --git a/Application/Validation/NameValidator.cs b/Application/Validation/NameValidator.cs
index 83b27f3..c6b0a52 100644
--- a/Application/Validation/NameValidator.cs
+++ b/Application/Validation/NameValidator.cs
@@ -1,13 +1,9 @@
-using Core.Dto;
-using Core.Dto.Request;
+using Core.Dto.Request;
using FluentValidation;
namespace Application.Validation
{
public class NameValidator : AbstractValidator
{
-
-
-
public NameValidator(GeoLocationValidator geoLocationValidator, EmbeddedVideoValidator embeddedVideoValidator, EtymologyValidator etymologyValidator)
{
RuleLevelCascadeMode = CascadeMode.Stop;
diff --git a/Core/Dto/CharacterSeparatedString.cs b/Core/Dto/CharacterSeparatedString.cs
index 1f5b5da..57eb98b 100644
--- a/Core/Dto/CharacterSeparatedString.cs
+++ b/Core/Dto/CharacterSeparatedString.cs
@@ -8,7 +8,7 @@ public abstract class CharacterSeparatedString where T : CharacterSeparatedSt
public CharacterSeparatedString(string? value)
{
- this.value = value ?? string.Empty;
+ this.value = value?.Trim() ?? string.Empty;
}
public override string ToString()
diff --git a/Core/Dto/Request/CreateGeoLocationDto.cs b/Core/Dto/Request/CreateGeoLocationDto.cs
new file mode 100644
index 0000000..e334b9c
--- /dev/null
+++ b/Core/Dto/Request/CreateGeoLocationDto.cs
@@ -0,0 +1,6 @@
+namespace Core.Dto.Request
+{
+ public record CreateGeoLocationDto(string Place, string Region)
+ {
+ }
+}
diff --git a/Core/Dto/Request/CreateSuggestedNameDto.cs b/Core/Dto/Request/CreateSuggestedNameDto.cs
index 7b53430..de0cb9c 100644
--- a/Core/Dto/Request/CreateSuggestedNameDto.cs
+++ b/Core/Dto/Request/CreateSuggestedNameDto.cs
@@ -1,15 +1,14 @@
-
-namespace Core.Dto.Request;
+namespace Core.Dto.Request;
public record CreateSuggestedNameDto
{
public string Name { get; init; }
public string Details { get; init; }
public string Email { get; init; }
- public List GeoLocation { get; set; }
+ public List GeoLocation { get; set; }
public CreateSuggestedNameDto()
{
- GeoLocation = new List();
+ GeoLocation = new List();
}
}
diff --git a/Core/Dto/Request/GeoLocationDto.cs b/Core/Dto/Request/GeoLocationDto.cs
deleted file mode 100644
index 0f797d0..0000000
--- a/Core/Dto/Request/GeoLocationDto.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-namespace Core.Dto.Request
-{
- public record GeoLocationDto(string Place, string Region)
- {
- public override string ToString()
- {
- return Place;
- }
- }
-
-}
diff --git a/Core/Dto/Request/NameDto.cs b/Core/Dto/Request/NameDto.cs
index 4ab175d..beb674e 100644
--- a/Core/Dto/Request/NameDto.cs
+++ b/Core/Dto/Request/NameDto.cs
@@ -1,5 +1,4 @@
using Core.Enums;
-using System.ComponentModel.DataAnnotations;
namespace Core.Dto.Request
{
@@ -13,7 +12,7 @@ public abstract class NameDto
public State? State { get; set; }
public List Etymology { get; set; }
public List Videos { get; set; }
- public List GeoLocation { get; set; }
+ public List GeoLocation { get; set; }
public CommaSeparatedString? FamousPeople { get; set; }
@@ -33,14 +32,14 @@ public NameDto(string name, string meaning)
Meaning = meaning ?? throw new ArgumentNullException(nameof(meaning));
Etymology = new List();
Videos = new List();
- GeoLocation = new List();
+ GeoLocation = new List();
}
public NameDto()
{
Etymology = new List();
Videos = new List();
- GeoLocation = new List();
+ GeoLocation = new List();
}
}
}
diff --git a/Core/Dto/Response/GeoLocationDto.cs b/Core/Dto/Response/GeoLocationDto.cs
new file mode 100644
index 0000000..99441ae
--- /dev/null
+++ b/Core/Dto/Response/GeoLocationDto.cs
@@ -0,0 +1,10 @@
+namespace Core.Dto.Response
+{
+ public record GeoLocationDto(string Id, string Place, string Region)
+ {
+ public override string ToString()
+ {
+ return Place;
+ }
+ }
+}
diff --git a/Core/Dto/Response/SuggestedNameDto.cs b/Core/Dto/Response/SuggestedNameDto.cs
index 5f6927f..c1d1cc8 100644
--- a/Core/Dto/Response/SuggestedNameDto.cs
+++ b/Core/Dto/Response/SuggestedNameDto.cs
@@ -1,6 +1,4 @@
-using Core.Dto.Request;
-
-namespace Core.Dto.Response;
+namespace Core.Dto.Response;
public record SuggestedNameDto
{
diff --git a/Core/Repositories/IGeoLocationsRepository.cs b/Core/Repositories/IGeoLocationsRepository.cs
index 6eca5b5..3f826ec 100644
--- a/Core/Repositories/IGeoLocationsRepository.cs
+++ b/Core/Repositories/IGeoLocationsRepository.cs
@@ -1,9 +1,4 @@
using Core.Entities;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
namespace Core.Repositories
{
@@ -13,5 +8,7 @@ public interface IGeoLocationsRepository
Task FindByPlace(string place);
Task FindByPlaceAndRegion(string region, string place);
+ Task Create(GeoLocation geoLocation);
+ Task Delete(string id, string place);
}
}
diff --git a/Infrastructure.MongoDB/Repositories/GeoLocationsRepository.cs b/Infrastructure.MongoDB/Repositories/GeoLocationsRepository.cs
index e4f6567..c50ccc5 100644
--- a/Infrastructure.MongoDB/Repositories/GeoLocationsRepository.cs
+++ b/Infrastructure.MongoDB/Repositories/GeoLocationsRepository.cs
@@ -5,7 +5,7 @@
namespace Infrastructure.MongoDB.Repositories
{
- public class GeoLocationsRepository : IGeoLocationsRepository
+ public class GeoLocationsRepository : MongoDBRepository, IGeoLocationsRepository
{
private readonly IMongoCollection _geoLocationsCollection;
@@ -18,25 +18,47 @@ public GeoLocationsRepository(IMongoDatabase database)
InitGeoLocation();
}
}
-
+
public async Task FindByPlace(string place)
{
- var filter = Builders.Filter.Eq("Place", place);
- return await _geoLocationsCollection.Find(filter).SingleOrDefaultAsync();
+ var filter = Builders.Filter.Eq( ge => ge.Place, place);
+ var options = SetCollationPrimary(new FindOptions());
+ return await _geoLocationsCollection.Find(filter, options).SingleOrDefaultAsync();
}
public async Task FindByPlaceAndRegion(string region, string place)
{
var filter = Builders.Filter.And(
- Builders.Filter.Eq("Region", region.ToUpper()),
- Builders.Filter.Eq("Place", place.ToUpper())
+ Builders.Filter.Eq(ge => ge.Region, region),
+ Builders.Filter.Eq(ge => ge.Place, place)
);
- return await _geoLocationsCollection.Find(filter).FirstOrDefaultAsync();
+ var options = SetCollationPrimary(new FindOptions());
+ return await _geoLocationsCollection.Find(filter, options).FirstOrDefaultAsync();
}
- public async Task> GetAll()
+ public async Task> GetAll() => await _geoLocationsCollection
+ .Find(Builders.Filter.Empty)
+ .Sort(Builders.Sort.Ascending(g => g.Place))
+ .ToListAsync();
+
+ public async Task Create(GeoLocation geoLocation)
{
- return await _geoLocationsCollection.Find(FilterDefinition.Empty).ToListAsync();
+ geoLocation.Id = ObjectId.GenerateNewId().ToString();
+ await _geoLocationsCollection.InsertOneAsync(geoLocation);
+ }
+
+ public async Task Delete(string id, string place)
+ {
+ var filterBuilder = Builders.Filter;
+
+ var filter = filterBuilder.And(
+ filterBuilder.Eq(g => g.Id, id),
+ filterBuilder.Eq(g => g.Place, place)
+ );
+
+ var options = SetCollationPrimary(new DeleteOptions());
+ var deleteResult = await _geoLocationsCollection.DeleteOneAsync(filter, options);
+ return (int)deleteResult.DeletedCount;
}
private void InitGeoLocation()
@@ -65,6 +87,12 @@ private void InitGeoLocation()
Region = "OYO"
},
new()
+ {
+ Id = ObjectId.GenerateNewId().ToString(),
+ Place = "OSUN",
+ Region = "OYO"
+ },
+ new()
{
Id = ObjectId.GenerateNewId().ToString(),
Place = "OGUN",
@@ -186,5 +214,6 @@ private void InitGeoLocation()
}
});
}
+
}
}
diff --git a/README.md b/README.md
index fd63673..f528172 100644
--- a/README.md
+++ b/README.md
@@ -48,7 +48,8 @@ To run the Website and API locally, follow these steps:
5. **Access the Application:**
- Once the Docker containers are up and running, you can access the Website in your browser. The website should launch automatically as soon as all the containers are ready.
- If the Website does not launch automatically, it will be running at `http://localhost:{port}` (the actual port will be displayed in the output window of Visual Studio: "Container Tools").
- - The API will also be running locally and accessible via a different URL. You can see its documentation and test the endpoints at `http://localhost:51515/swagger`.
+ - The API will also be running locally and accessible via a different URL. You can see its documentation and test the endpoints at `http://localhost:51515/swagger`.
+ - You can login to the API with any username from [the database initialization script](./mongo-init.js) and password: **Password@135**.
## Contributing
diff --git a/Website/Pages/SubmitName.cshtml.cs b/Website/Pages/SubmitName.cshtml.cs
index 35c0ba3..886918d 100644
--- a/Website/Pages/SubmitName.cshtml.cs
+++ b/Website/Pages/SubmitName.cshtml.cs
@@ -1,5 +1,5 @@
using Application.Services;
-using Core.Dto.Request;
+using Core.Dto.Response;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Localization;
using Website.Pages.Shared;
diff --git a/Website/Services/ApiService.cs b/Website/Services/ApiService.cs
index 43f0ec3..e924411 100644
--- a/Website/Services/ApiService.cs
+++ b/Website/Services/ApiService.cs
@@ -1,5 +1,4 @@
-using Core.Dto.Request;
-using Core.Dto.Response;
+using Core.Dto.Response;
using Microsoft.Extensions.Options;
using System.Text.Json;
using Website.Config;
diff --git a/YorubaNameDictionary.sln b/YorubaNameDictionary.sln
index a2cd3e4..4da7efc 100644
--- a/YorubaNameDictionary.sln
+++ b/YorubaNameDictionary.sln
@@ -24,6 +24,7 @@ EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{6626E809-1AC8-46E8-BBAF-1862FBC64D3D}"
ProjectSection(SolutionItems) = preProject
mongo-init.js = mongo-init.js
+ README.md = README.md
EndProjectSection
EndProject
Global