├── gradle.properties ├── settings.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── src ├── main │ ├── java │ │ └── com │ │ │ └── charity_hub │ │ │ ├── shared │ │ │ ├── abstractions │ │ │ │ ├── Command.java │ │ │ │ ├── Query.java │ │ │ │ ├── Request.java │ │ │ │ ├── package-info.java │ │ │ │ ├── VoidQueryHandler.java │ │ │ │ ├── QueryHandler.java │ │ │ │ ├── VoidCommandHandler.java │ │ │ │ └── CommandHandler.java │ │ │ ├── api │ │ │ │ └── package-info.java │ │ │ ├── auth │ │ │ │ ├── package-info.java │ │ │ │ ├── JWTPayload.java │ │ │ │ └── JwtVerifier.java │ │ │ ├── domain │ │ │ │ ├── model │ │ │ │ │ ├── DomainModel.java │ │ │ │ │ ├── DomainEvent.java │ │ │ │ │ ├── ValueObject.java │ │ │ │ │ ├── package-info.java │ │ │ │ │ ├── AmountType.java │ │ │ │ │ ├── Pair.java │ │ │ │ │ ├── Permission.java │ │ │ │ │ ├── AggregateRoot.java │ │ │ │ │ └── Entity.java │ │ │ │ ├── package-info.java │ │ │ │ ├── extension │ │ │ │ │ ├── package-info.java │ │ │ │ │ └── ValueValidator.java │ │ │ │ ├── IEventBus.java │ │ │ │ ├── ILogger.java │ │ │ │ └── EventBus.java │ │ │ ├── exceptions │ │ │ │ ├── package-info.java │ │ │ │ ├── BusinessRuleException.java │ │ │ │ ├── NotFoundException.java │ │ │ │ ├── BadRequestException.java │ │ │ │ └── UnAuthorized.java │ │ │ ├── ModuleMetadata.java │ │ │ └── infrastructure │ │ │ │ ├── JacksonConfig.java │ │ │ │ ├── MessageConfig.java │ │ │ │ ├── MessageService.java │ │ │ │ └── Logger.java │ │ │ ├── accounts │ │ │ ├── internal │ │ │ │ ├── shell │ │ │ │ │ ├── api │ │ │ │ │ │ ├── dtos │ │ │ │ │ │ │ ├── BasicResponse.java │ │ │ │ │ │ │ ├── ChangePermissionRequest.java │ │ │ │ │ │ │ ├── UpdateBasicInfoRequest.java │ │ │ │ │ │ │ ├── InviteUserRequest.java │ │ │ │ │ │ │ └── RegisterFCMTokenRequest.java │ │ │ │ │ │ └── controllers │ │ │ │ │ │ │ ├── BlockAccountController.java │ │ │ │ │ │ │ ├── UnblockUserController.java │ │ │ │ │ │ │ ├── AuthController.java │ │ │ │ │ │ │ ├── InviteUserController.java │ │ │ │ │ │ │ ├── GetConnectionsController.java │ │ │ │ │ │ │ ├── GetConnectionsAdminController.java │ │ │ │ │ │ │ ├── AddUserPermissionController.java │ │ │ │ │ │ │ ├── RemoveUserPermissionController.java │ │ │ │ │ │ │ ├── UpdateBasicInfoController.java │ │ │ │ │ │ │ └── RegisterFCMTokenController.java │ │ │ │ │ ├── db │ │ │ │ │ │ ├── InvitationEntity.java │ │ │ │ │ │ ├── RevokedAccountEntity.java │ │ │ │ │ │ ├── DeviceEntity.java │ │ │ │ │ │ └── AccountEntity.java │ │ │ │ │ ├── repositories │ │ │ │ │ │ ├── mappers │ │ │ │ │ │ │ ├── AccountReadMapper.java │ │ │ │ │ │ │ └── InvitationMapper.java │ │ │ │ │ │ ├── AccountReadRepo.java │ │ │ │ │ │ └── ReadAccountRepo.java │ │ │ │ │ └── services │ │ │ │ │ │ └── firebase │ │ │ │ │ │ └── FirebaseAuthProviderStub.java │ │ │ │ └── core │ │ │ │ │ ├── contracts │ │ │ │ │ ├── IAuthProvider.java │ │ │ │ │ ├── IAccountReadRepo.java │ │ │ │ │ ├── IInvitationRepo.java │ │ │ │ │ ├── IJWTGenerator.java │ │ │ │ │ └── IAccountRepo.java │ │ │ │ │ ├── commands │ │ │ │ │ ├── RefreshToken │ │ │ │ │ │ ├── RefreshTokenResponse.java │ │ │ │ │ │ └── RefreshToken.java │ │ │ │ │ ├── Authenticate │ │ │ │ │ │ ├── AuthenticateResponse.java │ │ │ │ │ │ └── Authenticate.java │ │ │ │ │ ├── BlockAccount │ │ │ │ │ │ ├── BlockAccount.java │ │ │ │ │ │ └── BlockAccountHandler.java │ │ │ │ │ ├── InviteAccount │ │ │ │ │ │ ├── InvitationAccount.java │ │ │ │ │ │ └── InviteAccountHandler.java │ │ │ │ │ ├── ChangePermission │ │ │ │ │ │ ├── ChangePermission.java │ │ │ │ │ │ └── ChangePermissionHandler.java │ │ │ │ │ ├── UpdateBasicInfo │ │ │ │ │ │ └── UpdateBasicInfo.java │ │ │ │ │ └── RegisterNotificationToken │ │ │ │ │ │ ├── RegisterNotificationToken.java │ │ │ │ │ │ └── RegisterNotificationTokenHandler.java │ │ │ │ │ ├── queriers │ │ │ │ │ ├── GetConnectionResponse.java │ │ │ │ │ ├── GetConnectionsQuery.java │ │ │ │ │ ├── Account.java │ │ │ │ │ └── GetConnectionsHandler.java │ │ │ │ │ ├── events │ │ │ │ │ ├── AccountBlocked.java │ │ │ │ │ ├── AccountEvent.java │ │ │ │ │ ├── AccountCreated.java │ │ │ │ │ ├── FCMTokenUpdated.java │ │ │ │ │ ├── AccountUnBlocked.java │ │ │ │ │ ├── PermissionGranted.java │ │ │ │ │ ├── PermissionRemoved.java │ │ │ │ │ ├── BasicInfoUpdated.java │ │ │ │ │ └── AccountAuthenticated.java │ │ │ │ │ ├── exceptions │ │ │ │ │ └── AlreadyInvitedException.java │ │ │ │ │ └── model │ │ │ │ │ ├── account │ │ │ │ │ ├── AccountId.java │ │ │ │ │ ├── FullName.java │ │ │ │ │ ├── PhotoUrl.java │ │ │ │ │ └── MobileNumber.java │ │ │ │ │ ├── invitation │ │ │ │ │ └── Invitation.java │ │ │ │ │ └── device │ │ │ │ │ ├── DeviceType.java │ │ │ │ │ ├── DeviceId.java │ │ │ │ │ ├── FCMToken.java │ │ │ │ │ └── RefreshToken.java │ │ │ └── shared │ │ │ │ ├── InvitationResponse.java │ │ │ │ ├── AccountDTO.java │ │ │ │ ├── IAccountsAPI.java │ │ │ │ ├── ModuleMetadata.java │ │ │ │ ├── DTOAccountMapper.java │ │ │ │ ├── AccountEventDto.java │ │ │ │ └── AccountsAPIs.java │ │ │ ├── cases │ │ │ ├── internal │ │ │ │ ├── application │ │ │ │ │ ├── commands │ │ │ │ │ │ ├── CreateCase │ │ │ │ │ │ │ ├── CaseResponse.java │ │ │ │ │ │ │ ├── CreateCase.java │ │ │ │ │ │ │ └── CreateCaseHandler.java │ │ │ │ │ │ ├── DeleteDraftCase │ │ │ │ │ │ │ ├── DeleteDraftCase.java │ │ │ │ │ │ │ └── DeleteDraftCaseHandler.java │ │ │ │ │ │ ├── ChangeCaseStatus │ │ │ │ │ │ │ ├── ChangeCaseStatus.java │ │ │ │ │ │ │ └── ChangeCaseStatusHandler.java │ │ │ │ │ │ ├── Contribute │ │ │ │ │ │ │ ├── Contribute.java │ │ │ │ │ │ │ ├── ContributeDefaultResponse.java │ │ │ │ │ │ │ └── ContributeHandler.java │ │ │ │ │ │ ├── ConfirmContribution │ │ │ │ │ │ │ ├── ConfirmContribution.java │ │ │ │ │ │ │ └── ConfirmContributionHandler.java │ │ │ │ │ │ ├── PayContribution │ │ │ │ │ │ │ ├── PayContribution.java │ │ │ │ │ │ │ └── PayContributionHandler.java │ │ │ │ │ │ └── UpdateCase │ │ │ │ │ │ │ ├── UpdateCase.java │ │ │ │ │ │ │ └── UpdateCaseHandler.java │ │ │ │ │ ├── queries │ │ │ │ │ │ ├── GetCase │ │ │ │ │ │ │ ├── IGetCaseHandler.java │ │ │ │ │ │ │ ├── GetCaseQuery.java │ │ │ │ │ │ │ └── GetCaseResponse.java │ │ │ │ │ │ ├── GetAllCases │ │ │ │ │ │ │ ├── IGetAllCasesHandler.java │ │ │ │ │ │ │ ├── GetAllCasesQuery.java │ │ │ │ │ │ │ └── GetCasesQueryResult.java │ │ │ │ │ │ └── GetDraftCases │ │ │ │ │ │ │ ├── GetDraftCases.java │ │ │ │ │ │ │ ├── GetDraftCasesResponse.java │ │ │ │ │ │ │ └── GetDraftCasesHandler.java │ │ │ │ │ ├── contracts │ │ │ │ │ │ └── ICaseReadRepo.java │ │ │ │ │ ├── eventHandlers │ │ │ │ │ │ ├── loggers │ │ │ │ │ │ │ ├── CaseOpenedLogger.java │ │ │ │ │ │ │ ├── CaseClosedLogger.java │ │ │ │ │ │ │ └── ContributionMadeLogger.java │ │ │ │ │ │ ├── CaseClosedHandler.java │ │ │ │ │ │ ├── CaseOpenedHandler.java │ │ │ │ │ │ ├── ContributionMadeHandler.java │ │ │ │ │ │ └── FCMTokenUpdatedHandler.java │ │ │ │ │ └── loggers │ │ │ │ │ │ └── FCMTokenLogger.java │ │ │ │ ├── api │ │ │ │ │ ├── dtos │ │ │ │ │ │ ├── ContributeRequest.java │ │ │ │ │ │ ├── PayContributionRequest.java │ │ │ │ │ │ ├── GetCasesRequest.java │ │ │ │ │ │ ├── UpdateCaseRequest.java │ │ │ │ │ │ └── CreateCaseRequest.java │ │ │ │ │ └── controllers │ │ │ │ │ │ ├── GetDraftCasesController.java │ │ │ │ │ │ ├── DeleteDraftCaseController.java │ │ │ │ │ │ ├── ConfirmContributionController.java │ │ │ │ │ │ ├── UpdateCaseController.java │ │ │ │ │ │ ├── GetAllCasesController.java │ │ │ │ │ │ ├── PayContributionController.java │ │ │ │ │ │ ├── GetCaseController.java │ │ │ │ │ │ ├── ChangeCaseStatusController.java │ │ │ │ │ │ ├── CreateCaseController.java │ │ │ │ │ │ └── ContributionController.java │ │ │ │ ├── domain │ │ │ │ │ ├── model │ │ │ │ │ │ ├── Case │ │ │ │ │ │ │ ├── Status.java │ │ │ │ │ │ │ ├── CaseCode.java │ │ │ │ │ │ │ ├── NewCaseProbs.java │ │ │ │ │ │ │ ├── Document.java │ │ │ │ │ │ │ ├── CaseDocuments.java │ │ │ │ │ │ │ └── CaseStatus.java │ │ │ │ │ │ ├── Contribution │ │ │ │ │ │ │ ├── ContributionId.java │ │ │ │ │ │ │ ├── ContributionStatus.java │ │ │ │ │ │ │ └── MoneyValue.java │ │ │ │ │ │ └── CaseDetails │ │ │ │ │ │ │ ├── Tag.java │ │ │ │ │ │ │ ├── Title.java │ │ │ │ │ │ │ ├── Goal.java │ │ │ │ │ │ │ ├── Description.java │ │ │ │ │ │ │ └── Tags.java │ │ │ │ │ ├── events │ │ │ │ │ │ ├── CaseDeleted.java │ │ │ │ │ │ ├── CaseUpdated.java │ │ │ │ │ │ ├── CaseEvent.java │ │ │ │ │ │ ├── CaseOpened.java │ │ │ │ │ │ ├── ContributionPaid.java │ │ │ │ │ │ ├── ContributionReminded.java │ │ │ │ │ │ ├── ContributionConfirmed.java │ │ │ │ │ │ ├── CaseClosed.java │ │ │ │ │ │ └── ContributionMade.java │ │ │ │ │ ├── exceptions │ │ │ │ │ │ ├── InvalidAmountException.java │ │ │ │ │ │ └── CaseExceptions.java │ │ │ │ │ └── contracts │ │ │ │ │ │ ├── INotificationService.java │ │ │ │ │ │ └── ICaseRepo.java │ │ │ │ └── infrastructure │ │ │ │ │ ├── db │ │ │ │ │ └── ContributionEntity.java │ │ │ │ │ └── gateways │ │ │ │ │ └── AccountsGateway.java │ │ │ └── shared │ │ │ │ ├── dtos │ │ │ │ ├── CaseOpenedDTO.java │ │ │ │ ├── ContributionRemindedDTO.java │ │ │ │ ├── ContributionPaidDTO.java │ │ │ │ ├── CaseClosedDTO.java │ │ │ │ ├── ContributionConfirmedDTO.java │ │ │ │ ├── ContributionMadeDTO.java │ │ │ │ ├── CaseEventDto.java │ │ │ │ ├── ModuleMetadata.java │ │ │ │ ├── ContributionDTO.java │ │ │ │ ├── CaseCreatedDTO.java │ │ │ │ └── CaseDTO.java │ │ │ │ ├── ModuleMetadata.java │ │ │ │ ├── ICasesAPI.java │ │ │ │ └── mappers │ │ │ │ ├── DTOContributionMapper.java │ │ │ │ └── DTOCaseMapper.java │ │ │ ├── ledger │ │ │ └── internal │ │ │ │ ├── application │ │ │ │ ├── models │ │ │ │ │ └── InvitationResponse.java │ │ │ │ ├── queries │ │ │ │ │ ├── GetLedger │ │ │ │ │ │ ├── LedgerResponse.java │ │ │ │ │ │ ├── GetLedger.java │ │ │ │ │ │ └── Contribution.java │ │ │ │ │ └── GetLedgerSummary │ │ │ │ │ │ ├── GetLedgerSummary.java │ │ │ │ │ │ └── LedgerSummaryDefaultResponse.java │ │ │ │ ├── eventHandlers │ │ │ │ │ ├── AccountCreated │ │ │ │ │ │ └── AccountCreated.java │ │ │ │ │ ├── loggers │ │ │ │ │ │ ├── ContributionPaidLogger.java │ │ │ │ │ │ ├── ContributionRemindedLogger.java │ │ │ │ │ │ └── ContributionConfirmedLogger.java │ │ │ │ │ ├── ContributionPaidHandler.java │ │ │ │ │ ├── ContributionRemindedHandler.java │ │ │ │ │ └── ContributionConfirmedHandler.java │ │ │ │ └── contracts │ │ │ │ │ ├── IMembersNetworkRepo.java │ │ │ │ │ ├── IAccountGateway.java │ │ │ │ │ └── ICasesGateway.java │ │ │ │ ├── infrastructure │ │ │ │ ├── db │ │ │ │ │ ├── MemberEntity.java │ │ │ │ │ └── MemberMapper.java │ │ │ │ ├── gateways │ │ │ │ │ ├── CasesGateway.java │ │ │ │ │ └── AccountsGateway.java │ │ │ │ └── integrationevents │ │ │ │ │ └── AccountCreatedHandler.java │ │ │ │ ├── domain │ │ │ │ ├── model │ │ │ │ │ ├── MemberId.java │ │ │ │ │ └── Member.java │ │ │ │ └── contracts │ │ │ │ │ └── INotificationService.java │ │ │ │ └── api │ │ │ │ ├── GetLedgerController.java │ │ │ │ ├── GetLedgerSummaryController.java │ │ │ │ └── GetOwnLedgerController.java │ │ │ ├── notifications │ │ │ ├── NotificationApi.java │ │ │ └── internal │ │ │ │ └── FCMServiceStub.java │ │ │ └── CharityHubApplication.java │ └── resources │ │ ├── logback-spring.xml │ │ ├── application-test.properties │ │ ├── messages_ar.properties │ │ ├── messages_en.properties │ │ └── application.properties └── test │ └── java │ └── com │ └── charity_hub │ ├── cases │ └── internal │ │ └── application │ │ └── commands │ │ └── UpdateCase │ │ └── UpdateCaseHandlerTest.java │ ├── testconfig │ ├── IntegrationTest.java │ └── MongoTestContainerConfig.java │ └── ModularityTest.java ├── http-tests └── http-client.env.json ├── Dockerfile └── .gitignore /gradle.properties: -------------------------------------------------------------------------------- 1 | coroutinesVersion=1.6.0 -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'charity_hub' -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TechMentors-LLC/charity_hub/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/shared/abstractions/Command.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.shared.abstractions; 2 | 3 | public interface Command { } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/shared/abstractions/Query.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.shared.abstractions; 2 | 3 | public interface Query {} 4 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/shared/abstractions/Request.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.shared.abstractions; 2 | 3 | public interface Request { } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/shared/api/package-info.java: -------------------------------------------------------------------------------- 1 | @org.springframework.modulith.NamedInterface("api") 2 | package com.charity_hub.shared.api; 3 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/shared/auth/package-info.java: -------------------------------------------------------------------------------- 1 | @org.springframework.modulith.NamedInterface("auth") 2 | package com.charity_hub.shared.auth; 3 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/shared/domain/model/DomainModel.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.shared.domain.model; 2 | 3 | public interface DomainModel { 4 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/shared/domain/package-info.java: -------------------------------------------------------------------------------- 1 | @org.springframework.modulith.NamedInterface("domain") 2 | package com.charity_hub.shared.domain; 3 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/shared/domain/model/DomainEvent.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.shared.domain.model; 2 | 3 | public interface DomainEvent extends DomainModel { } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/shared/domain/model/ValueObject.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.shared.domain.model; 2 | 3 | public interface ValueObject extends DomainModel { 4 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/shared/exceptions/package-info.java: -------------------------------------------------------------------------------- 1 | @org.springframework.modulith.NamedInterface("exceptions") 2 | package com.charity_hub.shared.exceptions; 3 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/shared/abstractions/package-info.java: -------------------------------------------------------------------------------- 1 | @org.springframework.modulith.NamedInterface("abstractions") 2 | package com.charity_hub.shared.abstractions; 3 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/shared/domain/model/package-info.java: -------------------------------------------------------------------------------- 1 | @org.springframework.modulith.NamedInterface("domain.model") 2 | package com.charity_hub.shared.domain.model; 3 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/shared/domain/extension/package-info.java: -------------------------------------------------------------------------------- 1 | @org.springframework.modulith.NamedInterface("domain.extension") 2 | package com.charity_hub.shared.domain.extension; 3 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/shell/api/dtos/BasicResponse.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.shell.api.dtos; 2 | 3 | public record BasicResponse(String accessToken) { 4 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/shared/domain/model/AmountType.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.shared.domain.model; 2 | 3 | public enum AmountType { 4 | MemberDueAmount, 5 | NetworkDueAmount 6 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/shell/db/InvitationEntity.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.shell.db; 2 | 3 | public record InvitationEntity(String mobileNumber, String inviterId) { 4 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/shell/db/RevokedAccountEntity.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.shell.db; 2 | 3 | public record RevokedAccountEntity(String _id, long revokedTime) { 4 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/application/commands/CreateCase/CaseResponse.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.application.commands.CreateCase; 2 | 3 | public record CaseResponse(int caseCode) { } -------------------------------------------------------------------------------- /src/test/java/com/charity_hub/cases/internal/application/commands/UpdateCase/UpdateCaseHandlerTest.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.application.commands.UpdateCase; 2 | 3 | class UpdateCaseHandlerTest { 4 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/shared/abstractions/VoidQueryHandler.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.shared.abstractions; 2 | 3 | public interface VoidQueryHandler { 4 | void handle(TQuery query); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/shared/abstractions/QueryHandler.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.shared.abstractions; 2 | 3 | public interface QueryHandler { 4 | TResult handle(TQuery query); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/shared/InvitationResponse.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.shared; 2 | 3 | import java.util.UUID; 4 | 5 | public record InvitationResponse(String invitedMobileNumber, UUID inviterId) { 6 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/core/contracts/IAuthProvider.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.core.contracts; 2 | 3 | public interface IAuthProvider { 4 | String getVerifiedMobileNumber(String idToken); 5 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/shell/api/dtos/ChangePermissionRequest.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.shell.api.dtos; 2 | 3 | public record ChangePermissionRequest(String permission, boolean shouldAdd) { 4 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/shell/api/dtos/UpdateBasicInfoRequest.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.shell.api.dtos; 2 | 3 | public record UpdateBasicInfoRequest(String fullName,String photoUrl) { 4 | } 5 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/core/commands/RefreshToken/RefreshTokenResponse.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.core.commands.RefreshToken; 2 | 3 | public record RefreshTokenResponse(String accessToken) { 4 | } 5 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/core/queriers/GetConnectionResponse.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.core.queriers; 2 | 3 | import java.util.List; 4 | 5 | public record GetConnectionResponse(List connections) { 6 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/core/commands/Authenticate/AuthenticateResponse.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.core.commands.Authenticate; 2 | 3 | public record AuthenticateResponse(String accessToken, String refreshToken) { 4 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/api/dtos/ContributeRequest.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.api.dtos; 2 | 3 | import com.charity_hub.shared.abstractions.Request; 4 | 5 | public record ContributeRequest(int amount) implements Request { 6 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/application/queries/GetCase/IGetCaseHandler.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.application.queries.GetCase; 2 | 3 | public interface IGetCaseHandler { 4 | GetCaseResponse handle(GetCaseQuery query); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/shared/dtos/CaseOpenedDTO.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.shared.dtos; 2 | 3 | public record CaseOpenedDTO( 4 | int caseCode, 5 | String title, 6 | String description 7 | ) implements CaseEventDto { 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/ledger/internal/application/models/InvitationResponse.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.ledger.internal.application.models; 2 | 3 | import java.util.UUID; 4 | 5 | public record InvitationResponse(String invitedMobileNumber, UUID inviterId) { 6 | } 7 | -------------------------------------------------------------------------------- /http-tests/http-client.env.json: -------------------------------------------------------------------------------- 1 | { 2 | "localhost": { 3 | "host": "http://localhost:8080" 4 | }, 5 | "test-server": { 6 | "host": "https://massive-petal-278513.ew.r.appspot.com" 7 | }, 8 | "prod-server": { 9 | "host": "http://localhost:8080" 10 | } 11 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/shared/dtos/ContributionRemindedDTO.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.shared.dtos; 2 | 3 | import java.util.UUID; 4 | 5 | public record ContributionRemindedDTO( 6 | UUID id, 7 | UUID contributorId 8 | ) implements CaseEventDto { 9 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/ledger/internal/application/queries/GetLedger/LedgerResponse.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.ledger.internal.application.queries.GetLedger; 2 | 3 | import java.util.List; 4 | 5 | public record LedgerResponse(List contributions) { 6 | } 7 | 8 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/ledger/internal/infrastructure/db/MemberEntity.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.ledger.internal.infrastructure.db; 2 | 3 | import java.util.List; 4 | 5 | public record MemberEntity(String _id, List ancestors, String parent, List children) { } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/shell/api/dtos/InviteUserRequest.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.shell.api.dtos; 2 | 3 | import com.charity_hub.shared.abstractions.Request; 4 | 5 | public record InviteUserRequest(String mobileNumber) implements Request { 6 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/api/dtos/PayContributionRequest.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.api.dtos; 2 | 3 | import com.charity_hub.shared.abstractions.Request; 4 | 5 | public record PayContributionRequest(String PaymentProof) implements Request { 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/application/queries/GetAllCases/IGetAllCasesHandler.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.application.queries.GetAllCases; 2 | 3 | public interface IGetAllCasesHandler { 4 | GetCasesQueryResult handle(GetAllCasesQuery query); 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/application/queries/GetDraftCases/GetDraftCases.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.application.queries.GetDraftCases; 2 | 3 | import com.charity_hub.shared.abstractions.Query; 4 | 5 | public class GetDraftCases implements Query { 6 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/ledger/internal/application/eventHandlers/AccountCreated/AccountCreated.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.ledger.internal.application.eventHandlers.AccountCreated; 2 | 3 | import java.util.UUID; 4 | 5 | public record AccountCreated(UUID id, String mobileNumber){} -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/core/events/AccountBlocked.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.core.events; 2 | 3 | import com.charity_hub.accounts.internal.core.model.account.AccountId; 4 | 5 | public record AccountBlocked(AccountId id) implements AccountEvent { 6 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/shared/dtos/ContributionPaidDTO.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.shared.dtos; 2 | 3 | import java.util.UUID; 4 | 5 | public record ContributionPaidDTO( 6 | UUID id, 7 | UUID contributorId 8 | ) implements CaseEventDto { 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/ledger/internal/domain/model/MemberId.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.ledger.internal.domain.model; 2 | 3 | import com.charity_hub.shared.domain.model.ValueObject; 4 | 5 | import java.util.UUID; 6 | 7 | public record MemberId(UUID value) implements ValueObject { 8 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/shared/exceptions/BusinessRuleException.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.shared.exceptions; 2 | 3 | public class BusinessRuleException extends RuntimeException { 4 | public BusinessRuleException(String description) { 5 | super(description); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/shell/api/dtos/RegisterFCMTokenRequest.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.shell.api.dtos; 2 | 3 | import com.charity_hub.shared.abstractions.Request; 4 | 5 | public record RegisterFCMTokenRequest(String fcmToken) implements Request { 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/shared/dtos/CaseClosedDTO.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.shared.dtos; 2 | 3 | public record CaseClosedDTO( 4 | int caseCode, 5 | String title, 6 | int goal, 7 | int contributionsTotal 8 | ) implements CaseEventDto { 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/shared/dtos/ContributionConfirmedDTO.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.shared.dtos; 2 | 3 | import java.util.UUID; 4 | 5 | public record ContributionConfirmedDTO( 6 | UUID id, 7 | UUID contributorId 8 | ) implements CaseEventDto { 9 | } 10 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/core/commands/BlockAccount/BlockAccount.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.core.commands.BlockAccount; 2 | 3 | import com.charity_hub.shared.abstractions.Command; 4 | 5 | public record BlockAccount(String userId, boolean isUnblock) implements Command { 6 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/core/queriers/GetConnectionsQuery.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.core.queriers; 2 | 3 | import com.charity_hub.shared.abstractions.Query; 4 | 5 | import java.util.UUID; 6 | 7 | public record GetConnectionsQuery(UUID userId) implements Query { 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/api/dtos/GetCasesRequest.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.api.dtos; 2 | 3 | import com.charity_hub.shared.abstractions.Request; 4 | 5 | public record GetCasesRequest(int offset, int limit, Integer code, String tag, String content) implements Request { 6 | 7 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/application/commands/DeleteDraftCase/DeleteDraftCase.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.application.commands.DeleteDraftCase; 2 | 3 | import com.charity_hub.shared.abstractions.Command; 4 | 5 | public record DeleteDraftCase(int caseCode) implements Command { 6 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/shared/domain/model/Pair.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.shared.domain.model; 2 | 3 | public class Pair { 4 | public final T first; 5 | public final U second; 6 | 7 | public Pair(T t, U u) { 8 | this.first = t; 9 | this.second = u; 10 | } 11 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/ledger/internal/application/queries/GetLedger/GetLedger.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.ledger.internal.application.queries.GetLedger; 2 | 3 | import com.charity_hub.shared.abstractions.Query; 4 | 5 | import java.util.UUID; 6 | 7 | public record GetLedger(UUID userId) implements Query { 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/core/commands/Authenticate/Authenticate.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.core.commands.Authenticate; 2 | 3 | import com.charity_hub.shared.abstractions.Command; 4 | 5 | public record Authenticate(String idToken, String deviceId, String deviceType) implements Command { 6 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/application/commands/ChangeCaseStatus/ChangeCaseStatus.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.application.commands.ChangeCaseStatus; 2 | 3 | import com.charity_hub.shared.abstractions.Command; 4 | 5 | public record ChangeCaseStatus(int caseCode, boolean isActionOpen) implements Command { 6 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/domain/model/Case/Status.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.domain.model.Case; 2 | 3 | public enum Status { 4 | DRAFT, 5 | OPENED, 6 | CLOSED; 7 | 8 | public static Status fromString(String status) { 9 | return valueOf(status.toUpperCase()); 10 | } 11 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/shared/dtos/ContributionMadeDTO.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.shared.dtos; 2 | 3 | import java.util.UUID; 4 | 5 | public record ContributionMadeDTO( 6 | UUID id, 7 | UUID contributorId, 8 | int caseCode, 9 | int amount 10 | ) implements CaseEventDto { 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/application/commands/Contribute/Contribute.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.application.commands.Contribute; 2 | 3 | import com.charity_hub.shared.abstractions.Command; 4 | 5 | import java.util.UUID; 6 | 7 | public record Contribute(int amount, UUID userId, int caseCode) implements Command { 8 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/ledger/internal/application/queries/GetLedger/Contribution.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.ledger.internal.application.queries.GetLedger; 2 | 3 | public record Contribution(String id, String contributorId, int caseCode, String caseName, int amount, String status, 4 | long contributionDate) { 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/ledger/internal/application/queries/GetLedgerSummary/GetLedgerSummary.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.ledger.internal.application.queries.GetLedgerSummary; 2 | 3 | import com.charity_hub.shared.abstractions.Query; 4 | 5 | import java.util.UUID; 6 | 7 | public record GetLedgerSummary(UUID userId) implements Query { 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/core/commands/InviteAccount/InvitationAccount.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.core.commands.InviteAccount; 2 | 3 | import com.charity_hub.shared.abstractions.Command; 4 | 5 | import java.util.UUID; 6 | 7 | public record InvitationAccount(String mobileNumber, UUID inviterId) implements Command { 8 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/domain/events/CaseDeleted.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.domain.events; 2 | 3 | import com.charity_hub.cases.internal.domain.model.Case.Case; 4 | 5 | public record CaseDeleted(Case case_) implements CaseEvent { 6 | 7 | public Case getCase() { 8 | return case_; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/domain/events/CaseUpdated.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.domain.events; 2 | 3 | import com.charity_hub.cases.internal.domain.model.Case.Case; 4 | 5 | public record CaseUpdated(Case case_) implements CaseEvent { 6 | 7 | public Case getCase() { 8 | return case_; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/core/contracts/IAccountReadRepo.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.core.contracts; 2 | 3 | import com.charity_hub.accounts.internal.core.queriers.Account; 4 | 5 | import java.util.List; 6 | import java.util.UUID; 7 | 8 | public interface IAccountReadRepo { 9 | List getConnections(UUID id); 10 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/application/commands/ConfirmContribution/ConfirmContribution.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.application.commands.ConfirmContribution; 2 | 3 | import com.charity_hub.shared.abstractions.Command; 4 | 5 | import java.util.UUID; 6 | 7 | public record ConfirmContribution(UUID contributionId) implements Command { 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/core/commands/ChangePermission/ChangePermission.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.core.commands.ChangePermission; 2 | 3 | import com.charity_hub.shared.abstractions.Command; 4 | 5 | import java.util.UUID; 6 | 7 | public record ChangePermission(UUID userId, String permission, boolean shouldAdd) implements Command { 8 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/application/commands/PayContribution/PayContribution.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.application.commands.PayContribution; 2 | 3 | import com.charity_hub.shared.abstractions.Command; 4 | 5 | import java.util.UUID; 6 | 7 | public record PayContribution(UUID contributionId, String paymentProof) implements Command { 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/shared/ModuleMetadata.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.shared; 2 | 3 | import org.springframework.modulith.ApplicationModule; 4 | import org.springframework.modulith.PackageInfo; 5 | 6 | @PackageInfo 7 | @ApplicationModule( 8 | type = ApplicationModule.Type.OPEN, 9 | allowedDependencies = {} 10 | ) 11 | public class ModuleMetadata { 12 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/core/commands/UpdateBasicInfo/UpdateBasicInfo.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.core.commands.UpdateBasicInfo; 2 | 3 | import com.charity_hub.shared.abstractions.Command; 4 | 5 | import java.util.UUID; 6 | 7 | public record UpdateBasicInfo(UUID userId, String deviceId, String fullName, String photoUrl) implements Command { 8 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/shell/db/DeviceEntity.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.shell.db; 2 | 3 | 4 | public record DeviceEntity(String deviceId, 5 | String deviceType, 6 | String refreshToken, 7 | String fcmToken, 8 | long lastAccessTime) { 9 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/application/queries/GetCase/GetCaseQuery.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.application.queries.GetCase; 2 | 3 | import com.charity_hub.shared.abstractions.Query; 4 | import com.charity_hub.shared.auth.AccessTokenPayload; 5 | 6 | public record GetCaseQuery(int caseCode, AccessTokenPayload accessTokenPayload) implements Query { 7 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/shared/AccountDTO.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.shared; 2 | 3 | import java.util.List; 4 | 5 | public record AccountDTO(String id, 6 | String mobileNumber, 7 | String fullName, 8 | String photoUrl, 9 | List devicesTokens) { 10 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/shared/exceptions/NotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.shared.exceptions; 2 | 3 | /** 4 | * Exception for not found errors. 5 | */ 6 | public class NotFoundException extends RuntimeException { 7 | public NotFoundException(String description) { 8 | super("Not Found: " + (description != null ? description : "")); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/core/exceptions/AlreadyInvitedException.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.core.exceptions; 2 | 3 | import com.charity_hub.shared.exceptions.BusinessRuleException; 4 | 5 | public class AlreadyInvitedException extends BusinessRuleException { 6 | public AlreadyInvitedException(String message) { 7 | super(message); 8 | } 9 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/shared/dtos/CaseEventDto.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.shared.dtos; 2 | 3 | public sealed interface CaseEventDto permits 4 | CaseCreatedDTO, 5 | CaseOpenedDTO, 6 | CaseClosedDTO, 7 | ContributionConfirmedDTO, 8 | ContributionMadeDTO, 9 | ContributionPaidDTO, 10 | ContributionRemindedDTO { 11 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/shared/IAccountsAPI.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.shared; 2 | 3 | import java.util.List; 4 | import java.util.UUID; 5 | 6 | public interface IAccountsAPI { 7 | InvitationResponse getInvitationByMobileNumber(String mobileNumber); 8 | 9 | AccountDTO getById(UUID id); 10 | 11 | List getAccountsByIds(List idsList); 12 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/shared/exceptions/BadRequestException.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.shared.exceptions; 2 | 3 | /** 4 | * Exception for bad request errors. 5 | */ 6 | public class BadRequestException extends RuntimeException { 7 | public BadRequestException(String description) { 8 | super("Bad Request: " + (description != null ? description : "")); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gradle:8.7-jdk21-alpine AS build 2 | WORKDIR /home/gradle/src 3 | COPY --chown=gradle:gradle . . 4 | RUN gradle build --no-daemon -x test 5 | 6 | 7 | FROM eclipse-temurin:21-jre-alpine 8 | WORKDIR /app 9 | COPY --from=build /home/gradle/src/build/libs/*.jar app.jar 10 | COPY --from=build /home/gradle/src/cert/ ./cert/ 11 | EXPOSE 8080 12 | ENTRYPOINT ["java", "-jar", "app.jar"] 13 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/domain/events/CaseEvent.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.domain.events; 2 | 3 | import com.charity_hub.shared.domain.model.DomainEvent; 4 | 5 | public sealed interface CaseEvent extends DomainEvent 6 | permits CaseClosed, CaseDeleted, CaseOpened, CaseUpdated, ContributionConfirmed, ContributionMade, ContributionPaid, ContributionReminded { 7 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/core/commands/RegisterNotificationToken/RegisterNotificationToken.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.core.commands.RegisterNotificationToken; 2 | 3 | import com.charity_hub.shared.abstractions.Command; 4 | 5 | import java.util.UUID; 6 | 7 | public record RegisterNotificationToken(String fcmToken, String deviceId, UUID userId) implements Command { 8 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/core/events/AccountEvent.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.core.events; 2 | 3 | import com.charity_hub.shared.domain.model.DomainEvent; 4 | 5 | public sealed interface AccountEvent extends DomainEvent permits AccountAuthenticated, AccountBlocked, AccountCreated, AccountUnBlocked, BasicInfoUpdated, FCMTokenUpdated, PermissionGranted, PermissionRemoved { 6 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/shared/exceptions/UnAuthorized.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.shared.exceptions; 2 | 3 | public class UnAuthorized extends RuntimeException { 4 | public UnAuthorized(String description) { 5 | super("Unauthorized: " + (description != null ? description : "")); 6 | } 7 | 8 | public UnAuthorized() { 9 | super("Unauthorized"); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/api/dtos/UpdateCaseRequest.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.api.dtos; 2 | 3 | import com.charity_hub.shared.abstractions.Request; 4 | 5 | import java.util.List; 6 | 7 | public record UpdateCaseRequest(String title, String description, int goal, boolean acceptZakat, 8 | List documents) implements Request { 9 | 10 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/domain/exceptions/InvalidAmountException.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.domain.exceptions; 2 | 3 | import com.charity_hub.shared.exceptions.BusinessRuleException; 4 | 5 | public class InvalidAmountException extends BusinessRuleException { 6 | 7 | public InvalidAmountException(int value) { 8 | super(value + " is not a valid amount"); 9 | } 10 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/core/contracts/IInvitationRepo.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.core.contracts; 2 | 3 | import com.charity_hub.accounts.internal.core.model.invitation.Invitation; 4 | 5 | public interface IInvitationRepo { 6 | void save(Invitation invitation); 7 | 8 | Invitation get(String mobileNumber); 9 | 10 | boolean hasInvitation(String mobileNumber); 11 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/api/dtos/CreateCaseRequest.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.api.dtos; 2 | 3 | import com.charity_hub.shared.abstractions.Request; 4 | 5 | import java.util.List; 6 | 7 | public record CreateCaseRequest(String title, String description, int goal, boolean publish, boolean acceptZakat, 8 | List documents) implements Request { 9 | 10 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/application/queries/GetAllCases/GetAllCasesQuery.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.application.queries.GetAllCases; 2 | 3 | import com.charity_hub.shared.abstractions.Query; 4 | 5 | public record GetAllCasesQuery( 6 | Integer code, 7 | String tag, 8 | String content, 9 | int offset, 10 | int limit 11 | ) implements Query { 12 | 13 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/shared/ModuleMetadata.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.shared; 2 | 3 | import org.springframework.modulith.ApplicationModule; 4 | import org.springframework.modulith.NamedInterface; 5 | import org.springframework.modulith.PackageInfo; 6 | 7 | @PackageInfo 8 | @NamedInterface("cases.shared") 9 | @ApplicationModule(type = ApplicationModule.Type.OPEN) 10 | public class ModuleMetadata { 11 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/core/commands/RefreshToken/RefreshToken.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.core.commands.RefreshToken; 2 | 3 | import com.charity_hub.shared.abstractions.Command; 4 | 5 | import java.util.UUID; 6 | 7 | public record RefreshToken(String encodedRefreshToken, 8 | UUID userId, 9 | String deviceId) implements Command { 10 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/domain/model/Case/CaseCode.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.domain.model.Case; 2 | 3 | import com.charity_hub.shared.domain.model.ValueObject; 4 | 5 | public record CaseCode(int value) implements ValueObject { 6 | public CaseCode { 7 | if (value <= 0) { 8 | throw new IllegalArgumentException("Code must be greater than 0"); 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/shared/abstractions/VoidCommandHandler.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.shared.abstractions; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | public abstract class VoidCommandHandler { 7 | protected final Logger logger = LoggerFactory.getLogger(this.getClass().getSimpleName()); 8 | 9 | public abstract void handle(TCommand command); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/shared/ModuleMetadata.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.shared; 2 | 3 | import org.springframework.modulith.ApplicationModule; 4 | import org.springframework.modulith.NamedInterface; 5 | import org.springframework.modulith.PackageInfo; 6 | 7 | @PackageInfo 8 | @NamedInterface("accounts.shared") 9 | @ApplicationModule(type = ApplicationModule.Type.OPEN) 10 | public class ModuleMetadata { 11 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/application/commands/CreateCase/CreateCase.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.application.commands.CreateCase; 2 | 3 | import com.charity_hub.shared.abstractions.Command; 4 | 5 | import java.util.List; 6 | 7 | public record CreateCase(String title, String description, int goal, boolean publish, boolean acceptZakat, 8 | List documents) implements Command { 9 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/shared/domain/IEventBus.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.shared.domain; 2 | 3 | public interface IEventBus { 4 | void subscribe(Object owner, Class event, EventCallback callback); 5 | 6 | void unsubscribe(Object owner); 7 | 8 | void push(T event); 9 | 10 | @FunctionalInterface 11 | public interface EventCallback { 12 | void handle(T event); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/shared/dtos/ModuleMetadata.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.shared.dtos; 2 | 3 | import org.springframework.modulith.ApplicationModule; 4 | import org.springframework.modulith.NamedInterface; 5 | import org.springframework.modulith.PackageInfo; 6 | 7 | @PackageInfo 8 | @NamedInterface("cases.shared.dtos") 9 | @ApplicationModule(type = ApplicationModule.Type.OPEN) 10 | public class ModuleMetadata { 11 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/application/commands/UpdateCase/UpdateCase.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.application.commands.UpdateCase; 2 | 3 | import com.charity_hub.shared.abstractions.Command; 4 | 5 | import java.util.List; 6 | 7 | public record UpdateCase(int caseCode, String title, String description, int goal, boolean acceptZakat, 8 | List documents) implements Command { 9 | 10 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/notifications/NotificationApi.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.notifications; 2 | 3 | import java.util.List; 4 | 5 | public interface NotificationApi { 6 | void notifyDevices(List tokens, String title, String body); 7 | 8 | void notifyTopicSubscribers(String topic, String event, Object extraJsonData, String title, String body); 9 | 10 | void subscribeToTopic(String topic, List tokens); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/domain/model/Contribution/ContributionId.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.domain.model.Contribution; 2 | 3 | import com.charity_hub.shared.domain.model.ValueObject; 4 | 5 | import java.util.UUID; 6 | 7 | public record ContributionId(UUID value) implements ValueObject { 8 | 9 | public static ContributionId generate() { 10 | return new ContributionId(UUID.randomUUID()); 11 | } 12 | 13 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/shared/dtos/ContributionDTO.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.shared.dtos; 2 | 3 | public record ContributionDTO(String id, 4 | String contributorId, 5 | int caseCode, 6 | int amount, 7 | int status, 8 | long contributionDate, 9 | String proofUrl) { 10 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/core/contracts/IJWTGenerator.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.core.contracts; 2 | 3 | import com.charity_hub.accounts.internal.core.model.account.Account; 4 | import com.charity_hub.accounts.internal.core.model.device.Device; 5 | 6 | public interface IJWTGenerator { 7 | String generateAccessToken(Account account, Device device); 8 | 9 | String generateRefreshToken(Account account, Device device); 10 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/shared/infrastructure/JacksonConfig.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.shared.infrastructure; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | @Configuration 8 | public class JacksonConfig { 9 | 10 | @Bean 11 | public ObjectMapper objectMapper() { 12 | return new ObjectMapper(); 13 | } 14 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/core/model/account/AccountId.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.core.model.account; 2 | 3 | import com.charity_hub.shared.domain.model.ValueObject; 4 | 5 | import java.util.UUID; 6 | 7 | public record AccountId(UUID value) implements ValueObject { 8 | 9 | // Static factory method (replacing companion object) 10 | public static AccountId generate() { 11 | return new AccountId(UUID.randomUUID()); 12 | } 13 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/infrastructure/db/ContributionEntity.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.infrastructure.db; 2 | 3 | public record ContributionEntity(String _id, String contributorId, int caseCode, int amount, int status, 4 | long contributionDate, String paymentProof) { 5 | public static final int STATUS_PLEDGED = 1; 6 | public static final int STATUS_PAID = 2; 7 | public static final int STATUS_CONFIRMED = 3; 8 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/shared/dtos/CaseCreatedDTO.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.shared.dtos; 2 | 3 | import java.util.Date; 4 | import java.util.List; 5 | 6 | public record CaseCreatedDTO( 7 | int caseCode, 8 | String title, 9 | String description, 10 | int goal, 11 | boolean acceptZakat, 12 | List tags, 13 | List documents, 14 | Date creationDate 15 | ) implements CaseEventDto { 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/ledger/internal/application/contracts/IMembersNetworkRepo.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.ledger.internal.application.contracts; 2 | 3 | import com.charity_hub.ledger.internal.domain.model.Member; 4 | import com.charity_hub.ledger.internal.domain.model.MemberId; 5 | 6 | import java.util.UUID; 7 | 8 | public interface IMembersNetworkRepo { 9 | Member getById(UUID id); 10 | 11 | void delete(MemberId id); 12 | 13 | void save(Member member); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/shell/db/AccountEntity.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.shell.db; 2 | 3 | import org.springframework.data.annotation.Id; 4 | 5 | import java.util.List; 6 | 7 | public record AccountEntity(@Id String accountId, String mobileNumber, String fullName, String photoUrl, 8 | boolean blocked, 9 | long joinedDate, long lastUpdated, List permissions, List devices) { 10 | 11 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/shared/abstractions/CommandHandler.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.shared.abstractions; 2 | 3 | import com.google.firebase.auth.FirebaseAuthException; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | public abstract class CommandHandler { 8 | protected final Logger logger = LoggerFactory.getLogger(this.getClass().getSimpleName()); 9 | 10 | public abstract TResult handle(TCommand command) throws FirebaseAuthException; 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/shared/domain/ILogger.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.shared.domain; 2 | 3 | public interface ILogger { 4 | void info(String message); 5 | 6 | void info(String message, Object... arguments); 7 | 8 | void debug(String message); 9 | 10 | void debug(String message, Object... arguments); 11 | 12 | void warn(String message); 13 | 14 | void warn(String message, Object... arguments); 15 | 16 | void error(String message); 17 | 18 | void error(String message, Object... arguments); 19 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/domain/model/Contribution/ContributionStatus.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.domain.model.Contribution; 2 | 3 | public enum ContributionStatus { 4 | PLEDGED, 5 | PAID, 6 | CONFIRMED; 7 | 8 | public static ContributionStatus fromString(String status) { 9 | return valueOf(status); 10 | } 11 | 12 | boolean isConfirmed() { 13 | return this == CONFIRMED; 14 | } 15 | 16 | boolean isNotPledged() { 17 | return this != PLEDGED; 18 | } 19 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/core/queriers/Account.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.core.queriers; 2 | 3 | import java.util.List; 4 | 5 | public record Account(String uuid, String fullName, String photoUrl, List permissions) { 6 | public Account(String uuid, String fullName, String photoUrl, List permissions) { 7 | this.uuid = uuid; 8 | this.fullName = fullName; 9 | this.photoUrl = photoUrl; 10 | this.permissions = List.copyOf(permissions); // Create immutable copy 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/ledger/internal/application/contracts/IAccountGateway.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.ledger.internal.application.contracts; 2 | 3 | import com.charity_hub.accounts.shared.AccountDTO; 4 | import com.charity_hub.ledger.internal.application.models.InvitationResponse; 5 | import com.charity_hub.ledger.internal.domain.model.MemberId; 6 | 7 | import java.util.List; 8 | 9 | public interface IAccountGateway { 10 | InvitationResponse getInvitationByMobileNumber(String mobileNumber); 11 | List getAccounts(List ids); 12 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/core/model/invitation/Invitation.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.core.model.invitation; 2 | 3 | import com.charity_hub.accounts.internal.core.model.account.MobileNumber; 4 | 5 | import java.util.UUID; 6 | 7 | public record Invitation(MobileNumber invitedMobileNumber, UUID inviterId) { 8 | public static Invitation of(String invitedMobileNumber, UUID inviterId) { 9 | return new Invitation( 10 | MobileNumber.create(invitedMobileNumber), 11 | inviterId 12 | ); 13 | } 14 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/domain/model/CaseDetails/Tag.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.domain.model.CaseDetails; 2 | 3 | import com.charity_hub.shared.domain.extension.ValueValidator; 4 | 5 | 6 | public record Tag(String value) { 7 | private static final int MIN_LENGTH = 3; 8 | private static final int MAX_LENGTH = 20; 9 | 10 | public Tag { 11 | ValueValidator.assertWithinRange(getClass(), value, MIN_LENGTH, MAX_LENGTH); 12 | } 13 | 14 | public static Tag create(String value) { 15 | return new Tag(value); 16 | } 17 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/core/model/device/DeviceType.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.core.model.device; 2 | 3 | import java.util.Locale; 4 | 5 | public record DeviceType(String value) { 6 | 7 | public static DeviceType create(String value) { 8 | String normalizedValue = value.toLowerCase(Locale.getDefault()); 9 | return switch (normalizedValue) { 10 | case "android", "ios", "web" -> new DeviceType(value); 11 | default -> throw new IllegalArgumentException("Invalid device type"); 12 | }; 13 | } 14 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/domain/model/CaseDetails/Title.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.domain.model.CaseDetails; 2 | 3 | public record Title(String value) { 4 | private static final int MIN_LENGTH = 5; 5 | private static final int MAX_LENGTH = 100; 6 | 7 | public Title { 8 | if (value.length() < MIN_LENGTH || value.length() > MAX_LENGTH) { 9 | throw new IllegalArgumentException( 10 | "Title must be between " + MIN_LENGTH + " and " + MAX_LENGTH + " characters" 11 | ); 12 | } 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/core/model/account/FullName.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.core.model.account; 2 | 3 | import com.charity_hub.shared.domain.extension.ValueValidator; 4 | 5 | public record FullName(String value) { 6 | private static final int MIN_LENGTH = 2; 7 | private static final int MAX_LENGTH = 50; 8 | 9 | public FullName { 10 | ValueValidator.assertWithinRange(getClass(), value, MIN_LENGTH, MAX_LENGTH); 11 | } 12 | 13 | public static FullName create(String value) { 14 | return new FullName(value); 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/shared/ICasesAPI.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.shared; 2 | 3 | import com.charity_hub.cases.shared.dtos.CaseDTO; 4 | import com.charity_hub.cases.shared.dtos.ContributionDTO; 5 | 6 | import java.util.List; 7 | import java.util.UUID; 8 | 9 | public interface ICasesAPI { 10 | List getUsersContributions(UUID userId); 11 | 12 | List getNotConfirmedContributions(UUID userId); 13 | 14 | List getUsersContributions(List usersIds); 15 | 16 | List getCasesByCodes(List casesCodes); 17 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/domain/events/CaseOpened.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.domain.events; 2 | 3 | import com.charity_hub.cases.internal.domain.model.Case.Case; 4 | import com.charity_hub.cases.internal.domain.model.Case.CaseCode; 5 | 6 | public record CaseOpened(CaseCode caseCode, String title, String description) implements CaseEvent { 7 | 8 | public static CaseOpened from(Case case_) { 9 | return new CaseOpened( 10 | case_.getCaseCode(), 11 | case_.getTitle(), 12 | case_.getDescription() 13 | ); 14 | } 15 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/domain/contracts/INotificationService.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.domain.contracts; 2 | 3 | import com.charity_hub.cases.shared.dtos.CaseClosedDTO; 4 | import com.charity_hub.cases.shared.dtos.CaseOpenedDTO; 5 | import com.charity_hub.cases.shared.dtos.ContributionMadeDTO; 6 | 7 | public interface INotificationService { 8 | void subscribeAccountToCaseUpdates(String token); 9 | 10 | void notifyCaseOpened(CaseOpenedDTO case_); 11 | 12 | void notifyCaseClosed(CaseClosedDTO case_); 13 | 14 | void notifyContributionMade(ContributionMadeDTO contribution); 15 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/shared/domain/model/Permission.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.shared.domain.model; 2 | 3 | public enum Permission { 4 | FULL_ACCESS, 5 | VIEW, 6 | CREATE_CASES; 7 | 8 | /** 9 | * Creates a Permission from its string representation. 10 | * 11 | * @param permission the string representation of the permission 12 | * @return the corresponding Permission enum value 13 | * @throws IllegalArgumentException if the string doesn't match any Permission value 14 | */ 15 | public static Permission fromString(String permission) { 16 | return valueOf(permission); 17 | } 18 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/shared/dtos/CaseDTO.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.shared.dtos; 2 | 3 | import java.util.List; 4 | 5 | public record CaseDTO(int code, 6 | String title, 7 | String description, 8 | int goal, 9 | int collected, 10 | int status, 11 | boolean acceptZakat, 12 | long creationDate, 13 | long lastUpdated, 14 | List tags, 15 | List documents, 16 | int contributions) { 17 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/ledger/internal/application/contracts/ICasesGateway.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.ledger.internal.application.contracts; 2 | 3 | import com.charity_hub.cases.shared.dtos.CaseDTO; 4 | import com.charity_hub.cases.shared.dtos.ContributionDTO; 5 | 6 | import java.util.List; 7 | import java.util.UUID; 8 | 9 | public interface ICasesGateway { 10 | List getContributions(UUID userId); 11 | 12 | List getCasesByIds(List casesCodes); 13 | 14 | List getNotConfirmedContributions(UUID userId); 15 | 16 | List getContributions(List usersIds); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/ledger/internal/application/queries/GetLedgerSummary/LedgerSummaryDefaultResponse.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.ledger.internal.application.queries.GetLedgerSummary; 2 | 3 | import java.util.List; 4 | 5 | public record LedgerSummaryDefaultResponse( 6 | int confirmed, 7 | int pledged, 8 | int paid, 9 | List connectionsLedger 10 | ) { 11 | public record ConnectionLedger( 12 | String uuid, 13 | String name, 14 | String photoUrl, 15 | int pledged, 16 | int paid, 17 | int confirmed 18 | ) { 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/domain/model/CaseDetails/Goal.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.domain.model.CaseDetails; 2 | 3 | import com.charity_hub.shared.exceptions.BadRequestException; 4 | 5 | public record Goal(int value) { 6 | private static final int MIN_AMOUNT = 100; 7 | private static final int MAX_AMOUNT = 9999999; 8 | 9 | public Goal { 10 | if (value < MIN_AMOUNT || value > MAX_AMOUNT) { 11 | throw new BadRequestException("Goal must be between " + MIN_AMOUNT + " and " + MAX_AMOUNT); 12 | } 13 | } 14 | public static Goal of(int value) { 15 | return new Goal(value); 16 | } 17 | } -------------------------------------------------------------------------------- /src/main/resources/logback-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | %d{yyyy-MM-dd HH:mm:ss} %highlight([%thread]) %highlight(%-5level) %highlight(%logger{36}) - %msg%n 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/**/build/ 6 | !**/src/test/**/build/ 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | bin/ 17 | !**/src/main/**/bin/ 18 | !**/src/test/**/bin/ 19 | 20 | ### IntelliJ IDEA ### 21 | .idea 22 | *.iws 23 | *.iml 24 | *.ipr 25 | out/ 26 | !**/src/main/**/out/ 27 | !**/src/test/**/out/ 28 | 29 | ### NetBeans ### 30 | /nbproject/private/ 31 | /nbbuild/ 32 | /dist/ 33 | /nbdist/ 34 | /.nb-gradle/ 35 | 36 | ### VS Code ### 37 | .vscode/ 38 | 39 | ### Kotlin ### 40 | .kotlin 41 | /node_modules/ 42 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/domain/events/ContributionPaid.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.domain.events; 2 | 3 | import com.charity_hub.cases.internal.domain.model.Contribution.Contribution; 4 | import com.charity_hub.cases.internal.domain.model.Contribution.ContributionId; 5 | 6 | import java.util.UUID; 7 | 8 | public record ContributionPaid(ContributionId id, UUID contributorId) implements CaseEvent { 9 | 10 | public static ContributionPaid from(Contribution contribution) { 11 | return new ContributionPaid( 12 | contribution.getId(), 13 | contribution.getContributorId() 14 | ); 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/core/events/AccountCreated.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.core.events; 2 | 3 | import com.charity_hub.accounts.internal.core.model.account.Account; 4 | import com.charity_hub.accounts.internal.core.model.account.AccountId; 5 | import com.charity_hub.accounts.internal.core.model.account.MobileNumber; 6 | 7 | public record AccountCreated(AccountId id, MobileNumber mobileNumber) implements AccountEvent { 8 | 9 | public static AccountCreated from(Account account) { 10 | return new AccountCreated( 11 | account.getId(), 12 | account.getMobileNumber() 13 | ); 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/core/model/device/DeviceId.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.core.model.device; 2 | 3 | import com.charity_hub.shared.domain.extension.ValueValidator; 4 | import com.charity_hub.shared.domain.model.ValueObject; 5 | 6 | public record DeviceId(String value) implements ValueObject { 7 | private static final int MIN_LENGTH = 15; 8 | private static final int MAX_LENGTH = 50; 9 | 10 | public DeviceId { 11 | ValueValidator.assertWithinRange(getClass(), value, MIN_LENGTH, MAX_LENGTH); 12 | } 13 | 14 | public static DeviceId create(String value) { 15 | return new DeviceId(value); 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/application/commands/Contribute/ContributeDefaultResponse.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.application.commands.Contribute; 2 | 3 | public class ContributeDefaultResponse { 4 | private final String contributionId; 5 | 6 | public ContributeDefaultResponse(String contributionId) { 7 | 8 | this.contributionId = contributionId; 9 | } 10 | 11 | public ContributeDefaultResponse(int id, String description, String contributionId) { 12 | 13 | this.contributionId = contributionId; 14 | } 15 | 16 | public String getContributionId() { 17 | return contributionId; 18 | } 19 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/domain/events/ContributionReminded.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.domain.events; 2 | 3 | import com.charity_hub.cases.internal.domain.model.Contribution.Contribution; 4 | import com.charity_hub.cases.internal.domain.model.Contribution.ContributionId; 5 | 6 | import java.util.UUID; 7 | 8 | public record ContributionReminded(ContributionId id, UUID contributorId) implements CaseEvent { 9 | 10 | public static ContributionReminded from(Contribution contribution) { 11 | return new ContributionReminded( 12 | contribution.getId(), 13 | contribution.getContributorId() 14 | ); 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/domain/events/ContributionConfirmed.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.domain.events; 2 | 3 | import com.charity_hub.cases.internal.domain.model.Contribution.Contribution; 4 | import com.charity_hub.cases.internal.domain.model.Contribution.ContributionId; 5 | 6 | import java.util.UUID; 7 | 8 | public record ContributionConfirmed(ContributionId id, UUID contributorId) implements CaseEvent { 9 | 10 | public static ContributionConfirmed from(Contribution contribution) { 11 | return new ContributionConfirmed( 12 | contribution.getId(), 13 | contribution.getContributorId() 14 | ); 15 | } 16 | 17 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/core/contracts/IAccountRepo.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.core.contracts; 2 | 3 | import com.charity_hub.accounts.internal.core.model.account.Account; 4 | 5 | import java.util.List; 6 | import java.util.Optional; 7 | import java.util.UUID; 8 | 9 | public interface IAccountRepo { 10 | Optional getById(UUID id); 11 | 12 | List getConnections(UUID id); 13 | 14 | Optional getByMobileNumber(String mobileNumber); 15 | 16 | void save(Account account); 17 | 18 | boolean isAdmin(String mobileNumber); 19 | 20 | void revoke(UUID uuid); 21 | 22 | boolean isRevoked(UUID id, long tokenIssueDate); 23 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/CharityHubApplication.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.boot.context.properties.ConfigurationPropertiesScan; 6 | import org.springframework.modulith.core.ApplicationModules; 7 | 8 | @SpringBootApplication 9 | @ConfigurationPropertiesScan(basePackageClasses = CharityHubApplication.class) 10 | public class CharityHubApplication { 11 | 12 | public static void main(String[] args) { 13 | ApplicationModules.of(CharityHubApplication.class).verify(); 14 | SpringApplication.run(CharityHubApplication.class, args); 15 | } 16 | } -------------------------------------------------------------------------------- /src/main/resources/application-test.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=charity_hub 2 | server.port=0 3 | spring.data.mongodb.uri=mongodb://localhost:27017/charity_hub_test?serverSelectionTimeoutMS=3000 4 | spring.data.mongodb.username= 5 | spring.data.mongodb.password= 6 | auth.secretKey=test-secret-key-for-testing 7 | accounts.admins=test@example.com 8 | firebase.service-account-path=./cert/adminsdk.json 9 | firebase.test-mode=true 10 | cases.initial-code=20039 11 | management.endpoints.web.exposure.include=health,info 12 | 13 | # Disable Spring Modulith event publication completion during tests 14 | spring.modulith.events.jdbc.schema-initialization.enabled=false 15 | spring.modulith.republish-outstanding-events-on-restart=false -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/domain/events/CaseClosed.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.domain.events; 2 | 3 | import com.charity_hub.cases.internal.domain.model.Case.Case; 4 | import com.charity_hub.cases.internal.domain.model.Case.CaseCode; 5 | 6 | public record CaseClosed(CaseCode caseCode, String title, int goal, int totalValue, String description) implements CaseEvent { 7 | 8 | public static CaseClosed from(Case case_) { 9 | return new CaseClosed( 10 | case_.getCaseCode(), 11 | case_.getTitle(), 12 | case_.getGoal(), 13 | case_.totalContributions(), 14 | case_.getDescription() 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/shell/repositories/mappers/AccountReadMapper.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.shell.repositories.mappers; 2 | 3 | import com.charity_hub.accounts.internal.core.queriers.Account; 4 | import com.charity_hub.accounts.internal.shell.db.AccountEntity; 5 | import org.springframework.stereotype.Component; 6 | 7 | @Component 8 | public class AccountReadMapper { 9 | public Account toQueryModel(AccountEntity entity) { 10 | return new Account( 11 | entity.accountId(), 12 | entity.fullName() != null ? entity.fullName() : "Unknown", 13 | entity.photoUrl(), 14 | entity.permissions() 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/shared/infrastructure/MessageConfig.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.shared.infrastructure; 2 | 3 | import org.springframework.context.MessageSource; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.context.support.ResourceBundleMessageSource; 7 | 8 | @Configuration 9 | public class MessageConfig { 10 | 11 | @Bean 12 | public MessageSource messageSource() { 13 | ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); 14 | messageSource.setBasenames("messages"); 15 | messageSource.setDefaultEncoding("UTF-8"); 16 | return messageSource; 17 | } 18 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/infrastructure/gateways/AccountsGateway.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.infrastructure.gateways; 2 | 3 | import com.charity_hub.accounts.shared.AccountDTO; 4 | import com.charity_hub.accounts.shared.IAccountsAPI; 5 | import org.springframework.stereotype.Component; 6 | 7 | import java.util.List; 8 | import java.util.UUID; 9 | 10 | @Component 11 | public class AccountsGateway { 12 | private final IAccountsAPI accountsAPI; 13 | 14 | public AccountsGateway(IAccountsAPI accountsAPI) { 15 | this.accountsAPI = accountsAPI; 16 | } 17 | 18 | public List getAccountsByIds(List idsList) { 19 | return accountsAPI.getAccountsByIds(idsList); 20 | } 21 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/domain/contracts/ICaseRepo.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.domain.contracts; 2 | 3 | import com.charity_hub.cases.internal.domain.model.Case.Case; 4 | import com.charity_hub.cases.internal.domain.model.Case.CaseCode; 5 | import com.charity_hub.cases.internal.domain.model.Contribution.Contribution; 6 | 7 | import java.util.Optional; 8 | import java.util.UUID; 9 | 10 | public interface ICaseRepo { 11 | int nextCaseCode(); 12 | 13 | Optional getByCode(CaseCode caseCode); 14 | 15 | void save(Case case_); 16 | 17 | void delete(CaseCode caseCode); 18 | 19 | void save(Contribution contribution); 20 | 21 | Optional getContributionById(UUID id); 22 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/domain/model/CaseDetails/Description.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.domain.model.CaseDetails; 2 | 3 | import com.charity_hub.shared.exceptions.BadRequestException; 4 | 5 | 6 | public record Description(String value) { 7 | private static final int MIN_LENGTH = 5; 8 | private static final int MAX_LENGTH = 2000; 9 | 10 | public Description { 11 | if (value.length() < MIN_LENGTH || value.length() > MAX_LENGTH) { 12 | throw new BadRequestException("Description must be between " + MIN_LENGTH + " and " + MAX_LENGTH + " characters"); 13 | } 14 | } 15 | 16 | public static Description create(String value) { 17 | return new Description(value); 18 | } 19 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/ledger/internal/domain/contracts/INotificationService.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.ledger.internal.domain.contracts; 2 | 3 | import com.charity_hub.cases.shared.dtos.ContributionConfirmedDTO; 4 | import com.charity_hub.cases.shared.dtos.ContributionPaidDTO; 5 | import com.charity_hub.cases.shared.dtos.ContributionRemindedDTO; 6 | import com.charity_hub.ledger.internal.domain.model.Member; 7 | 8 | public interface INotificationService { 9 | void notifyContributionPaid(ContributionPaidDTO contribution); 10 | 11 | void notifyContributionConfirmed(ContributionConfirmedDTO contribution); 12 | 13 | void notifyContributorToPay(ContributionRemindedDTO contribution); 14 | 15 | void notifyNewConnectionAdded(Member member); 16 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/domain/model/Case/NewCaseProbs.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.domain.model.Case; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | import java.util.List; 6 | 7 | public record NewCaseProbs( 8 | int caseCode, 9 | String title, 10 | String description, 11 | int goal, 12 | Status status, 13 | boolean acceptZakat, 14 | List documents 15 | ) { 16 | public NewCaseProbs { 17 | documents = documents != null ? new ArrayList<>(documents) : null; 18 | } 19 | 20 | @Override 21 | public List documents() { 22 | return documents != null ? Collections.unmodifiableList(documents) : null; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/shared/mappers/DTOContributionMapper.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.shared.mappers; 2 | 3 | import com.charity_hub.cases.internal.infrastructure.db.ContributionEntity; 4 | import com.charity_hub.cases.shared.dtos.ContributionDTO; 5 | import org.springframework.stereotype.Component; 6 | 7 | @Component 8 | public class DTOContributionMapper { 9 | 10 | public ContributionDTO toDTO(ContributionEntity entity) { 11 | return new ContributionDTO( 12 | entity._id(), 13 | entity.contributorId(), 14 | entity.caseCode(), 15 | entity.amount(), 16 | entity.status(), 17 | entity.contributionDate(), 18 | entity.paymentProof() 19 | ); 20 | } 21 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/domain/model/Case/Document.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.domain.model.Case; 2 | 3 | import com.charity_hub.shared.domain.extension.ValueValidator; 4 | 5 | import java.util.regex.Pattern; 6 | 7 | public record Document(String value) { 8 | private static final Pattern URL_PATTERN = Pattern.compile( 9 | "https?://(www\\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b([-a-zA-Z0-9()@:%_+.~#?&/=]*)" 10 | ); 11 | 12 | public Document { 13 | ValueValidator.assertNotEmpty(value, getClass()); 14 | ValueValidator.assertValidFormat(value, URL_PATTERN, getClass()); 15 | } 16 | 17 | public static Document create(String value) { 18 | return new Document(value); 19 | } 20 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/domain/model/CaseDetails/Tags.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.domain.model.CaseDetails; 2 | 3 | import lombok.*; 4 | 5 | import java.util.Collections; 6 | import java.util.List; 7 | import java.util.stream.Collectors; 8 | 9 | @RequiredArgsConstructor(access = AccessLevel.PRIVATE) 10 | @ToString(includeFieldNames = false) 11 | @EqualsAndHashCode 12 | @Value 13 | public class Tags { 14 | List values; 15 | 16 | public static Tags of(List tags) { 17 | List tagList = tags.stream() 18 | .map(Tag::create) 19 | .collect(Collectors.toList()); 20 | return new Tags(tagList); 21 | } 22 | 23 | public List values() { 24 | return Collections.unmodifiableList(values); 25 | } 26 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/domain/model/Contribution/MoneyValue.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.domain.model.Contribution; 2 | 3 | import com.charity_hub.cases.internal.domain.exceptions.InvalidAmountException; 4 | 5 | public record MoneyValue(int value) { 6 | 7 | public MoneyValue { 8 | if (value < 0) { 9 | throw new InvalidAmountException(value); 10 | } 11 | } 12 | 13 | public static MoneyValue of(int value) { 14 | return new MoneyValue(value); 15 | } 16 | 17 | public MoneyValue plus(MoneyValue increment) { 18 | return new MoneyValue(this.value + increment.value()); 19 | } 20 | 21 | public MoneyValue minus(MoneyValue decrement) { 22 | return new MoneyValue(this.value - decrement.value()); 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/application/queries/GetAllCases/GetCasesQueryResult.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.application.queries.GetAllCases; 2 | 3 | import java.util.Collections; 4 | import java.util.List; 5 | 6 | public record GetCasesQueryResult(List cases, int count) { 7 | 8 | public record Case( 9 | int code, 10 | String title, 11 | String description, 12 | int goal, 13 | int collected, 14 | boolean acceptZakat, 15 | String status, 16 | long creationDate, 17 | long lastUpdated, 18 | List documents 19 | ) { 20 | public Case { 21 | documents = documents != null ? documents : Collections.emptyList(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/core/queriers/GetConnectionsHandler.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.core.queriers; 2 | 3 | import com.charity_hub.accounts.internal.core.contracts.IAccountReadRepo; 4 | import com.charity_hub.shared.abstractions.QueryHandler; 5 | import org.springframework.stereotype.Service; 6 | 7 | import java.util.List; 8 | 9 | @Service 10 | public class GetConnectionsHandler implements QueryHandler> { 11 | private final IAccountReadRepo accountReadRepo; 12 | 13 | GetConnectionsHandler(IAccountReadRepo accountReadRepo) { 14 | this.accountReadRepo = accountReadRepo; 15 | } 16 | 17 | @Override 18 | public List handle(GetConnectionsQuery query) { 19 | return accountReadRepo.getConnections(query.userId()); 20 | } 21 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/core/events/FCMTokenUpdated.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.core.events; 2 | 3 | import com.charity_hub.accounts.internal.core.model.device.Device; 4 | import com.charity_hub.accounts.internal.core.model.device.DeviceId; 5 | import com.charity_hub.accounts.internal.core.model.device.DeviceType; 6 | import com.charity_hub.accounts.internal.core.model.device.FCMToken; 7 | 8 | public record FCMTokenUpdated(DeviceId deviceId, DeviceType deviceType, 9 | FCMToken deviceFCMToken) implements AccountEvent { 10 | 11 | public static FCMTokenUpdated from(Device device) { 12 | return new FCMTokenUpdated( 13 | device.getDeviceId(), 14 | device.getDeviceType(), 15 | device.getFcmToken() 16 | ); 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /src/test/java/com/charity_hub/testconfig/IntegrationTest.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.testconfig; 2 | 3 | import org.springframework.boot.test.context.SpringBootTest; 4 | import org.springframework.test.context.ActiveProfiles; 5 | 6 | import java.lang.annotation.ElementType; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | import java.lang.annotation.Target; 10 | 11 | /** 12 | * Meta-annotation for integration tests that require MongoDB. 13 | * This annotation sets up the test with: 14 | * - Full Spring Boot context 15 | * - Test profile 16 | * 17 | * Integration tests should extend MongoTestContainerConfig to get MongoDB support. 18 | */ 19 | @Target(ElementType.TYPE) 20 | @Retention(RetentionPolicy.RUNTIME) 21 | @SpringBootTest 22 | @ActiveProfiles("test") 23 | public @interface IntegrationTest { 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/shared/DTOAccountMapper.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.shared; 2 | 3 | import com.charity_hub.accounts.internal.shell.db.AccountEntity; 4 | import com.charity_hub.accounts.internal.shell.db.DeviceEntity; 5 | import org.springframework.stereotype.Component; 6 | 7 | import java.util.stream.Collectors; 8 | 9 | @Component 10 | public class DTOAccountMapper { 11 | 12 | public AccountDTO toDTO(AccountEntity entity) { 13 | return new AccountDTO( 14 | entity.accountId(), 15 | entity.mobileNumber(), 16 | entity.fullName() != null ? entity.fullName() : "بدون إسم", 17 | entity.photoUrl() != null ? entity.photoUrl() : "", 18 | entity.devices().stream().map(DeviceEntity::fcmToken).collect(Collectors.toList()) 19 | ); 20 | } 21 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/core/model/account/PhotoUrl.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.core.model.account; 2 | 3 | import com.charity_hub.shared.domain.extension.ValueValidator; 4 | 5 | import java.util.regex.Pattern; 6 | 7 | public record PhotoUrl(String value) { 8 | private static final Pattern URL_PATTERN = Pattern.compile( 9 | "https?://(www\\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)" 10 | ); 11 | 12 | 13 | public PhotoUrl { 14 | if (value != null && !value.trim().isEmpty()) { 15 | ValueValidator.assertValidFormat(value, URL_PATTERN, getClass()); 16 | } 17 | } 18 | 19 | public static PhotoUrl create(String value) { 20 | return new PhotoUrl(value); 21 | } 22 | 23 | public String value() { 24 | return value; 25 | } 26 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/core/events/AccountUnBlocked.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.core.events; 2 | 3 | import com.charity_hub.accounts.internal.core.model.account.AccountId; 4 | 5 | import java.util.Objects; 6 | 7 | public final class AccountUnBlocked implements AccountEvent { 8 | private final AccountId id; 9 | 10 | public AccountUnBlocked(AccountId id) { 11 | this.id = id; 12 | } 13 | 14 | public AccountId getId() { 15 | return id; 16 | } 17 | 18 | @Override 19 | public boolean equals(Object o) { 20 | if (this == o) return true; 21 | if (o == null || getClass() != o.getClass()) return false; 22 | AccountUnBlocked that = (AccountUnBlocked) o; 23 | return Objects.equals(id, that.id); 24 | } 25 | 26 | @Override 27 | public int hashCode() { 28 | return Objects.hash(id); 29 | } 30 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/core/events/PermissionGranted.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.core.events; 2 | 3 | import com.charity_hub.accounts.internal.core.model.account.Account; 4 | import com.charity_hub.accounts.internal.core.model.account.AccountId; 5 | import com.charity_hub.shared.domain.model.Permission; 6 | 7 | import java.util.List; 8 | 9 | public record PermissionGranted(AccountId id, List permissions) implements AccountEvent { 10 | public PermissionGranted(AccountId id, List permissions) { 11 | this.id = id; 12 | this.permissions = List.copyOf(permissions); // Creates an unmodifiable copy of the list 13 | } 14 | 15 | public static PermissionGranted from(Account account) { 16 | return new PermissionGranted( 17 | account.getId(), 18 | account.getPermissions() 19 | ); 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/core/events/PermissionRemoved.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.core.events; 2 | 3 | import com.charity_hub.accounts.internal.core.model.account.Account; 4 | import com.charity_hub.accounts.internal.core.model.account.AccountId; 5 | import com.charity_hub.shared.domain.model.Permission; 6 | 7 | import java.util.List; 8 | 9 | public record PermissionRemoved(AccountId id, List permissions) implements AccountEvent { 10 | public PermissionRemoved(AccountId id, List permissions) { 11 | this.id = id; 12 | this.permissions = List.copyOf(permissions); // Creates an unmodifiable copy of the list 13 | } 14 | 15 | public static PermissionRemoved from(Account account) { 16 | return new PermissionRemoved( 17 | account.getId(), 18 | account.getPermissions() 19 | ); 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/shell/repositories/mappers/InvitationMapper.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.shell.repositories.mappers; 2 | 3 | import com.charity_hub.accounts.internal.core.model.invitation.Invitation; 4 | import com.charity_hub.accounts.internal.shell.db.InvitationEntity; 5 | import org.springframework.stereotype.Component; 6 | 7 | import java.util.UUID; 8 | 9 | @Component 10 | public class InvitationMapper { 11 | 12 | public Invitation fromEntity(InvitationEntity entity) { 13 | return Invitation.of( 14 | entity.mobileNumber(), 15 | UUID.fromString(entity.inviterId()) 16 | ); 17 | } 18 | 19 | public InvitationEntity toEntity(Invitation invitation) { 20 | return new InvitationEntity( 21 | invitation.invitedMobileNumber().value(), 22 | invitation.inviterId().toString() 23 | ); 24 | } 25 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/shared/mappers/DTOCaseMapper.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.shared.mappers; 2 | 3 | import com.charity_hub.cases.internal.infrastructure.db.CaseEntity; 4 | import com.charity_hub.cases.shared.dtos.CaseDTO; 5 | import org.springframework.stereotype.Component; 6 | 7 | @Component 8 | public class DTOCaseMapper { 9 | 10 | public CaseDTO toDTO(CaseEntity entity) { 11 | return new CaseDTO( 12 | entity.code(), 13 | entity.title(), 14 | entity.description(), 15 | entity.goal(), 16 | entity.collected(), 17 | entity.status(), 18 | entity.acceptZakat(), 19 | entity.creationDate(), 20 | entity.lastUpdated(), 21 | entity.tags(), 22 | entity.documents(), 23 | entity.contributions() 24 | ); 25 | } 26 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/shell/services/firebase/FirebaseAuthProviderStub.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.shell.services.firebase; 2 | 3 | import com.charity_hub.accounts.internal.core.contracts.IAuthProvider; 4 | import com.charity_hub.accounts.internal.core.model.account.MobileNumber; 5 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 6 | import org.springframework.context.annotation.Primary; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Component 10 | @Primary 11 | @ConditionalOnProperty( 12 | name = "firebase.test-mode", 13 | havingValue = "true", 14 | matchIfMissing = false 15 | ) 16 | public class FirebaseAuthProviderStub implements IAuthProvider { 17 | 18 | @Override 19 | public String getVerifiedMobileNumber(String idToken) { 20 | // In test mode, treat the token as a mobile number directly 21 | return MobileNumber.create(idToken).value(); 22 | } 23 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/domain/events/ContributionMade.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.domain.events; 2 | 3 | import com.charity_hub.cases.internal.domain.model.Case.CaseCode; 4 | import com.charity_hub.cases.internal.domain.model.Contribution.Contribution; 5 | import com.charity_hub.cases.internal.domain.model.Contribution.ContributionId; 6 | import com.charity_hub.cases.internal.domain.model.Contribution.MoneyValue; 7 | 8 | import java.util.UUID; 9 | 10 | public record ContributionMade(ContributionId id, UUID contributorId, CaseCode caseCode, 11 | MoneyValue moneyValue) implements CaseEvent { 12 | 13 | public static ContributionMade from(Contribution contribution) { 14 | return new ContributionMade( 15 | contribution.getId(), 16 | contribution.getContributorId(), 17 | contribution.getCaseId(), 18 | contribution.getMoneyValue() 19 | ); 20 | } 21 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/shared/infrastructure/MessageService.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.shared.infrastructure; 2 | 3 | import org.springframework.context.MessageSource; 4 | import org.springframework.context.i18n.LocaleContextHolder; 5 | import org.springframework.stereotype.Service; 6 | 7 | import java.util.Locale; 8 | 9 | @Service 10 | public class MessageService { 11 | private final MessageSource messageSource; 12 | 13 | public MessageService(MessageSource messageSource) { 14 | this.messageSource = messageSource; 15 | } 16 | 17 | public String getMessage(String code, Object... args) { 18 | return messageSource.getMessage( 19 | code, 20 | args, 21 | LocaleContextHolder.getLocale() 22 | ); 23 | } 24 | 25 | public String getMessage(String code, Locale locale, Object... args) { 26 | return messageSource.getMessage( 27 | code, 28 | args, 29 | locale 30 | ); 31 | } 32 | } -------------------------------------------------------------------------------- /src/test/java/com/charity_hub/testconfig/MongoTestContainerConfig.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.testconfig; 2 | 3 | import org.springframework.test.context.DynamicPropertyRegistry; 4 | import org.springframework.test.context.DynamicPropertySource; 5 | import org.testcontainers.containers.MongoDBContainer; 6 | import org.testcontainers.utility.DockerImageName; 7 | 8 | /** 9 | * Base class for integration tests that require MongoDB. 10 | * Uses Testcontainers to spin up a real MongoDB instance. 11 | * Extend this class in your integration tests. 12 | */ 13 | public abstract class MongoTestContainerConfig { 14 | 15 | static final MongoDBContainer mongoDBContainer; 16 | 17 | static { 18 | mongoDBContainer = new MongoDBContainer(DockerImageName.parse("mongo:7.0")); 19 | mongoDBContainer.start(); 20 | } 21 | 22 | @DynamicPropertySource 23 | static void setProperties(DynamicPropertyRegistry registry) { 24 | registry.add("spring.data.mongodb.uri", mongoDBContainer::getReplicaSetUrl); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/core/events/BasicInfoUpdated.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.core.events; 2 | 3 | import com.charity_hub.accounts.internal.core.model.account.Account; 4 | import com.charity_hub.accounts.internal.core.model.account.AccountId; 5 | import com.charity_hub.accounts.internal.core.model.device.Device; 6 | import com.charity_hub.accounts.internal.core.model.device.DeviceId; 7 | import com.charity_hub.accounts.internal.core.model.device.DeviceType; 8 | import com.charity_hub.accounts.internal.core.model.device.FCMToken; 9 | 10 | public record BasicInfoUpdated(AccountId id, DeviceId deviceId, DeviceType deviceType, 11 | FCMToken deviceFCMToken) implements AccountEvent { 12 | 13 | public static BasicInfoUpdated from(Account account, Device device) { 14 | return new BasicInfoUpdated( 15 | account.getId(), 16 | device.getDeviceId(), 17 | device.getDeviceType(), 18 | device.getFcmToken() 19 | ); 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/shared/domain/model/AggregateRoot.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.shared.domain.model; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | public abstract class AggregateRoot extends Entity { 10 | private static final Logger log = LoggerFactory.getLogger(AggregateRoot.class); 11 | private final List occurredEvents = new ArrayList<>(); 12 | 13 | protected AggregateRoot(T id) { 14 | super(id); 15 | } 16 | 17 | public List occurredEvents() { 18 | List events = new ArrayList<>(this.occurredEvents); 19 | this.occurredEvents.clear(); 20 | log.trace("Return occurred domain events. [numberOfEvents={}]", events.size()); 21 | return events; 22 | } 23 | 24 | protected void raiseEvent(DomainEvent event) { 25 | occurredEvents.add(event); 26 | log.debug("Raised new domain event. [type={}]", event.getClass().getSimpleName()); 27 | } 28 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/core/events/AccountAuthenticated.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.core.events; 2 | 3 | import com.charity_hub.accounts.internal.core.model.account.Account; 4 | import com.charity_hub.accounts.internal.core.model.account.AccountId; 5 | import com.charity_hub.accounts.internal.core.model.device.Device; 6 | import com.charity_hub.accounts.internal.core.model.device.DeviceId; 7 | import com.charity_hub.accounts.internal.core.model.device.DeviceType; 8 | import com.charity_hub.accounts.internal.core.model.device.FCMToken; 9 | 10 | public record AccountAuthenticated(AccountId id, DeviceId deviceId, DeviceType deviceType, 11 | FCMToken deviceFCMToken) implements AccountEvent { 12 | 13 | public static AccountAuthenticated from(Account account, Device device) { 14 | return new AccountAuthenticated( 15 | account.getId(), 16 | device.getDeviceId(), 17 | device.getDeviceType(), 18 | device.getFcmToken() 19 | ); 20 | } 21 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/application/contracts/ICaseReadRepo.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.application.contracts; 2 | 3 | import com.charity_hub.cases.internal.infrastructure.db.CaseEntity; 4 | import com.charity_hub.cases.internal.infrastructure.db.ContributionEntity; 5 | import org.bson.conversions.Bson; 6 | 7 | import java.util.List; 8 | import java.util.UUID; 9 | import java.util.function.Supplier; 10 | 11 | public interface ICaseReadRepo { 12 | CaseEntity getByCode(int code); 13 | 14 | List getByCodes(List codes); 15 | 16 | List getContributionsByCaseCode(int caseCode); 17 | 18 | int getCasesCount(Supplier filter); 19 | 20 | List search(int offset, int limit, Supplier filter); 21 | 22 | List getNotConfirmedContributions(UUID contributorId); 23 | 24 | List getContributions(List contributorsIds); 25 | 26 | List getContributions(UUID contributorId); 27 | 28 | List getDraftCases(); 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/domain/exceptions/CaseExceptions.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.domain.exceptions; 2 | 3 | import com.charity_hub.shared.exceptions.BusinessRuleException; 4 | 5 | public class CaseExceptions { 6 | public static class CannotDeleteCaseException extends BusinessRuleException { 7 | public CannotDeleteCaseException() { 8 | super("You can't delete this case"); 9 | } 10 | } 11 | 12 | public static class CaseAlreadyOpenedException extends BusinessRuleException { 13 | public CaseAlreadyOpenedException() { 14 | super("The case is already opened"); 15 | } 16 | } 17 | 18 | public static class CaseAlreadyClosedException extends BusinessRuleException { 19 | public CaseAlreadyClosedException() { 20 | super("The case is already closed"); 21 | } 22 | } 23 | 24 | public static class CannotContributeException extends BusinessRuleException { 25 | public CannotContributeException() { 26 | super("You can't contribute in a closed case!"); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/core/model/account/MobileNumber.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.core.model.account; 2 | 3 | import com.charity_hub.shared.domain.extension.ValueValidator; 4 | 5 | import java.util.regex.Pattern; 6 | 7 | public record MobileNumber(String value) { 8 | private static final int MIN_LENGTH = 7; 9 | private static final int MAX_LENGTH = 15; 10 | private static final Pattern MOBILE_PATTERN = Pattern.compile("^[0-9]*$"); 11 | 12 | public MobileNumber { 13 | ValueValidator.assertWithinRange(getClass(), value, MIN_LENGTH, MAX_LENGTH); 14 | ValueValidator.assertValidFormat(value, MOBILE_PATTERN, getClass()); 15 | } 16 | 17 | public static MobileNumber create(String value) { 18 | String modifiedNumber = value; 19 | if (modifiedNumber.startsWith("00")) { 20 | modifiedNumber = modifiedNumber.replace("00", ""); 21 | } 22 | if (modifiedNumber.startsWith("+")) { 23 | modifiedNumber = modifiedNumber.replace("+", ""); 24 | } 25 | return new MobileNumber(modifiedNumber); 26 | } 27 | } -------------------------------------------------------------------------------- /src/main/resources/messages_ar.properties: -------------------------------------------------------------------------------- 1 | notification.contribution.pending.title=مشاركة تنتظر المراجعة 2 | notification.contribution.pending.body=راجع مشاركة {0} 3 | 4 | notification.contribution.confirmed.title=مشاركتك تمت مراجعتها 5 | notification.contribution.confirmed.body=جزيت خيرا كثيرا {0} 6 | 7 | notification.contribution.reminder.title=تذكير بدفع المشاركات السابقة 8 | notification.contribution.reminder.body=رجاءاً سرعة دفع المشاركات 9 | 10 | notification.connection.added.title=تمت إضافة {0} تحت مسؤوليتك 11 | notification.connection.added.body=نرجوا من الله أن يكون في ميزان حسناتك 12 | 13 | notification.case.new.title=حالة جديدة 14 | notification.case.closed.exceeded=🎉🎉🥳 تمت بحمد الله وزيادة {0} ج 15 | notification.case.closed.exact=♥️🤍 تمت بحمد الله {0} ج 16 | notification.case.closed.incomplete=️تم إنهاء الحالة 17 | notification.case.closed.body=♥️🤍 جزيتم الجنة 18 | 19 | notification.contribution.first=أولنا {0} ج 20 | notification.contribution.additional=كمان {0} ج 21 | 22 | notification.contribution.messages=يارب أتمها,جزاكم الله خيراً,اللهم استعملنا ولا تستبدلنا,تصدقوا ولو بالقليل,الصدقة تطفئ غضب الرب,أملي في الله كبير -------------------------------------------------------------------------------- /src/test/java/com/charity_hub/ModularityTest.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.modulith.core.ApplicationModules; 5 | import org.springframework.modulith.docs.Documenter; 6 | 7 | /** 8 | * Test to verify the modularity structure of the application. 9 | * This ensures that module boundaries are properly defined and respected. 10 | */ 11 | class ModularityTest { 12 | 13 | ApplicationModules modules = ApplicationModules.of(CharityHubApplication.class); 14 | 15 | @Test 16 | void verifiesModularStructure() { 17 | // This will fail if any module violates the defined boundaries 18 | modules.verify(); 19 | } 20 | 21 | @Test 22 | void createModuleDocumentation() { 23 | // Generate documentation for the module structure 24 | new Documenter(modules) 25 | .writeDocumentation() 26 | .writeIndividualModulesAsPlantUml(); 27 | } 28 | 29 | @Test 30 | void printModuleStructure() { 31 | // Print the module structure for debugging 32 | modules.forEach(System.out::println); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/api/controllers/GetDraftCasesController.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.api.controllers; 2 | 3 | import com.charity_hub.cases.internal.application.queries.GetDraftCases.GetDraftCases; 4 | import com.charity_hub.cases.internal.application.queries.GetDraftCases.GetDraftCasesHandler; 5 | import com.charity_hub.cases.internal.application.queries.GetDraftCases.GetDraftCasesResponse; 6 | import org.springframework.http.ResponseEntity; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | 11 | @RestController 12 | public class GetDraftCasesController { 13 | 14 | private final GetDraftCasesHandler getDraftCasesHandler; 15 | 16 | public GetDraftCasesController(GetDraftCasesHandler getDraftCasesHandler) { 17 | this.getDraftCasesHandler = getDraftCasesHandler; 18 | } 19 | 20 | @GetMapping("/v1/draft-cases") 21 | public ResponseEntity handle() { 22 | var response = getDraftCasesHandler.handle(new GetDraftCases()); 23 | return ResponseEntity.ok(response); 24 | } 25 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/shared/domain/extension/ValueValidator.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.shared.domain.extension; 2 | 3 | import java.util.regex.Pattern; 4 | 5 | import com.charity_hub.shared.exceptions.BadRequestException; 6 | 7 | public class ValueValidator { 8 | 9 | public static void assertNotEmpty(String value, Class aClass) { 10 | if (value == null || value.trim().isEmpty()) { 11 | throw new BadRequestException(aClass.getSimpleName() + " cannot be empty"); 12 | } 13 | } 14 | 15 | public static void assertWithinRange(Class aClass, String value, int minLength, int maxLength) { 16 | if (value.length() < minLength || value.length() > maxLength) { 17 | throw new BadRequestException(aClass.getSimpleName() + " must be between " + minLength + " and " + maxLength + " characters"); 18 | } 19 | } 20 | 21 | public static void assertValidFormat(String value, Pattern pattern, Class aClass) { 22 | if (!pattern.matcher(value).matches()) { 23 | throw new BadRequestException(aClass.getSimpleName() + " has an invalid format"); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/ledger/internal/api/GetLedgerController.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.ledger.internal.api; 2 | 3 | import com.charity_hub.ledger.internal.application.queries.GetLedger.GetLedger; 4 | import com.charity_hub.ledger.internal.application.queries.GetLedger.GetLedgerHandler; 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.security.access.prepost.PreAuthorize; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.PathVariable; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | import java.util.UUID; 12 | 13 | @RestController 14 | public class GetLedgerController { 15 | private final GetLedgerHandler getLedgerHandler; 16 | 17 | public GetLedgerController(GetLedgerHandler getLedgerHandler) { 18 | this.getLedgerHandler = getLedgerHandler; 19 | } 20 | 21 | @PreAuthorize("hasAuthority('FULL_ACCESS')") 22 | @GetMapping("/v1/ledger/{userId}") 23 | public ResponseEntity handle(@PathVariable UUID userId) { 24 | GetLedger command = new GetLedger(userId); 25 | var result = getLedgerHandler.handle(command); 26 | return ResponseEntity.ok(result); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/shared/auth/JWTPayload.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.shared.auth; 2 | 3 | import lombok.Getter; 4 | 5 | import java.util.Date; 6 | import java.util.Map; 7 | 8 | @Getter 9 | public abstract class JWTPayload { 10 | private final String issuer; 11 | private final String subject; 12 | private final String type; 13 | private final String audience; 14 | private final String jwtId; 15 | private final Date expireAt; 16 | private final Date issuedAt; 17 | 18 | protected JWTPayload( 19 | String type, 20 | String audience, 21 | String jwtId, 22 | Date expireAt, 23 | Date issuedAt 24 | ) { 25 | this.issuer ="https://tech-mentors.net"; 26 | this.subject = "authentication"; 27 | this.type = type; 28 | this.audience = audience; 29 | this.jwtId = jwtId; 30 | this.expireAt = expireAt; 31 | this.issuedAt = issuedAt; 32 | } 33 | 34 | public abstract Map toMap(); 35 | 36 | public Date getExpireAt() { 37 | return new Date(expireAt.getTime()); 38 | } 39 | 40 | public Date getIssuedAt() { 41 | return new Date(issuedAt.getTime()); 42 | } 43 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/application/queries/GetCase/GetCaseResponse.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.application.queries.GetCase; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class GetCaseResponse { 7 | private final CaseDetails case_; 8 | 9 | public GetCaseResponse(CaseDetails case_) { 10 | this.case_ = case_; 11 | } 12 | 13 | public CaseDetails getCase() { 14 | return case_; 15 | } 16 | 17 | public record CaseDetails( 18 | int code, 19 | String title, 20 | String description, 21 | int goal, 22 | int collected, 23 | boolean acceptZakat, 24 | String status, 25 | long creationDate, 26 | long lastUpdated, 27 | List contributions, 28 | List documents 29 | ) { 30 | public CaseDetails { 31 | documents = documents != null ? documents : new ArrayList<>(); 32 | } 33 | 34 | } 35 | 36 | public record Contribution(int amount, Contributor contributor) { 37 | } 38 | 39 | public record Contributor(String id, String fullName, String photoUrl) { 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/application/queries/GetDraftCases/GetDraftCasesResponse.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.application.queries.GetDraftCases; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public record GetDraftCasesResponse(List cases) { 7 | 8 | public record DraftCase( 9 | int code, 10 | String title, 11 | String description, 12 | int goal, 13 | long creationDate, 14 | long lastUpdated, 15 | List documents 16 | ) { 17 | public DraftCase( 18 | int code, 19 | String title, 20 | String description, 21 | int goal, 22 | long creationDate, 23 | long lastUpdated, 24 | List documents 25 | ) { 26 | this.code = code; 27 | this.title = title; 28 | this.description = description; 29 | this.goal = goal; 30 | this.creationDate = creationDate; 31 | this.lastUpdated = lastUpdated; 32 | this.documents = documents != null ? documents : new ArrayList<>(); 33 | } 34 | 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/domain/model/Case/CaseDocuments.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.domain.model.Case; 2 | 3 | import com.charity_hub.shared.domain.model.ValueObject; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Collections; 7 | import java.util.List; 8 | 9 | public record CaseDocuments(List documents) implements ValueObject { 10 | 11 | public CaseDocuments { 12 | // Defensive copy in constructor 13 | documents = new ArrayList<>(documents); 14 | } 15 | 16 | public static CaseDocuments of(List documentUrls) { 17 | if (documentUrls == null) { 18 | return new CaseDocuments(new ArrayList<>()); 19 | } 20 | List docs = documentUrls.stream() 21 | .map(Document::new) 22 | .toList(); 23 | return new CaseDocuments(docs); 24 | } 25 | 26 | public CaseDocuments update(List documentUrls) { 27 | return of(documentUrls); 28 | } 29 | 30 | @Override 31 | public List documents() { 32 | return Collections.unmodifiableList(documents); 33 | } 34 | 35 | public boolean isEmpty() { 36 | return documents.isEmpty(); 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/application/eventHandlers/loggers/CaseOpenedLogger.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.application.eventHandlers.loggers; 2 | 3 | import com.charity_hub.cases.shared.dtos.CaseOpenedDTO; 4 | import com.charity_hub.shared.domain.ILogger; 5 | import org.springframework.stereotype.Component; 6 | 7 | @Component 8 | public class CaseOpenedLogger { 9 | private final ILogger logger; 10 | 11 | public CaseOpenedLogger(ILogger logger) { 12 | this.logger = logger; 13 | } 14 | 15 | public void handlerRegistered() { 16 | logger.info("Registering CaseOpenedHandler"); 17 | } 18 | 19 | public void processingEvent(CaseOpenedDTO case_) { 20 | logger.info("Processing CaseOpenedEvent - Case Code: {}, Title: {}", 21 | case_.caseCode(), case_.title()); 22 | } 23 | 24 | public void notificationSent(int caseCode) { 25 | logger.info("Successfully sent notification for case opened - Case Code: {}", 26 | caseCode); 27 | } 28 | 29 | public void notificationFailed(int caseCode, Exception e) { 30 | logger.error("Failed to send notification for case opened - Case Code: {} - Error: {}", 31 | caseCode, e.getMessage(), e); 32 | } 33 | } -------------------------------------------------------------------------------- /src/main/resources/messages_en.properties: -------------------------------------------------------------------------------- 1 | notification.contribution.pending.title=Pending Contribution Review 2 | notification.contribution.pending.body=Review contribution from {0} 3 | 4 | notification.contribution.confirmed.title=Contribution Reviewed 5 | notification.contribution.confirmed.body=Thank you {0} for your contribution 6 | 7 | notification.contribution.reminder.title=Contribution Payment Reminder 8 | notification.contribution.reminder.body=Please pay your pending contributions 9 | 10 | notification.connection.added.title={0} has been added under your responsibility 11 | notification.connection.added.body=May Allah reward you for your efforts 12 | 13 | notification.case.new.title=New Case 14 | notification.case.closed.exceeded=🎉🎉🥳 Completed with extra {0} EGP 15 | notification.case.closed.exact=♥️🤍 Completed with {0} EGP 16 | notification.case.closed.incomplete=️Case Closed 17 | notification.case.closed.body=♥️🤍 May Allah reward you with Paradise 18 | 19 | notification.contribution.first=First contribution: {0} EGP 20 | notification.contribution.additional=Another {0} EGP 21 | 22 | notification.contribution.messages=May Allah accept it,May Allah reward you,May Allah keep us serving,Every little helps,Charity extinguishes the Lord's anger,Great hopes in Allah -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/core/model/device/FCMToken.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.core.model.device; 2 | 3 | import com.charity_hub.shared.domain.extension.ValueValidator; 4 | 5 | import java.util.Objects; 6 | 7 | public class FCMToken { 8 | private final String value; 9 | 10 | private FCMToken(String value) { 11 | ValueValidator.assertNotEmpty(value, getClass()); 12 | this.value = value; 13 | } 14 | 15 | // Static factory method (replacing companion object) 16 | public static FCMToken create(String value) { 17 | return new FCMToken(value); 18 | } 19 | 20 | public String getValue() { 21 | return value; 22 | } 23 | 24 | @Override 25 | public boolean equals(Object o) { 26 | if (this == o) return true; 27 | if (o == null || getClass() != o.getClass()) return false; 28 | FCMToken fcmToken = (FCMToken) o; 29 | return Objects.equals(value, fcmToken.value); 30 | } 31 | 32 | @Override 33 | public int hashCode() { 34 | return Objects.hash(value); 35 | } 36 | 37 | @Override 38 | public String toString() { 39 | return "FCMToken{" + 40 | "value='" + value + '\'' + 41 | '}'; 42 | } 43 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/api/controllers/DeleteDraftCaseController.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.api.controllers; 2 | 3 | import com.charity_hub.cases.internal.application.commands.DeleteDraftCase.DeleteDraftCase; 4 | import com.charity_hub.cases.internal.application.commands.DeleteDraftCase.DeleteDraftCaseHandler; 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.security.access.prepost.PreAuthorize; 7 | import org.springframework.web.bind.annotation.DeleteMapping; 8 | import org.springframework.web.bind.annotation.PathVariable; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | @RestController 12 | public class DeleteDraftCaseController { 13 | 14 | private final DeleteDraftCaseHandler deleteDraftCaseHandler; 15 | 16 | public DeleteDraftCaseController(DeleteDraftCaseHandler deleteDraftCaseHandler) { 17 | this.deleteDraftCaseHandler = deleteDraftCaseHandler; 18 | } 19 | 20 | @DeleteMapping("/v1/cases/{caseCode}") 21 | @PreAuthorize("hasAnyAuthority('FULL_ACCESS')") 22 | public ResponseEntity handle(@PathVariable int caseCode) { 23 | deleteDraftCaseHandler.handle(new DeleteDraftCase(caseCode)); 24 | return ResponseEntity.ok().build(); 25 | } 26 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/domain/model/Case/CaseStatus.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.domain.model.Case; 2 | 3 | import com.charity_hub.shared.domain.model.ValueObject; 4 | 5 | import static com.charity_hub.cases.internal.domain.exceptions.CaseExceptions.CaseAlreadyClosedException; 6 | import static com.charity_hub.cases.internal.domain.exceptions.CaseExceptions.CaseAlreadyOpenedException; 7 | 8 | public record CaseStatus(Status value) implements ValueObject { 9 | 10 | public static CaseStatus of(Status status) { 11 | return new CaseStatus(status); 12 | } 13 | 14 | public CaseStatus open() { 15 | if (isOpened()) { 16 | throw new CaseAlreadyOpenedException(); 17 | } 18 | return new CaseStatus(Status.OPENED); 19 | } 20 | 21 | public CaseStatus close() { 22 | if (isClosed()) { 23 | throw new CaseAlreadyClosedException(); 24 | } 25 | return new CaseStatus(Status.CLOSED); 26 | } 27 | 28 | public boolean isOpened() { 29 | return value == Status.OPENED; 30 | } 31 | 32 | public boolean isClosed() { 33 | return value == Status.CLOSED; 34 | } 35 | 36 | public boolean isDraft() { 37 | return value == Status.DRAFT; 38 | } 39 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/shell/api/controllers/BlockAccountController.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.shell.api.controllers; 2 | 3 | import com.charity_hub.accounts.internal.core.commands.BlockAccount.BlockAccount; 4 | import com.charity_hub.accounts.internal.core.commands.BlockAccount.BlockAccountHandler; 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.security.access.prepost.PreAuthorize; 7 | import org.springframework.web.bind.annotation.PathVariable; 8 | import org.springframework.web.bind.annotation.PostMapping; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | @RestController 12 | public class BlockAccountController { 13 | private final BlockAccountHandler blockAccountHandler; 14 | 15 | public BlockAccountController(BlockAccountHandler blockAccountHandler) { 16 | this.blockAccountHandler = blockAccountHandler; 17 | } 18 | 19 | @PostMapping("/v1/accounts/{userId}/block") 20 | @PreAuthorize("hasAnyAuthority('FULL_ACCESS')") 21 | public ResponseEntity handle(@PathVariable String userId) { 22 | BlockAccount command = new BlockAccount(userId, false); 23 | blockAccountHandler.handle(command); 24 | return ResponseEntity.ok().build(); 25 | } 26 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/application/eventHandlers/loggers/CaseClosedLogger.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.application.eventHandlers.loggers; 2 | 3 | import com.charity_hub.cases.shared.dtos.CaseClosedDTO; 4 | import com.charity_hub.shared.domain.ILogger; 5 | import org.springframework.stereotype.Component; 6 | 7 | @Component 8 | public class CaseClosedLogger { 9 | private final ILogger logger; 10 | 11 | public CaseClosedLogger(ILogger logger) { 12 | this.logger = logger; 13 | } 14 | 15 | public void handlerRegistered() { 16 | logger.info("Registering CaseClosedHandler"); 17 | } 18 | 19 | public void processingEvent(CaseClosedDTO case_) { 20 | logger.info("Processing CaseClosedEvent - Case Code: {}, Goal: {}, Total: {}", 21 | case_.caseCode(), case_.goal(), case_.contributionsTotal()); 22 | } 23 | 24 | public void notificationSent(int caseCode) { 25 | logger.info("Successfully sent notification for case closed - Case Code: {}", 26 | caseCode); 27 | } 28 | 29 | public void notificationFailed(int caseCode, Exception e) { 30 | logger.error("Failed to send notification for case closed - Case Code: {} - Error: {}", 31 | caseCode, e.getMessage(), e); 32 | } 33 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/core/model/device/RefreshToken.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.core.model.device; 2 | 3 | import com.charity_hub.shared.domain.extension.ValueValidator; 4 | 5 | import java.util.Objects; 6 | 7 | public class RefreshToken { 8 | private final String value; 9 | 10 | private RefreshToken(String value) { 11 | ValueValidator.assertNotEmpty(value, getClass()); 12 | this.value = value; 13 | } 14 | 15 | // Static factory method (replacing companion object) 16 | public static RefreshToken create(String value) { 17 | return new RefreshToken(value); 18 | } 19 | 20 | public String getValue() { 21 | return value; 22 | } 23 | 24 | @Override 25 | public boolean equals(Object o) { 26 | if (this == o) return true; 27 | if (o == null || getClass() != o.getClass()) return false; 28 | RefreshToken that = (RefreshToken) o; 29 | return Objects.equals(value, that.value); 30 | } 31 | 32 | @Override 33 | public int hashCode() { 34 | return Objects.hash(value); 35 | } 36 | 37 | @Override 38 | public String toString() { 39 | return "RefreshToken{" + 40 | "value='" + value + '\'' + 41 | '}'; 42 | } 43 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/shell/api/controllers/UnblockUserController.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.shell.api.controllers; 2 | 3 | import com.charity_hub.accounts.internal.core.commands.BlockAccount.BlockAccount; 4 | import com.charity_hub.accounts.internal.core.commands.BlockAccount.BlockAccountHandler; 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.security.access.prepost.PreAuthorize; 7 | import org.springframework.web.bind.annotation.PathVariable; 8 | import org.springframework.web.bind.annotation.PostMapping; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | @RestController 12 | public class UnblockUserController { 13 | private final BlockAccountHandler blockAccountHandler; 14 | 15 | public UnblockUserController(BlockAccountHandler blockAccountHandler) { 16 | this.blockAccountHandler = blockAccountHandler; 17 | } 18 | 19 | @PostMapping("/v1/accounts/{userId}/un-block") 20 | @PreAuthorize("hasAnyAuthority('FULL_ACCESS')") 21 | public ResponseEntity handle(@PathVariable String userId) { 22 | BlockAccount command = new BlockAccount(userId, true); 23 | 24 | blockAccountHandler.handle(command); 25 | 26 | return ResponseEntity.ok().build(); 27 | } 28 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/shared/auth/JwtVerifier.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.shared.auth; 2 | 3 | import com.charity_hub.shared.exceptions.UnAuthorized; 4 | import io.jsonwebtoken.Claims; 5 | import io.jsonwebtoken.Jwts; 6 | import io.jsonwebtoken.security.Keys; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.stereotype.Component; 9 | 10 | import javax.crypto.SecretKey; 11 | import java.util.Date; 12 | 13 | @Component 14 | public class JwtVerifier { 15 | private final String secretKey; 16 | 17 | public JwtVerifier(@Value("${auth.secretKey}") String secretKey) { 18 | this.secretKey = secretKey; 19 | } 20 | 21 | public Claims verify(String token) { 22 | Claims claims = Jwts.parser() 23 | .verifyWith(secretKey()) 24 | .build() 25 | .parseSignedClaims(token) 26 | .getPayload(); 27 | validateExpiry(claims); 28 | return claims; 29 | } 30 | 31 | private SecretKey secretKey() { 32 | return Keys.hmacShaKeyFor(secretKey.getBytes()); 33 | } 34 | 35 | private void validateExpiry(Claims claims) { 36 | if (!claims.getExpiration().after(new Date())) { 37 | throw new UnAuthorized("JWT token expired"); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/api/controllers/ConfirmContributionController.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.api.controllers; 2 | 3 | import com.charity_hub.cases.internal.application.commands.ConfirmContribution.ConfirmContribution; 4 | import com.charity_hub.cases.internal.application.commands.ConfirmContribution.ConfirmContributionHandler; 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.web.bind.annotation.PathVariable; 7 | import org.springframework.web.bind.annotation.PostMapping; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | import java.util.UUID; 11 | 12 | @RestController 13 | public class ConfirmContributionController { 14 | private final ConfirmContributionHandler confirmContributionHandler; 15 | 16 | public ConfirmContributionController(ConfirmContributionHandler confirmContributionHandler){ 17 | this.confirmContributionHandler = confirmContributionHandler; 18 | } 19 | 20 | @PostMapping("/v1/contributions/{contributionId}/confirm") 21 | public ResponseEntity handle(@PathVariable UUID contributionId){ 22 | ConfirmContribution command = new ConfirmContribution(contributionId); 23 | confirmContributionHandler.handle(command); 24 | return ResponseEntity.ok().build(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/ledger/internal/api/GetLedgerSummaryController.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.ledger.internal.api; 2 | 3 | import com.charity_hub.ledger.internal.application.queries.GetLedgerSummary.GetLedgerSummary; 4 | import com.charity_hub.ledger.internal.application.queries.GetLedgerSummary.GetLedgerSummaryHandler; 5 | import com.charity_hub.shared.auth.AccessTokenPayload; 6 | import org.springframework.http.ResponseEntity; 7 | import org.springframework.security.core.annotation.AuthenticationPrincipal; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | @RestController 12 | public class GetLedgerSummaryController { 13 | private final GetLedgerSummaryHandler getLedgerSummaryHandler; 14 | 15 | public GetLedgerSummaryController(GetLedgerSummaryHandler getLedgerSummaryHandler) { 16 | this.getLedgerSummaryHandler = getLedgerSummaryHandler; 17 | } 18 | 19 | @GetMapping("/v1/ledger/summary") 20 | public ResponseEntity handle(@AuthenticationPrincipal AccessTokenPayload accessTokenPayload){ 21 | GetLedgerSummary command = new GetLedgerSummary(accessTokenPayload.getUserId()); 22 | var result = getLedgerSummaryHandler.handle(command); 23 | return ResponseEntity.ok(result); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/application/eventHandlers/loggers/ContributionMadeLogger.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.application.eventHandlers.loggers; 2 | 3 | import com.charity_hub.cases.shared.dtos.ContributionMadeDTO; 4 | import com.charity_hub.shared.domain.ILogger; 5 | import org.springframework.stereotype.Component; 6 | 7 | @Component 8 | public class ContributionMadeLogger { 9 | private final ILogger logger; 10 | 11 | public ContributionMadeLogger(ILogger logger) { 12 | this.logger = logger; 13 | } 14 | 15 | public void handlerRegistered() { 16 | logger.info("Registering ContributionMadeHandler"); 17 | } 18 | 19 | public void processingEvent(ContributionMadeDTO contribution) { 20 | logger.info("Processing ContributionMadeEvent - Case Code: {}, Amount: {}", 21 | contribution.caseCode(), contribution.amount()); 22 | } 23 | 24 | public void notificationSent(int caseCode, int amount) { 25 | logger.info("Successfully sent notification for contribution - Case Code: {}, Amount: {}", 26 | caseCode, amount); 27 | } 28 | 29 | public void notificationFailed(int caseCode, int amount, Exception e) { 30 | logger.error("Failed to send notification for contribution - Case Code: {}, Amount: {} - Error: {}", 31 | caseCode, amount, e.getMessage(), e); 32 | } 33 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/ledger/internal/infrastructure/gateways/CasesGateway.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.ledger.internal.infrastructure.gateways; 2 | 3 | import com.charity_hub.cases.shared.ICasesAPI; 4 | import com.charity_hub.cases.shared.dtos.CaseDTO; 5 | import com.charity_hub.cases.shared.dtos.ContributionDTO; 6 | import com.charity_hub.ledger.internal.application.contracts.ICasesGateway; 7 | import org.springframework.stereotype.Component; 8 | 9 | import java.util.List; 10 | import java.util.UUID; 11 | 12 | @Component 13 | public class CasesGateway implements ICasesGateway { 14 | private final ICasesAPI casesAPI; 15 | 16 | public CasesGateway(ICasesAPI casesAPI) { 17 | this.casesAPI = casesAPI; 18 | } 19 | 20 | @Override 21 | public List getContributions(UUID userId) { 22 | return casesAPI.getUsersContributions(userId); 23 | } 24 | 25 | @Override 26 | public List getCasesByIds(List casesCodes) { 27 | return casesAPI.getCasesByCodes(casesCodes); 28 | } 29 | 30 | @Override 31 | public List getNotConfirmedContributions(UUID userId) { 32 | return casesAPI.getNotConfirmedContributions(userId); 33 | } 34 | 35 | @Override 36 | public List getContributions(List usersIds) { 37 | return casesAPI.getUsersContributions(usersIds); 38 | } 39 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/application/commands/DeleteDraftCase/DeleteDraftCaseHandler.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.application.commands.DeleteDraftCase; 2 | 3 | import com.charity_hub.cases.internal.domain.contracts.ICaseRepo; 4 | import com.charity_hub.cases.internal.domain.model.Case.CaseCode; 5 | import com.charity_hub.shared.abstractions.VoidCommandHandler; 6 | import com.charity_hub.shared.exceptions.NotFoundException; 7 | import org.springframework.stereotype.Service; 8 | 9 | @Service 10 | public class DeleteDraftCaseHandler extends VoidCommandHandler { 11 | private final ICaseRepo caseRepo; 12 | 13 | public DeleteDraftCaseHandler(ICaseRepo caseRepo) { 14 | this.caseRepo = caseRepo; 15 | } 16 | 17 | @Override 18 | public void handle(DeleteDraftCase command) { 19 | logger.info("Deleting draft case - CaseCode: {}", command.caseCode()); 20 | 21 | var case_ = caseRepo.getByCode(new CaseCode(command.caseCode())) 22 | .orElseThrow(() -> { 23 | logger.warn("Draft case not found for deletion - CaseCode: {}", command.caseCode()); 24 | return new NotFoundException("This case is not found"); 25 | }); 26 | 27 | case_.delete(caseRepo); 28 | logger.info("Draft case deleted successfully - CaseCode: {}", command.caseCode()); 29 | } 30 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/ledger/internal/api/GetOwnLedgerController.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.ledger.internal.api; 2 | 3 | import com.charity_hub.ledger.internal.application.queries.GetLedger.GetLedger; 4 | import com.charity_hub.ledger.internal.application.queries.GetLedger.GetLedgerHandler; 5 | import com.charity_hub.shared.auth.AccessTokenPayload; 6 | import org.springframework.http.ResponseEntity; 7 | import org.springframework.security.access.prepost.PreAuthorize; 8 | import org.springframework.security.core.annotation.AuthenticationPrincipal; 9 | import org.springframework.web.bind.annotation.GetMapping; 10 | import org.springframework.web.bind.annotation.RestController; 11 | 12 | import java.util.UUID; 13 | 14 | @RestController 15 | public class GetOwnLedgerController { 16 | private final GetLedgerHandler getLedgerHandler; 17 | 18 | public GetOwnLedgerController(GetLedgerHandler getLedgerHandler) { 19 | this.getLedgerHandler = getLedgerHandler; 20 | } 21 | 22 | @PreAuthorize("isAuthenticated()") 23 | @GetMapping("/v1/ledger/me") 24 | public ResponseEntity handle( 25 | @AuthenticationPrincipal AccessTokenPayload payload 26 | ) { 27 | UUID userId = UUID.fromString(payload.getUuid()); 28 | GetLedger command = new GetLedger(userId); 29 | var result = getLedgerHandler.handle(command); 30 | return ResponseEntity.ok(result); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/application/queries/GetDraftCases/GetDraftCasesHandler.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.application.queries.GetDraftCases; 2 | 3 | import com.charity_hub.cases.internal.application.contracts.ICaseReadRepo; 4 | import com.charity_hub.shared.abstractions.QueryHandler; 5 | import org.springframework.stereotype.Service; 6 | 7 | import java.util.List; 8 | import java.util.stream.Collectors; 9 | 10 | @Service 11 | public class GetDraftCasesHandler implements QueryHandler { 12 | private final ICaseReadRepo caseRepo; 13 | 14 | public GetDraftCasesHandler(ICaseReadRepo caseRepo) { 15 | this.caseRepo = caseRepo; 16 | } 17 | 18 | @Override 19 | public GetDraftCasesResponse handle(GetDraftCases query) { 20 | List draftCases = caseRepo.getDraftCases() 21 | .stream() 22 | .map(it -> new GetDraftCasesResponse.DraftCase( 23 | it.code(), 24 | it.title(), 25 | it.description(), 26 | it.goal(), 27 | it.creationDate(), 28 | it.lastUpdated(), 29 | it.documents() 30 | )) 31 | .collect(Collectors.toList()); 32 | 33 | return new GetDraftCasesResponse(draftCases); 34 | } 35 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/ledger/internal/infrastructure/db/MemberMapper.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.ledger.internal.infrastructure.db; 2 | 3 | import com.charity_hub.ledger.internal.domain.model.Member; 4 | import com.charity_hub.ledger.internal.domain.model.MemberId; 5 | 6 | import java.util.UUID; 7 | import java.util.stream.Collectors; 8 | 9 | public class MemberMapper { 10 | private MemberMapper() { 11 | // Private constructor to prevent instantiation of utility class 12 | } 13 | 14 | public static Member toDomain(MemberEntity entity) { 15 | return new Member( 16 | new MemberId(UUID.fromString(entity._id())), 17 | new MemberId(UUID.fromString(entity.parent())), 18 | entity.ancestors().stream() 19 | .map(UUID::fromString) 20 | .map(MemberId::new) 21 | .collect(Collectors.toList()), 22 | entity.children().stream() 23 | .map(UUID::fromString) 24 | .map(MemberId::new) 25 | .collect(Collectors.toList()) 26 | ); 27 | } 28 | 29 | public static MemberEntity toDB(Member domain) { 30 | return new MemberEntity( 31 | domain.memberIdValue(), 32 | domain.childrenIds(), 33 | domain.parentId(), 34 | domain.ancestorsIds() 35 | ); 36 | } 37 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/shared/infrastructure/Logger.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.shared.infrastructure; 2 | 3 | import com.charity_hub.shared.domain.ILogger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.stereotype.Component; 6 | 7 | @Component 8 | public class Logger implements ILogger { 9 | private static final org.slf4j.Logger logger = LoggerFactory.getLogger("[Charity-hub - App]"); 10 | 11 | @Override 12 | public void info(String message) { 13 | logger.debug(message); 14 | } 15 | 16 | @Override 17 | public void info(String message, Object... arguments) { 18 | logger.info(message, arguments); 19 | } 20 | 21 | @Override 22 | public void debug(String message) { 23 | logger.debug(message); 24 | } 25 | 26 | @Override 27 | public void debug(String message, Object... arguments) { 28 | logger.debug(message, arguments); 29 | } 30 | 31 | @Override 32 | public void warn(String message) { 33 | logger.warn(message); 34 | } 35 | 36 | @Override 37 | public void warn(String message, Object... arguments) { 38 | logger.warn(message, arguments); 39 | } 40 | 41 | @Override 42 | public void error(String message) { 43 | logger.error(message); 44 | } 45 | 46 | @Override 47 | public void error(String message, Object... arguments) { 48 | logger.error(message, arguments); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/ledger/internal/application/eventHandlers/loggers/ContributionPaidLogger.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.ledger.internal.application.eventHandlers.loggers; 2 | 3 | import com.charity_hub.cases.shared.dtos.ContributionPaidDTO; 4 | import com.charity_hub.shared.domain.ILogger; 5 | import org.springframework.stereotype.Component; 6 | 7 | import java.util.UUID; 8 | 9 | @Component 10 | public class ContributionPaidLogger { 11 | private final ILogger logger; 12 | 13 | public ContributionPaidLogger(ILogger logger) { 14 | this.logger = logger; 15 | } 16 | 17 | public void handlerRegistered() { 18 | logger.info("Registering ContributionPaidHandler"); 19 | } 20 | 21 | public void processingEvent(ContributionPaidDTO contribution) { 22 | logger.info("Processing contribution payment - Contribution Id: {}, Contributor ID: {}", 23 | contribution.id(), contribution.contributorId()); 24 | } 25 | 26 | public void notificationSent(UUID id, UUID contributorId) { 27 | logger.info("Successfully sent payment notification - Contribution Id: {}, Contributor ID: {}", 28 | id, contributorId); 29 | } 30 | 31 | public void notificationFailed(UUID id, UUID contributorId, Exception e) { 32 | logger.error("Failed to send payment notification - Contribution Id: {}, Contributor ID: {} - Error: {}", 33 | id, contributorId, e.getMessage(), e); 34 | } 35 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/ledger/internal/infrastructure/gateways/AccountsGateway.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.ledger.internal.infrastructure.gateways; 2 | 3 | import com.charity_hub.accounts.shared.AccountDTO; 4 | import com.charity_hub.accounts.shared.IAccountsAPI; 5 | import com.charity_hub.ledger.internal.application.contracts.IAccountGateway; 6 | import com.charity_hub.ledger.internal.application.models.InvitationResponse; 7 | import com.charity_hub.ledger.internal.domain.model.MemberId; 8 | import org.springframework.stereotype.Component; 9 | 10 | import java.util.List; 11 | 12 | @Component("ledgerAccountsGateway") 13 | public class AccountsGateway implements IAccountGateway { 14 | private final IAccountsAPI accountsAPI; 15 | 16 | public AccountsGateway(IAccountsAPI accountsAPI) { 17 | this.accountsAPI = accountsAPI; 18 | } 19 | 20 | @Override 21 | public InvitationResponse getInvitationByMobileNumber(String mobileNumber) { 22 | var account = accountsAPI.getInvitationByMobileNumber(mobileNumber); 23 | if (account == null) { 24 | return null; 25 | } 26 | return new InvitationResponse( 27 | account.invitedMobileNumber(), 28 | account.inviterId() 29 | ); 30 | } 31 | 32 | @Override 33 | public List getAccounts(List ids) { 34 | return accountsAPI.getAccountsByIds(ids.stream().map(MemberId::value).toList()); 35 | } 36 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/application/commands/PayContribution/PayContributionHandler.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.application.commands.PayContribution; 2 | 3 | import com.charity_hub.cases.internal.domain.contracts.ICaseRepo; 4 | import com.charity_hub.shared.abstractions.VoidCommandHandler; 5 | import com.charity_hub.shared.domain.ILogger; 6 | import com.charity_hub.shared.exceptions.NotFoundException; 7 | import org.springframework.stereotype.Service; 8 | 9 | @Service 10 | public class PayContributionHandler extends VoidCommandHandler { 11 | private final ICaseRepo caseRepo; 12 | private final ILogger logger; 13 | 14 | public PayContributionHandler(ICaseRepo caseRepo, ILogger logger) { 15 | this.caseRepo = caseRepo; 16 | this.logger = logger; 17 | } 18 | 19 | @Override 20 | public void handle(PayContribution command) { 21 | var contribution = caseRepo.getContributionById(command.contributionId()) 22 | .orElseThrow(() -> { 23 | logger.error("Contribution not found with ID {} ", command.contributionId()); 24 | return new NotFoundException("Contribution not found with ID " + command.contributionId()); 25 | }); 26 | contribution.pay(command.paymentProof()); 27 | caseRepo.save(contribution); 28 | logger.info("Contribution paid and saved with ID {} ", command.contributionId()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/shell/api/controllers/AuthController.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.shell.api.controllers; 2 | 3 | import com.charity_hub.accounts.internal.core.commands.Authenticate.Authenticate; 4 | import com.charity_hub.accounts.internal.core.commands.Authenticate.AuthenticateHandler; 5 | import com.charity_hub.accounts.internal.core.commands.Authenticate.AuthenticateResponse; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.http.ResponseEntity; 9 | import org.springframework.web.bind.annotation.PostMapping; 10 | import org.springframework.web.bind.annotation.RequestBody; 11 | import org.springframework.web.bind.annotation.RestController; 12 | 13 | @RestController 14 | public class AuthController { 15 | private final AuthenticateHandler authenticateHandler; 16 | private final Logger log = LoggerFactory.getLogger(this.getClass().getSimpleName()); 17 | 18 | public AuthController(AuthenticateHandler authenticateHandler) { 19 | this.authenticateHandler = authenticateHandler; 20 | } 21 | 22 | @PostMapping("/v1/accounts/authenticate") 23 | public ResponseEntity login(@RequestBody Authenticate authenticate) { 24 | log.info("Processing authentication request"); 25 | AuthenticateResponse response = authenticateHandler.handle(authenticate); 26 | return ResponseEntity.ok(response); 27 | 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/ledger/internal/application/eventHandlers/loggers/ContributionRemindedLogger.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.ledger.internal.application.eventHandlers.loggers; 2 | 3 | import com.charity_hub.cases.shared.dtos.ContributionRemindedDTO; 4 | import com.charity_hub.shared.domain.ILogger; 5 | import org.springframework.stereotype.Component; 6 | 7 | import java.util.UUID; 8 | 9 | @Component 10 | public class ContributionRemindedLogger { 11 | private final ILogger logger; 12 | 13 | public ContributionRemindedLogger(ILogger logger) { 14 | this.logger = logger; 15 | } 16 | 17 | public void handlerRegistered() { 18 | logger.info("Registering ContributionRemindedHandler"); 19 | } 20 | 21 | public void processingEvent(ContributionRemindedDTO contribution) { 22 | logger.info("Processing contribution reminder - Contribution Id: {}, Contributor ID: {}", 23 | contribution.id(), contribution.contributorId()); 24 | } 25 | 26 | public void notificationSent(UUID id, UUID contributorId) { 27 | logger.info("Successfully sent reminder notification - Contribution Id: {}, Contributor ID: {}", 28 | id, contributorId); 29 | } 30 | 31 | public void notificationFailed(UUID id, UUID contributorId, Exception e) { 32 | logger.error("Failed to send reminder notification - Contribution Id: {}, Contributor ID: {} - Error: {}", 33 | id, contributorId, e.getMessage(), e); 34 | } 35 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/ledger/internal/application/eventHandlers/loggers/ContributionConfirmedLogger.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.ledger.internal.application.eventHandlers.loggers; 2 | 3 | import com.charity_hub.cases.shared.dtos.ContributionConfirmedDTO; 4 | import com.charity_hub.shared.domain.ILogger; 5 | import org.springframework.stereotype.Component; 6 | 7 | import java.util.UUID; 8 | 9 | @Component 10 | public class ContributionConfirmedLogger { 11 | private final ILogger logger; 12 | 13 | public ContributionConfirmedLogger(ILogger logger) { 14 | this.logger = logger; 15 | } 16 | 17 | public void handlerRegistered() { 18 | logger.info("Registering ContributionConfirmedHandler"); 19 | } 20 | 21 | public void processingEvent(ContributionConfirmedDTO contribution) { 22 | logger.info("Processing contribution confirmation - Contribution Id: {}, Contributor ID: {}", 23 | contribution.id(), contribution.contributorId()); 24 | } 25 | 26 | public void notificationSent(UUID id, UUID contributorId) { 27 | logger.info("Successfully sent confirmation notification - Contribution Id: {}, Contributor ID: {}", 28 | id, contributorId); 29 | } 30 | 31 | public void notificationFailed(UUID uuid, UUID contributorId, Exception e) { 32 | logger.error("Failed to send confirmation notification - Contribution Id: {}, Contributor ID: {} - Error: {}", 33 | uuid, contributorId, e.getMessage(), e); 34 | } 35 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/application/commands/ConfirmContribution/ConfirmContributionHandler.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.application.commands.ConfirmContribution; 2 | 3 | import com.charity_hub.cases.internal.domain.contracts.ICaseRepo; 4 | import com.charity_hub.shared.abstractions.VoidCommandHandler; 5 | import com.charity_hub.shared.domain.ILogger; 6 | import com.charity_hub.shared.exceptions.NotFoundException; 7 | import org.springframework.stereotype.Service; 8 | 9 | @Service 10 | public class ConfirmContributionHandler extends VoidCommandHandler { 11 | 12 | private final ICaseRepo caseRepo; 13 | private final ILogger logger; 14 | 15 | public ConfirmContributionHandler(ICaseRepo caseRepo, ILogger logger) { 16 | this.caseRepo = caseRepo; 17 | this.logger = logger; 18 | } 19 | 20 | @Override 21 | public void handle(ConfirmContribution command) { 22 | 23 | var contribution = caseRepo.getContributionById(command.contributionId()) 24 | .orElseGet(()->{ 25 | logger.error("Contribution not found with ID {} ", command.contributionId()); 26 | throw new NotFoundException("Contribution not found with ID " + command.contributionId()); 27 | }); 28 | contribution.confirm(); 29 | caseRepo.save(contribution); 30 | logger.info("Contribution confirmed and saved with ID {}", command.contributionId()); 31 | 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/application/loggers/FCMTokenLogger.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.application.loggers; 2 | 3 | import com.charity_hub.shared.domain.ILogger; 4 | import org.springframework.stereotype.Component; 5 | 6 | @Component 7 | public class FCMTokenLogger { 8 | private final ILogger logger; 9 | 10 | public FCMTokenLogger(ILogger logger) { 11 | this.logger = logger; 12 | } 13 | 14 | public void handlerRegistered() { 15 | logger.info("Registering FCMTokenUpdatedHandler"); 16 | } 17 | 18 | public void processingToken(String token) { 19 | logger.info("Processing FCM token update: {}", maskToken(token)); 20 | } 21 | 22 | public void tokenSubscribed(String token) { 23 | logger.info("Successfully subscribed FCM token to case updates: {}", maskToken(token)); 24 | } 25 | 26 | public void tokenSubscriptionFailed(String token, Exception e) { 27 | logger.error("Failed to subscribe FCM token to case updates: {} - Error: {}", 28 | maskToken(token), e.getMessage(), e); 29 | } 30 | 31 | public void nullTokenReceived() { 32 | logger.warn("Received null FCM token, skipping subscription"); 33 | } 34 | 35 | // Helper method to mask sensitive token data in logs 36 | private String maskToken(String token) { 37 | if (token == null) return "null"; 38 | if (token.length() <= 8) return "***"; 39 | return token.substring(0, 4) + "..." + token.substring(token.length() - 4); 40 | } 41 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/api/controllers/UpdateCaseController.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.api.controllers; 2 | 3 | import com.charity_hub.cases.internal.application.commands.UpdateCase.UpdateCase; 4 | import com.charity_hub.cases.internal.application.commands.UpdateCase.UpdateCaseHandler; 5 | import com.charity_hub.cases.internal.api.dtos.UpdateCaseRequest; 6 | import org.springframework.http.ResponseEntity; 7 | import org.springframework.web.bind.annotation.PathVariable; 8 | import org.springframework.web.bind.annotation.PutMapping; 9 | import org.springframework.web.bind.annotation.RequestBody; 10 | import org.springframework.web.bind.annotation.RestController; 11 | 12 | @RestController 13 | public class UpdateCaseController { 14 | 15 | private final UpdateCaseHandler updateCaseHandler; 16 | 17 | public UpdateCaseController(UpdateCaseHandler updateCaseHandler) { 18 | this.updateCaseHandler = updateCaseHandler; 19 | } 20 | 21 | @PutMapping("/v1/cases/{caseCode}") 22 | public ResponseEntity handle( 23 | @PathVariable int caseCode, 24 | @RequestBody UpdateCaseRequest request) { 25 | UpdateCase command = new UpdateCase( 26 | caseCode, 27 | request.title(), 28 | request.description(), 29 | request.goal(), 30 | request.acceptZakat(), 31 | request.documents() 32 | ); 33 | updateCaseHandler.handle(command); 34 | return ResponseEntity.ok().build(); 35 | } 36 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/application/eventHandlers/CaseClosedHandler.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.application.eventHandlers; 2 | 3 | import com.charity_hub.cases.shared.dtos.CaseClosedDTO; 4 | import com.charity_hub.cases.internal.domain.contracts.INotificationService; 5 | import com.charity_hub.cases.internal.application.eventHandlers.loggers.CaseClosedLogger; 6 | import com.charity_hub.shared.domain.IEventBus; 7 | import jakarta.annotation.PostConstruct; 8 | import org.springframework.stereotype.Service; 9 | 10 | @Service 11 | public class CaseClosedHandler { 12 | private final IEventBus eventBus; 13 | private final INotificationService notificationService; 14 | private final CaseClosedLogger logger; 15 | 16 | public CaseClosedHandler(IEventBus eventBus, INotificationService notificationService, CaseClosedLogger logger) { 17 | this.eventBus = eventBus; 18 | this.notificationService = notificationService; 19 | this.logger = logger; 20 | } 21 | 22 | @PostConstruct 23 | public void start() { 24 | logger.handlerRegistered(); 25 | eventBus.subscribe(this, CaseClosedDTO.class, this::handle); 26 | } 27 | 28 | private void handle(CaseClosedDTO case_) { 29 | logger.processingEvent(case_); 30 | 31 | try { 32 | notificationService.notifyCaseClosed(case_); 33 | logger.notificationSent(case_.caseCode()); 34 | } catch (Exception e) { 35 | logger.notificationFailed(case_.caseCode(), e); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/application/eventHandlers/CaseOpenedHandler.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.application.eventHandlers; 2 | 3 | import com.charity_hub.cases.internal.application.eventHandlers.loggers.CaseOpenedLogger; 4 | import com.charity_hub.cases.shared.dtos.CaseOpenedDTO; 5 | import com.charity_hub.cases.internal.domain.contracts.INotificationService; 6 | import com.charity_hub.shared.domain.IEventBus; 7 | import jakarta.annotation.PostConstruct; 8 | import org.springframework.stereotype.Service; 9 | 10 | @Service 11 | public class CaseOpenedHandler { 12 | private final IEventBus eventBus; 13 | private final INotificationService notificationService; 14 | private final CaseOpenedLogger logger; 15 | 16 | public CaseOpenedHandler(IEventBus eventBus, INotificationService notificationService, CaseOpenedLogger logger) { 17 | this.eventBus = eventBus; 18 | this.notificationService = notificationService; 19 | this.logger = logger; 20 | } 21 | 22 | @PostConstruct 23 | public void start() { 24 | logger.handlerRegistered(); 25 | eventBus.subscribe(this, CaseOpenedDTO.class, this::handle); 26 | } 27 | 28 | private void handle(CaseOpenedDTO case_) { 29 | logger.processingEvent(case_); 30 | 31 | try { 32 | notificationService.notifyCaseOpened(case_); 33 | logger.notificationSent(case_.caseCode()); 34 | } catch (Exception e) { 35 | logger.notificationFailed(case_.caseCode(), e); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/api/controllers/GetAllCasesController.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.api.controllers; 2 | 3 | import com.charity_hub.cases.internal.api.dtos.GetCasesRequest; 4 | import com.charity_hub.cases.internal.application.queries.GetAllCases.GetAllCasesQuery; 5 | import com.charity_hub.cases.internal.application.queries.GetAllCases.GetCasesQueryResult; 6 | import com.charity_hub.cases.internal.infrastructure.queryhandlers.GetAllCasesHandler; 7 | import org.springframework.http.ResponseEntity; 8 | import org.springframework.web.bind.annotation.GetMapping; 9 | import org.springframework.web.bind.annotation.ModelAttribute; 10 | import org.springframework.web.bind.annotation.RestController; 11 | 12 | @RestController 13 | public class GetAllCasesController { 14 | 15 | private final GetAllCasesHandler getAllCasesHandler; 16 | 17 | public GetAllCasesController(GetAllCasesHandler getAllCasesHandler) { 18 | this.getAllCasesHandler = getAllCasesHandler; 19 | } 20 | 21 | @GetMapping("/v1/cases") 22 | public ResponseEntity getCases(@ModelAttribute GetCasesRequest request) { 23 | GetAllCasesQuery query = new GetAllCasesQuery( 24 | request.code(), 25 | request.tag(), 26 | request.content(), 27 | Math.max(request.offset(), 0), 28 | Math.min(Math.max(request.limit(), 1), 100) 29 | ); 30 | var result = getAllCasesHandler.handle(query); 31 | return ResponseEntity.ok(result); 32 | } 33 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/shell/api/controllers/InviteUserController.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.shell.api.controllers; 2 | 3 | import com.charity_hub.accounts.internal.core.commands.InviteAccount.InvitationAccount; 4 | import com.charity_hub.accounts.internal.core.commands.InviteAccount.InviteAccountHandler; 5 | import com.charity_hub.accounts.internal.shell.api.dtos.InviteUserRequest; 6 | import com.charity_hub.shared.auth.AccessTokenPayload; 7 | import org.springframework.http.ResponseEntity; 8 | import org.springframework.security.core.annotation.AuthenticationPrincipal; 9 | import org.springframework.web.bind.annotation.PostMapping; 10 | import org.springframework.web.bind.annotation.RequestBody; 11 | import org.springframework.web.bind.annotation.RestController; 12 | 13 | @RestController 14 | public class InviteUserController { 15 | private final InviteAccountHandler inviteAccountHandler; 16 | 17 | public InviteUserController(InviteAccountHandler inviteAccountHandler) { 18 | this.inviteAccountHandler = inviteAccountHandler; 19 | } 20 | 21 | @PostMapping("/v1/accounts/invite") 22 | public ResponseEntity handle( 23 | @RequestBody InviteUserRequest inviteUserRequest, 24 | @AuthenticationPrincipal AccessTokenPayload accessTokenPayload 25 | ) { 26 | InvitationAccount command = new InvitationAccount(inviteUserRequest.mobileNumber(), accessTokenPayload.getUserId()); 27 | inviteAccountHandler.handle(command); 28 | return ResponseEntity.ok().build(); 29 | } 30 | } -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.config.import=optional:file:./secrets.properties 2 | 3 | 4 | spring.application.name=charity_hub 5 | spring.data.mongodb.host=localhost 6 | spring.data.mongodb.database=charity_db 7 | spring.data.mongodb.authentication-database=admin 8 | spring.data.mongodb.password=${MONGO_PASSWORD} 9 | spring.data.mongodb.username=${MONGO_USERNAME} 10 | spring.data.mongodb.port=27017 11 | server.port=8080 12 | #management.datadog.metrics.export.apiKey 13 | #logging.level.org.springframework.security=TRACE 14 | 15 | management.tracing.sampling.probability=1.0 16 | management.endpoints.web.exposure.include=health,info,metrics,prometheus 17 | 18 | spring.task.execution.shutdown.await-termination=true 19 | spring.task.execution.shutdown.await-termination-period=10s 20 | 21 | spring.threads.virtual.enabled=true 22 | 23 | 24 | auth.secretKey=58fb365a7d09b11ac793400a2a85ecddc966099576c4d10510abce378ad216ca56232c9110b9a8c54aedfa80660a8653cc29460f04b3301171123b1e5cca8d031dea452d932fefb9f8092d4342ccb9514693581cf4708437620a53c65cf7a076bc820588aff4aa3b01876a8dcb04005152c5978afe9882b7bbaaa2b006a960e7c5ad89ba36f838c3cd1c8e3a841468e7ed8bd1a5adf276f974f52cabcdf6d8e7e84d266282b1b7464703064a2853f5982a46239066e4002c017635b8eedcf0c55a0759734891f07b706c6620c79cec329603bd9ebb92d34eb7c2f797da54f1a81402fa0fb246fd54406a9368505d952b5c0162bdaf7e65fe3cfcf0a723a955df 25 | accounts.admins=201260032636,201260032632,201260032631 26 | firebase.service-account-path=./cert/adminsdk.json 27 | firebase.test-mode=true 28 | logging.structured.format.console=ecs 29 | cases.initial-code=20039 30 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/api/controllers/PayContributionController.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.api.controllers; 2 | 3 | import com.charity_hub.cases.internal.api.dtos.PayContributionRequest; 4 | import com.charity_hub.cases.internal.application.commands.PayContribution.PayContribution; 5 | import com.charity_hub.cases.internal.application.commands.PayContribution.PayContributionHandler; 6 | import org.springframework.http.ResponseEntity; 7 | import org.springframework.web.bind.annotation.PathVariable; 8 | import org.springframework.web.bind.annotation.PostMapping; 9 | import org.springframework.web.bind.annotation.RequestBody; 10 | import org.springframework.web.bind.annotation.RestController; 11 | 12 | import java.util.UUID; 13 | 14 | @RestController 15 | public class PayContributionController { 16 | private final PayContributionHandler payContributionHandler; 17 | 18 | public PayContributionController(PayContributionHandler payContributionHandler){ 19 | this.payContributionHandler = payContributionHandler; 20 | } 21 | 22 | @PostMapping("/v1/contributions/{contributionId}/pay") 23 | public ResponseEntity handle( 24 | @PathVariable UUID contributionId, 25 | @RequestBody(required = false) PayContributionRequest request) { 26 | String paymentProof = request != null ? request.PaymentProof() : null; 27 | PayContribution command = new PayContribution(contributionId, paymentProof); 28 | payContributionHandler.handle(command); 29 | return ResponseEntity.ok().build(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/shared/domain/model/Entity.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.shared.domain.model; 2 | 3 | import lombok.Getter; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | 7 | /** 8 | * This class should be used to describe any entity domain class (DDD) 9 | * Entities should have id that identify it, this id should be a value object 10 | *

11 | * Example usage: 12 | *

13 |  * {@code
14 |  * public class UserId implements ValueObject {
15 |  *     private final UUID id;
16 |  *
17 |  *     public UserId(UUID id) {
18 |  *         this.id = id;
19 |  *     }
20 |  * }
21 |  *
22 |  * public class User extends Entity {
23 |  *     public User(UserId id) {
24 |  *         super(id);
25 |  *     }
26 |  *     // some code
27 |  * }
28 |  * }
29 |  * 
30 | */ 31 | @Getter 32 | public abstract class Entity implements DomainModel { 33 | protected final Logger log; 34 | private final T id; 35 | 36 | protected Entity(T id) { 37 | this.id = id; 38 | this.log = LoggerFactory.getLogger(this.getClass().getSimpleName()); 39 | } 40 | 41 | @Override 42 | public int hashCode() { 43 | return id.hashCode(); 44 | } 45 | 46 | @Override 47 | public boolean equals(Object obj) { 48 | if (obj == null) { 49 | return false; 50 | } 51 | 52 | if (obj.getClass() != this.getClass()) { 53 | return false; 54 | } 55 | 56 | Entity otherEntity = (Entity) obj; 57 | return this.id.equals(otherEntity.getId()); 58 | } 59 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/shell/api/controllers/GetConnectionsController.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.shell.api.controllers; 2 | 3 | import com.charity_hub.accounts.internal.core.queriers.Account; 4 | import com.charity_hub.accounts.internal.core.queriers.GetConnectionResponse; 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.security.core.annotation.AuthenticationPrincipal; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | import com.charity_hub.accounts.internal.core.queriers.GetConnectionsQuery; 11 | import com.charity_hub.accounts.internal.core.queriers.GetConnectionsHandler; 12 | import com.charity_hub.shared.auth.AccessTokenPayload; 13 | 14 | import java.util.List; 15 | 16 | @RestController 17 | public class GetConnectionsController { 18 | private final GetConnectionsHandler getConnectionsHandler; 19 | 20 | public GetConnectionsController(GetConnectionsHandler getConnectionsHandler) { 21 | this.getConnectionsHandler = getConnectionsHandler; 22 | } 23 | 24 | @GetMapping("/v1/accounts/connections") 25 | public ResponseEntity handle(@AuthenticationPrincipal AccessTokenPayload accessTokenPayload) { 26 | GetConnectionsQuery command = new GetConnectionsQuery(accessTokenPayload.getUserId()); 27 | 28 | List accountList = getConnectionsHandler.handle(command); 29 | 30 | return ResponseEntity.ok(new GetConnectionResponse(accountList)); 31 | 32 | 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/shell/repositories/AccountReadRepo.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.shell.repositories; 2 | 3 | import com.charity_hub.accounts.internal.core.contracts.IAccountReadRepo; 4 | import com.charity_hub.accounts.internal.core.queriers.Account; 5 | import com.charity_hub.accounts.internal.shell.db.AccountEntity; 6 | import com.charity_hub.accounts.internal.shell.repositories.mappers.AccountReadMapper; 7 | import com.mongodb.client.MongoCollection; 8 | import com.mongodb.client.MongoDatabase; 9 | import org.springframework.stereotype.Repository; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | import java.util.UUID; 14 | 15 | import static com.mongodb.client.model.Filters.eq; 16 | 17 | @Repository 18 | public class AccountReadRepo implements IAccountReadRepo { 19 | 20 | private static final String ACCOUNTS_COLLECTION = "accounts"; 21 | 22 | private final MongoCollection collection; 23 | private final AccountReadMapper accountReadMapper; 24 | 25 | public AccountReadRepo( 26 | MongoDatabase mongoDatabase, 27 | AccountReadMapper accountReadMapper 28 | ) { 29 | this.collection = mongoDatabase.getCollection(ACCOUNTS_COLLECTION, AccountEntity.class); 30 | this.accountReadMapper = accountReadMapper; 31 | } 32 | 33 | @Override 34 | public List getConnections(UUID id) { 35 | return collection.find(eq("connections.userId", id.toString())) 36 | .map(accountReadMapper::toQueryModel) 37 | .into(new ArrayList<>()); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/api/controllers/GetCaseController.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.api.controllers; 2 | 3 | import com.charity_hub.cases.internal.application.queries.GetCase.GetCaseQuery; 4 | import com.charity_hub.cases.internal.application.queries.GetCase.GetCaseResponse; 5 | import com.charity_hub.cases.internal.application.queries.GetCase.IGetCaseHandler; 6 | import com.charity_hub.shared.auth.AccessTokenPayload; 7 | import org.springframework.http.HttpStatus; 8 | import org.springframework.http.ResponseEntity; 9 | import org.springframework.security.access.prepost.PreAuthorize; 10 | import org.springframework.security.core.annotation.AuthenticationPrincipal; 11 | import org.springframework.web.bind.annotation.GetMapping; 12 | import org.springframework.web.bind.annotation.PathVariable; 13 | import org.springframework.web.bind.annotation.RestController; 14 | 15 | @RestController 16 | public class GetCaseController { 17 | 18 | private final IGetCaseHandler getCaseHandler; 19 | 20 | public GetCaseController(IGetCaseHandler getCaseHandler) { 21 | this.getCaseHandler = getCaseHandler; 22 | } 23 | 24 | @GetMapping("/v1/cases/{caseCode}") 25 | @PreAuthorize("hasAnyAuthority('FULL_ACCESS')") 26 | public ResponseEntity getCase( 27 | @PathVariable int caseCode, 28 | @AuthenticationPrincipal AccessTokenPayload accessTokenPayload) { 29 | 30 | var response = getCaseHandler.handle(new GetCaseQuery(caseCode, accessTokenPayload)); 31 | return ResponseEntity.status(HttpStatus.CREATED).body(response); 32 | } 33 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/application/commands/UpdateCase/UpdateCaseHandler.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.application.commands.UpdateCase; 2 | 3 | import com.charity_hub.cases.internal.domain.contracts.ICaseRepo; 4 | import com.charity_hub.cases.internal.domain.model.Case.CaseCode; 5 | import com.charity_hub.shared.abstractions.VoidCommandHandler; 6 | import com.charity_hub.shared.exceptions.NotFoundException; 7 | import org.springframework.stereotype.Service; 8 | 9 | @Service 10 | public class UpdateCaseHandler extends VoidCommandHandler { 11 | private final ICaseRepo caseRepo; 12 | 13 | public UpdateCaseHandler(ICaseRepo caseRepo) { 14 | this.caseRepo = caseRepo; 15 | } 16 | 17 | @Override 18 | public void handle(UpdateCase command) { 19 | logger.info("Updating case - CaseCode: {}, Title: {}", command.caseCode(), command.title()); 20 | 21 | var case_ = caseRepo.getByCode(new CaseCode(command.caseCode())) 22 | .orElseThrow(() -> { 23 | logger.warn("Case not found for update - CaseCode: {}", command.caseCode()); 24 | return new NotFoundException("This case is not found"); 25 | }); 26 | 27 | case_.update( 28 | command.title(), 29 | command.description(), 30 | command.goal(), 31 | command.acceptZakat(), 32 | command.documents() 33 | ); 34 | caseRepo.save(case_); 35 | logger.info("Case updated successfully - CaseCode: {}", command.caseCode()); 36 | } 37 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/shell/api/controllers/GetConnectionsAdminController.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.shell.api.controllers; 2 | 3 | import com.charity_hub.accounts.internal.core.queriers.Account; 4 | import com.charity_hub.accounts.internal.core.queriers.GetConnectionResponse; 5 | import com.charity_hub.accounts.internal.core.queriers.GetConnectionsQuery; 6 | import com.charity_hub.accounts.internal.core.queriers.GetConnectionsHandler; 7 | import org.springframework.http.ResponseEntity; 8 | import org.springframework.security.access.prepost.PreAuthorize; 9 | import org.springframework.web.bind.annotation.GetMapping; 10 | import org.springframework.web.bind.annotation.PathVariable; 11 | import org.springframework.web.bind.annotation.RestController; 12 | 13 | import java.util.List; 14 | import java.util.UUID; 15 | 16 | @RestController 17 | public class GetConnectionsAdminController { 18 | private final GetConnectionsHandler getConnectionsHandler; 19 | 20 | public GetConnectionsAdminController(GetConnectionsHandler getConnectionsHandler) { 21 | this.getConnectionsHandler = getConnectionsHandler; 22 | } 23 | 24 | @GetMapping("v1/accounts/{userId}/connections") 25 | @PreAuthorize("hasAnyAuthority('FULL_ACCESS')") 26 | public ResponseEntity handle(@PathVariable UUID userId) { 27 | GetConnectionsQuery command = new GetConnectionsQuery(userId); 28 | 29 | List accountList = getConnectionsHandler.handle(command); 30 | 31 | return ResponseEntity.ok(new GetConnectionResponse(accountList)); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/ledger/internal/infrastructure/integrationevents/AccountCreatedHandler.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.ledger.internal.infrastructure.integrationevents; 2 | 3 | import com.charity_hub.accounts.shared.AccountEventDto; 4 | import com.charity_hub.ledger.internal.application.eventHandlers.AccountCreated.AccountCreated; 5 | import com.charity_hub.ledger.internal.application.eventHandlers.AccountCreated.AccountCreatedEventHandler; 6 | import com.charity_hub.shared.domain.IEventBus; 7 | import com.charity_hub.shared.domain.ILogger; 8 | import org.springframework.stereotype.Component; 9 | 10 | @Component 11 | public class AccountCreatedHandler { 12 | private final IEventBus eventBus; 13 | private final ILogger logger; 14 | private final AccountCreatedEventHandler accountCreatedEventHandler; 15 | 16 | public AccountCreatedHandler( 17 | IEventBus eventBus, 18 | ILogger logger, 19 | AccountCreatedEventHandler accountCreatedEventHandler) { 20 | this.eventBus = eventBus; 21 | this.logger = logger; 22 | this.accountCreatedEventHandler = accountCreatedEventHandler; 23 | } 24 | 25 | public void start() { 26 | eventBus.subscribe(this, AccountEventDto.AccountCreatedDTO.class, this::addConnection); 27 | } 28 | 29 | private void addConnection(AccountEventDto.AccountCreatedDTO account) { 30 | logger.info("AddConnection Service received AccountCreated event, add the connection this account {}", account.id()); 31 | accountCreatedEventHandler.accountCreatedHandler(new AccountCreated(account.id(), account.mobileNumber())); 32 | } 33 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/shell/api/controllers/AddUserPermissionController.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.shell.api.controllers; 2 | 3 | import com.charity_hub.accounts.internal.core.commands.ChangePermission.ChangePermission; 4 | import com.charity_hub.accounts.internal.core.commands.ChangePermission.ChangePermissionHandler; 5 | import com.charity_hub.accounts.internal.shell.api.dtos.ChangePermissionRequest; 6 | import org.springframework.http.ResponseEntity; 7 | import org.springframework.security.access.prepost.PreAuthorize; 8 | import org.springframework.web.bind.annotation.PathVariable; 9 | import org.springframework.web.bind.annotation.PostMapping; 10 | import org.springframework.web.bind.annotation.RequestBody; 11 | import org.springframework.web.bind.annotation.RestController; 12 | 13 | import java.util.UUID; 14 | 15 | @RestController 16 | public class AddUserPermissionController { 17 | private final ChangePermissionHandler changePermissionHandler; 18 | 19 | public AddUserPermissionController(ChangePermissionHandler changePermissionHandler) { 20 | this.changePermissionHandler = changePermissionHandler; 21 | } 22 | 23 | @PostMapping("/v1/accounts/{userId}/add-permission") 24 | @PreAuthorize("hasAnyAuthority('FULL_ACCESS')") 25 | public ResponseEntity handle( 26 | @PathVariable UUID userId, 27 | @RequestBody ChangePermissionRequest request 28 | ) { 29 | ChangePermission command = new ChangePermission(userId, request.permission(), true); 30 | changePermissionHandler.handle(command); 31 | return ResponseEntity.ok().build(); 32 | } 33 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/ledger/internal/domain/model/Member.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.ledger.internal.domain.model; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | import java.util.List; 6 | import java.util.UUID; 7 | 8 | public record Member(MemberId memberId, MemberId parent, List ancestors, List children) { 9 | 10 | public static Member newMember(Member parent, UUID memberId) { 11 | List newAncestors = new ArrayList<>(parent.ancestors()); 12 | newAncestors.add(parent.memberId()); 13 | 14 | return new Member( 15 | new MemberId(memberId), 16 | parent.memberId(), 17 | newAncestors, 18 | Collections.emptyList() 19 | ); 20 | } 21 | 22 | public List ancestorsIds() { 23 | return ancestors().stream() 24 | .map(memberId -> memberId.value().toString()) 25 | .toList(); 26 | } 27 | 28 | public String parentId() { 29 | return parent().value().toString(); 30 | } 31 | 32 | public List childrenIds() { 33 | return children().stream() 34 | .map(memberId -> memberId.value().toString()) 35 | .toList(); 36 | } 37 | 38 | public String memberIdValue() { 39 | return memberId().value().toString(); 40 | } 41 | 42 | @Override 43 | public List ancestors() { 44 | return Collections.unmodifiableList(ancestors); 45 | } 46 | 47 | @Override 48 | public List children() { 49 | return Collections.unmodifiableList(children); 50 | } 51 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/shared/domain/EventBus.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.shared.domain; 2 | 3 | import org.springframework.stereotype.Component; 4 | 5 | import java.util.List; 6 | import java.util.concurrent.CopyOnWriteArrayList; 7 | 8 | @Component 9 | public class EventBus implements IEventBus { 10 | // CopyOnWriteArrayList is thread-safe and doesn't require synchronization for iteration. 11 | // This avoids virtual thread pinning when handlers perform blocking I/O. 12 | private final List> listeners = new CopyOnWriteArrayList<>(); 13 | 14 | @Override 15 | public void subscribe(Object owner, Class event, EventCallback callback) { 16 | EventListener listener = new EventListener<>(owner, event, callback); 17 | listeners.add(listener); 18 | } 19 | 20 | @Override 21 | public void unsubscribe(Object owner) { 22 | listeners.removeIf(listener -> listener.owner().equals(owner)); 23 | } 24 | 25 | @Override 26 | public void push(T event) { 27 | // No synchronization needed - CopyOnWriteArrayList handles thread-safety 28 | // and virtual threads won't be pinned during handler execution 29 | listeners.forEach(listener -> { 30 | if (listener.event().isInstance(event)) { 31 | @SuppressWarnings("unchecked") 32 | EventListener typedListener = (EventListener) listener; 33 | typedListener.callback().handle(event); 34 | } 35 | }); 36 | } 37 | } 38 | 39 | record EventListener(Object owner, Class event, IEventBus.EventCallback callback) { 40 | } 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/shared/AccountEventDto.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.shared; 2 | 3 | import java.util.List; 4 | import java.util.UUID; 5 | 6 | public sealed interface AccountEventDto 7 | permits AccountEventDto.AccountAuthenticated, AccountEventDto.AccountBlocked, AccountEventDto.AccountCreatedDTO, AccountEventDto.AccountUnBlockedDTO, AccountEventDto.BasicInfoUpdatedDTO, AccountEventDto.FCMTokenUpdatedDTO, AccountEventDto.PermissionAddedDTO, AccountEventDto.PermissionRemovedDTO { 8 | 9 | record AccountBlocked(UUID id) implements AccountEventDto { 10 | } 11 | 12 | record AccountAuthenticated( 13 | UUID id, 14 | String deviceId, 15 | String deviceType, 16 | String deviceFCMToken 17 | ) implements AccountEventDto { 18 | } 19 | 20 | record AccountCreatedDTO(UUID id, String mobileNumber) implements AccountEventDto { 21 | } 22 | 23 | record AccountUnBlockedDTO(UUID id) implements AccountEventDto { 24 | } 25 | 26 | record BasicInfoUpdatedDTO( 27 | UUID id, 28 | String deviceId, 29 | String deviceType, 30 | String deviceFCMToken 31 | ) implements AccountEventDto { 32 | } 33 | 34 | record FCMTokenUpdatedDTO( 35 | String deviceId, 36 | String deviceType, 37 | String deviceFCMToken 38 | ) implements AccountEventDto { 39 | } 40 | 41 | record PermissionAddedDTO(UUID id, List permissions) implements AccountEventDto { 42 | } 43 | 44 | record PermissionRemovedDTO(UUID id, List permissions) implements AccountEventDto { 45 | } 46 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/shell/api/controllers/RemoveUserPermissionController.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.shell.api.controllers; 2 | 3 | import com.charity_hub.accounts.internal.core.commands.ChangePermission.ChangePermission; 4 | import com.charity_hub.accounts.internal.core.commands.ChangePermission.ChangePermissionHandler; 5 | import com.charity_hub.accounts.internal.shell.api.dtos.ChangePermissionRequest; 6 | import org.springframework.http.ResponseEntity; 7 | import org.springframework.security.access.prepost.PreAuthorize; 8 | import org.springframework.web.bind.annotation.PathVariable; 9 | import org.springframework.web.bind.annotation.PostMapping; 10 | import org.springframework.web.bind.annotation.RequestBody; 11 | import org.springframework.web.bind.annotation.RestController; 12 | 13 | import java.util.UUID; 14 | 15 | @RestController 16 | public class RemoveUserPermissionController { 17 | private final ChangePermissionHandler changePermissionHandler; 18 | 19 | public RemoveUserPermissionController(ChangePermissionHandler changePermissionHandler) { 20 | this.changePermissionHandler = changePermissionHandler; 21 | } 22 | 23 | @PostMapping("/v1/accounts/{userId}/remove-permission") 24 | @PreAuthorize("hasAnyAuthority('FULL_ACCESS')") 25 | public ResponseEntity handle( 26 | @PathVariable UUID userId, 27 | @RequestBody ChangePermissionRequest request 28 | ) { 29 | ChangePermission command = new ChangePermission(userId, request.permission(), false); 30 | 31 | changePermissionHandler.handle(command); 32 | 33 | return ResponseEntity.ok().build(); 34 | } 35 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/application/eventHandlers/ContributionMadeHandler.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.application.eventHandlers; 2 | 3 | import com.charity_hub.cases.shared.dtos.ContributionMadeDTO; 4 | import com.charity_hub.cases.internal.domain.contracts.INotificationService; 5 | import com.charity_hub.cases.internal.application.eventHandlers.loggers.ContributionMadeLogger; 6 | import com.charity_hub.shared.domain.IEventBus; 7 | import jakarta.annotation.PostConstruct; 8 | import org.springframework.stereotype.Service; 9 | 10 | @Service 11 | public class ContributionMadeHandler { 12 | private final IEventBus eventBus; 13 | private final INotificationService notificationService; 14 | private final ContributionMadeLogger logger; 15 | 16 | public ContributionMadeHandler(IEventBus eventBus, INotificationService notificationService, ContributionMadeLogger logger) { 17 | this.eventBus = eventBus; 18 | this.notificationService = notificationService; 19 | this.logger = logger; 20 | } 21 | 22 | @PostConstruct 23 | public void start() { 24 | logger.handlerRegistered(); 25 | eventBus.subscribe(this, ContributionMadeDTO.class, this::handle); 26 | } 27 | 28 | private void handle(ContributionMadeDTO contribution) { 29 | logger.processingEvent(contribution); 30 | 31 | try { 32 | notificationService.notifyContributionMade(contribution); 33 | logger.notificationSent(contribution.caseCode(), contribution.amount()); 34 | } catch (Exception e) { 35 | logger.notificationFailed(contribution.caseCode(), contribution.amount(), e); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/core/commands/BlockAccount/BlockAccountHandler.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.core.commands.BlockAccount; 2 | 3 | import com.charity_hub.accounts.internal.core.contracts.IAccountRepo; 4 | import com.charity_hub.shared.abstractions.VoidCommandHandler; 5 | import com.charity_hub.shared.exceptions.NotFoundException; 6 | import org.springframework.stereotype.Service; 7 | import org.springframework.transaction.annotation.Transactional; 8 | 9 | import java.util.UUID; 10 | 11 | @Service 12 | public class BlockAccountHandler extends VoidCommandHandler { 13 | private final IAccountRepo accountRepo; 14 | 15 | public BlockAccountHandler(IAccountRepo accountRepo) { 16 | this.accountRepo = accountRepo; 17 | } 18 | 19 | @Override 20 | @Transactional 21 | public void handle(BlockAccount command) { 22 | String action = command.isUnblock() ? "UNBLOCK" : "BLOCK"; 23 | logger.info("Account {} requested - UserId: {}", action, command.userId()); 24 | 25 | var identity = accountRepo.getById(UUID.fromString(command.userId())) 26 | .orElseThrow(() -> { 27 | logger.warn("Account not found for {} - UserId: {}", action, command.userId()); 28 | return new NotFoundException("User with Id " + command.userId() + " not found"); 29 | }); 30 | 31 | if (command.isUnblock()) { 32 | identity.unBlock(); 33 | } else { 34 | identity.block(); 35 | } 36 | 37 | accountRepo.save(identity); 38 | logger.info("Account {} completed successfully - UserId: {}", action, command.userId()); 39 | } 40 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/shared/AccountsAPIs.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.shared; 2 | 3 | import com.charity_hub.accounts.internal.shell.repositories.InvitationRepo; 4 | import com.charity_hub.accounts.internal.shell.repositories.ReadAccountRepo; 5 | import org.springframework.stereotype.Component; 6 | 7 | import java.util.List; 8 | import java.util.UUID; 9 | import java.util.stream.Collectors; 10 | 11 | @Component 12 | public class AccountsAPIs implements IAccountsAPI { 13 | private final InvitationRepo invitationRepo; 14 | private final ReadAccountRepo readAccountRepo; 15 | private final DTOAccountMapper dtoAccountMapper; 16 | 17 | public AccountsAPIs(InvitationRepo invitationRepo, ReadAccountRepo readAccountRepo, DTOAccountMapper dtoAccountMapper) { 18 | this.invitationRepo = invitationRepo; 19 | this.readAccountRepo = readAccountRepo; 20 | this.dtoAccountMapper = dtoAccountMapper; 21 | } 22 | 23 | @Override 24 | public InvitationResponse getInvitationByMobileNumber(String mobileNumber) { 25 | var invitation = invitationRepo.get(mobileNumber); 26 | if (invitation == null) return null; 27 | return new InvitationResponse(invitation.invitedMobileNumber().value(), invitation.inviterId()); 28 | } 29 | 30 | @Override 31 | public AccountDTO getById(UUID id) { 32 | return dtoAccountMapper.toDTO(readAccountRepo.getById(id)); 33 | } 34 | 35 | @Override 36 | public List getAccountsByIds(List ids) { 37 | return readAccountRepo.getAccountsByIds(ids) 38 | .stream() 39 | .map(dtoAccountMapper::toDTO) 40 | .collect(Collectors.toList()); 41 | } 42 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/application/commands/ChangeCaseStatus/ChangeCaseStatusHandler.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.application.commands.ChangeCaseStatus; 2 | 3 | import com.charity_hub.cases.internal.domain.contracts.ICaseRepo; 4 | import com.charity_hub.cases.internal.domain.model.Case.CaseCode; 5 | import com.charity_hub.shared.abstractions.VoidCommandHandler; 6 | import com.charity_hub.shared.exceptions.NotFoundException; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Component 10 | public class ChangeCaseStatusHandler extends VoidCommandHandler { 11 | private final ICaseRepo caseRepo; 12 | 13 | public ChangeCaseStatusHandler(ICaseRepo caseRepo) { 14 | this.caseRepo = caseRepo; 15 | } 16 | 17 | @Override 18 | public void handle(ChangeCaseStatus command) { 19 | String action = command.isActionOpen() ? "OPEN" : "CLOSE"; 20 | logger.info("Changing case status - CaseCode: {}, Action: {}", command.caseCode(), action); 21 | 22 | var case_ = caseRepo.getByCode(new CaseCode(command.caseCode())) 23 | .orElseThrow(() -> { 24 | logger.warn("Case not found for status change - CaseCode: {}", command.caseCode()); 25 | return new NotFoundException("This case is not found"); 26 | }); 27 | 28 | if (command.isActionOpen()) { 29 | case_.open(); 30 | } else { 31 | case_.close(); 32 | } 33 | 34 | caseRepo.save(case_); 35 | logger.info("Case status changed successfully - CaseCode: {}, NewStatus: {}", command.caseCode(), action); 36 | } 37 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/shell/api/controllers/UpdateBasicInfoController.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.shell.api.controllers; 2 | 3 | import com.charity_hub.accounts.internal.core.commands.UpdateBasicInfo.UpdateBasicInfo; 4 | import com.charity_hub.accounts.internal.core.commands.UpdateBasicInfo.UpdateBasicInfoHandler; 5 | import com.charity_hub.accounts.internal.shell.api.dtos.BasicResponse; 6 | import com.charity_hub.accounts.internal.shell.api.dtos.UpdateBasicInfoRequest; 7 | import com.charity_hub.shared.auth.AccessTokenPayload; 8 | import org.springframework.http.ResponseEntity; 9 | import org.springframework.security.core.annotation.AuthenticationPrincipal; 10 | import org.springframework.web.bind.annotation.PostMapping; 11 | import org.springframework.web.bind.annotation.RequestBody; 12 | import org.springframework.web.bind.annotation.RestController; 13 | 14 | @RestController 15 | public class UpdateBasicInfoController { 16 | private final UpdateBasicInfoHandler updateBasicInfoHandler; 17 | 18 | public UpdateBasicInfoController(UpdateBasicInfoHandler updateBasicInfoHandler) { 19 | this.updateBasicInfoHandler = updateBasicInfoHandler; 20 | } 21 | 22 | @PostMapping("/v1/accounts/update-basic-info") 23 | public ResponseEntity handle(@RequestBody UpdateBasicInfoRequest request, @AuthenticationPrincipal AccessTokenPayload accessTokenPayload) { 24 | UpdateBasicInfo command = new UpdateBasicInfo(accessTokenPayload.getUserId(), accessTokenPayload.getDeviceId(), request.fullName(), request.photoUrl()); 25 | 26 | String accessToken = updateBasicInfoHandler.handle(command); 27 | return ResponseEntity.ok(new BasicResponse(accessToken)); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/api/controllers/ChangeCaseStatusController.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.api.controllers; 2 | 3 | import com.charity_hub.cases.internal.application.commands.ChangeCaseStatus.ChangeCaseStatus; 4 | import com.charity_hub.cases.internal.application.commands.ChangeCaseStatus.ChangeCaseStatusHandler; 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.security.access.prepost.PreAuthorize; 7 | import org.springframework.web.bind.annotation.PathVariable; 8 | import org.springframework.web.bind.annotation.PostMapping; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | @RestController 12 | public class ChangeCaseStatusController { 13 | 14 | private final ChangeCaseStatusHandler changeCaseStatusHandler; 15 | 16 | public ChangeCaseStatusController(ChangeCaseStatusHandler changeCaseStatusHandler) { 17 | this.changeCaseStatusHandler = changeCaseStatusHandler; 18 | } 19 | 20 | @PostMapping("/v1/cases/{caseCode}/open") 21 | @PreAuthorize("hasAnyAuthority('CREATE_CASES', 'FULL_ACCESS')") 22 | public ResponseEntity open(@PathVariable int caseCode) { 23 | ChangeCaseStatus command = new ChangeCaseStatus(caseCode, true); 24 | changeCaseStatusHandler.handle(command); 25 | return ResponseEntity.ok().build(); 26 | } 27 | 28 | @PostMapping("/v1/cases/{caseCode}/close") 29 | @PreAuthorize("hasAnyAuthority('CREATE_CASES', 'FULL_ACCESS')") 30 | public ResponseEntity close(@PathVariable int caseCode) { 31 | ChangeCaseStatus command = new ChangeCaseStatus(caseCode, false); 32 | changeCaseStatusHandler.handle(command); 33 | return ResponseEntity.ok().build(); 34 | } 35 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/ledger/internal/application/eventHandlers/ContributionPaidHandler.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.ledger.internal.application.eventHandlers; 2 | 3 | import com.charity_hub.cases.shared.dtos.ContributionPaidDTO; 4 | import com.charity_hub.ledger.internal.domain.contracts.INotificationService; 5 | import com.charity_hub.ledger.internal.application.eventHandlers.loggers.ContributionPaidLogger; 6 | import com.charity_hub.shared.domain.IEventBus; 7 | import jakarta.annotation.PostConstruct; 8 | import org.springframework.stereotype.Service; 9 | 10 | @Service 11 | public class ContributionPaidHandler { 12 | private final IEventBus eventBus; 13 | private final INotificationService notificationService; 14 | private final ContributionPaidLogger logger; 15 | 16 | public ContributionPaidHandler( 17 | IEventBus eventBus, 18 | INotificationService notificationService, 19 | ContributionPaidLogger logger 20 | ) { 21 | this.eventBus = eventBus; 22 | this.notificationService = notificationService; 23 | this.logger = logger; 24 | } 25 | 26 | @PostConstruct 27 | public void start() { 28 | logger.handlerRegistered(); 29 | eventBus.subscribe(this, ContributionPaidDTO.class, this::handle); 30 | } 31 | 32 | private void handle(ContributionPaidDTO contribution) { 33 | logger.processingEvent(contribution); 34 | 35 | try { 36 | notificationService.notifyContributionPaid(contribution); 37 | logger.notificationSent(contribution.id(), contribution.contributorId()); 38 | } catch (Exception e) { 39 | logger.notificationFailed(contribution.id(), contribution.contributorId(), e); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/core/commands/InviteAccount/InviteAccountHandler.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.core.commands.InviteAccount; 2 | 3 | import com.charity_hub.accounts.internal.core.contracts.IInvitationRepo; 4 | import com.charity_hub.accounts.internal.core.exceptions.AlreadyInvitedException; 5 | import com.charity_hub.accounts.internal.core.model.invitation.Invitation; 6 | import com.charity_hub.shared.abstractions.VoidCommandHandler; 7 | import org.springframework.stereotype.Service; 8 | 9 | @Service 10 | public class InviteAccountHandler extends VoidCommandHandler { 11 | private final IInvitationRepo invitationRepo; 12 | 13 | public InviteAccountHandler(IInvitationRepo invitationRepo) { 14 | this.invitationRepo = invitationRepo; 15 | } 16 | 17 | @Override 18 | public void handle(InvitationAccount command) { 19 | logger.info("Processing invitation - MobileNumber: {}, InviterId: {}", 20 | command.mobileNumber(), command.inviterId()); 21 | 22 | boolean hasInvitation = invitationRepo.hasInvitation(command.mobileNumber()); 23 | 24 | if (hasInvitation) { 25 | logger.warn("Duplicate invitation attempt - MobileNumber: {} already invited", command.mobileNumber()); 26 | throw new AlreadyInvitedException("already invited"); 27 | } 28 | 29 | Invitation newInvitation = Invitation.of( 30 | command.mobileNumber(), 31 | command.inviterId() 32 | ); 33 | 34 | invitationRepo.save(newInvitation); 35 | logger.info("Invitation created successfully - MobileNumber: {}, InviterId: {}", 36 | command.mobileNumber(), command.inviterId()); 37 | } 38 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/application/commands/Contribute/ContributeHandler.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.application.commands.Contribute; 2 | 3 | import com.charity_hub.cases.internal.domain.contracts.ICaseRepo; 4 | import com.charity_hub.cases.internal.domain.model.Case.CaseCode; 5 | import com.charity_hub.shared.abstractions.CommandHandler; 6 | import org.springframework.stereotype.Service; 7 | 8 | import com.charity_hub.shared.exceptions.NotFoundException; 9 | 10 | @Service 11 | public class ContributeHandler extends CommandHandler { 12 | private final ICaseRepo caseRepo; 13 | 14 | public ContributeHandler(ICaseRepo caseRepo) { 15 | this.caseRepo = caseRepo; 16 | } 17 | 18 | @Override 19 | public ContributeDefaultResponse handle(Contribute command) { 20 | logger.info("Processing contribution - CaseCode: {}, UserId: {}, Amount: {}", 21 | command.caseCode(), command.userId(), command.amount()); 22 | 23 | var case_ = caseRepo.getByCode(new CaseCode(command.caseCode())) 24 | .orElseThrow(() -> { 25 | logger.warn("Case not found for contribution - CaseCode: {}", command.caseCode()); 26 | return new NotFoundException("This case is not found"); 27 | }); 28 | 29 | var contribution = case_.contribute(command.userId(), command.amount()); 30 | 31 | caseRepo.save(case_); 32 | logger.info("Contribution created successfully - ContributionId: {}, CaseCode: {}, Amount: {}", 33 | contribution.contributionId(), command.caseCode(), command.amount()); 34 | return new ContributeDefaultResponse(contribution.contributionId()); 35 | } 36 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/api/controllers/CreateCaseController.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.api.controllers; 2 | 3 | import com.charity_hub.cases.internal.application.commands.CreateCase.CreateCase; 4 | import com.charity_hub.cases.internal.application.commands.CreateCase.CreateCaseHandler; 5 | import com.charity_hub.cases.internal.application.commands.CreateCase.CaseResponse; 6 | import com.charity_hub.cases.internal.api.dtos.CreateCaseRequest; 7 | import org.springframework.http.HttpStatus; 8 | import org.springframework.http.ResponseEntity; 9 | import org.springframework.security.access.prepost.PreAuthorize; 10 | import org.springframework.web.bind.annotation.PostMapping; 11 | import org.springframework.web.bind.annotation.RequestBody; 12 | import org.springframework.web.bind.annotation.RestController; 13 | 14 | @RestController 15 | public class CreateCaseController { 16 | 17 | private final CreateCaseHandler createCaseHandler; 18 | 19 | public CreateCaseController(CreateCaseHandler createCaseHandler) { 20 | this.createCaseHandler = createCaseHandler; 21 | } 22 | 23 | @PostMapping("/v1/cases") 24 | @PreAuthorize("hasAnyAuthority('CREATE_CASES', 'FULL_ACCESS')") 25 | public ResponseEntity createCase(@RequestBody CreateCaseRequest request) { 26 | CreateCase createCommand = new CreateCase( 27 | request.title(), 28 | request.description(), 29 | request.goal(), 30 | request.publish(), 31 | request.acceptZakat(), 32 | request.documents() 33 | ); 34 | 35 | var response = createCaseHandler.handle(createCommand); 36 | return ResponseEntity.status(HttpStatus.CREATED).body(response); 37 | } 38 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/ledger/internal/application/eventHandlers/ContributionRemindedHandler.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.ledger.internal.application.eventHandlers; 2 | 3 | import com.charity_hub.cases.shared.dtos.ContributionRemindedDTO; 4 | import com.charity_hub.ledger.internal.domain.contracts.INotificationService; 5 | import com.charity_hub.ledger.internal.application.eventHandlers.loggers.ContributionRemindedLogger; 6 | import com.charity_hub.shared.domain.IEventBus; 7 | import jakarta.annotation.PostConstruct; 8 | import org.springframework.stereotype.Service; 9 | 10 | @Service 11 | public class ContributionRemindedHandler { 12 | private final IEventBus eventBus; 13 | private final INotificationService notificationService; 14 | private final ContributionRemindedLogger logger; 15 | 16 | public ContributionRemindedHandler( 17 | IEventBus eventBus, 18 | INotificationService notificationService, 19 | ContributionRemindedLogger logger 20 | ) { 21 | this.eventBus = eventBus; 22 | this.notificationService = notificationService; 23 | this.logger = logger; 24 | } 25 | 26 | @PostConstruct 27 | public void start() { 28 | logger.handlerRegistered(); 29 | eventBus.subscribe(this, ContributionRemindedDTO.class, this::handle); 30 | } 31 | 32 | private void handle(ContributionRemindedDTO contribution) { 33 | logger.processingEvent(contribution); 34 | 35 | try { 36 | notificationService.notifyContributorToPay(contribution); 37 | logger.notificationSent(contribution.id(), contribution.contributorId()); 38 | } catch (Exception e) { 39 | logger.notificationFailed(contribution.id(), contribution.contributorId(), e); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/application/eventHandlers/FCMTokenUpdatedHandler.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.application.eventHandlers; 2 | 3 | import com.charity_hub.accounts.shared.AccountEventDto; 4 | import com.charity_hub.cases.internal.domain.contracts.INotificationService; 5 | import com.charity_hub.cases.internal.application.loggers.FCMTokenLogger; 6 | import com.charity_hub.shared.domain.IEventBus; 7 | import jakarta.annotation.PostConstruct; 8 | import org.springframework.stereotype.Service; 9 | 10 | @Service 11 | public class FCMTokenUpdatedHandler { 12 | private final IEventBus eventBus; 13 | private final INotificationService notificationService; 14 | private final FCMTokenLogger logger; 15 | 16 | public FCMTokenUpdatedHandler(IEventBus eventBus, INotificationService notificationService, FCMTokenLogger baseLogger) { 17 | this.eventBus = eventBus; 18 | this.notificationService = notificationService; 19 | this.logger = baseLogger; 20 | } 21 | 22 | @PostConstruct 23 | public void start() { 24 | logger.handlerRegistered(); 25 | eventBus.subscribe(this, AccountEventDto.FCMTokenUpdatedDTO.class, this::handle); 26 | } 27 | 28 | public void handle(AccountEventDto.FCMTokenUpdatedDTO event) { 29 | if (event.deviceFCMToken() == null) { 30 | logger.nullTokenReceived(); 31 | return; 32 | } 33 | 34 | logger.processingToken(event.deviceFCMToken()); 35 | 36 | try { 37 | notificationService.subscribeAccountToCaseUpdates(event.deviceFCMToken()); 38 | logger.tokenSubscribed(event.deviceFCMToken()); 39 | } catch (Exception e) { 40 | logger.tokenSubscriptionFailed(event.deviceFCMToken(), e); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/ledger/internal/application/eventHandlers/ContributionConfirmedHandler.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.ledger.internal.application.eventHandlers; 2 | 3 | import com.charity_hub.cases.shared.dtos.ContributionConfirmedDTO; 4 | import com.charity_hub.ledger.internal.domain.contracts.INotificationService; 5 | import com.charity_hub.ledger.internal.application.eventHandlers.loggers.ContributionConfirmedLogger; 6 | import com.charity_hub.shared.domain.IEventBus; 7 | import jakarta.annotation.PostConstruct; 8 | import org.springframework.stereotype.Service; 9 | 10 | @Service 11 | public class ContributionConfirmedHandler { 12 | private final IEventBus eventBus; 13 | private final INotificationService notificationService; 14 | private final ContributionConfirmedLogger logger; 15 | 16 | public ContributionConfirmedHandler( 17 | IEventBus eventBus, 18 | INotificationService notificationService, 19 | ContributionConfirmedLogger logger 20 | ) { 21 | this.eventBus = eventBus; 22 | this.notificationService = notificationService; 23 | this.logger = logger; 24 | } 25 | 26 | @PostConstruct 27 | public void start() { 28 | logger.handlerRegistered(); 29 | eventBus.subscribe(this, ContributionConfirmedDTO.class, this::handle); 30 | } 31 | 32 | private void handle(ContributionConfirmedDTO contribution) { 33 | logger.processingEvent(contribution); 34 | 35 | try { 36 | notificationService.notifyContributionConfirmed(contribution); 37 | logger.notificationSent(contribution.id(), contribution.contributorId()); 38 | } catch (Exception e) { 39 | logger.notificationFailed(contribution.id(), contribution.contributorId(), e); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/core/commands/RegisterNotificationToken/RegisterNotificationTokenHandler.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.core.commands.RegisterNotificationToken; 2 | 3 | import com.charity_hub.accounts.internal.core.contracts.IAccountRepo; 4 | import com.charity_hub.shared.abstractions.VoidCommandHandler; 5 | import com.charity_hub.shared.exceptions.NotFoundException; 6 | import org.springframework.stereotype.Service; 7 | 8 | @Service 9 | public class RegisterNotificationTokenHandler extends VoidCommandHandler { 10 | private final IAccountRepo accountRepo; 11 | 12 | public RegisterNotificationTokenHandler(IAccountRepo accountRepo) { 13 | this.accountRepo = accountRepo; 14 | } 15 | 16 | @Override 17 | public void handle( 18 | RegisterNotificationToken command 19 | ) { 20 | logger.info("Registering FCM token - UserId: {}, DeviceId: {}", 21 | command.userId(), command.deviceId()); 22 | 23 | var identity = accountRepo.getById(command.userId()) 24 | .orElseThrow(()-> { 25 | logger.warn("Account not found for FCM registration - UserId: {}", command.userId()); 26 | return new NotFoundException("User with Id " + command.userId() + " not found"); 27 | }); 28 | 29 | identity.registerFCMToken( 30 | command.deviceId(), 31 | command.fcmToken() 32 | ); 33 | 34 | accountRepo.save(identity); 35 | logger.info("FCM token registered successfully - UserId: {}, DeviceId: {}", 36 | command.userId(), command.deviceId()); 37 | } 38 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/shell/repositories/ReadAccountRepo.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.shell.repositories; 2 | 3 | import com.charity_hub.accounts.internal.shell.db.AccountEntity; 4 | import com.mongodb.client.MongoCollection; 5 | import com.mongodb.client.MongoDatabase; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.stereotype.Repository; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | import java.util.UUID; 13 | 14 | import static com.mongodb.client.model.Filters.eq; 15 | import static com.mongodb.client.model.Filters.in; 16 | 17 | @Repository 18 | public class ReadAccountRepo { 19 | private static final Logger logger = LoggerFactory.getLogger(ReadAccountRepo.class); 20 | 21 | public static final String ACCOUNTS_COLLECTION = "accounts"; 22 | private final MongoCollection collection; 23 | 24 | public ReadAccountRepo(MongoDatabase mongoDatabase) { 25 | this.collection = mongoDatabase.getCollection(ACCOUNTS_COLLECTION, AccountEntity.class); 26 | } 27 | 28 | public AccountEntity getById(UUID id) { 29 | logger.debug("Looking up account by id: {}", id); 30 | AccountEntity entity = collection.find(eq("accountId", id.toString())).first(); 31 | if (entity == null) { 32 | logger.debug("Account not found with id: {}", id); 33 | } 34 | return entity; 35 | } 36 | 37 | public List getAccountsByIds(List ids) { 38 | logger.debug("Looking up accounts by {} ids", ids.size()); 39 | List accounts = collection.find(in("accountId", ids)).into(new ArrayList<>()); 40 | logger.debug("Found {} accounts for {} ids", accounts.size(), ids.size()); 41 | return accounts; 42 | } 43 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/notifications/internal/FCMServiceStub.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.notifications.internal; 2 | 3 | import com.charity_hub.notifications.NotificationApi; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 7 | import org.springframework.stereotype.Component; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | * Stub implementation of NotificationApi for test mode. 13 | * This is used when firebase.test-mode=true to avoid requiring 14 | * real Firebase credentials during development/testing. 15 | */ 16 | @Component 17 | @ConditionalOnProperty( 18 | name = "firebase.test-mode", 19 | havingValue = "true" 20 | ) 21 | public class FCMServiceStub implements NotificationApi { 22 | private static final Logger logger = LoggerFactory.getLogger(FCMServiceStub.class); 23 | 24 | public FCMServiceStub() { 25 | logger.info("FCMServiceStub initialized - Firebase notifications are disabled in test mode"); 26 | } 27 | 28 | @Override 29 | public void notifyDevices(List tokens, String title, String body) { 30 | logger.debug("[TEST MODE] Would send notification to {} devices - Title: {}, Body: {}", 31 | tokens.size(), title, body); 32 | } 33 | 34 | @Override 35 | public void notifyTopicSubscribers(String topic, String event, Object extraJsonData, String title, String body) { 36 | logger.debug("[TEST MODE] Would send topic notification - Topic: {}, Event: {}, Title: {}", 37 | topic, event, title); 38 | } 39 | 40 | @Override 41 | public void subscribeToTopic(String topic, List tokens) { 42 | logger.debug("[TEST MODE] Would subscribe {} tokens to topic: {}", tokens.size(), topic); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/application/commands/CreateCase/CreateCaseHandler.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.application.commands.CreateCase; 2 | 3 | import com.charity_hub.cases.internal.domain.contracts.ICaseRepo; 4 | import com.charity_hub.cases.internal.domain.model.Case.Case; 5 | import com.charity_hub.cases.internal.domain.model.Case.NewCaseProbs; 6 | import com.charity_hub.cases.internal.domain.model.Case.Status; 7 | import com.charity_hub.shared.abstractions.CommandHandler; 8 | import org.springframework.stereotype.Component; 9 | 10 | @Component 11 | public class CreateCaseHandler extends CommandHandler { 12 | private final ICaseRepo caseRepo; 13 | 14 | public CreateCaseHandler(ICaseRepo caseRepo) { 15 | this.caseRepo = caseRepo; 16 | } 17 | 18 | @Override 19 | public CaseResponse handle(CreateCase command) { 20 | logger.info("Creating new case - Title: {}, Goal: {}, Publish: {}", 21 | command.title(), command.goal(), command.publish()); 22 | 23 | var newCase = Case.newCase( 24 | new NewCaseProbs( 25 | caseRepo.nextCaseCode(), 26 | command.title(), 27 | command.description(), 28 | command.goal(), 29 | command.publish() ? Status.OPENED : Status.DRAFT, 30 | command.acceptZakat(), 31 | command.documents() 32 | ) 33 | ); 34 | 35 | caseRepo.save(newCase); 36 | logger.info("Case created successfully - CaseCode: {}, Status: {}", 37 | newCase.getCaseCode().value(), command.publish() ? "OPENED" : "DRAFT"); 38 | return new CaseResponse(newCase.getCaseCode().value()); 39 | } 40 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/cases/internal/api/controllers/ContributionController.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.cases.internal.api.controllers; 2 | 3 | import com.charity_hub.cases.internal.application.commands.Contribute.Contribute; 4 | import com.charity_hub.cases.internal.application.commands.Contribute.ContributeHandler; 5 | import com.charity_hub.cases.internal.application.commands.Contribute.ContributeDefaultResponse; 6 | import com.charity_hub.cases.internal.api.dtos.ContributeRequest; 7 | import com.charity_hub.shared.auth.AccessTokenPayload; 8 | import org.springframework.http.ResponseEntity; 9 | import org.springframework.security.core.annotation.AuthenticationPrincipal; 10 | import org.springframework.web.bind.annotation.PathVariable; 11 | import org.springframework.web.bind.annotation.PostMapping; 12 | import org.springframework.web.bind.annotation.RequestBody; 13 | import org.springframework.web.bind.annotation.RestController; 14 | 15 | @RestController 16 | public class ContributionController { 17 | 18 | private final ContributeHandler contributeHandler; 19 | 20 | public ContributionController(ContributeHandler contributeHandler) { 21 | this.contributeHandler = contributeHandler; 22 | } 23 | 24 | @PostMapping("/v1/cases/{caseCode}/contributions") 25 | public ResponseEntity contribute( 26 | @PathVariable int caseCode, 27 | @AuthenticationPrincipal AccessTokenPayload accessTokenPayload, 28 | @RequestBody ContributeRequest contributeRequest) { 29 | 30 | var command = new Contribute( 31 | contributeRequest.amount(), 32 | accessTokenPayload.getUserId(), 33 | caseCode 34 | ); 35 | var response = contributeHandler.handle(command); 36 | return ResponseEntity.ok(response); 37 | } 38 | } -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/shell/api/controllers/RegisterFCMTokenController.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.shell.api.controllers; 2 | 3 | import com.charity_hub.accounts.internal.core.commands.RegisterNotificationToken.RegisterNotificationToken; 4 | import com.charity_hub.accounts.internal.core.commands.RegisterNotificationToken.RegisterNotificationTokenHandler; 5 | import com.charity_hub.accounts.internal.shell.api.dtos.RegisterFCMTokenRequest; 6 | import com.charity_hub.shared.auth.AccessTokenPayload; 7 | import org.springframework.http.HttpStatus; 8 | import org.springframework.http.ResponseEntity; 9 | import org.springframework.security.core.annotation.AuthenticationPrincipal; 10 | import org.springframework.web.bind.annotation.PostMapping; 11 | import org.springframework.web.bind.annotation.RequestBody; 12 | import org.springframework.web.bind.annotation.RestController; 13 | 14 | @RestController 15 | public class RegisterFCMTokenController { 16 | private final RegisterNotificationTokenHandler registerNotificationTokenHandler; 17 | 18 | public RegisterFCMTokenController(RegisterNotificationTokenHandler registerNotificationTokenHandler) { 19 | this.registerNotificationTokenHandler = registerNotificationTokenHandler; 20 | } 21 | 22 | @PostMapping("/v1/accounts/register-fcm-token") 23 | public ResponseEntity handle(@RequestBody RegisterFCMTokenRequest request, @AuthenticationPrincipal AccessTokenPayload accessTokenPayload) { 24 | 25 | RegisterNotificationToken command = 26 | new RegisterNotificationToken(request.fcmToken(), 27 | accessTokenPayload.getDeviceId(), 28 | accessTokenPayload.getUserId()); 29 | 30 | registerNotificationTokenHandler.handle(command); 31 | 32 | return ResponseEntity.status(HttpStatus.CREATED).build(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/charity_hub/accounts/internal/core/commands/ChangePermission/ChangePermissionHandler.java: -------------------------------------------------------------------------------- 1 | package com.charity_hub.accounts.internal.core.commands.ChangePermission; 2 | 3 | import com.charity_hub.accounts.internal.core.contracts.IAccountRepo; 4 | import com.charity_hub.shared.abstractions.VoidCommandHandler; 5 | import com.charity_hub.shared.exceptions.NotFoundException; 6 | import org.springframework.stereotype.Service; 7 | 8 | @Service 9 | public class ChangePermissionHandler extends VoidCommandHandler { 10 | private final IAccountRepo accountRepo; 11 | 12 | public ChangePermissionHandler(IAccountRepo accountRepo) { 13 | this.accountRepo = accountRepo; 14 | } 15 | 16 | @Override 17 | public void handle(ChangePermission command) { 18 | String action = command.shouldAdd() ? "ADD" : "REMOVE"; 19 | logger.info("Permission change requested - UserId: {}, Permission: {}, Action: {}", 20 | command.userId(), command.permission(), action); 21 | 22 | var identity = accountRepo.getById(command.userId()) 23 | .orElseThrow(()-> { 24 | logger.warn("Account not found for permission change - UserId: {}", command.userId()); 25 | return new NotFoundException("User with Id " + command.userId() + " not found"); 26 | }); 27 | 28 | if (command.shouldAdd()) { 29 | identity.addPermission(command.permission()); 30 | } else { 31 | identity.removePermission(command.permission()); 32 | } 33 | 34 | accountRepo.save(identity); 35 | logger.info("Permission change completed - UserId: {}, Permission: {}, Action: {}", 36 | command.userId(), command.permission(), action); 37 | } 38 | } --------------------------------------------------------------------------------