diff --git a/server/nt.microservice/aggregatorservices/UserIdentityAggregatorService/UserIdentityAggregatorService.Api/Controllers/UserController.cs b/server/nt.microservice/aggregatorservices/UserIdentityAggregatorService/UserIdentityAggregatorService.Api/Controllers/UserController.cs index d0a23b85..f4339fbf 100644 --- a/server/nt.microservice/aggregatorservices/UserIdentityAggregatorService/UserIdentityAggregatorService.Api/Controllers/UserController.cs +++ b/server/nt.microservice/aggregatorservices/UserIdentityAggregatorService/UserIdentityAggregatorService.Api/Controllers/UserController.cs @@ -46,11 +46,11 @@ public async Task> ValidateUser(Vali return Ok(new ValidateUserResponseViewModel { IsAuthenticated = true, - Token = authenticateResponse.Token, + Token = authenticateResponse.Token?? string.Empty, LoginTime = authenticateResponse.LoginTime, UserName = authenticateResponse.UserName, - Bio = userDetails.User.Bio, - DisplayName = userDetails.User.DisplayName, + Bio = userDetails?.User.Bio ?? string.Empty, + DisplayName = userDetails?.User.DisplayName, }); } catch (Exception ex) diff --git a/server/nt.microservice/docker-compose.yml b/server/nt.microservice/docker-compose.yml index cd1c4b76..0ca203cf 100644 --- a/server/nt.microservice/docker-compose.yml +++ b/server/nt.microservice/docker-compose.yml @@ -299,15 +299,20 @@ services: context: . dockerfile: services/ReviewService/ReviewService.Presentation.Api/Dockerfile - nt-reviewservice-db: - image: couchbase:community - container_name: nt-reviewservice-db + nt.reviewservice.db: + image: mongo:latest + hostname : nt.reviewservice.db + container_name : nt.reviewservice.db + restart: always environment: - - CB_USERNAME=Administrator - - CB_PASSWORD=password + MONGO_INITDB_ROOT_USERNAME: root + MONGO_INITDB_ROOT_PASSWORD: mypass + networks: + nt.reviewservice.network: ports: - - "8091:8091" # Couchbase Web UI (Dashboard) - - "8093:8093" # Query Service (N1QL) + - "27018:27017" + volumes: + - nt.movieservice.db.volume:/data/db ############################################################################################################### # Infrastructure Services @@ -426,6 +431,8 @@ networks: driver: bridge nt.movieservice.network: driver: bridge + nt.reviewservice.network: + driver: bridge nt.common.network: driver: bridge diff --git a/server/nt.microservice/infrastructure/nt.gateway/OcelotExtensions/DynamicHostReplacementHandler.cs b/server/nt.microservice/infrastructure/nt.gateway/OcelotExtensions/DynamicHostReplacementHandler.cs index e468d6c5..752cebee 100644 --- a/server/nt.microservice/infrastructure/nt.gateway/OcelotExtensions/DynamicHostReplacementHandler.cs +++ b/server/nt.microservice/infrastructure/nt.gateway/OcelotExtensions/DynamicHostReplacementHandler.cs @@ -4,10 +4,10 @@ protected override async Task SendAsync(HttpRequestMessage { if (Environment.GetEnvironmentVariable("RUNNING_WITH")?.ToUpper() == "ASPIRE") { - request.RequestUri = new Uri($"http://localhost:{request.RequestUri.Port}" + request.RequestUri.PathAndQuery); // Set the base URL for the request + request.RequestUri = new Uri($"http://localhost:{request?.RequestUri?.Port}" + request?.RequestUri?.PathAndQuery); // Set the base URL for the request } - var response = await base.SendAsync(request, token); + var response = await base.SendAsync(request!, token); // Do post-processing of the response... return response; } diff --git a/server/nt.microservice/infrastructure/nt.helper/Constants.cs b/server/nt.microservice/infrastructure/nt.helper/Constants.cs index 49f1f082..03ac84ae 100644 --- a/server/nt.microservice/infrastructure/nt.helper/Constants.cs +++ b/server/nt.microservice/infrastructure/nt.helper/Constants.cs @@ -135,4 +135,25 @@ public static class EnvironmentVariable public const string DbCollection = "MovieDatabase__MovieCollectionName"; } } + + public static class ReviewService + { + public const string ServiceName = "nt-reviewservice-service"; + + public static class Database + { + public const string InstanceName = "nt-reviewservice-db"; + public const string ContainerName = "nt.reviewservice.db"; + public const string UserNameKey = $"{ServiceName}-UserName"; + public const string PasswordKey = $"{ServiceName}-Password"; + } + + public static class EnvironmentVariable + { + public const string DbUserNameKey = "MONGO_INITDB_ROOT_USERNAME"; + public const string DbPasswordKey = "MONGO_INITDB_ROOT_PASSWORD"; + public const string DbName = "ReviewDatabase__DatabaseName"; + public const string DbCollection = "ReviewDatabase__ReviewCollectionName"; + } + } } diff --git a/server/nt.microservice/infrastructure/nt.orchestrator.AppHost/Program.cs b/server/nt.microservice/infrastructure/nt.orchestrator.AppHost/Program.cs index 4967efb7..25daa839 100644 --- a/server/nt.microservice/infrastructure/nt.orchestrator.AppHost/Program.cs +++ b/server/nt.microservice/infrastructure/nt.orchestrator.AppHost/Program.cs @@ -50,7 +50,7 @@ var mongoDbUsername = builder.AddParameter(Constants.MovieService.Database.UserNameKey, infrastructureSettings.MongoDb.UserName, secret: true); var mongoDbPassword = builder.AddParameter(Constants.MovieService.Database.PasswordKey, infrastructureSettings.MongoDb.Password, secret: true); -var mongoDb = builder.AddMongoDB(Constants.MovieService.Database.InstanceName, 27017, userName: mongoDbUsername, password: mongoDbPassword) +var mongoDbMovie = builder.AddMongoDB(Constants.MovieService.Database.InstanceName, 27017, userName: mongoDbUsername, password: mongoDbPassword) .WithEnvironment(Constants.MovieService.EnvironmentVariable.DbUserNameKey, infrastructureSettings.MongoDb.UserName) .WithEnvironment(Constants.MovieService.EnvironmentVariable.DbPasswordKey, infrastructureSettings.MongoDb.Password) //.WithEndpoint(port: 27017, targetPort: 27017, isProxied: true) @@ -58,6 +58,14 @@ .WithDataVolume() .WithMongoExpress(); +var mongoDbReview = builder.AddMongoDB(Constants.ReviewService.Database.InstanceName, 27018, userName: mongoDbUsername, password: mongoDbPassword) + .WithEnvironment(Constants.ReviewService.EnvironmentVariable.DbUserNameKey, infrastructureSettings.MongoDb.UserName) + .WithEnvironment(Constants.ReviewService.EnvironmentVariable.DbPasswordKey, infrastructureSettings.MongoDb.Password) + //.WithEndpoint(port: 27017, targetPort: 27017, isProxied: true) + .WithContainerName(Constants.ReviewService.Database.ContainerName) + .WithDataVolume() + .WithMongoExpress(); + var blobStorage = builder.AddContainer("nt-userservice-blobstorage", infrastructureSettings.BlobStorage.DockerImage) .WithVolume("//d/Source/nt/server/nt.microservice/services/UserService/BlobStorage:/data") .WithArgs("azurite-blob", "--blobHost", "0.0.0.0", "-l", "/data") @@ -70,14 +78,6 @@ .WithEnvironment("MSSQL_SA_PASSWORD", infrastructureSettings.SqlServer.Password) .WithHttpEndpoint(port: infrastructureSettings.SqlServer.HostPort, targetPort: infrastructureSettings.SqlServer.TargetPort, isProxied: true); -var couchbase = builder.AddContainer("nt-reviewservice-db", "couchbase:community") - .WithEnvironment("CB_USERNAME", "Administrator") - .WithEnvironment("CB_PASSWORD", "password") - .WithEndpoint(port: 8091, targetPort:8091, scheme:"http") - .WithEndpoint(port: 8093, targetPort:8093) - .WithHttpHealthCheck("/pools"); - - var authServiceInstances = new List>(); foreach(var port in serviceSettings.AuthService.InstancePorts) { @@ -167,8 +167,8 @@ .WithEnvironment(Constants.Infrastructure.Consul.Environement.ServiceHost, serviceSettings.MovieService.ServiceRegistrationConfig.ServiceHost) .WithEnvironment(Constants.Infrastructure.Consul.Environement.RegistryUri, consulServiceDiscovery.GetEndpoint("http")) .WithEnvironment(Constants.Infrastructure.Consul.Environement.DeregisterAfter, serviceSettings.MovieService.ServiceRegistrationConfig.DeregisterAfterMinutes.ToString()) - .WithReference(mongoDb) - .WaitFor(mongoDb) + .WithReference(mongoDbMovie) + .WaitFor(mongoDbMovie) .WaitFor(consulServiceDiscovery) .WithUrls(c => c.Urls.ForEach(u => u.DisplayText = $"Open API ({u.Endpoint?.Port})")); ; @@ -179,7 +179,8 @@ var reviewService = builder.AddProject("nt-reviewservice-service") .WithEnvironment(Constants.Global.EnvironmentVariables.RunningWithVariable, Constants.Global.EnvironmentVariables.RunningWithValue) .WithUrls(c => c.Urls.ForEach(u => u.DisplayText = $"Open API ({u.Endpoint?.EndpointName})")) - .WaitFor(couchbase); + .WithReference(mongoDbReview) + .WaitFor(mongoDbReview); var gateway = builder.AddProject(Constants.Gateway.ServiceName, launchProfileName: Constants.Gateway.LaunchProfile) diff --git a/server/nt.microservice/services/MovieService/MovieService.Api/BackgroundServices/ServiceRegistration.cs b/server/nt.microservice/services/MovieService/MovieService.Api/BackgroundServices/ConsulServiceRegistrationService.cs similarity index 89% rename from server/nt.microservice/services/MovieService/MovieService.Api/BackgroundServices/ServiceRegistration.cs rename to server/nt.microservice/services/MovieService/MovieService.Api/BackgroundServices/ConsulServiceRegistrationService.cs index d17aa872..ae5fe18d 100644 --- a/server/nt.microservice/services/MovieService/MovieService.Api/BackgroundServices/ServiceRegistration.cs +++ b/server/nt.microservice/services/MovieService/MovieService.Api/BackgroundServices/ConsulServiceRegistrationService.cs @@ -4,15 +4,15 @@ namespace MovieService.Api.BackgroundServices; -public class ServiceRegistration : BackgroundService +public class ConsulServiceRegistrationService : BackgroundService { private readonly IConsulClient _consulClient; private readonly ServiceRegistrationConfig _serviceDiscoveryConfiguration; - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly IHostApplicationLifetime _lifetime; - public ServiceRegistration(IConsulClient consultClient, + public ConsulServiceRegistrationService(IConsulClient consultClient, IOptions serviceDiscoveryConfigurations, - ILogger logger, + ILogger logger, IHostApplicationLifetime lifetime) { _consulClient = consultClient ?? throw new ArgumentNullException(nameof(consultClient)); diff --git a/server/nt.microservice/services/MovieService/MovieService.Api/ModuleInitializer.cs b/server/nt.microservice/services/MovieService/MovieService.Api/ModuleInitializer.cs index b71e8099..62724245 100644 --- a/server/nt.microservice/services/MovieService/MovieService.Api/ModuleInitializer.cs +++ b/server/nt.microservice/services/MovieService/MovieService.Api/ModuleInitializer.cs @@ -5,7 +5,6 @@ using MovieService.Data; using MovieService.Data.Interfaces.Entities; using MovieService.Data.Seed; -using MovieService.Service.Interfaces.Dtos; namespace MovieService.Api; diff --git a/server/nt.microservice/services/MovieService/MovieService.Api/Program.cs b/server/nt.microservice/services/MovieService/MovieService.Api/Program.cs index 6ec34676..ef5ed0c4 100644 --- a/server/nt.microservice/services/MovieService/MovieService.Api/Program.cs +++ b/server/nt.microservice/services/MovieService/MovieService.Api/Program.cs @@ -63,7 +63,7 @@ return new ConsulClient(consulConfig); }); -builder.Services.AddHostedService(); +builder.Services.AddHostedService(); var app = builder.Build(); diff --git a/server/nt.microservice/services/ReviewService/ReviewService.Infrastructure.Repository/Documents/ReviewDocument.cs b/server/nt.microservice/services/ReviewService/ReviewService.Infrastructure.Repository/Documents/ReviewDocument.cs new file mode 100644 index 00000000..a0ffa456 --- /dev/null +++ b/server/nt.microservice/services/ReviewService/ReviewService.Infrastructure.Repository/Documents/ReviewDocument.cs @@ -0,0 +1,26 @@ +using MongoDB.Entities; + +namespace ReviewService.Infrastructure.Repository.Documents; + +[Collection(ReviewDocument.CollectionName)] +public class ReviewDocument:Entity +{ + internal const string CollectionName = "reviews"; + + + [Field("movieId")] + public Guid MovieId { get; set; } + + [Field("title")] + public string Title { get; set; } = string.Empty; + + [Field("content")] + public string Content { get; set; } = string.Empty; + + [Field("rating")] + public int Rating { get; set; } + + [Field("userName")] + public string UserName { get; set; } = string.Empty; + +} diff --git a/server/nt.microservice/services/ReviewService/ReviewService.Infrastructure.Repository/ReviewService.Infrastructure.Repository.csproj b/server/nt.microservice/services/ReviewService/ReviewService.Infrastructure.Repository/ReviewService.Infrastructure.Repository.csproj index 04125aee..1a510628 100644 --- a/server/nt.microservice/services/ReviewService/ReviewService.Infrastructure.Repository/ReviewService.Infrastructure.Repository.csproj +++ b/server/nt.microservice/services/ReviewService/ReviewService.Infrastructure.Repository/ReviewService.Infrastructure.Repository.csproj @@ -6,6 +6,10 @@ enable + + + + diff --git a/server/nt.microservice/services/ReviewService/ReviewService.Infrastructure.Repository/Seed/MalayalamReviewsSeed.cs b/server/nt.microservice/services/ReviewService/ReviewService.Infrastructure.Repository/Seed/MalayalamReviewsSeed.cs new file mode 100644 index 00000000..26db74b5 --- /dev/null +++ b/server/nt.microservice/services/ReviewService/ReviewService.Infrastructure.Repository/Seed/MalayalamReviewsSeed.cs @@ -0,0 +1,160 @@ +using ReviewService.Infrastructure.Repository.Documents; + +namespace ReviewService.Infrastructure.Repository.Seed; + +public static class MalayalamReviewsSeed +{ + public static IEnumerable Reviews => [ + new() { + Content = "A heartwarming tale with mouthwatering visuals and a soulful story. Loved it!", + ID = Guid.NewGuid().ToString(), + MovieId = Guid.Parse("6191e634-14c8-45d1-898f-191060cdbec1"), + Rating = 5, + Title = "Ustad Hotel Feels", + UserName = "moviebuff_91" + }, + new() { + Content = "Dulquer and Thilakan make this movie a beautiful emotional ride. Music was perfect.", + ID = Guid.NewGuid().ToString(), + MovieId = Guid.Parse("6191e634-14c8-45d1-898f-191060cdbec1"), + Rating = 4, + Title = "Beautiful Blend", + UserName = "cinemalover" + }, + new() { + Content = "Joji is an intense, slow burn thriller. Brilliant acting by Fahadh as always.", + ID = Guid.NewGuid().ToString(), + MovieId = Guid.Parse("bea50cb7-41b6-4a35-b77f-358e8c43f850"), + Rating = 4, + Title = "Dark and Gripping", + UserName = "screenaddict" + }, + new() { + Content = "Minimal dialogues, maximum impact. Joji is a masterclass in modern Malayalam cinema.", + ID = Guid.NewGuid().ToString(), + MovieId = Guid.Parse("bea50cb7-41b6-4a35-b77f-358e8c43f850"), + Rating = 5, + Title = "Minimalist Brilliance", + UserName = "cinecritic" + }, + new() { + Content = "A hilarious ride with unexpected twists. Oru Vadakkan Selfie is pure fun.", + ID = Guid.NewGuid().ToString(), + MovieId = Guid.Parse("bd4b80a0-1516-4b71-887b-714c52459f23"), + Rating = 4, + Title = "Comedy Hit", + UserName = "ajay.m" + }, + new() { + Content = "Selfie gone wrong but in the best way possible. Loved the storytelling and humor.", + ID = Guid.NewGuid().ToString(), + MovieId = Guid.Parse("bd4b80a0-1516-4b71-887b-714c52459f23"), + Rating = 5, + Title = "Smart Comedy", + UserName = "techjunkie" + }, + new() { + Content = "Malik is powerful. A political saga with gripping performances. Fahadh nailed it!", + ID = Guid.NewGuid().ToString(), + MovieId = Guid.Parse("0003be11-f19a-4b9c-a1b2-b7e195b53d3e"), + Rating = 5, + Title = "Political Power", + UserName = "seriouscinema" + }, + new() { + Content = "One of the best performances by Fahadh. Malik stays with you long after it ends.", + ID = Guid.NewGuid().ToString(), + MovieId = Guid.Parse("0003be11-f19a-4b9c-a1b2-b7e195b53d3e"), + Rating = 5, + Title = "Brilliant Execution", + UserName = "rajfilm" + }, + new() { + Content = "Premam is nostalgic, fun, and full of charm. Every phase of love was shown beautifully.", + ID = Guid.NewGuid().ToString(), + MovieId = Guid.Parse("af3e9bed-3e04-4f06-856d-3c572605bf4d"), + Rating = 5, + Title = "Love Story Goals", + UserName = "dreamy_eyes" + }, + new() { + Content = "Great music, wonderful performances. Premam is a modern Malayalam classic.", + ID = Guid.NewGuid().ToString(), + MovieId = Guid.Parse("af3e9bed-3e04-4f06-856d-3c572605bf4d"), + Rating = 5, + Title = "Evergreen", + UserName = "anu_reviews" + }, + new() { + Content = "Jana Gana Mana is thought-provoking. Raises valid questions about justice and media.", + ID = Guid.NewGuid().ToString(), + MovieId = Guid.Parse("47435a1d-6b24-4cfc-b3a6-0be543322187"), + Rating = 4, + Title = "Relevant and Bold", + UserName = "truthseeker" + }, + new() { + Content = "Powerful script and solid performances. Worth watching more than once.", + ID = Guid.NewGuid().ToString(), + MovieId = Guid.Parse("47435a1d-6b24-4cfc-b3a6-0be543322187"), + Rating = 5, + Title = "Must Watch", + UserName = "prithvi_rules" + }, + new() { + Content = "Ustad Hotel remains a comfort movie. It warms the soul and stirs the appetite.", + ID = Guid.NewGuid().ToString(), + MovieId = Guid.Parse("6191e634-14c8-45d1-898f-191060cdbec1"), + Rating = 5, + Title = "Feel-Good Watch", + UserName = "greenchili" + }, + new() { + Content = "Malik's political layers and gripping visuals make it a standout Malayalam film.", + ID = Guid.NewGuid().ToString(), + MovieId = Guid.Parse("0003be11-f19a-4b9c-a1b2-b7e195b53d3e"), + Rating = 4, + Title = "Strong Narrative", + UserName = "malayalicritic" + }, + new() { + Content = "Premam redefined romance in Malayalam cinema. A perfect blend of nostalgia and music.", + ID = Guid.NewGuid().ToString(), + MovieId = Guid.Parse("af3e9bed-3e04-4f06-856d-3c572605bf4d"), + Rating = 5, + Title = "Romantic Classic", + UserName = "cinepulse" + }, + new() { + Content = "Joji’s silence speaks louder than words. A chilling adaptation of Macbeth.", + ID = Guid.NewGuid().ToString(), + MovieId = Guid.Parse("bea50cb7-41b6-4a35-b77f-358e8c43f850"), + Rating = 4, + Title = "Psychological Drama", + UserName = "joji_fan" + }, + new() { + Content = "Loved every bit of Oru Vadakkan Selfie. Light-hearted and perfect for a rewatch!", + ID = Guid.NewGuid().ToString(), + MovieId = Guid.Parse("bd4b80a0-1516-4b71-887b-714c52459f23"), + Rating = 4, + Title = "Laugh Riot", + UserName = "minnal_m" + }, + new() { + Content = "Jana Gana Mana deserves more attention. Thought-provoking and very well directed.", + ID = Guid.NewGuid().ToString(), + MovieId = Guid.Parse("47435a1d-6b24-4cfc-b3a6-0be543322187"), + Rating = 5, + Title = "Social Eye-Opener", + UserName = "vocalviewer" + }, + new() { + Content = "Premam’s characters grow on you. Sai Pallavi was just magical!", + ID = Guid.NewGuid().ToString(), + MovieId = Guid.Parse("af3e9bed-3e04-4f06-856d-3c572605bf4d"), + Rating = 5, + Title = "Romance and Charm" + } + ]; +} diff --git a/server/nt.microservice/services/ReviewService/ReviewService.Infrastructure.Repository/Seed/Seed.cs b/server/nt.microservice/services/ReviewService/ReviewService.Infrastructure.Repository/Seed/Seed.cs new file mode 100644 index 00000000..36031f0e --- /dev/null +++ b/server/nt.microservice/services/ReviewService/ReviewService.Infrastructure.Repository/Seed/Seed.cs @@ -0,0 +1,9 @@ +using ReviewService.Infrastructure.Repository.Documents; + +namespace ReviewService.Infrastructure.Repository.Seed; + +public static class Seed +{ + public static IEnumerable MalayalamReviews => MalayalamReviewsSeed.Reviews; + +} diff --git a/server/nt.microservice/services/ReviewService/ReviewService.Presentation.Api/Controllers/UserReviewsController.cs b/server/nt.microservice/services/ReviewService/ReviewService.Presentation.Api/Controllers/UserReviewsController.cs index bf8bd4bc..30abe7cc 100644 --- a/server/nt.microservice/services/ReviewService/ReviewService.Presentation.Api/Controllers/UserReviewsController.cs +++ b/server/nt.microservice/services/ReviewService/ReviewService.Presentation.Api/Controllers/UserReviewsController.cs @@ -56,7 +56,7 @@ public async Task> CreateReview(CreateReviewR } catch (Exception e) { - _logger.LogError(@"An error occurred while creating the review.", e); + _logger.LogError(e, "An error occurred while creating the review."); return BadRequest(e); } } diff --git a/server/nt.microservice/services/ReviewService/ReviewService.Presentation.Api/Helpers/IServiceCollectionExtension.cs b/server/nt.microservice/services/ReviewService/ReviewService.Presentation.Api/Helpers/IServiceCollectionExtension.cs new file mode 100644 index 00000000..c4b1d9cb --- /dev/null +++ b/server/nt.microservice/services/ReviewService/ReviewService.Presentation.Api/Helpers/IServiceCollectionExtension.cs @@ -0,0 +1,23 @@ +using ReviewService.Application.Interfaces.Operations; + +namespace ReviewService.Presenation.Api.Helpers; + +public static class IServiceCollectionExtension +{ + public static void RegisterServices(this IServiceCollection serviceCollection) + { + // Register your services here + // Example: serviceCollection.AddSingleton(); + + // Register initializers and providers + RegisterInitializersAndProviders(serviceCollection); + } + private static void RegisterInitializersAndProviders(IServiceCollection serviceCollection) + { + // Add any initializers or providers needed for the application + // Example: serviceCollection.AddSingleton(); + + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + } +} diff --git a/server/nt.microservice/services/ReviewService/ReviewService.Presentation.Api/HostedServices/ClusterBootstrapService.cs b/server/nt.microservice/services/ReviewService/ReviewService.Presentation.Api/HostedServices/ClusterBootstrapService.cs deleted file mode 100644 index ff1f69aa..00000000 --- a/server/nt.microservice/services/ReviewService/ReviewService.Presentation.Api/HostedServices/ClusterBootstrapService.cs +++ /dev/null @@ -1,138 +0,0 @@ -using Microsoft.Extensions.Options; -using ReviewService.Presenation.Api.Options; -using System.Net.Http.Headers; -using System.Text; -using System.Text.Json; - -namespace ReviewService.Presenation.Api.HostedServices; -public class ClusterBootstrapService : IHostedService -{ - private readonly ILogger _logger; - private readonly HttpClient _httpClient = new(); - - private string CouchbaseHost => _settings.ConnectionString.Replace("couchbase://", "http://"); // for REST API - private string Username => _settings.Username; - private string Password => _settings.Password; - - private string ClusterName => _settings.ClusterName; - private readonly CouchbaseSettings _settings; - - public ClusterBootstrapService(IOptions settings, ILogger logger) - { - _logger = logger; - _settings = settings.Value ?? throw new ArgumentNullException(nameof(settings)); - } - - public async Task StartAsync(CancellationToken cancellationToken) - { - _logger.LogInformation("Starting Couchbase cluster bootstrap..."); - - // crude wait — replace with health check polling if needed - await Task.Delay(5000, cancellationToken); - - try - { - if (!await IsNodeFullyInitialized()) - { - await SetupServices(); - await ConfigureClusterMemory(); - await SetupAdminCredentials(); - } - - _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue( - "Basic", - Convert.ToBase64String(Encoding.ASCII.GetBytes($"{Username}:{Password}"))); - - await RenameNode(); - _logger.LogInformation("Couchbase cluster bootstrap complete."); - } - catch (Exception ex) - { - _logger.LogError(ex, "Cluster bootstrap failed"); - } - } - - private async Task IsNodeFullyInitialized() - { - var response = await _httpClient.GetAsync($"{CouchbaseHost}/pools/default"); - var json = await response.Content.ReadAsStringAsync(); - var doc = JsonDocument.Parse(json); - var node = doc.RootElement.GetProperty("nodes")[0]; - var services = node.GetProperty("services").EnumerateArray().Select(e => e.GetString()).ToList(); - return services.Contains("mgmt") && services.Contains("n1ql") && services.Contains("index"); - } - - public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; - - private async Task SetupServices() - { - await PostForm($"{CouchbaseHost}/node/controller/setupServices", new Dictionary - { - { "services", "kv,n1ql,index,mgmt" } - }); - } - - private async Task ConfigureClusterMemory() - { - await PostForm($"{CouchbaseHost}/pools/default", new Dictionary - { - { "memoryQuota", "256" }, - { "indexMemoryQuota", "256" }, - { "clusterName", ClusterName }, - { "ftsMemoryQuota", "256" } - }); - - //await PostForm($"{CouchbaseHost}/nodes/self/controller/settings", new Dictionary - //{ - // { "path", "/opt/couchbase/var/lib/couchbase" }, - // { "index_path", "/opt/couchbase/var/lib/couchbase" } - //}); - - - } - - private async Task RenameNode() - { - //await PostForm($"{CouchbaseHost}/node/controller/rename", new Dictionary - //{ - // { "hostname", "nt-reviewservice-db" } // Or: "nt-reviewservice-db" if using that container name in Aspire - //}); - } - private async Task SetupAdminCredentials() - { - await PostForm($"{CouchbaseHost}/settings/web", new Dictionary - { - { "username", Username }, - { "password", Password }, - { "port", "8091" } - }); - } - - private async Task PostForm(string url, Dictionary formData, bool addAuth = true) - { - var request = new HttpRequestMessage(HttpMethod.Post, url) - { - Content = new FormUrlEncodedContent(formData) - }; - - if (addAuth && _httpClient.DefaultRequestHeaders.Authorization == null) - { - var byteArray = Encoding.ASCII.GetBytes($"{Username}:{Password}"); - request.Headers.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(byteArray)); - } - - var response = await _httpClient.SendAsync(request); - - if (!response.IsSuccessStatusCode) - { - var body = await response.Content.ReadAsStringAsync(); - _logger.LogError("POST to {Url} failed with status {StatusCode}: {Body}", url, response.StatusCode, body); - response.EnsureSuccessStatusCode(); // will throw - - } - else - { - _logger.LogInformation("POST {Url} succeeded", url); - } - } -} diff --git a/server/nt.microservice/services/ReviewService/ReviewService.Presentation.Api/HostedServices/EnsureBucketService.cs b/server/nt.microservice/services/ReviewService/ReviewService.Presentation.Api/HostedServices/EnsureBucketService.cs deleted file mode 100644 index 8f32486e..00000000 --- a/server/nt.microservice/services/ReviewService/ReviewService.Presentation.Api/HostedServices/EnsureBucketService.cs +++ /dev/null @@ -1,83 +0,0 @@ -using Couchbase; -using Couchbase.Diagnostics; -using Couchbase.Management.Buckets; -using Microsoft.Extensions.Options; -using ReviewService.Api; -using ReviewService.Presenation.Api.Options; - -namespace ReviewService.Presenation.Api.HostedServices; - -public class EnsureBucketService : IHostedService -{ - private readonly IClusterProvider _provider; - private readonly CouchbaseSettings _options; - private readonly ILogger _logger; - - public EnsureBucketService(IClusterProvider clusterProvider, IOptions options, ILogger logger) - { - _provider = clusterProvider; - _options = options.Value; - _logger = logger ?? throw new ArgumentNullException(nameof(logger)); - } - - public async Task StartAsync(CancellationToken cancellationToken) - { - var cluster = await _provider.GetClusterAsync(); - int retries = 10; - // Wait until cluster is ready before any operations - try - { - _logger.LogInformation("Waiting for Couchbase cluster to be ready..."); - await cluster.WaitUntilReadyAsync(TimeSpan.FromSeconds(60)); - } - catch (Exception ex) - { - _logger.LogError(ex, "Cluster not ready. Aborting bucket check."); - throw; - } - - var bucketManager = cluster.Buckets; - while (retries-- > 0) - { - try - { - _logger.LogInformation("Ensuring bucket '{BucketName}' exists...", _options.BucketName); - - var allBuckets = await bucketManager.GetAllBucketsAsync(); - if (!allBuckets.ContainsKey(_options.BucketName)) - { - try - { - var settings = new BucketSettings - { - Name = _options.BucketName, - BucketType = BucketType.Couchbase, - RamQuotaMB = 100, - FlushEnabled = true - }; - await bucketManager.CreateBucketAsync(settings); - break; - } - catch (Exception ex) - { - _logger.LogError(ex, "Failed to create bucket '{BucketName}'. Retrying...", _options.BucketName); - } - - } - else - { - _logger.LogInformation("Bucket '{BucketName}' already exists.", _options.BucketName); - break; - } - } - catch (ServiceNotAvailableException e) - { - _logger.LogError(e,"Couchbase service not available, retrying in 3 seconds..."); - await Task.Delay(3000); - } - } - } - - public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; -} - diff --git a/server/nt.microservice/services/ReviewService/ReviewService.Presentation.Api/ModuleInitializer.cs b/server/nt.microservice/services/ReviewService/ReviewService.Presentation.Api/ModuleInitializer.cs new file mode 100644 index 00000000..b58a1c4e --- /dev/null +++ b/server/nt.microservice/services/ReviewService/ReviewService.Presentation.Api/ModuleInitializer.cs @@ -0,0 +1,81 @@ +using Microsoft.Extensions.Options; +using MongoDB.Bson; +using MongoDB.Driver; +using MongoDB.Entities; +using ReviewService.Infrastructure.Repository.Documents; +using ReviewService.Infrastructure.Repository.Seed; +using ReviewService.Presenation.Api.Options; + +namespace ReviewService.Presenation.Api; + +public class ModuleInitializer +{ + private readonly DatabaseInitializer _dbInitializer; + public ModuleInitializer(DatabaseInitializer dbInitializer) + { + _dbInitializer = dbInitializer; + } + public async Task Initialize() + { + await ExecuteDatabaseSeeding().ConfigureAwait(false); + } + + private async Task ExecuteDatabaseSeeding() + { + await _dbInitializer.Initialize(); + } +} + +public class DatabaseInitializer +{ + private readonly DatabaseOptions _databaseSettings; + private readonly IMongoDatabase _database; + public DatabaseInitializer(IOptions databaseSettings) + { + + _databaseSettings = databaseSettings.Value; + + _databaseSettings.ConnectionString = Environment.GetEnvironmentVariable("ConnectionStrings__nt-reviewservice-db") ?? _databaseSettings.ConnectionString; + + DB.InitAsync(_databaseSettings.DatabaseName, MongoClientSettings.FromConnectionString(_databaseSettings.ConnectionString)); + _database = DB.Database(_databaseSettings.DatabaseName); + } + + public async Task Initialize() + { + await IndexInitializer.Setup(); + + var collection = await GetCollection(_databaseSettings.ReviewCollectionName); + bool exists = await collection.Find(FilterDefinition.Empty) + .AnyAsync(); + + if (collection is not null && !exists) + { + var documents = Seed.MalayalamReviews; + await collection.InsertManyAsync(documents).ConfigureAwait(false); + } + } + + + private async Task?> GetCollection(string collectionName) + { + var filter = new BsonDocument("name", collectionName); + var collections = await _database.ListCollectionNamesAsync(new ListCollectionNamesOptions + { + Filter = filter + }); + + return _database.GetCollection(_databaseSettings.ReviewCollectionName); + } +} + +public static class IndexInitializer +{ + public static async Task Setup() + { + await DB.Index() + .Key(x => x.Title, KeyType.Text) + //.Key(x => x.Description, KeyType.Text) TODO For Future + .CreateAsync(); + } +} diff --git a/server/nt.microservice/services/ReviewService/ReviewService.Presentation.Api/Options/CouchbaseSettings.cs b/server/nt.microservice/services/ReviewService/ReviewService.Presentation.Api/Options/CouchbaseSettings.cs deleted file mode 100644 index 8b5453b3..00000000 --- a/server/nt.microservice/services/ReviewService/ReviewService.Presentation.Api/Options/CouchbaseSettings.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace ReviewService.Presenation.Api.Options; - -public record CouchbaseSettings -{ - public string ConnectionString { get; init; } = string.Empty; - public string ClusterName { get; init; } = string.Empty; - public string BucketName { get; init; } = string.Empty; - public string Username { get; init; } = string.Empty; - public string Password { get; init; } = string.Empty; -} - -// EnsureBucketService.cs - - - diff --git a/server/nt.microservice/services/ReviewService/ReviewService.Presentation.Api/Options/DatabaseOptions.cs b/server/nt.microservice/services/ReviewService/ReviewService.Presentation.Api/Options/DatabaseOptions.cs new file mode 100644 index 00000000..369d6c99 --- /dev/null +++ b/server/nt.microservice/services/ReviewService/ReviewService.Presentation.Api/Options/DatabaseOptions.cs @@ -0,0 +1,10 @@ +namespace ReviewService.Presenation.Api.Options; + +public class DatabaseOptions +{ + public string ConnectionString { get; set; } = null!; + + public string DatabaseName { get; set; } = null!; + + public string ReviewCollectionName { get; set; } = null!; +} \ No newline at end of file diff --git a/server/nt.microservice/services/ReviewService/ReviewService.Presentation.Api/Program.cs b/server/nt.microservice/services/ReviewService/ReviewService.Presentation.Api/Program.cs index e9753bdc..7d69f589 100644 --- a/server/nt.microservice/services/ReviewService/ReviewService.Presentation.Api/Program.cs +++ b/server/nt.microservice/services/ReviewService/ReviewService.Presentation.Api/Program.cs @@ -1,42 +1,38 @@ - -using Couchbase; -using Microsoft.Extensions.Options; -using ReviewService.Presenation.Api.HostedServices; +using ReviewService.Presenation.Api; +using ReviewService.Presenation.Api.Helpers; using ReviewService.Presenation.Api.Options; namespace ReviewService.Api; public class Program { - public static void Main(string[] args) + public static async Task Main(string[] args) { var builder = WebApplication.CreateBuilder(args); builder.AddServiceDefaults(); + builder.Services.Configure(builder.Configuration.GetSection(nameof(DatabaseOptions))); - - builder.Services.Configure(builder.Configuration.GetSection(nameof(CouchbaseSettings))); - - //builder.Services.AddSingleton(sp => - //{ - // var options = sp.GetRequiredService>().Value; - // var cluster = Cluster.ConnectAsync(options.ConnectionString, options.Username, options.Password) - // .GetAwaiter().GetResult(); - // Cluster.ConnectAsync() - // return cluster; - //}); - builder.Services.AddSingleton(); - builder.Services.AddHostedService(); - builder.Services.AddHostedService(); // Add services to the container. builder.Services.AddControllers(); // Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi builder.Services.AddOpenApi(); + builder.Services.RegisterServices(); var app = builder.Build(); app.MapDefaultEndpoints(); + + using (var scope = app.Services.CreateScope()) + { + var moduleInitializer = scope.ServiceProvider.GetService(); + if (moduleInitializer is not null) + { + await moduleInitializer.Initialize(); + } + } + // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { @@ -52,32 +48,3 @@ public static void Main(string[] args) } - - -public interface IClusterProvider -{ - Task GetClusterAsync(); -} - -public class LazyClusterProvider : IClusterProvider -{ - private readonly CouchbaseSettings _settings; - private ICluster _cluster; - - public LazyClusterProvider(IOptions settings) - { - _settings = settings.Value; - } - - public async Task GetClusterAsync() - { - if (_cluster != null) return _cluster; - - _cluster = await Cluster.ConnectAsync( - _settings.ConnectionString, - _settings.Username, - _settings.Password); - - return _cluster; - } -} diff --git a/server/nt.microservice/services/ReviewService/ReviewService.Presentation.Api/ReviewService.Presenation.Api.csproj b/server/nt.microservice/services/ReviewService/ReviewService.Presentation.Api/ReviewService.Presenation.Api.csproj index b79afe8e..8f686d5a 100644 --- a/server/nt.microservice/services/ReviewService/ReviewService.Presentation.Api/ReviewService.Presenation.Api.csproj +++ b/server/nt.microservice/services/ReviewService/ReviewService.Presentation.Api/ReviewService.Presenation.Api.csproj @@ -1,4 +1,4 @@ - + net9.0 @@ -12,17 +12,19 @@ - + + + diff --git a/server/nt.microservice/services/ReviewService/ReviewService.Presentation.Api/appsettings.json b/server/nt.microservice/services/ReviewService/ReviewService.Presentation.Api/appsettings.json index d7177c05..aa4a93c6 100644 --- a/server/nt.microservice/services/ReviewService/ReviewService.Presentation.Api/appsettings.json +++ b/server/nt.microservice/services/ReviewService/ReviewService.Presentation.Api/appsettings.json @@ -5,12 +5,11 @@ "Microsoft.AspNetCore": "Warning" } }, - "CouchbaseSettings": { - "ConnectionString": "couchbase://localhost:8091", - "ClusterName": "nt-reviewservice-cluster", - "BucketName": "reviews", - "Username": "Administrator", - "Password": "password" - }, - "AllowedHosts": "*" + "AllowedHosts": "*", + "DatabaseOptions": { + //"ConnectionString": "mongodb://root:mypass@nt.movieservice.db:27017", + "ConnectionString": "mongodb://root:mypass@nt.reviewservice.db:27018/?authSource=admin", + "DatabaseName": "ntreviewstore", + "ReviewCollectionName": "reviews" + } }