Skip to content

Feat/enhance community models#56

Closed
Rashed99Azm wants to merge 84 commits into
mainfrom
feat/enhance-community-models
Closed

Feat/enhance community models#56
Rashed99Azm wants to merge 84 commits into
mainfrom
feat/enhance-community-models

Conversation

@Rashed99Azm

Copy link
Copy Markdown
Collaborator

Summary

  • Enrich feed responseCommunityFeedItemDto now includes DownvoteCount, TopicNameAr/En, IsExpert, IsWatchlisted, and VoteStatus (-1/0/1); all user-specific flags are populated from the current user's JWT and fall back to neutral defaults for anonymous requests
  • Enrich post-detail responseGET /api/community/posts/{id} returns a new PostDetailDto with a nested PostAuthorDto (id, name, avatar, expert flag, posts count, follower count) plus topic names and user-specific flags; handler rewritten as a single JOIN query (one SQL round trip) and aligned with Response<T> + MessageFactory
  • User activity counters — add PostsCount and CommentsCount to the User aggregate (same denormalized pattern as Post); maintained atomically in PublishPost, SoftDeletePost, CreateReply, and SoftDeleteReply handlers; migration AddUserActivityCounters adds both columns as int NOT NULL DEFAULT 0
  • Feed sort & filter — new MostCommented sort option (PostFeedSort = 3); new optional PostType filter (Info/Question/Poll); PostType filter bypasses the Redis fast-path to SQL
  • MessageFactory alignment — migrate GetMyFollows, ListPublicPostReplies, and ListPublicPostsInTopic handlers from raw return types to Response<T>; fix four endpoints that were calling Results.Ok/NotFound manually to use ToHttpResult()

Test plan

  • GET /api/community/feed — anonymous request returns feed with topicNameAr/En, downvoteCount, isExpert, isWatchlisted=false, voteStatus=0
  • GET /api/community/feed with JWT — isWatchlisted and voteStatus reflect the authenticated user's state
  • GET /api/community/feed?sort=3 — results ordered by commentsCount descending
  • GET /api/community/feed?postType=1 — only Question posts returned
  • GET /api/community/posts/{id} — response shape has nested author object, topicNameAr/En, isWatchlisted, voteStatus; 404 returns standard error envelope
  • Publish a post → author's postsCount increments; soft-delete it → decrements
  • Create a reply → author's commentsCount increments; soft-delete the reply → decrements
  • GET /api/community/topics/{slug}, /topics/{id}/posts, /posts/{id}/replies, /api/me/follows — all return standard Response<T> envelope (not raw JSON)

rashed99mm and others added 30 commits May 14, 2026 16:56
- Fix CA1859/CA2263 analyzer errors in Domain and Application tests
- Suppress CA1873 false positives in Directory.Build.props with justification
- Add JWT Bearer security definition to Swagger (Authorize button)
- Catch RedisException in output-cache middleware to allow startup without Redis
- Apply EF migrations to remote dev SQL Server
- Seed demo data (categories, news, events, posts, roles)
Bumps all project TFMs from net8.0 to net10.0 and updates
package references to compatible versions.
- Directory.Build.props: <TargetFramework>net10.0</TargetFramework>
- Roslyn source generator remains on netstandard2.0
- All test projects updated to net10.0
- Adds ApplicationErrors with typed error codes (Identity.PASSWORD_RESET,
  EMAIL_EXISTS, INVALID_CREDENTIALS, etc.)
- Introduces Result<T> monad for command/query return types
- Adds localization service with YAML-backed stores
- Integrates ValidationBehavior and ResultValidationBehavior
  into MediatR pipeline
- DomainException / ConcurrencyException / DuplicateException
  mapped to problem details via middleware
Replaces coarse-grained domain services (ICommunityWriteService,
ICountryProfileService, etc.) with aggregate-specific repository
interfaces aligned to DDD per-aggregate persistence.
- Introduces I*Repository interfaces per aggregate root
- Updates command/query handlers to depend on repositories
- Infrastructure layer implements repository interfaces with EF Core
- Removes obsolete service abstractions from Application layer
- Added AuditableAggregateRoot for creation and update tracking
- Added SoftDeleteAggregateRoot for logical deletion support

- Improved domain consistency across aggregates
…y, unify repository commit pattern

-
IAuthService (Login, RefreshToken, Logout, Register, ForgotPassword, ResetPassword): all 6 auth handlers reduced to thin wrappers (2 deps each instead of 4–6); duplicated token rotation/issuance logic eliminated; wrong ResetPassword domain keys fixed (INVALID_REFRESH_TOKEN → INVALID_RESET_TOKEN, REGISTRATION_FAILED → RESET_FAILED)
-
Refresh token repos no longer own commits: SaveChangesAsync removed from IRefreshTokenRepository / RefreshTokenRepository — AuthService owns commit via ICceDbContext
-
DDD hierarchy aligned: Entity<TId> constrained to IEquatable<TId>, domain events moved to AggregateRoot<TId> (inherits SoftDeletableEntity), deleted AuditableAggregateRoot/SoftDeletableAggregateRoot, upgraded 3 entities to AggregateRoot, updated 12 entity base classes
-
Generic repository pattern: IRepository<T,TId> + Repository<T,TId> base for aggregate repos
-
Handler commit ownership: IStateRepAssignmentRepository, IExpertRequestSubmissionRepository, IExpertWorkflowRepository, IUserProfileRepository — all SaveChangesAsync calls moved to handl
BREAKING CHANGE: Response.message is now a plain string instead of
{ ar, en } bilingual object, and FieldError.message is also a string.
LocalizationService.GetString() now defaults to
CultureInfo.CurrentUICulture (set by LocalizationMiddleware from
the Accept-Language header) instead of hardcoded "ar".
Changes:
- Response<T>.Message: LocalizedMessage → string
- FieldError.Message: LocalizedMessage → string
- MessageFactory uses _l.GetString() instead of GetLocalizedMessage()
- ExceptionHandlingMiddleware returns single message string
- ResponseValidationBehavior uses GetString() for validation errors
-manage user status
- Optimize ListUsersQuery: single-projection with inline role sub-select, replace join-based role filter with EXISTS subquery
- Optimize GetUserByIdQuery: collapse two DB round-trips into one Select projection, replace ToList+SingleOrDefault with FirstOrDefaultAsync
- Fix auth token handling in user management flow
…ables

- Replace bilingual video_url with single field on HomepageSettings
- Extract KnowledgePartner as separate aggregate from AboutSettings
- Extract PolicySection with PolicySectionType enum from PoliciesSettings
- Promote GlossaryEntry, HomepageCountry to AggregateRoot<Guid>
- Add order_index to HomepageCountry and all collection tables
- Switch all handlers to Response<T> + MessageFactory pattern
- Add ICceDbContext.Add/Delete/DeleteRange generic write methods
- Add 12 new admin + public endpoints for CRUD (glossary, partners, sections)
- Register new repos, EF configs, DI, SystemCode mappings, Resources.yaml keys
- Regenerate AddPlatformSettings migration with updated schema (7 tables)
- Add MediaFile entity, EF config, repository, upload options
- Add UploadMedia, UpdateMediaMetadata, DeleteMedia, GetMediaById commands/queries
- Add MediaFileBriefDto for consistent POST/PUT/DELETE response shape
- Add Internal (port 5002) and External (port 5001) REST endpoints
- Add MEDIA_UPLOADED/UPDATED/DELETED localized success messages (AR/EN)
- Add ERR110-ERR113 error codes for file validation
- Create and apply EF migration AddMediaService
- Build 0 errors, 773 tests pass (4 pre-existing failures)
- Add Serilog.Sinks.Seq to ship structured logs to Seq
- Add OpenTelemetry with ASP.NET Core + HttpClient instrumentation
- Export traces to Seq via OTLP (http://localhost:5341/ingest/otlp)
- Enrich Serilog logs with TraceId/SpanId from Activity.Current
- Register AddCceOpenTelemetry in both External and Internal APIs
- Add Seq configuration section to appsettings.json (empty in prod, localhost in dev)
- Keep existing Prometheus metrics untouched
- Add `BypassSettings` flag to `NotificationDispatchRequest` so security
  notifications (password reset) always send regardless of user opt-out
- Replace `IPasswordResetEmailSender` with `INotificationGateway` in
  `AuthService.ForgotPasswordAsync` — sends through the notification system
- Add `NotificationChannel.Sms` to password reset dispatch alongside Email
- Add `PASSWORD_RESET` SMS template to `ReferenceDataSeeder` (short Arabic
  and English bodies suitable for SMS character limits)
- Fix seeder idempotency check in `SeedNotificationTemplatesAsync` to query
  by `(code, channel)` instead of deterministic ID, preventing duplicate-key
  errors on re-seed
- Delete unused `IPasswordResetEmailSender` interface and
  `PasswordResetEmailSender` implementation
…ices

Feat/add system notificaton services
- Domain: add OtpVerification aggregate, UserVerification entity, OtpVerificationType enum
- Application: add RequestVerification and VerifyOtp commands, handlers, validators, DTOs
- Application: add IOtpVerificationRepository, IUserVerificationRepository, IUserRepository, IOtpCodeGenerator
- Application: add OTP system codes (ERR120–ERR125, CON060–CON061) and message factory shortcuts
- Infrastructure: add EF configurations, repository implementations, HMAC-based OtpCodeGenerator
- Infrastructure: register verification services and UserRepository in DI
- API: add POST /verification/request and POST /verification/verify endpoints (anonymous)
- Seeder: add OTP_VERIFICATION notification template for Email and SMS channels
…rmation

- Domain: add OtpVerification aggregate, UserVerification entity, OtpVerificationType enum
- Application: add RequestVerification and VerifyOtp commands/handlers/validators/DTOs
- Application: add IUserRepository, IOtpVerificationRepository, IUserVerificationRepository, IOtpCodeGenerator
- Application: add OTP system codes (ERR120–ERR125, CON060–CON061) and message factory shortcuts
- Application: refactor VerifyOtpCommandHandler to use repositories instead of direct ICceDbContext queries
- Infrastructure: add EF configurations, repository implementations, HMAC-based OtpCodeGenerator
- Infrastructure: add UserRepository with FindUserIdByContactAsync and StampConfirmedAsync
- Infrastructure: fix ConcurrencyStamp null stub causing DbUpdateConcurrencyException on user confirm
- Infrastructure: restore pre-existing missing sub-namespace usings in DependencyInjection.cs
- Infrastructure: fix missing using in MessagingOptions.xml doc cref
- API: add POST /verification/request and POST /verification/verify endpoints (anonymous)
- API: add Otp:HmacSecret to appsettings (dev + base configs)
- Seeder: add OTP_VERIFICATION notification template
- Migration: AddOtpVerification
rashed99mm and others added 28 commits June 3, 2026 18:10
- Remove `Slug` from `News` entity and all related code
- Add new `Tag` entity with NameAr/NameEn/Color and unique index
- Add many-to-many join tables: `news_tag` and `event_tag`
- Extend `IRepository<T>` with `GetByIdAsync(..., include, ...)` overload
- Create Tag admin CRUD: `POST/PUT/DELETE /api/admin/tags`
- Update News/Event commands to accept optional `TagIds`
- Update News/Event queries with multi-tag filtering via `WhereIf`
- Replace public slug endpoint `GET /api/news/{slug}` → `GET /api/news/{id:guid}`
- Add public `GET /api/tags` with optional `search` filter
- Update all DTOs to include `IReadOnlyList<TagDto> Tags`
- Seed 5 initial tags (Climate, Energy, Policy, Technology, Sustainability)
- Add migration `20260603143020_ReplaceNewsSlugsWithTags` and apply to database
…gration

Feat(sprint-05)/counry profile kapsarc integration
add flag_url -- edit view expert-request
…lly work

  The RabbitMQ/MassTransit branch had a sound topology (APIs publish-only →
  EF transactional outbox → CCE.Worker consumes) but the runtime path was
  broken and inconsistent. This wires it end-to-end and unifies event
  emission on a single Clean-Architecture pattern.

  Blocker
  - DomainEventDispatcher and AuditingInterceptor were registered only as
    concrete types, so `AddInterceptors(sp.GetServices<IInterceptor>())`
    never attached them — domain events never dispatched and audit columns
    stopped writing. Register both as IInterceptor so they attach (alongside
    MassTransit's outbox interceptor).

  Unify on aggregate → domain event → bridge → outbox → consumer
  - Add domain events PostVotedEvent, ReplyCreatedEvent,
    CommunityJoinRequestedEvent and the aggregate methods that raise them
    (Post.RegisterVote, Post.RegisterReply, Community.RegisterJoinRequest).
  - Add bridge handlers (PostVoted/ReplyCreated/CommunityJoinRequested
    BusPublisher) that translate domain events to integration events
    pre-commit, so the publish is captured atomically by the EF outbox.
  - Command handlers (VotePost, CreateReply, JoinCommunity, FollowUser,
    UnfollowUser) no longer inject IIntegrationEventPublisher. This removes
    the lost-message bug where Join/Follow/Unfollow published AFTER
    SaveChanges (outbox row never persisted) and the random-GUID mismatch in
    the join-request event.
  - Fix MassTransitIntegrationEventPublisher to use IPublishEndpoint (the
    non-existent IScopedBusContextProvider<> meant the branch didn't compile).

  Realtime (hybrid, deduped)
  - VoteConsumer no longer pushes VoteChanged (the API handler owns the
    instant push); it keeps only the Redis hot-counter update.
  - Remove the dead ICommunityRealtimePublisher injection from CreatePost.

  Move work off the API thread + cleanup
  - PostCreated notification fan-out now runs in NotificationConsumer
    (Worker); delete PostCreatedNotificationHandler.
  - Carry the real join-request id and the post Locale on the integration
    events.
  - Remove unconsumed dead contracts (UserFollowed, UserUnfollowed,
    ResourcePublished) and their inline publishing.

  Tests + docs
  - Add CommunityIntegrationEventConsumerHarnessTests (broker-free MassTransit
    harness): VoteCreated→VoteConsumer, PostCreated→NotificationConsumer
    (fan-out + Locale), PostCreated→SignalRConsumer.
  - Add docs/community-async-events-guide.md and document the canonical
    pattern in docs/masstransit-messaging-guide.md.

  Verified: solution builds clean; Domain 320/320; new harness tests 3/3;
  Application_does_not_depend_on_Infrastructure passes.
…events-implementation

Feat/rabbitmq masstransit async events implementation
…idempotent PUT upsert

  Consolidate the eight follow endpoints (topic, user, post, community) into four
  idempotent PUT upserts driven by a `{ "status": "Followed" | "Unfollowed" }` body,
  so a single RESTful call sets the desired follow state.

  - Endpoints: PUT /api/me/follows/{topics,users,posts}/{id} and
    PUT /api/community/communities/{id}/follow; drop manual 401 guards (auth is
    enforced by RequireAuthorization + defensive NotAuthenticated)
  - Add SetTopicFollow/SetUserFollow/SetPostFollow/SetCommunityFollow commands;
    delete the 8 Follow*/Unfollow* command folders
  - Standardize all follow handlers on Response<VoidData> + MessageFactory +
    ToHttpResult (§A); add FollowStatus enum
  - Self-follow now returns a clean 400 (CANNOT_FOLLOW_SELF / ERR144, localized ar+en)
  - Topic/Post/User targets return 404 when the target does not exist
  - Add in-memory-test-safe AnyAsyncEither / FirstOrDefaultAsyncEither helpers
  - Rewrite unit tests as SetFollowCommandHandlerTests; update integration 401 tests to PUT

  BREAKING CHANGE: follow/unfollow no longer use POST/DELETE. Clients must call
  PUT with a `status` body instead.
 refactor(community): replace follow/unfollow POST+DELETE pairs with …
…proval

- Add ProposedCategoryId to CountryContentRequest entity + EF config
- Pass CategoryId to SubmitResource and FeaturedImageAssetId to SubmitEvent factories
- Create CountryContentRequestApprovedContentHandler that reads approved request
  and creates + publishes the corresponding content aggregate (News.Draft/Publish,
  Resource.Draft/Publish, Event.Schedule) in the same transaction
- MediatR auto-discovers handler via assembly scanning
- Add migration for proposed_category_id column
feat: create actual News/Event/Resource content on country request ap…
…r email support

- Add ContentNotificationConsumer handling News/Resource/Event publish events
  with per-subscriber fan-out (RecipientName, ContentBody, ArticleUrl)
- Fix DbContext concurrency bug in FanOutAsync (sequential awaits)
- Add NewsletterSubscriptionRepository + INewsletterSubscriptionRepository
  for fetching audience with user names (RecipientName)
- Add INewsRepository.GetNotificationDataAsync returning bilingual
  title + content body for email rendering
- Update SmtpEmailSender with shared responsive HTML layout wrapper
- Add NewsPublishedIntegrationEvent + bus publishers for async dispatch
- Update NotificationTemplateSeeder: NEWS_PUBLISHED Email now renders
  rich HTML with RecipientName, ContentBodyAr/En, and ArticleUrl CTA
- Add update-if-changed logic to seeder (no longer skip-only)
- Add fallback lookup by (Code, Channel) to fix duplicate-key on reseed
- Add Frontend:BaseUrl to appsettings for article link construction
- Add newsletter subscription endpoints to External API
…structure

Feat/refactor notification infrastructure
…ty counters

Feed endpoint:
- Add DownvoteCount, TopicNameAr/En, IsExpert, IsWatchlisted, VoteStatus to CommunityFeedItemDto
- Add MostCommented sort (PostFeedSort = 3) ordered by CommentsCount DESC
- Add PostType filter (Info/Question/Poll) to ListCommunityFeedQuery; bypasses Redis fast-path

Get post by id:
- Replace flat author fields with nested PostAuthorDto (Id, Name, AvatarUrl, IsExpert, PostsCount, FollowerCount)
- Add TopicNameAr/En, IsWatchlisted, VoteStatus to PostDetailDto
- Rewrite handler as a single JOIN query (one SQL round trip); align with Response<T> + MessageFactory

User activity counters:
- Add PostsCount and CommentsCount to User domain model with Increment/Decrement methods
- Add EF configuration (DEFAULT 0) and migration AddUserActivityCounters
- Maintain PostsCount in PublishPostCommandHandler and SoftDeletePostCommandHandler
- Maintain CommentsCount in CreateReplyCommandHandler and SoftDeleteReplyCommandHandler

MessageFactory alignment:
- Migrate GetMyFollows, ListPublicPostReplies, ListPublicPostsInTopic handlers to Response<T>
- Fix four endpoints still using manual Results.Ok/NotFound to use ToHttpResult()
const line = document.createElement('div');
line.className = 'log-entry';
const typeClass = type === 'error' ? 'log-error' : type === 'info' ? 'log-info' : type === 'event' ? 'log-event' : 'log-payload';
line.innerHTML = '<span class="log-time">[' + time + ']</span> <span class="' + typeClass + '">' + eventName + '</span>' + (payload ? ' ' + syntaxHighlight(payload) : '');
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants