From 5c641650c9427343b0b6c305b723cb5690aec0f1 Mon Sep 17 00:00:00 2001 From: "Matthew D. Groves" Date: Tue, 8 Aug 2023 11:13:19 -0400 Subject: [PATCH] #24 added endpoint to get all valid tags --- .../Conduit.Migrations/004_CreateTagData.cs | 39 ++++++++++++++++ Conduit/Conduit.Tests/Conduit.Tests.csproj | 1 + .../Articles/Handlers/GetTagsHandlerTests.cs | 46 +++++++++++++++++++ .../Articles/Controllers/TagController.cs | 32 +++++++++++++ .../Articles/Handlers/GetTagsHandler.cs | 24 ++++++++++ .../Articles/Handlers/GetTagsRequest.cs | 13 ++++++ .../Articles/Services/TagsDataService.cs | 28 +++++++++++ Conduit/Conduit.Web/Conduit.Web.csproj | 6 +++ .../Models/IConduitTagsCollectionProvider.cs | 8 ++++ Conduit/Conduit.Web/Models/TagData.cs | 6 +++ Conduit/Conduit.Web/Program.cs | 5 ++ Conduit/Conduit.Web/appsettings.json | 3 +- README.md | 1 + 13 files changed, 211 insertions(+), 1 deletion(-) create mode 100644 Conduit/Conduit.Migrations/004_CreateTagData.cs create mode 100644 Conduit/Conduit.Tests/Integration/Articles/Handlers/GetTagsHandlerTests.cs create mode 100644 Conduit/Conduit.Web/Articles/Controllers/TagController.cs create mode 100644 Conduit/Conduit.Web/Articles/Handlers/GetTagsHandler.cs create mode 100644 Conduit/Conduit.Web/Articles/Handlers/GetTagsRequest.cs create mode 100644 Conduit/Conduit.Web/Articles/Services/TagsDataService.cs create mode 100644 Conduit/Conduit.Web/Models/IConduitTagsCollectionProvider.cs create mode 100644 Conduit/Conduit.Web/Models/TagData.cs diff --git a/Conduit/Conduit.Migrations/004_CreateTagData.cs b/Conduit/Conduit.Migrations/004_CreateTagData.cs new file mode 100644 index 0000000000..b50b44c507 --- /dev/null +++ b/Conduit/Conduit.Migrations/004_CreateTagData.cs @@ -0,0 +1,39 @@ +using NoSqlMigrator.Infrastructure; + +namespace Conduit.Migrations; + +[Migration(4)] +public class CreateTagData : MigrateBase +{ + private readonly string? _collectionName; + private readonly string? _scopeName; + + public CreateTagData() + { + _collectionName = _config["Couchbase:TagsCollectionName"]; + _scopeName = _config["Couchbase:ScopeName"]; + } + + public override void Up() + { + Create.Collection(_collectionName) + .InScope(_scopeName); + + Insert.Into + .Scope(_scopeName) + .Collection(_collectionName) + .Document("tagData", new + { + tags = new List + { + "Couchbase", "NoSQL", ".NET", "cruising", "baseball" + } + }); + } + + public override void Down() + { + Delete.Collection(_collectionName) + .FromScope(_scopeName); + } +} \ No newline at end of file diff --git a/Conduit/Conduit.Tests/Conduit.Tests.csproj b/Conduit/Conduit.Tests/Conduit.Tests.csproj index 4470f8f507..ec72a952f8 100644 --- a/Conduit/Conduit.Tests/Conduit.Tests.csproj +++ b/Conduit/Conduit.Tests/Conduit.Tests.csproj @@ -41,6 +41,7 @@ + diff --git a/Conduit/Conduit.Tests/Integration/Articles/Handlers/GetTagsHandlerTests.cs b/Conduit/Conduit.Tests/Integration/Articles/Handlers/GetTagsHandlerTests.cs new file mode 100644 index 0000000000..328e0770de --- /dev/null +++ b/Conduit/Conduit.Tests/Integration/Articles/Handlers/GetTagsHandlerTests.cs @@ -0,0 +1,46 @@ +using Conduit.Web.Articles.Handlers; +using Conduit.Web.Articles.Services; +using Conduit.Web.Models; +using Couchbase.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; + +namespace Conduit.Tests.Integration.Articles.Handlers; + +[TestFixture] +public class GetTagsHandlerTests : CouchbaseIntegrationTest +{ + private GetTagsHandler _handler; + private IConduitTagsCollectionProvider _tagsCollectionProvider; + + [SetUp] + public async Task SetUp() + { + await base.Setup(); + + ServiceCollection.AddCouchbaseBucket("ConduitIntegrationTests", b => + { + b + .AddScope("_default") + .AddCollection("Tags"); + }); + + _tagsCollectionProvider = ServiceProvider.GetRequiredService(); + + // setup handler and dependencies + _handler = new GetTagsHandler(new TagsDataService(_tagsCollectionProvider)); + } + + [Test] + public async Task Handle_ReturnsGetTagsResult() + { + // Arrange + var request = new GetTagsRequest(); + + // Act + var result = await _handler.Handle(request, CancellationToken.None); + + // Assert + Assert.NotNull(result); + Assert.That(result.Tags.Contains("cruising"), Is.True); + } +} diff --git a/Conduit/Conduit.Web/Articles/Controllers/TagController.cs b/Conduit/Conduit.Web/Articles/Controllers/TagController.cs new file mode 100644 index 0000000000..2a93443e51 --- /dev/null +++ b/Conduit/Conduit.Web/Articles/Controllers/TagController.cs @@ -0,0 +1,32 @@ +using Conduit.Web.Articles.Handlers; +using MediatR; +using Microsoft.AspNetCore.Mvc; + +namespace Conduit.Web.Articles.Controllers; + +public class TagController : Controller +{ + private readonly IMediator _mediator; + + public TagController(IMediator mediator) + { + _mediator = mediator; + } + + /// + /// List all (allowed) tags + /// + /// + /// Conduit Spec for get tags endpoint + /// + /// List of tags + /// Returns all allowed tags + [Route("/api/tags")] + [HttpGet] + public async Task Get() + { + var tags = await _mediator.Send(new GetTagsRequest()); + + return Ok(tags); + } +} \ No newline at end of file diff --git a/Conduit/Conduit.Web/Articles/Handlers/GetTagsHandler.cs b/Conduit/Conduit.Web/Articles/Handlers/GetTagsHandler.cs new file mode 100644 index 0000000000..7aa4c6d87f --- /dev/null +++ b/Conduit/Conduit.Web/Articles/Handlers/GetTagsHandler.cs @@ -0,0 +1,24 @@ +using Conduit.Web.Articles.Services; +using MediatR; + +namespace Conduit.Web.Articles.Handlers; + +public class GetTagsHandler : IRequestHandler +{ + private readonly ITagsDataService _tagsDataService; + + public GetTagsHandler(ITagsDataService tagsDataService) + { + _tagsDataService = tagsDataService; + } + + public async Task Handle(GetTagsRequest request, CancellationToken cancellationToken) + { + var result = await _tagsDataService.GetAllTags(); + + return new GetTagsResult + { + Tags = result + }; + } +} \ No newline at end of file diff --git a/Conduit/Conduit.Web/Articles/Handlers/GetTagsRequest.cs b/Conduit/Conduit.Web/Articles/Handlers/GetTagsRequest.cs new file mode 100644 index 0000000000..3f96374311 --- /dev/null +++ b/Conduit/Conduit.Web/Articles/Handlers/GetTagsRequest.cs @@ -0,0 +1,13 @@ +using MediatR; + +namespace Conduit.Web.Articles.Handlers; + +public class GetTagsRequest : IRequest +{ + +} + +public class GetTagsResult +{ + public List Tags { get; set; } +} \ No newline at end of file diff --git a/Conduit/Conduit.Web/Articles/Services/TagsDataService.cs b/Conduit/Conduit.Web/Articles/Services/TagsDataService.cs new file mode 100644 index 0000000000..5e649a5e20 --- /dev/null +++ b/Conduit/Conduit.Web/Articles/Services/TagsDataService.cs @@ -0,0 +1,28 @@ +using Conduit.Web.Models; + +namespace Conduit.Web.Articles.Services; + +public interface ITagsDataService +{ + Task> GetAllTags(); +} + +public class TagsDataService : ITagsDataService +{ + private readonly IConduitTagsCollectionProvider _tagsCollectionProvider; + + public TagsDataService(IConduitTagsCollectionProvider tagsCollectionProvider) + { + _tagsCollectionProvider = tagsCollectionProvider; + } + + public async Task> GetAllTags() + { + var collection = await _tagsCollectionProvider.GetCollectionAsync(); + + var tagsDoc = await collection.GetAsync("tagData"); + var tagData = tagsDoc.ContentAs(); + + return tagData.Tags; + } +} \ No newline at end of file diff --git a/Conduit/Conduit.Web/Conduit.Web.csproj b/Conduit/Conduit.Web/Conduit.Web.csproj index b58341090d..7d0452c041 100644 --- a/Conduit/Conduit.Web/Conduit.Web.csproj +++ b/Conduit/Conduit.Web/Conduit.Web.csproj @@ -20,4 +20,10 @@ + + + + + + diff --git a/Conduit/Conduit.Web/Models/IConduitTagsCollectionProvider.cs b/Conduit/Conduit.Web/Models/IConduitTagsCollectionProvider.cs new file mode 100644 index 0000000000..1f12163313 --- /dev/null +++ b/Conduit/Conduit.Web/Models/IConduitTagsCollectionProvider.cs @@ -0,0 +1,8 @@ +using Couchbase.Extensions.DependencyInjection; + +namespace Conduit.Web.Models; + +public interface IConduitTagsCollectionProvider : INamedCollectionProvider +{ + +} \ No newline at end of file diff --git a/Conduit/Conduit.Web/Models/TagData.cs b/Conduit/Conduit.Web/Models/TagData.cs new file mode 100644 index 0000000000..c7377a3867 --- /dev/null +++ b/Conduit/Conduit.Web/Models/TagData.cs @@ -0,0 +1,6 @@ +namespace Conduit.Web.Models; + +public class TagData +{ + public List Tags { get; set; } +} \ No newline at end of file diff --git a/Conduit/Conduit.Web/Program.cs b/Conduit/Conduit.Web/Program.cs index fc1e5414e1..bee153d209 100644 --- a/Conduit/Conduit.Web/Program.cs +++ b/Conduit/Conduit.Web/Program.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.IdentityModel.Tokens; using System.Text; +using Conduit.Web.Articles.Services; using Conduit.Web.Follows.Services; using Conduit.Web.Models; using Conduit.Web.Users.Handlers; @@ -74,6 +75,7 @@ public static void AddConduitServiceDependencies(this IServiceCollection @this, @this.AddTransient(); @this.AddTransient(); @this.AddTransient(); + @this.AddTransient(); @this.AddMediatR(cfg => cfg.RegisterServicesFromAssemblyContaining()); @this.AddCouchbase(configManager.GetSection("Couchbase")); @this.AddCouchbaseBucket(configManager["Couchbase:BucketName"], b => @@ -84,6 +86,9 @@ public static void AddConduitServiceDependencies(this IServiceCollection @this, b .AddScope(configManager["Couchbase:ScopeName"]) .AddCollection(configManager["Couchbase:FollowsCollectionName"]); + b + .AddScope(configManager["Couchbase:ScopeName"]) + .AddCollection(configManager["Couchbase:TagsCollectionName"]); }); } } diff --git a/Conduit/Conduit.Web/appsettings.json b/Conduit/Conduit.Web/appsettings.json index 51f8fb7ced..53f46e7b47 100644 --- a/Conduit/Conduit.Web/appsettings.json +++ b/Conduit/Conduit.Web/appsettings.json @@ -19,6 +19,7 @@ "BucketName": "Conduit", "ScopeName": "_default", "UsersCollectionName": "Users", - "FollowsCollectionName": "Follows" + "FollowsCollectionName": "Follows", + "TagsCollectionName": "Tags" } } diff --git a/README.md b/README.md index f1252d280c..85205c2db9 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ You'll need to understand at least a little bit: * *Sliced* folders - these contain ASP.NET Core Controller(s), Mediatr request, response, and handler classes, viewmodels, and services for the functionality of the slice. * Users - Authorization/authentication, JWT, registration, login, anything for Users * Follows - Follow/unfollow + * Articles - Articles and tags for articles * ...more on the way... * *Extensions* folder - extension methods for ASP.NET functionality