Commit Graph

56 Commits

Author SHA1 Message Date
Anatolii Grynchuk 18f7981ccc merge: development -> main 2026-05-02 14:23:18 +03:00
Anatolii Grynchuk 5003ab8764 fix: move ManagePackageVersionsCentrally to Directory.Packages.props
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-02 14:23:17 +03:00
Anatolii Grynchuk 4a431ec6c6 merge: IT-628 RabbitMQ worker, contracts, usage UI in channels screen 2026-05-02 14:01:04 +03:00
Anatolii Grynchuk b0996833bc feat: add RabbitMQ worker, contracts, usage UI in channels screen
- Add HrynCo.NotificationService.Contracts project with SendEmailMessage and NotificationResultMessage
- Add SendEmailConsumer (RabbitMQ worker) with reply-to pattern via CorrelationContext.ReplyTo
- Add SendEmailHandler owning SMTP send + usage increment as business logic
- Add GetChannelUsageSummaryHandler with single DB query via navigation property
- Merge usage stats inline into channels list (daily/monthly with progress bars)
- Refactor AdminChannelsController.Index to use GetChannelUsageSummaryQuery
- Add RabbitMQ service to docker-compose files
- Remove dead AdminChannelUsageController, ChannelUsageViewModel, ChannelUsageSummary

Ref: IT-628

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-02 14:00:58 +03:00
Anatolii Grynchuk 395f5573a1 refactor: replace mailkit with system.net.mail for smtp test
MailKit/MimeKit are unnecessary third-party dependencies with known
vulnerabilities. .NET's built-in System.Net.Mail.SmtpClient handles
the same test send without any additional packages.

Ref: IT-628

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-02 03:48:36 +03:00
Anatolii Grynchuk f7c35671b7 fix: correct smtp ssl negotiation mode per port
- Port 465: SslOnConnect (implicit SSL)
- Port 587 + UseSsl: StartTls (STARTTLS upgrade)
- UseSsl=false: None

Ref: IT-628

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-02 03:45:59 +03:00
Anatolii Grynchuk 6434e3e636 fix: serve bootstrap locally, fix modal button null reference
- Replace CDN bootstrap with local /lib/bootstrap/ - CDN SRI hash was
  mismatching and blocking the script entirely (no Bootstrap = no modals)
- Fix addEventListener null error: script runs before @section FormActions
  renders the button, so use document-level event delegation instead

Ref: IT-628

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-02 03:45:20 +03:00
Anatolii Grynchuk 7587e7fd17 fix: move test modal to view body, wire button via addEventListener
Razor section forwarding across nested layouts is unreliable.
Modal div and script are now directly in Edit.cshtml body (not in
any section) so they are always in the DOM when the page renders.
Button uses addEventListener instead of inline onclick to decouple
from layout rendering order.

Ref: IT-628

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-02 03:43:25 +03:00
Anatolii Grynchuk c9f776de80 feat: add razor runtime compilation for development
Views are now recompiled on request without needing a full rebuild.
This makes .cshtml changes take effect immediately during development.

Ref: IT-628

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-02 03:41:03 +03:00
Anatolii Grynchuk 349a5ac560 fix: serialize smtp settings using runtime type not base class
JsonSerializer.Serialize(p.Settings) uses the compile-time type
EmailChannelSettings, producing only {EmailChannelType:1} in the DB.
Use p.Settings.GetType() to force runtime type so all SmtpChannelSettings
properties (Host, Port, Username, Password, etc.) are actually persisted.

Ref: IT-628

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-02 03:32:50 +03:00
Anatolii Grynchuk c2ad3c7f3e fix: prevent browser from clearing username field on channel edit
autocomplete=off is ignored by Chrome/Firefox when a password field is
present - they clear the username value after page load. Use
autocomplete=new-password on both fields to signal a new-credential
context and prevent autofill interference.

Ref: IT-628

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-02 03:30:44 +03:00
Anatolii Grynchuk 215285d3c0 fix: channel save tracking conflict and test modal rendering
- Use AsNoTracking() on all EmailChannelRepository read methods to prevent
  EF identity conflict when Update() attaches a new entity with same key
- Move test modal to @section Scripts rendered at end of <body> so
  bootstrap.Modal is available and modal is not nested inside card DOM
- Add @RenderSection('Scripts') forwarding in _EditorLayout to bubble
  child scripts sections up to _Layout
- Switch Test button to programmatic bootstrap.Modal() open instead of
  data-bs-toggle (more reliable across layout section boundaries)

Ref: IT-628

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-02 03:29:54 +03:00
Anatolii Grynchuk b5214973ce chore: exclude NuGet.Config from docker build context
NuGet.Config sets globalPackagesFolder to %USERPROFILE% for Windows.
Docker Linux builds must not inherit this - they use /root/.nuget/packages by default.

Ref: IT-628

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-02 03:23:04 +03:00
Anatolii Grynchuk 5f09f7f4fe chore: fix nuget package resolution for local windows builds
- Add NuGet.Config with explicit globalPackagesFolder using %USERPROFILE%
  to prevent Docker-generated obj/ files (with /root/.nuget/packages/)
  from breaking Rider/local dotnet builds
- Restore Microsoft.AspNetCore.OpenApi to Directory.Packages.props
  (was accidentally removed; required by Web project)

Ref: IT-628

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-02 03:20:53 +03:00
Anatolii Grynchuk 00ee5b8add fix: disable StaticWebAssetsEnabled to prevent wwwroot manifest path crash
The development static web assets manifest embedded a stale C:\src\ path,
crashing the app on startup via UseStaticWebAssets(). Disabling the manifest
is correct for a containerized service — wwwroot files are still copied to
bin output and served normally via UseStaticFiles().

Ref: IT-628

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-02 03:14:25 +03:00
Anatolii Grynchuk 2b272e989b refactor: channel holds delivery config only — remove AppDisplayName, AppBaseUrl
- SmtpChannelSettings: Host, Port, Username, Password, UseSsl, FromEmail, FromName only
- AppDisplayName and AppBaseUrl moved to template variables (payload responsibility)
- Updated ViewModel, controller Save/Edit mapping, and Edit view accordingly

Ref: IT-628

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-02 03:08:12 +03:00
Anatolii Grynchuk 61ccf9c777 feat: add test channel feature in admin UI
- POST /admin/channels/{id}/test — direct SMTP send via MailKit
- Test button shown only on existing channels (not create)
- Bootstrap modal with recipient email input and spinner
- Inline success/error result inside the modal

Ref: IT-628

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-02 02:57:36 +03:00
Anatolii Grynchuk 936d41c2f1 feat: improved admin UI styles and layout
- Max-width 860px on editor card
- Two-column row for ServiceName/Key/Language fields
- Gradient card header, proper shadow, grey footer
- Footer buttons right-aligned
- Smaller uppercase table headers, better typography
- Overhauled admin.css with CSS variables and cleaner rules

Ref: IT-628

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-02 02:51:58 +03:00
Anatolii Grynchuk c90b07386d feat: polished admin UI styles + email channels admin CRUD
- Extract inline styles to wwwroot/css/admin.css
- Bootstrap Icons for nav and buttons
- Styled page headers, table, empty state, readonly fields
- Email Channels admin: list, create, edit, delete
- GetAllEmailChannelsQuery + handler
- AdminChannelsController with full CRUD
- form id + form= attribute pattern for EditorLayout footer buttons

Ref: IT-628

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-02 02:43:59 +03:00
Anatolii Grynchuk 855d0862f9 fix: save button not submitting — form id + form= attribute on submit button; migrator connection string in dev compose
- _EditorLayout renders FormActions section outside <form> in the DOM
- Added id='templateForm' to form, form='templateForm' to submit button (HTML5 form association)
- Added migrator env override in docker-compose.Development.yml so connection string is not read from \

Ref: IT-628

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-02 02:35:01 +03:00
Anatolii Grynchuk d36bfd2b97 fix: pin Seq to 2024 image, add no-auth flag, restore dedicated Seq service
- datalust/seq:latest (2025.x) requires mandatory auth — pin to :2024
- Add SEQ_FIRSTRUN_NOAUTHENTICATION=true to match ItemTracker pattern
- Seq UI on port 5342, api/worker point to internal http://seq
- Restore notification_seq volume

Ref: IT-628

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-02 02:31:05 +03:00
Anatolii Grynchuk 7ee811ca8e fix: reuse host Seq instance in Development, remove local seq service
- Seq on 5341 conflicts with ItemTracker dev environment
- Dev compose now points to host.docker.internal:5341 (shared Seq)
- Removed seq service and notification_seq volume from dev compose

Ref: IT-628

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-02 02:28:51 +03:00
Anatolii Grynchuk d23717d123 fix: move api port mapping to dev override, fix Web Dockerfile project name
- Base compose has no ports (env overrides define them)
- Dev override maps api to 5200:8080
- Web Dockerfile updated to reference .Web project name

Ref: IT-628

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-02 02:27:51 +03:00
Anatolii Grynchuk a8bc26fe38 feat: add Migrator console project
- Dedicated HrynCo.NotificationService.Migrator console app
- Reads App:ConnectionString from config/env
- Calls db.Database.MigrateAsync() with clear log output
- Own Dockerfile (runtime:10.0 base, no SDK overhead)
- Replaces SDK volume-mount approach in docker-compose
- Added to solution with /docker/Migrator/ folder

Ref: IT-628

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-02 02:24:45 +03:00
Anatolii Grynchuk 40b4071eb5 fix: add Microsoft.EntityFrameworkCore.Design to Web project for dotnet-ef migrations
Ref: IT-628

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-02 02:20:23 +03:00
Anatolii Grynchuk 2e6dacc3a2 feat: add docker migrator service following ItemTracker pattern
- migrations service uses mcr.microsoft.com/dotnet/sdk:10.0
- mounts source, installs dotnet-ef, retries until DB is ready
- api and worker depend on migrator completing successfully
- removed startup migration from Program.cs

Ref: IT-628

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-02 02:16:47 +03:00
Anatolii Grynchuk 8a54b6de7a feat: redirect root / to /admin/templates
Ref: IT-634

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-02 02:13:19 +03:00
Anatolii Grynchuk 7adce77063 fix: ensure Microsoft.Hosting.Lifetime logs at Information in Development
Allows 'Now listening on: http://...' URLs to appear in console output

Ref: IT-634

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-02 02:10:57 +03:00
Anatolii Grynchuk 238b798a28 fix: create wwwroot folder and fix dev connection string port
- wwwroot was missing, causing UseStaticFiles warning on startup
- appsettings.Development.json now overrides port to 5433 (Docker dev mapping)

Ref: IT-634

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-02 02:09:11 +03:00
Anatolii Grynchuk 2a0a5f737d feat: add MVC Razor admin UI for email templates
- GetAllEmailTemplatesQuery + handler (new GetAll use case)
- IEmailTemplateRepository.GetAllAsync + EF implementation
- AdminTemplatesController: Index, Create, Edit, Save, Delete
- EmailTemplateEditViewModel with IsNew/PageTitle helpers
- Views/_ViewStart, _ViewImports, Shared/_Layout (Bootstrap 5)
- Shared/_EditorLayout (chained layout for all edit screens)
- Views/AdminTemplates/Index (table with edit/delete actions)
- Views/AdminTemplates/Edit (card form, readonly composite key on edit)
- Program.cs: AddControllersWithViews, UseStaticFiles, MapDefaultControllerRoute

Ref: IT-634

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-02 02:06:08 +03:00
Anatolii Grynchuk cec8f42ece refactor: move REST API controllers into Controllers/Api subfolder
- Prepares Controllers/ for both Api/ and Admin/ groupings
- Namespaces updated accordingly

Ref: IT-628

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-02 01:58:14 +03:00
Anatolii Grynchuk ab44ad117c refactor: rename Api project to Web
- HrynCo.NotificationService.Api -> HrynCo.NotificationService.Web
- HrynCo.NotificationService.Api.IntegrationTests -> HrynCo.NotificationService.Web.IntegrationTests
- Updated slnx, docker-compose, project references, and namespaces
- Project serves both REST API and admin UI

Ref: IT-628

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-02 01:55:57 +03:00
Anatolii Grynchuk 2cc8b6b7f2 feat: add Scalar API reference UI
- Scalar.AspNetCore 2.14.9
- Available in Development at /scalar/v1
- Theme: DeepSpace

Ref: IT-628

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-02 01:39:50 +03:00
Anatolii Grynchuk 3bead79ca0 chore: organize Dockerfiles into Api/Worker sub-folders in solution
Ref: IT-628

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-02 01:34:42 +03:00
Anatolii Grynchuk 6a31f380a2 chore: add Dockerfiles for Api and Worker
- Api: mcr.microsoft.com/dotnet/aspnet:10.0 base, exposes 8080
- Worker: mcr.microsoft.com/dotnet/runtime:10.0 base (no HTTP stack)
- Both use multi-stage build with layer-cached csproj restore
- .dockerignore excludes bin/obj/git/IDE folders
- Dockerfiles added to /docker/ solution folder

Ref: IT-628

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-02 01:33:22 +03:00
Anatolii Grynchuk ae724e4aee chore: add docker compose files and solution folder
- docker/environments/docker-compose.yml: base Api + Worker service definitions
- docker/environments/docker-compose.Development.yml: local PostgreSQL + Seq + port mappings
- Solution folder /docker/ in .slnx for IDE access

Ref: IT-628

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-02 01:31:40 +03:00
Anatolii Grynchuk 61130130ff fix: always wrap responses in ApiResponse<T>
Success responses now use ApiResponse<T>{ Success=true, Data=T }
instead of returning raw T — consistent shape for all outcomes.

Ref: IT-628

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-02 01:23:22 +03:00
Anatolii Grynchuk a26f41af18 feat: add API controllers for template and channel management
- ApiResponse<T>, ApiError in Api/Infrastructure
- ApiControllerBase with IMediator, FromServiceResult, MapServiceError
- EmailTemplatesController: GET list, GET one, POST, PUT, DELETE
- EmailChannelsController: GET list, GET one, POST, PUT, DELETE

Ref: IT-628

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-02 01:18:51 +03:00
Anatolii Grynchuk 92be035f51 feat: replace manual Stopwatch with IProfiler in TransactionBehavior
- Add HrynCo.Common to Services project
- TransactionBehavior now uses IProfiler.MeasureExecutionAsync:
  MeasureExecutionAsync -> ExecuteInTransactionAsync -> next() -> SaveChangesAsync
- Profiler logs Start/End with duration + memory delta via Serilog PerformanceLog context
- Register IProfiler as singleton in ServiceCollectionExtensions (uses Log.Logger)

Ref: IT-628

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-02 01:15:10 +03:00
Anatolii Grynchuk a03d2269a6 feat: add MediatR handlers for template and channel CRUD
- ServiceResult<T>, ServiceError, ServiceErrorCode, Unit, ServiceResultHelper in Services/Core
- RequestHandler<TRequest, TResponse> base class (MediatR-adapted, DoOnHandle pattern)
- EmailTemplate handlers: Create, Update, Delete, Get, GetByService
- EmailChannel handlers: Create, Update, Delete, Get, GetByService

Ref: IT-628

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-02 01:07:13 +03:00
Anatolii Grynchuk 73b506992c feat: add EF migrations and design-time factory
- Fix EmailEmailTemplateEntityConfiguration -> EmailTemplateEntityConfiguration
- Rename tables to match domain: templates->email_templates,
  providers->email_channels, provider_usage->email_channel_usage
- Add NotificationDbContextFactory for design-time migrations tooling
- Add InitialCreate migration: email_templates, email_channels, email_channel_usage

Ref: IT-628

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-02 00:46:49 +03:00
Anatolii Grynchuk a9bea183c1 feat: add SerilogRegistrar and ContextualSerilogLogger
- IContextualSerilogLogger<T> / ContextualSerilogLogger<T> in Services/Logging
  (handlers get a ForContext<T>-scoped logger via DI, consistent with ItemTracker)
- SerilogRegistrar extension on WebApplicationBuilder (Api)
- SerilogRegistrar extension on HostApplicationBuilder (Worker)
- Both registrars: set Log.Logger, wire Logging + Host/Services, register ILogger singleton
- ServiceCollectionExtensions: register IContextualSerilogLogger<> as transient
- Program.cs in both apps simplified to single builder.AddSerilog() call

Ref: IT-628

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-02 00:38:32 +03:00
Anatolii Grynchuk 4ea57b2068 feat: introduce AppSettings per application
- AppSettings class in Api and Worker with SectionName constant
- appsettings.json: replaced ConnectionStrings section with App section
- Program.cs: bind AppSettings at startup, register as singleton
- Connection string now sourced from AppSettings.ConnectionString

Ref: IT-628

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-02 00:30:19 +03:00
Anatolii Grynchuk 1dccd9f5f7 fix: add Serilog.Settings.Configuration to Api project
Required for ReadFrom.Configuration extension method.

Ref: IT-628

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-02 00:26:08 +03:00
Anatolii Grynchuk 5cf5f888eb feat: wire up Program.cs, DI extensions, Serilog, and appsettings
- ServiceCollectionExtensions in DAL.EF: AddNotificationDataAccess (DbContext, UoW, repositories)
- ServiceCollectionExtensions in Services: AddNotificationServices (MediatR + TransactionBehavior)
- Api/Program.cs: Serilog, OpenAPI, controllers, DI wiring
- Worker/Program.cs: Serilog, DI wiring
- appsettings.json: Serilog config with Console + Seq sinks, connection string
- appsettings.Development.json: Debug log level overrides

Ref: IT-628

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-02 00:24:54 +03:00
Anatolii Grynchuk 101bb908bd feat: add Serilog with Console and Seq sinks, log TransactionBehavior
- Add Serilog.AspNetCore + Sinks.Console + Sinks.Seq to Api
- Add Serilog.Extensions.Hosting + Sinks.Console + Sinks.Seq to Worker
- Add Microsoft.Extensions.Logging.Abstractions to Services
- TransactionBehavior logs handler name, elapsed time, and errors

Ref: IT-628

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-02 00:21:25 +03:00
Anatolii Grynchuk 6dcc911fc2 refactor: rename domain types and introduce TransactionBehavior pattern
- Rename Template -> EmailTemplate, Provider -> EmailChannel,
  ProviderSettings -> EmailChannelSettings, ProviderType -> EmailChannelType,
  ProviderUsage -> EmailChannelUsage throughout all layers
- Add Undefined = 0 to EmailChannelType enum for safe default handling
- Remove SaveChangesAsync from EfRepository methods — repositories now only stage changes
- Add SaveChangesAsync to IUnitOfWork and EfUnitOfWork
- Add TransactionBehavior MediatR pipeline: wraps every handler in a transaction,
  saves and commits on success, rolls back on exception
- Add MediatR package reference to Services project

Ref: IT-628

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-02 00:16:47 +03:00
Anatolii Grynchuk 088eab0428 refactor: rename NotificationUnitOfWork to UnitOfWork
Ref: IT-628

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-01 23:34:45 +03:00
Anatolii Grynchuk ce1ef1fea6 refactor: rename NotificationEfRepository to EfRepository
Ref: IT-628

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-01 23:34:11 +03:00
Anatolii Grynchuk c2a4f3b9d7 fix: inherit EF entities from Entity base class
- TemplateEntity, ProviderEntity, ProviderUsageEntity now inherit Entity (from DAL.Abstract)
- Removes duplicated Id, Created, Updated properties from each entity

Ref: IT-628

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-01 23:27:38 +03:00