From 6b8cf9961bdffbfdc4520dd7b10e3cea708c45cc Mon Sep 17 00:00:00 2001 From: gamer147 Date: Fri, 21 Nov 2025 23:08:29 -0500 Subject: [PATCH] [FA-10] Adds user service and authentication service --- .../AuthenticationWebhookController.cs | 36 +++++++++ .../Dockerfile | 23 ++++++ ...chive.Service.AuthenticationService.csproj | 29 +++++++ ...Archive.Service.AuthenticationService.http | 6 ++ .../IntegrationEvents/AuthUserAddedEvent.cs | 16 ++++ .../Requests/UserRegisteredWebhookPayload.cs | 17 ++++ .../Program.cs | 49 ++++++++++++ .../Properties/launchSettings.json | 41 ++++++++++ .../appsettings.Development.json | 8 ++ .../appsettings.json | 13 +++ .../ChapterPullRequestedEvent.cs | 2 +- .../NovelUpdateRequestedEvent.cs | 2 +- .../TranslationRequestCompletedEvent.cs | 2 +- .../TranslationRequestCreatedEvent.cs | 2 +- .../Services/EventBus/EventBusBuilder.cs | 2 +- .../Services/EventBus/IEventBus.cs | 2 +- .../Services/EventBus/IIntegrationEvent.cs | 7 ++ .../EventBus/IIntegrationEventHandler.cs | 6 +- .../Implementations/RabbitMQEventBus.cs | 25 +++--- .../Services/EventBus/IntegrationEvent.cs | 9 --- .../TranslationRequestCompletedEvent.cs | 2 +- .../TranslationRequestCreatedEvent.cs | 2 +- FictionArchive.Service.UserService/Dockerfile | 23 ++++++ .../FictionArchive.Service.UserService.csproj | 27 +++++++ .../GraphQL/Mutation.cs | 13 +++ .../GraphQL/Query.cs | 12 +++ .../20251122034145_Initial.Designer.cs | 77 ++++++++++++++++++ .../Migrations/20251122034145_Initial.cs | 51 ++++++++++++ ...22040724_UniqueOAuthProviderId.Designer.cs | 80 +++++++++++++++++++ .../20251122040724_UniqueOAuthProviderId.cs | 28 +++++++ .../UserServiceDbContextModelSnapshot.cs | 77 ++++++++++++++++++ .../Models/Database/User.cs | 20 +++++ .../IntegrationEvents/AuthUserAddedEvent.cs | 16 ++++ FictionArchive.Service.UserService/Program.cs | 52 ++++++++++++ .../Properties/launchSettings.json | 39 +++++++++ .../AuthUserAddedEventHandler.cs | 23 ++++++ .../Services/UserManagementService.cs | 45 +++++++++++ .../Services/UserServiceDbContext.cs | 14 ++++ .../appsettings.Development.json | 8 ++ .../appsettings.json | 16 ++++ FictionArchive.sln | 17 ++++ 41 files changed, 910 insertions(+), 29 deletions(-) create mode 100644 FictionArchive.Service.AuthenticationService/Controllers/AuthenticationWebhookController.cs create mode 100644 FictionArchive.Service.AuthenticationService/Dockerfile create mode 100644 FictionArchive.Service.AuthenticationService/FictionArchive.Service.AuthenticationService.csproj create mode 100644 FictionArchive.Service.AuthenticationService/FictionArchive.Service.AuthenticationService.http create mode 100644 FictionArchive.Service.AuthenticationService/Models/IntegrationEvents/AuthUserAddedEvent.cs create mode 100644 FictionArchive.Service.AuthenticationService/Models/Requests/UserRegisteredWebhookPayload.cs create mode 100644 FictionArchive.Service.AuthenticationService/Program.cs create mode 100644 FictionArchive.Service.AuthenticationService/Properties/launchSettings.json create mode 100644 FictionArchive.Service.AuthenticationService/appsettings.Development.json create mode 100644 FictionArchive.Service.AuthenticationService/appsettings.json create mode 100644 FictionArchive.Service.Shared/Services/EventBus/IIntegrationEvent.cs delete mode 100644 FictionArchive.Service.Shared/Services/EventBus/IntegrationEvent.cs create mode 100644 FictionArchive.Service.UserService/Dockerfile create mode 100644 FictionArchive.Service.UserService/FictionArchive.Service.UserService.csproj create mode 100644 FictionArchive.Service.UserService/GraphQL/Mutation.cs create mode 100644 FictionArchive.Service.UserService/GraphQL/Query.cs create mode 100644 FictionArchive.Service.UserService/Migrations/20251122034145_Initial.Designer.cs create mode 100644 FictionArchive.Service.UserService/Migrations/20251122034145_Initial.cs create mode 100644 FictionArchive.Service.UserService/Migrations/20251122040724_UniqueOAuthProviderId.Designer.cs create mode 100644 FictionArchive.Service.UserService/Migrations/20251122040724_UniqueOAuthProviderId.cs create mode 100644 FictionArchive.Service.UserService/Migrations/UserServiceDbContextModelSnapshot.cs create mode 100644 FictionArchive.Service.UserService/Models/Database/User.cs create mode 100644 FictionArchive.Service.UserService/Models/IntegrationEvents/AuthUserAddedEvent.cs create mode 100644 FictionArchive.Service.UserService/Program.cs create mode 100644 FictionArchive.Service.UserService/Properties/launchSettings.json create mode 100644 FictionArchive.Service.UserService/Services/EventHandlers/AuthUserAddedEventHandler.cs create mode 100644 FictionArchive.Service.UserService/Services/UserManagementService.cs create mode 100644 FictionArchive.Service.UserService/Services/UserServiceDbContext.cs create mode 100644 FictionArchive.Service.UserService/appsettings.Development.json create mode 100644 FictionArchive.Service.UserService/appsettings.json diff --git a/FictionArchive.Service.AuthenticationService/Controllers/AuthenticationWebhookController.cs b/FictionArchive.Service.AuthenticationService/Controllers/AuthenticationWebhookController.cs new file mode 100644 index 0000000..7ab6272 --- /dev/null +++ b/FictionArchive.Service.AuthenticationService/Controllers/AuthenticationWebhookController.cs @@ -0,0 +1,36 @@ +using FictionArchive.Service.AuthenticationService.Models.Requests; +using FictionArchive.Service.AuthenticationService.Models.IntegrationEvents; +using FictionArchive.Service.Shared.Services.EventBus; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace FictionArchive.Service.AuthenticationService.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class AuthenticationWebhookController : ControllerBase + { + private readonly IEventBus _eventBus; + + public AuthenticationWebhookController(IEventBus eventBus) + { + _eventBus = eventBus; + } + + [HttpPost(nameof(UserRegistered))] + public async Task UserRegistered([FromBody] UserRegisteredWebhookPayload payload) + { + var authUserAddedEvent = new AuthUserAddedEvent + { + OAuthProviderId = payload.OAuthProviderId, + InviterOAuthProviderId = payload.InviterOAuthProviderId, + EventUserEmail = payload.EventUserEmail, + EventUserUsername = payload.EventUserUsername + }; + + await _eventBus.Publish(authUserAddedEvent); + + return Ok(); + } + } +} \ No newline at end of file diff --git a/FictionArchive.Service.AuthenticationService/Dockerfile b/FictionArchive.Service.AuthenticationService/Dockerfile new file mode 100644 index 0000000..5907b71 --- /dev/null +++ b/FictionArchive.Service.AuthenticationService/Dockerfile @@ -0,0 +1,23 @@ +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base +USER $APP_UID +WORKDIR /app +EXPOSE 8080 +EXPOSE 8081 + +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +ARG BUILD_CONFIGURATION=Release +WORKDIR /src +COPY ["FictionArchive.Service.AuthenticationService/FictionArchive.Service.AuthenticationService.csproj", "FictionArchive.Service.AuthenticationService/"] +RUN dotnet restore "FictionArchive.Service.AuthenticationService/FictionArchive.Service.AuthenticationService.csproj" +COPY . . +WORKDIR "/src/FictionArchive.Service.AuthenticationService" +RUN dotnet build "./FictionArchive.Service.AuthenticationService.csproj" -c $BUILD_CONFIGURATION -o /app/build + +FROM build AS publish +ARG BUILD_CONFIGURATION=Release +RUN dotnet publish "./FictionArchive.Service.AuthenticationService.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "FictionArchive.Service.AuthenticationService.dll"] diff --git a/FictionArchive.Service.AuthenticationService/FictionArchive.Service.AuthenticationService.csproj b/FictionArchive.Service.AuthenticationService/FictionArchive.Service.AuthenticationService.csproj new file mode 100644 index 0000000..6ac3804 --- /dev/null +++ b/FictionArchive.Service.AuthenticationService/FictionArchive.Service.AuthenticationService.csproj @@ -0,0 +1,29 @@ + + + + net8.0 + enable + enable + Linux + + + + + + + + + + .dockerignore + + + + + + + + + + + + diff --git a/FictionArchive.Service.AuthenticationService/FictionArchive.Service.AuthenticationService.http b/FictionArchive.Service.AuthenticationService/FictionArchive.Service.AuthenticationService.http new file mode 100644 index 0000000..8668e05 --- /dev/null +++ b/FictionArchive.Service.AuthenticationService/FictionArchive.Service.AuthenticationService.http @@ -0,0 +1,6 @@ +@FictionArchive.Service.AuthenticationService_HostAddress = http://localhost:5091 + +GET {{FictionArchive.Service.AuthenticationService_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/FictionArchive.Service.AuthenticationService/Models/IntegrationEvents/AuthUserAddedEvent.cs b/FictionArchive.Service.AuthenticationService/Models/IntegrationEvents/AuthUserAddedEvent.cs new file mode 100644 index 0000000..93ace36 --- /dev/null +++ b/FictionArchive.Service.AuthenticationService/Models/IntegrationEvents/AuthUserAddedEvent.cs @@ -0,0 +1,16 @@ +using FictionArchive.Service.Shared.Services.EventBus; + +namespace FictionArchive.Service.AuthenticationService.Models.IntegrationEvents; + +public class AuthUserAddedEvent : IIntegrationEvent +{ + public string OAuthProviderId { get; set; } + + public string InviterOAuthProviderId { get; set; } + + // The email of the user that created the event + public string EventUserEmail { get; set; } + + // The username of the user that created the event + public string EventUserUsername { get; set; } +} \ No newline at end of file diff --git a/FictionArchive.Service.AuthenticationService/Models/Requests/UserRegisteredWebhookPayload.cs b/FictionArchive.Service.AuthenticationService/Models/Requests/UserRegisteredWebhookPayload.cs new file mode 100644 index 0000000..fffcb1c --- /dev/null +++ b/FictionArchive.Service.AuthenticationService/Models/Requests/UserRegisteredWebhookPayload.cs @@ -0,0 +1,17 @@ +namespace FictionArchive.Service.AuthenticationService.Models.Requests; + +public class UserRegisteredWebhookPayload +{ + // The body of the notification message + public string Body { get; set; } + + public string OAuthProviderId { get; set; } + + public string InviterOAuthProviderId { get; set; } + + // The email of the user that created the event + public string EventUserEmail { get; set; } + + // The username of the user that created the event + public string EventUserUsername { get; set; } +} \ No newline at end of file diff --git a/FictionArchive.Service.AuthenticationService/Program.cs b/FictionArchive.Service.AuthenticationService/Program.cs new file mode 100644 index 0000000..897d20e --- /dev/null +++ b/FictionArchive.Service.AuthenticationService/Program.cs @@ -0,0 +1,49 @@ +using FictionArchive.Service.Shared; +using FictionArchive.Service.Shared.Services.EventBus.Implementations; + +namespace FictionArchive.Service.AuthenticationService; + +public class Program +{ + public static void Main(string[] args) + { + var builder = WebApplication.CreateBuilder(args); + + // Add services to the container. + + builder.Services.AddControllers(); + // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle + builder.Services.AddEndpointsApiExplorer(); + builder.Services.AddSwaggerGen(); + + #region Event Bus + + builder.Services.AddRabbitMQ(opt => + { + builder.Configuration.GetSection("RabbitMQ").Bind(opt); + }); + + #endregion + + builder.Services.AddHealthChecks(); + + var app = builder.Build(); + + // Configure the HTTP request pipeline. + if (app.Environment.IsDevelopment()) + { + app.UseSwagger(); + app.UseSwaggerUI(); + } + + app.UseHttpsRedirection(); + + app.MapHealthChecks("/healthz"); + + app.UseAuthorization(); + + app.MapControllers(); + + app.Run(); + } +} \ No newline at end of file diff --git a/FictionArchive.Service.AuthenticationService/Properties/launchSettings.json b/FictionArchive.Service.AuthenticationService/Properties/launchSettings.json new file mode 100644 index 0000000..c6a404f --- /dev/null +++ b/FictionArchive.Service.AuthenticationService/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:23522", + "sslPort": 44397 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5091", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7223;http://localhost:5091", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/FictionArchive.Service.AuthenticationService/appsettings.Development.json b/FictionArchive.Service.AuthenticationService/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/FictionArchive.Service.AuthenticationService/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/FictionArchive.Service.AuthenticationService/appsettings.json b/FictionArchive.Service.AuthenticationService/appsettings.json new file mode 100644 index 0000000..9087a1b --- /dev/null +++ b/FictionArchive.Service.AuthenticationService/appsettings.json @@ -0,0 +1,13 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "RabbitMQ": { + "ConnectionString": "amqp://localhost", + "ClientIdentifier": "AuthenticationService" + }, + "AllowedHosts": "*" +} diff --git a/FictionArchive.Service.NovelService/Models/IntegrationEvents/ChapterPullRequestedEvent.cs b/FictionArchive.Service.NovelService/Models/IntegrationEvents/ChapterPullRequestedEvent.cs index 4c1e9b5..5f64294 100644 --- a/FictionArchive.Service.NovelService/Models/IntegrationEvents/ChapterPullRequestedEvent.cs +++ b/FictionArchive.Service.NovelService/Models/IntegrationEvents/ChapterPullRequestedEvent.cs @@ -2,7 +2,7 @@ using FictionArchive.Service.Shared.Services.EventBus; namespace FictionArchive.Service.NovelService.Models.IntegrationEvents; -public class ChapterPullRequestedEvent : IntegrationEvent +public class ChapterPullRequestedEvent : IIntegrationEvent { public uint NovelId { get; set; } public uint ChapterNumber { get; set; } diff --git a/FictionArchive.Service.NovelService/Models/IntegrationEvents/NovelUpdateRequestedEvent.cs b/FictionArchive.Service.NovelService/Models/IntegrationEvents/NovelUpdateRequestedEvent.cs index 46de067..5243a3f 100644 --- a/FictionArchive.Service.NovelService/Models/IntegrationEvents/NovelUpdateRequestedEvent.cs +++ b/FictionArchive.Service.NovelService/Models/IntegrationEvents/NovelUpdateRequestedEvent.cs @@ -2,7 +2,7 @@ using FictionArchive.Service.Shared.Services.EventBus; namespace FictionArchive.Service.NovelService.Models.IntegrationEvents; -public class NovelUpdateRequestedEvent : IntegrationEvent +public class NovelUpdateRequestedEvent : IIntegrationEvent { public string NovelUrl { get; set; } } \ No newline at end of file diff --git a/FictionArchive.Service.NovelService/Models/IntegrationEvents/TranslationRequestCompletedEvent.cs b/FictionArchive.Service.NovelService/Models/IntegrationEvents/TranslationRequestCompletedEvent.cs index 7c441f1..98d5ee6 100644 --- a/FictionArchive.Service.NovelService/Models/IntegrationEvents/TranslationRequestCompletedEvent.cs +++ b/FictionArchive.Service.NovelService/Models/IntegrationEvents/TranslationRequestCompletedEvent.cs @@ -3,7 +3,7 @@ using FictionArchive.Service.Shared.Services.EventBus; namespace FictionArchive.Service.NovelService.Models.IntegrationEvents; -public class TranslationRequestCompletedEvent : IntegrationEvent +public class TranslationRequestCompletedEvent : IIntegrationEvent { /// /// Maps this event back to a triggering request. diff --git a/FictionArchive.Service.NovelService/Models/IntegrationEvents/TranslationRequestCreatedEvent.cs b/FictionArchive.Service.NovelService/Models/IntegrationEvents/TranslationRequestCreatedEvent.cs index ea33982..e6623f0 100644 --- a/FictionArchive.Service.NovelService/Models/IntegrationEvents/TranslationRequestCreatedEvent.cs +++ b/FictionArchive.Service.NovelService/Models/IntegrationEvents/TranslationRequestCreatedEvent.cs @@ -3,7 +3,7 @@ using FictionArchive.Service.Shared.Services.EventBus; namespace FictionArchive.Service.NovelService.Models.IntegrationEvents; -public class TranslationRequestCreatedEvent : IntegrationEvent +public class TranslationRequestCreatedEvent : IIntegrationEvent { public Guid TranslationRequestId { get; set; } public Language From { get; set; } diff --git a/FictionArchive.Service.Shared/Services/EventBus/EventBusBuilder.cs b/FictionArchive.Service.Shared/Services/EventBus/EventBusBuilder.cs index b09c202..1331fba 100644 --- a/FictionArchive.Service.Shared/Services/EventBus/EventBusBuilder.cs +++ b/FictionArchive.Service.Shared/Services/EventBus/EventBusBuilder.cs @@ -16,7 +16,7 @@ public class EventBusBuilder where TEventBus : class, IEventBus _services.AddSingleton(_subscriptionManager); } - public EventBusBuilder Subscribe() where TEvent : IntegrationEvent where TEventHandler : class, IIntegrationEventHandler + public EventBusBuilder Subscribe() where TEvent : IIntegrationEvent where TEventHandler : class, IIntegrationEventHandler { _services.AddKeyedTransient(typeof(TEvent).Name); _subscriptionManager.RegisterSubscription(); diff --git a/FictionArchive.Service.Shared/Services/EventBus/IEventBus.cs b/FictionArchive.Service.Shared/Services/EventBus/IEventBus.cs index 46c7e75..46b28eb 100644 --- a/FictionArchive.Service.Shared/Services/EventBus/IEventBus.cs +++ b/FictionArchive.Service.Shared/Services/EventBus/IEventBus.cs @@ -2,6 +2,6 @@ namespace FictionArchive.Service.Shared.Services.EventBus; public interface IEventBus { - Task Publish(TEvent integrationEvent) where TEvent : IntegrationEvent; + Task Publish(TEvent integrationEvent) where TEvent : IIntegrationEvent; Task Publish(object integrationEvent, string eventType); } \ No newline at end of file diff --git a/FictionArchive.Service.Shared/Services/EventBus/IIntegrationEvent.cs b/FictionArchive.Service.Shared/Services/EventBus/IIntegrationEvent.cs new file mode 100644 index 0000000..499f92c --- /dev/null +++ b/FictionArchive.Service.Shared/Services/EventBus/IIntegrationEvent.cs @@ -0,0 +1,7 @@ +using NodaTime; + +namespace FictionArchive.Service.Shared.Services.EventBus; + +public interface IIntegrationEvent +{ +} \ No newline at end of file diff --git a/FictionArchive.Service.Shared/Services/EventBus/IIntegrationEventHandler.cs b/FictionArchive.Service.Shared/Services/EventBus/IIntegrationEventHandler.cs index 7aa3049..0541637 100644 --- a/FictionArchive.Service.Shared/Services/EventBus/IIntegrationEventHandler.cs +++ b/FictionArchive.Service.Shared/Services/EventBus/IIntegrationEventHandler.cs @@ -1,12 +1,12 @@ namespace FictionArchive.Service.Shared.Services.EventBus; -public interface IIntegrationEventHandler : IIntegrationEventHandler where TEvent : IntegrationEvent +public interface IIntegrationEventHandler : IIntegrationEventHandler where TEvent : IIntegrationEvent { Task Handle(TEvent @event); - Task IIntegrationEventHandler.Handle(IntegrationEvent @event) => Handle((TEvent)@event); + Task IIntegrationEventHandler.Handle(IIntegrationEvent @event) => Handle((TEvent)@event); } public interface IIntegrationEventHandler { - Task Handle(IntegrationEvent @event); + Task Handle(IIntegrationEvent @event); } \ No newline at end of file diff --git a/FictionArchive.Service.Shared/Services/EventBus/Implementations/RabbitMQEventBus.cs b/FictionArchive.Service.Shared/Services/EventBus/Implementations/RabbitMQEventBus.cs index 6650e18..7faa200 100644 --- a/FictionArchive.Service.Shared/Services/EventBus/Implementations/RabbitMQEventBus.cs +++ b/FictionArchive.Service.Shared/Services/EventBus/Implementations/RabbitMQEventBus.cs @@ -22,6 +22,8 @@ public class RabbitMQEventBus : IEventBus, IHostedService private readonly JsonSerializerSettings _jsonSerializerSettings; private const string ExchangeName = "fiction-archive-event-bus"; + private const string CreatedAtHeader = "X-Created-At"; + private const string EventIdHeader = "X-Event-Id"; public RabbitMQEventBus(IServiceScopeFactory serviceScopeFactory, RabbitMQConnectionProvider connectionProvider, IOptions options, SubscriptionManager subscriptionManager, ILogger logger) { @@ -33,14 +35,10 @@ public class RabbitMQEventBus : IEventBus, IHostedService _jsonSerializerSettings = new JsonSerializerSettings().ConfigureForNodaTime(DateTimeZoneProviders.Tzdb); } - public async Task Publish(TEvent integrationEvent) where TEvent : IntegrationEvent + public async Task Publish(TEvent integrationEvent) where TEvent : IIntegrationEvent { var routingKey = typeof(TEvent).Name; - // Set integration event values - integrationEvent.CreatedAt = Instant.FromDateTimeUtc(DateTime.UtcNow); - integrationEvent.EventId = Guid.NewGuid(); - await Publish(integrationEvent, routingKey); } @@ -48,7 +46,16 @@ public class RabbitMQEventBus : IEventBus, IHostedService { var channel = await _connectionProvider.GetDefaultChannelAsync(); var body = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(integrationEvent)); - await channel.BasicPublishAsync(ExchangeName, eventType, true, body); + + // headers + var props = new BasicProperties(); + props.Headers = new Dictionary() + { + { CreatedAtHeader, Instant.FromDateTimeUtc(DateTime.UtcNow).ToString() }, + { EventIdHeader, Guid.NewGuid().ToString() } + }; + + await channel.BasicPublishAsync(ExchangeName, eventType, true, props, body); _logger.LogInformation("Published event {EventName}", eventType); } @@ -70,14 +77,14 @@ public class RabbitMQEventBus : IEventBus, IHostedService return OnReceivedEvent(sender, @event, channel); }; + await channel.BasicConsumeAsync(_options.ClientIdentifier, false, consumer, cancellationToken: cancellationToken); + foreach (var subscription in _subscriptionManager.Subscriptions) { await channel.QueueBindAsync(_options.ClientIdentifier, ExchangeName, subscription.Key, cancellationToken: cancellationToken); _logger.LogInformation("Subscribed to {SubscriptionKey}", subscription.Key); } - - await channel.BasicConsumeAsync(_options.ClientIdentifier, false, consumer, cancellationToken: cancellationToken); _logger.LogInformation("RabbitMQ EventBus started."); } @@ -106,7 +113,7 @@ public class RabbitMQEventBus : IEventBus, IHostedService } var eventBody = Encoding.UTF8.GetString(@event.Body.Span); - var eventObject = JsonConvert.DeserializeObject(eventBody, _subscriptionManager.Subscriptions[eventName], _jsonSerializerSettings) as IntegrationEvent; + var eventObject = JsonConvert.DeserializeObject(eventBody, _subscriptionManager.Subscriptions[eventName], _jsonSerializerSettings) as IIntegrationEvent; using var scope = _serviceScopeFactory.CreateScope(); diff --git a/FictionArchive.Service.Shared/Services/EventBus/IntegrationEvent.cs b/FictionArchive.Service.Shared/Services/EventBus/IntegrationEvent.cs deleted file mode 100644 index efa4b16..0000000 --- a/FictionArchive.Service.Shared/Services/EventBus/IntegrationEvent.cs +++ /dev/null @@ -1,9 +0,0 @@ -using NodaTime; - -namespace FictionArchive.Service.Shared.Services.EventBus; - -public abstract class IntegrationEvent -{ - public Guid EventId { get; set; } - public Instant CreatedAt { get; set; } -} \ No newline at end of file diff --git a/FictionArchive.Service.TranslationService/Models/IntegrationEvents/TranslationRequestCompletedEvent.cs b/FictionArchive.Service.TranslationService/Models/IntegrationEvents/TranslationRequestCompletedEvent.cs index 6e2c2e2..e2ead5f 100644 --- a/FictionArchive.Service.TranslationService/Models/IntegrationEvents/TranslationRequestCompletedEvent.cs +++ b/FictionArchive.Service.TranslationService/Models/IntegrationEvents/TranslationRequestCompletedEvent.cs @@ -4,7 +4,7 @@ using FictionArchive.Service.TranslationService.Models.Enums; namespace FictionArchive.Service.TranslationService.Models.IntegrationEvents; -public class TranslationRequestCompletedEvent : IntegrationEvent +public class TranslationRequestCompletedEvent : IIntegrationEvent { /// /// Maps this event back to a triggering request. diff --git a/FictionArchive.Service.TranslationService/Models/IntegrationEvents/TranslationRequestCreatedEvent.cs b/FictionArchive.Service.TranslationService/Models/IntegrationEvents/TranslationRequestCreatedEvent.cs index 028b283..4d627cb 100644 --- a/FictionArchive.Service.TranslationService/Models/IntegrationEvents/TranslationRequestCreatedEvent.cs +++ b/FictionArchive.Service.TranslationService/Models/IntegrationEvents/TranslationRequestCreatedEvent.cs @@ -3,7 +3,7 @@ using FictionArchive.Service.Shared.Services.EventBus; namespace FictionArchive.Service.TranslationService.Models.IntegrationEvents; -public class TranslationRequestCreatedEvent : IntegrationEvent +public class TranslationRequestCreatedEvent : IIntegrationEvent { public Guid TranslationRequestId { get; set; } public Language From { get; set; } diff --git a/FictionArchive.Service.UserService/Dockerfile b/FictionArchive.Service.UserService/Dockerfile new file mode 100644 index 0000000..0436943 --- /dev/null +++ b/FictionArchive.Service.UserService/Dockerfile @@ -0,0 +1,23 @@ +FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base +USER $APP_UID +WORKDIR /app +EXPOSE 8080 +EXPOSE 8081 + +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build +ARG BUILD_CONFIGURATION=Release +WORKDIR /src +COPY ["FictionArchive.Service.UserService/FictionArchive.Service.UserService.csproj", "FictionArchive.Service.UserService/"] +RUN dotnet restore "FictionArchive.Service.UserService/FictionArchive.Service.UserService.csproj" +COPY . . +WORKDIR "/src/FictionArchive.Service.UserService" +RUN dotnet build "./FictionArchive.Service.UserService.csproj" -c $BUILD_CONFIGURATION -o /app/build + +FROM build AS publish +ARG BUILD_CONFIGURATION=Release +RUN dotnet publish "./FictionArchive.Service.UserService.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +ENTRYPOINT ["dotnet", "FictionArchive.Service.UserService.dll"] diff --git a/FictionArchive.Service.UserService/FictionArchive.Service.UserService.csproj b/FictionArchive.Service.UserService/FictionArchive.Service.UserService.csproj new file mode 100644 index 0000000..633eb80 --- /dev/null +++ b/FictionArchive.Service.UserService/FictionArchive.Service.UserService.csproj @@ -0,0 +1,27 @@ + + + + net8.0 + enable + enable + Linux + + + + + .dockerignore + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/FictionArchive.Service.UserService/GraphQL/Mutation.cs b/FictionArchive.Service.UserService/GraphQL/Mutation.cs new file mode 100644 index 0000000..dfd0409 --- /dev/null +++ b/FictionArchive.Service.UserService/GraphQL/Mutation.cs @@ -0,0 +1,13 @@ +using FictionArchive.Service.UserService.Models.Database; +using FictionArchive.Service.UserService.Services; + +namespace FictionArchive.Service.UserService.GraphQL; + +public class Mutation +{ + public async Task RegisterUser(string username, string email, string oAuthProviderId, + string? inviterOAuthProviderId, UserManagementService userManagementService) + { + return await userManagementService.RegisterUser(username, email, oAuthProviderId, inviterOAuthProviderId); + } +} \ No newline at end of file diff --git a/FictionArchive.Service.UserService/GraphQL/Query.cs b/FictionArchive.Service.UserService/GraphQL/Query.cs new file mode 100644 index 0000000..c6ceb62 --- /dev/null +++ b/FictionArchive.Service.UserService/GraphQL/Query.cs @@ -0,0 +1,12 @@ +using FictionArchive.Service.UserService.Models.Database; +using FictionArchive.Service.UserService.Services; + +namespace FictionArchive.Service.UserService.GraphQL; + +public class Query +{ + public async Task> GetUsers(UserManagementService userManagementService) + { + return userManagementService.GetUsers(); + } +} \ No newline at end of file diff --git a/FictionArchive.Service.UserService/Migrations/20251122034145_Initial.Designer.cs b/FictionArchive.Service.UserService/Migrations/20251122034145_Initial.Designer.cs new file mode 100644 index 0000000..41bc29d --- /dev/null +++ b/FictionArchive.Service.UserService/Migrations/20251122034145_Initial.Designer.cs @@ -0,0 +1,77 @@ +// +using System; +using FictionArchive.Service.UserService.Services; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NodaTime; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace FictionArchive.Service.UserService.Migrations +{ + [DbContext(typeof(UserServiceDbContext))] + [Migration("20251122034145_Initial")] + partial class Initial + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("FictionArchive.Service.UserService.Models.Database.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Disabled") + .HasColumnType("boolean"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("InviterId") + .HasColumnType("uuid"); + + b.Property("LastUpdatedTime") + .HasColumnType("timestamp with time zone"); + + b.Property("OAuthProviderId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Username") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("InviterId"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("FictionArchive.Service.UserService.Models.Database.User", b => + { + b.HasOne("FictionArchive.Service.UserService.Models.Database.User", "Inviter") + .WithMany() + .HasForeignKey("InviterId"); + + b.Navigation("Inviter"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/FictionArchive.Service.UserService/Migrations/20251122034145_Initial.cs b/FictionArchive.Service.UserService/Migrations/20251122034145_Initial.cs new file mode 100644 index 0000000..a2f5d53 --- /dev/null +++ b/FictionArchive.Service.UserService/Migrations/20251122034145_Initial.cs @@ -0,0 +1,51 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; +using NodaTime; + +#nullable disable + +namespace FictionArchive.Service.UserService.Migrations +{ + /// + public partial class Initial : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Users", + columns: table => new + { + Id = table.Column(type: "uuid", nullable: false), + Username = table.Column(type: "text", nullable: false), + Email = table.Column(type: "text", nullable: false), + OAuthProviderId = table.Column(type: "text", nullable: false), + Disabled = table.Column(type: "boolean", nullable: false), + InviterId = table.Column(type: "uuid", nullable: true), + CreatedTime = table.Column(type: "timestamp with time zone", nullable: false), + LastUpdatedTime = table.Column(type: "timestamp with time zone", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Users", x => x.Id); + table.ForeignKey( + name: "FK_Users_Users_InviterId", + column: x => x.InviterId, + principalTable: "Users", + principalColumn: "Id"); + }); + + migrationBuilder.CreateIndex( + name: "IX_Users_InviterId", + table: "Users", + column: "InviterId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Users"); + } + } +} diff --git a/FictionArchive.Service.UserService/Migrations/20251122040724_UniqueOAuthProviderId.Designer.cs b/FictionArchive.Service.UserService/Migrations/20251122040724_UniqueOAuthProviderId.Designer.cs new file mode 100644 index 0000000..bd65cac --- /dev/null +++ b/FictionArchive.Service.UserService/Migrations/20251122040724_UniqueOAuthProviderId.Designer.cs @@ -0,0 +1,80 @@ +// +using System; +using FictionArchive.Service.UserService.Services; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NodaTime; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace FictionArchive.Service.UserService.Migrations +{ + [DbContext(typeof(UserServiceDbContext))] + [Migration("20251122040724_UniqueOAuthProviderId")] + partial class UniqueOAuthProviderId + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("FictionArchive.Service.UserService.Models.Database.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Disabled") + .HasColumnType("boolean"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("InviterId") + .HasColumnType("uuid"); + + b.Property("LastUpdatedTime") + .HasColumnType("timestamp with time zone"); + + b.Property("OAuthProviderId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Username") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("InviterId"); + + b.HasIndex("OAuthProviderId") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("FictionArchive.Service.UserService.Models.Database.User", b => + { + b.HasOne("FictionArchive.Service.UserService.Models.Database.User", "Inviter") + .WithMany() + .HasForeignKey("InviterId"); + + b.Navigation("Inviter"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/FictionArchive.Service.UserService/Migrations/20251122040724_UniqueOAuthProviderId.cs b/FictionArchive.Service.UserService/Migrations/20251122040724_UniqueOAuthProviderId.cs new file mode 100644 index 0000000..02e452f --- /dev/null +++ b/FictionArchive.Service.UserService/Migrations/20251122040724_UniqueOAuthProviderId.cs @@ -0,0 +1,28 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace FictionArchive.Service.UserService.Migrations +{ + /// + public partial class UniqueOAuthProviderId : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateIndex( + name: "IX_Users_OAuthProviderId", + table: "Users", + column: "OAuthProviderId", + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_Users_OAuthProviderId", + table: "Users"); + } + } +} diff --git a/FictionArchive.Service.UserService/Migrations/UserServiceDbContextModelSnapshot.cs b/FictionArchive.Service.UserService/Migrations/UserServiceDbContextModelSnapshot.cs new file mode 100644 index 0000000..ffc0bd4 --- /dev/null +++ b/FictionArchive.Service.UserService/Migrations/UserServiceDbContextModelSnapshot.cs @@ -0,0 +1,77 @@ +// +using System; +using FictionArchive.Service.UserService.Services; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using NodaTime; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace FictionArchive.Service.UserService.Migrations +{ + [DbContext(typeof(UserServiceDbContext))] + partial class UserServiceDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "9.0.11") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("FictionArchive.Service.UserService.Models.Database.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid"); + + b.Property("CreatedTime") + .HasColumnType("timestamp with time zone"); + + b.Property("Disabled") + .HasColumnType("boolean"); + + b.Property("Email") + .IsRequired() + .HasColumnType("text"); + + b.Property("InviterId") + .HasColumnType("uuid"); + + b.Property("LastUpdatedTime") + .HasColumnType("timestamp with time zone"); + + b.Property("OAuthProviderId") + .IsRequired() + .HasColumnType("text"); + + b.Property("Username") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("InviterId"); + + b.HasIndex("OAuthProviderId") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("FictionArchive.Service.UserService.Models.Database.User", b => + { + b.HasOne("FictionArchive.Service.UserService.Models.Database.User", "Inviter") + .WithMany() + .HasForeignKey("InviterId"); + + b.Navigation("Inviter"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/FictionArchive.Service.UserService/Models/Database/User.cs b/FictionArchive.Service.UserService/Models/Database/User.cs new file mode 100644 index 0000000..aa7b4e3 --- /dev/null +++ b/FictionArchive.Service.UserService/Models/Database/User.cs @@ -0,0 +1,20 @@ +using FictionArchive.Service.Shared.Models; +using Microsoft.EntityFrameworkCore; + +namespace FictionArchive.Service.UserService.Models.Database; + +[Index(nameof(OAuthProviderId), IsUnique = true)] +public class User : BaseEntity +{ + public string Username { get; set; } + public string Email { get; set; } + public string OAuthProviderId { get; set; } + + + public bool Disabled { get; set; } + + /// + /// The user that generated an invite used by this user. + /// + public User? Inviter { get; set; } +} \ No newline at end of file diff --git a/FictionArchive.Service.UserService/Models/IntegrationEvents/AuthUserAddedEvent.cs b/FictionArchive.Service.UserService/Models/IntegrationEvents/AuthUserAddedEvent.cs new file mode 100644 index 0000000..13a832c --- /dev/null +++ b/FictionArchive.Service.UserService/Models/IntegrationEvents/AuthUserAddedEvent.cs @@ -0,0 +1,16 @@ +using FictionArchive.Service.Shared.Services.EventBus; + +namespace FictionArchive.Service.UserService.Models.IntegrationEvents; + +public class AuthUserAddedEvent : IIntegrationEvent +{ + public string OAuthProviderId { get; set; } + + public string InviterOAuthProviderId { get; set; } + + // The email of the user that created the event + public string EventUserEmail { get; set; } + + // The username of the user that created the event + public string EventUserUsername { get; set; } +} \ No newline at end of file diff --git a/FictionArchive.Service.UserService/Program.cs b/FictionArchive.Service.UserService/Program.cs new file mode 100644 index 0000000..fa035a4 --- /dev/null +++ b/FictionArchive.Service.UserService/Program.cs @@ -0,0 +1,52 @@ +using FictionArchive.Service.Shared.Extensions; +using FictionArchive.Service.Shared.Services.EventBus.Implementations; +using FictionArchive.Service.UserService.GraphQL; +using FictionArchive.Service.UserService.Models.IntegrationEvents; +using FictionArchive.Service.UserService.Services; +using FictionArchive.Service.UserService.Services.EventHandlers; + +namespace FictionArchive.Service.UserService; + +public class Program +{ + public static void Main(string[] args) + { + var builder = WebApplication.CreateBuilder(args); + + #region Event Bus + + builder.Services.AddRabbitMQ(opt => + { + builder.Configuration.GetSection("RabbitMQ").Bind(opt); + }) + .Subscribe(); + + #endregion + + #region GraphQL + + builder.Services.AddDefaultGraphQl(); + + #endregion + + builder.Services.RegisterDbContext(builder.Configuration.GetConnectionString("DefaultConnection")); + builder.Services.AddTransient(); + + builder.Services.AddHealthChecks(); + + var app = builder.Build(); + + // Update database + using (var scope = app.Services.CreateScope()) + { + var dbContext = scope.ServiceProvider.GetRequiredService(); + dbContext.UpdateDatabase(); + } + + app.MapGraphQL(); + + app.MapHealthChecks("/healthz"); + + app.Run(); + } +} \ No newline at end of file diff --git a/FictionArchive.Service.UserService/Properties/launchSettings.json b/FictionArchive.Service.UserService/Properties/launchSettings.json new file mode 100644 index 0000000..2f02890 --- /dev/null +++ b/FictionArchive.Service.UserService/Properties/launchSettings.json @@ -0,0 +1,39 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:20480", + "sslPort": 44309 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:5145", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "graphql", + "applicationUrl": "https://localhost:7157;http://localhost:5145", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/FictionArchive.Service.UserService/Services/EventHandlers/AuthUserAddedEventHandler.cs b/FictionArchive.Service.UserService/Services/EventHandlers/AuthUserAddedEventHandler.cs new file mode 100644 index 0000000..120380b --- /dev/null +++ b/FictionArchive.Service.UserService/Services/EventHandlers/AuthUserAddedEventHandler.cs @@ -0,0 +1,23 @@ +using FictionArchive.Service.Shared.Services.EventBus; +using FictionArchive.Service.UserService.Models.IntegrationEvents; +using FictionArchive.Service.UserService.Models.Database; +using Microsoft.EntityFrameworkCore; // Add this line to include the UserModel + +namespace FictionArchive.Service.UserService.Services.EventHandlers; + +public class AuthUserAddedEventHandler : IIntegrationEventHandler +{ + private readonly UserManagementService _userManagementService; + private readonly ILogger _logger; + + public AuthUserAddedEventHandler(UserServiceDbContext dbContext, ILogger logger, UserManagementService userManagementService) + { + _logger = logger; + _userManagementService = userManagementService; + } + + public async Task Handle(AuthUserAddedEvent @event) + { + await _userManagementService.RegisterUser(@event.EventUserUsername, @event.EventUserEmail, @event.OAuthProviderId, @event.InviterOAuthProviderId); + } +} \ No newline at end of file diff --git a/FictionArchive.Service.UserService/Services/UserManagementService.cs b/FictionArchive.Service.UserService/Services/UserManagementService.cs new file mode 100644 index 0000000..b1de631 --- /dev/null +++ b/FictionArchive.Service.UserService/Services/UserManagementService.cs @@ -0,0 +1,45 @@ +using FictionArchive.Service.UserService.Models.Database; +using Microsoft.EntityFrameworkCore; + +namespace FictionArchive.Service.UserService.Services; + +public class UserManagementService +{ + private readonly ILogger _logger; + private readonly UserServiceDbContext _dbContext; + + public UserManagementService(UserServiceDbContext dbContext, ILogger logger) + { + _dbContext = dbContext; + _logger = logger; + } + + public async Task RegisterUser(string username, string email, string oAuthProviderId, + string? inviterOAuthProviderId) + { + var newUser = new User(); + User? inviter = + await _dbContext.Users.FirstOrDefaultAsync(user => user.OAuthProviderId == inviterOAuthProviderId); + if (inviter == null && inviterOAuthProviderId != null) + { + _logger.LogCritical( + "A user with OAuthProviderId {OAuthProviderId} was marked as having inviter with OAuthProviderId {inviterOAuthProviderId}, but no user was found with that value.", + inviterOAuthProviderId, inviterOAuthProviderId); + newUser.Disabled = true; + } + + newUser.Username = username; + newUser.Email = email; + newUser.OAuthProviderId = oAuthProviderId; + + _dbContext.Users.Add(newUser); // Add the new user to the DbContext + await _dbContext.SaveChangesAsync(); // Save changes to the database + + return newUser; + } + + public IQueryable GetUsers() + { + return _dbContext.Users.AsQueryable(); + } +} \ No newline at end of file diff --git a/FictionArchive.Service.UserService/Services/UserServiceDbContext.cs b/FictionArchive.Service.UserService/Services/UserServiceDbContext.cs new file mode 100644 index 0000000..d743f68 --- /dev/null +++ b/FictionArchive.Service.UserService/Services/UserServiceDbContext.cs @@ -0,0 +1,14 @@ +using FictionArchive.Service.Shared.Services.Database; +using FictionArchive.Service.UserService.Models.Database; +using Microsoft.EntityFrameworkCore; + +namespace FictionArchive.Service.UserService.Services; + +public class UserServiceDbContext : FictionArchiveDbContext +{ + public DbSet Users { get; set; } + + public UserServiceDbContext(DbContextOptions options, ILogger logger) : base(options, logger) + { + } +} \ No newline at end of file diff --git a/FictionArchive.Service.UserService/appsettings.Development.json b/FictionArchive.Service.UserService/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/FictionArchive.Service.UserService/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/FictionArchive.Service.UserService/appsettings.json b/FictionArchive.Service.UserService/appsettings.json new file mode 100644 index 0000000..ac07d77 --- /dev/null +++ b/FictionArchive.Service.UserService/appsettings.json @@ -0,0 +1,16 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "ConnectionStrings": { + "DefaultConnection": "Host=localhost;Database=FictionArchive_UserService;Username=postgres;password=postgres" + }, + "RabbitMQ": { + "ConnectionString": "amqp://localhost", + "ClientIdentifier": "UserService" + }, + "AllowedHosts": "*" +} diff --git a/FictionArchive.sln b/FictionArchive.sln index aa199ef..e999c3b 100644 --- a/FictionArchive.sln +++ b/FictionArchive.sln @@ -12,6 +12,15 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FictionArchive.Service.Shar EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FictionArchive.Service.SchedulerService", "FictionArchive.Service.SchedulerService\FictionArchive.Service.SchedulerService.csproj", "{6813A8AD-A071-4F86-B227-BC4A5BCD7F3C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FictionArchive.Service.UserService", "FictionArchive.Service.UserService\FictionArchive.Service.UserService.csproj", "{EE4D4795-2F79-4614-886D-AF8DA77120AC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FictionArchive.Service.AuthenticationService", "FictionArchive.Service.AuthenticationService\FictionArchive.Service.AuthenticationService.csproj", "{70C4AE82-B01E-421D-B590-C0F47E63CD0C}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{35C67E32-A17F-4EAB-B141-88AFCE11FF9C}" + ProjectSection(SolutionItems) = preProject + compose.yaml = compose.yaml + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -42,5 +51,13 @@ Global {6813A8AD-A071-4F86-B227-BC4A5BCD7F3C}.Debug|Any CPU.Build.0 = Debug|Any CPU {6813A8AD-A071-4F86-B227-BC4A5BCD7F3C}.Release|Any CPU.ActiveCfg = Release|Any CPU {6813A8AD-A071-4F86-B227-BC4A5BCD7F3C}.Release|Any CPU.Build.0 = Release|Any CPU + {EE4D4795-2F79-4614-886D-AF8DA77120AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EE4D4795-2F79-4614-886D-AF8DA77120AC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EE4D4795-2F79-4614-886D-AF8DA77120AC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EE4D4795-2F79-4614-886D-AF8DA77120AC}.Release|Any CPU.Build.0 = Release|Any CPU + {70C4AE82-B01E-421D-B590-C0F47E63CD0C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {70C4AE82-B01E-421D-B590-C0F47E63CD0C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {70C4AE82-B01E-421D-B590-C0F47E63CD0C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {70C4AE82-B01E-421D-B590-C0F47E63CD0C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal