Skip to content

Commit 552863b

Browse files
committed
add event-driven architecture with EventBus, domain events & repository listeners
1 parent 227d946 commit 552863b

52 files changed

Lines changed: 721 additions & 146 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

projects/social_platform/src/app/api/auth/facades/ui/auth-ui-info.service.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/** @format */
22

3-
import { inject, Injectable, signal } from "@angular/core";
3+
import { computed, inject, Injectable, signal } from "@angular/core";
44
import { FormBuilder, FormGroup, Validators } from "@angular/forms";
55
import { ValidationService } from "@corelib";
66
import dayjs from "dayjs";
@@ -10,7 +10,12 @@ import {
1010
} from "projects/social_platform/src/app/domain/auth/results/login.result";
1111
import { PasswordError } from "projects/social_platform/src/app/domain/auth/results/password.result";
1212
import { RegisterError } from "projects/social_platform/src/app/domain/auth/results/register.result";
13-
import { AsyncState, initial } from "projects/social_platform/src/app/domain/shared/async-state";
13+
import {
14+
AsyncState,
15+
initial,
16+
isFailure,
17+
isLoading,
18+
} from "projects/social_platform/src/app/domain/shared/async-state";
1419

1520
@Injectable()
1621
export class AuthUIInfoService {
@@ -22,17 +27,27 @@ export class AuthUIInfoService {
2227
readonly showPassword = signal<boolean>(false);
2328

2429
readonly login$ = signal<AsyncState<LoginResult, LoginError>>(initial());
30+
readonly loginIsSubmitting = computed(() => isLoading(this.login$()));
31+
readonly errorWrongAuth = computed(() => isFailure(this.login$()));
2532

2633
// password
2734
readonly credsSubmitInitiated = signal<boolean>(false);
2835
readonly password$ = signal<AsyncState<void, PasswordError>>(initial());
36+
readonly isSubmitting = computed(() => isLoading(this.password$()));
37+
readonly errorServer = computed(() => {
38+
const s = this.password$();
39+
return isFailure(s) && s.error.kind === "server_error";
40+
});
41+
readonly errorRequest = computed(() => isFailure(this.password$()));
2942

3043
// register
3144
readonly registerAgreement = signal<boolean>(false);
3245
readonly ageAgreement = signal<boolean>(false);
3346
readonly infoSubmitInitiated = signal<boolean>(false);
3447

3548
readonly register$ = signal<AsyncState<void, RegisterError>>(initial());
49+
readonly registerIsSubmitting = computed(() => isLoading(this.register$()));
50+
readonly isUserCreationModalError = computed(() => isFailure(this.register$()));
3651
readonly step = signal<"credentials" | "info">("credentials");
3752

3853
// login

projects/social_platform/src/app/api/invite/use-cases/accept-invite.use-case.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,21 @@
22

33
import { inject, Injectable } from "@angular/core";
44
import { InviteRepositoryPort } from "../../../domain/invite/ports/invite.repository.port";
5-
import { catchError, map, Observable, of } from "rxjs";
5+
import { catchError, map, Observable, of, tap } from "rxjs";
66
import { fail, ok, Result } from "../../../domain/shared/result.type";
7+
import { EventBus } from "../../../domain/shared/event-bus";
8+
import { acceptInvite } from "../../../domain/invite/events/accept-invite.event";
79

810
@Injectable({ providedIn: "root" })
911
export class AcceptInviteUseCase {
1012
private readonly inviteRepositoryPort = inject(InviteRepositoryPort);
13+
private readonly eventBus = inject(EventBus);
1114

1215
execute(inviteId: number): Observable<Result<void, { kind: "unknown" }>> {
1316
return this.inviteRepositoryPort.acceptInvite(inviteId).pipe(
17+
tap(invite =>
18+
this.eventBus.emit(acceptInvite(invite.id, invite.project.id, invite.user.id, invite.role))
19+
),
1420
map(() => ok<void>(undefined)),
1521
catchError(() => of(fail({ kind: "unknown" as const })))
1622
);

projects/social_platform/src/app/api/invite/use-cases/reject-invite.use-case.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,19 @@
22

33
import { inject, Injectable } from "@angular/core";
44
import { InviteRepositoryPort } from "../../../domain/invite/ports/invite.repository.port";
5-
import { catchError, map, Observable, of } from "rxjs";
5+
import { catchError, map, Observable, of, tap } from "rxjs";
66
import { fail, ok, Result } from "../../../domain/shared/result.type";
7+
import { EventBus } from "../../../domain/shared/event-bus";
8+
import { rejectInvite } from "../../../domain/invite/events/reject-invite.event";
79

810
@Injectable({ providedIn: "root" })
911
export class RejectInviteUseCase {
1012
private readonly inviteRepositoryPort = inject(InviteRepositoryPort);
13+
private readonly eventBus = inject(EventBus);
1114

1215
execute(inviteId: number): Observable<Result<void, { kind: "unknown" }>> {
1316
return this.inviteRepositoryPort.rejectInvite(inviteId).pipe(
17+
tap(invite => this.eventBus.emit(rejectInvite(invite.id, invite.project.id, invite.user.id))),
1418
map(() => ok<void>(undefined)),
1519
catchError(() => of(fail({ kind: "unknown" as const })))
1620
);

projects/social_platform/src/app/api/invite/use-cases/revoke-invite.use-case.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,21 @@
22

33
import { inject, Injectable } from "@angular/core";
44
import { InviteRepositoryPort } from "../../../domain/invite/ports/invite.repository.port";
5-
import { catchError, map, Observable, of } from "rxjs";
5+
import { catchError, map, Observable, of, tap } from "rxjs";
66
import { fail, ok, Result } from "../../../domain/shared/result.type";
7+
import { EventBus } from "../../../domain/shared/event-bus";
8+
import { revokeInvite } from "../../../domain/invite/events/revoke-invite.event";
79

810
@Injectable({ providedIn: "root" })
911
export class RevokeInviteUseCase {
1012
private readonly inviteRepositoryPort = inject(InviteRepositoryPort);
13+
private readonly eventBus = inject(EventBus);
1114

1215
execute(
1316
invitationId: number
1417
): Observable<Result<void, { kind: "revoke_invite_error"; cause?: unknown }>> {
1518
return this.inviteRepositoryPort.revokeInvite(invitationId).pipe(
19+
tap(invite => this.eventBus.emit(revokeInvite(invite.id, invite.project.id, invite.user.id))),
1620
map(() => ok<void>(undefined)),
1721
catchError(error => of(fail({ kind: "revoke_invite_error" as const, cause: error })))
1822
);

projects/social_platform/src/app/api/office/facades/office-info.service.ts

Lines changed: 35 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/** @format */
22

33
import { inject, Injectable } from "@angular/core";
4-
import { map, Subject, takeUntil } from "rxjs";
4+
import { map, Subject, takeUntil, tap } from "rxjs";
55
import { ActivatedRoute, Router } from "@angular/router";
66
import { ChatService } from "../../chat/chat.service";
77
import { Invite } from "../../../domain/invite/invite.model";
@@ -52,20 +52,25 @@ export class OfficeInfoService {
5252
}
5353

5454
private initializationNavItems(): void {
55-
this.authRepository.profile.pipe(takeUntil(this.destroy$)).subscribe(profile => {
56-
this.officeUIInfoService.applyCreateNavItems(profile.id);
57-
58-
if (!profile?.doesCompleted()) {
59-
this.router
60-
.navigateByUrl("/office/onboarding")
61-
.then(() => this.logger.debug("Route changed from OfficeComponent"));
62-
} else if (
63-
profile?.verificationDate === null &&
64-
localStorage.getItem("waitVerificationAccepted") !== "true"
65-
) {
66-
this.officeUIInfoService.applyOpenVerificationModal();
67-
}
68-
});
55+
this.authRepository.profile
56+
.pipe(
57+
tap(profile => {
58+
this.officeUIInfoService.applyCreateNavItems(profile.id);
59+
60+
if (!profile?.doesCompleted()) {
61+
this.router
62+
.navigateByUrl("/office/onboarding")
63+
.then(() => this.logger.debug("Route changed from OfficeComponent"));
64+
} else if (
65+
profile?.verificationDate === null &&
66+
localStorage.getItem("waitVerificationAccepted") !== "true"
67+
) {
68+
this.officeUIInfoService.applyOpenVerificationModal();
69+
}
70+
}),
71+
takeUntil(this.destroy$)
72+
)
73+
.subscribe();
6974
}
7075

7176
private initializationStatus(): void {
@@ -125,9 +130,8 @@ export class OfficeInfoService {
125130

126131
this.acceptInviteUseCase
127132
.execute(inviteId)
128-
.pipe(takeUntil(this.destroy$))
129-
.subscribe({
130-
next: result => {
133+
.pipe(
134+
tap(result => {
131135
if (!result.ok) {
132136
this.officeUIInfoService.applyOpenInviteErrorModal();
133137
return;
@@ -138,18 +142,23 @@ export class OfficeInfoService {
138142
this.router
139143
.navigateByUrl(`/office/projects/${invite.project.id}`)
140144
.then(() => this.logger.debug("Route changed from SidebarComponent"));
141-
},
142-
});
145+
}),
146+
takeUntil(this.destroy$)
147+
)
148+
.subscribe();
143149
}
144150

145151
onLogout() {
146152
this.authRepository
147153
.logout()
148-
.pipe(takeUntil(this.destroy$))
149-
.subscribe(() =>
150-
this.router
151-
.navigateByUrl("/auth")
152-
.then(() => this.logger.debug("Route changed from OfficeComponent"))
153-
);
154+
.pipe(
155+
tap(() => {
156+
this.router
157+
.navigateByUrl("/auth")
158+
.then(() => this.logger.debug("Route changed from OfficeComponent"));
159+
}),
160+
takeUntil(this.destroy$)
161+
)
162+
.subscribe();
154163
}
155164
}

projects/social_platform/src/app/api/onboarding/facades/stages/onboarding-stage-one-info.service.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ export class OnboardingStageOneInfoService {
3232

3333
private stageForm = this.onboardingStageOneUIInfoService.stageForm;
3434

35-
private readonly stageSubmitting = this.onboardingUIInfoService.stageSubmitting;
36-
private readonly skipSubmitting = this.onboardingUIInfoService.skipSubmitting;
35+
private readonly stageSubmitting = this.onboardingUIInfoService.stageSubmitting$;
36+
private readonly skipSubmitting = this.onboardingUIInfoService.skipSubmitting$;
3737

3838
readonly inlineSpecializations = this.searchesService.inlineSpecs;
3939

projects/social_platform/src/app/api/onboarding/facades/stages/onboarding-stage-three-info.service.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/** @format */
22

33
import { inject, Injectable } from "@angular/core";
4-
import { concatMap, Subject, take, takeUntil } from "rxjs";
4+
import { concatMap, Subject, take, takeUntil, tap } from "rxjs";
55
import { OnboardingService } from "../../onboarding.service";
66
import { Router } from "@angular/router";
77
import { OnboardingUIInfoService } from "./ui/onboarding-ui-info.service";
@@ -24,7 +24,7 @@ export class OnboardingStageThreeInfoService {
2424
private readonly userRole = this.onboardingStageThreeUIInfoService.userRole;
2525

2626
private readonly stageTouched = this.onboardingUIInfoService.stageTouched;
27-
private readonly stageSubmitting = this.onboardingUIInfoService.stageSubmitting;
27+
private readonly stageSubmitting = this.onboardingUIInfoService.stageSubmitting$;
2828

2929
destroy(): void {
3030
this.destroy$.next();
@@ -49,12 +49,13 @@ export class OnboardingStageThreeInfoService {
4949
.updateProfile({ userType: this.userRole() })
5050
.pipe(
5151
concatMap(() => this.authRepository.updateOnboardingStage(null)),
52+
tap(() => {
53+
this.router
54+
.navigateByUrl("/office")
55+
.then(() => this.logger.debug("Route changed from OnboardingStageTwo"));
56+
}),
5257
takeUntil(this.destroy$)
5358
)
54-
.subscribe(() => {
55-
this.router
56-
.navigateByUrl("/office")
57-
.then(() => this.logger.debug("Route changed from OnboardingStageTwo"));
58-
});
59+
.subscribe();
5960
}
6061
}

projects/social_platform/src/app/api/onboarding/facades/stages/onboarding-stage-two-info.service.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ export class OnboardingStageTwoInfoService {
3030

3131
private readonly stageForm = this.onboardingStageTwoUIInfoService.stageForm;
3232

33-
private readonly stageSubmitting = this.onboardingUIInfoService.stageSubmitting;
34-
private readonly skipSubmitting = this.onboardingUIInfoService.skipSubmitting;
33+
private readonly stageSubmitting = this.onboardingUIInfoService.stageSubmitting$;
34+
private readonly skipSubmitting = this.onboardingUIInfoService.skipSubmitting$;
3535

3636
destroy(): void {
3737
this.destroy$.next();

projects/social_platform/src/app/api/onboarding/facades/stages/onboarding-stage-zero-info.service.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ export class OnboardingStageZeroInfoService {
3232
private readonly workExperience = this.onboardingStageZeroUIInfoService.workExperience;
3333
private readonly userLanguages = this.onboardingStageZeroUIInfoService.userLanguages;
3434

35-
private readonly stageSubmitting = this.onboardingUIInfoService.stageSubmitting;
36-
private readonly skipSubmitting = this.onboardingUIInfoService.skipSubmitting;
35+
private readonly stageSubmitting = this.onboardingUIInfoService.stageSubmitting$;
36+
private readonly skipSubmitting = this.onboardingUIInfoService.skipSubmitting$;
3737

3838
initializationStageZero(): void {
3939
this.authRepository.profile.pipe(takeUntil(this.destroy$)).subscribe(p => {
Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
/** @format */
22

3-
import { Injectable, signal } from "@angular/core";
4-
import { AsyncState, initial } from "../../../../../domain/shared/async-state";
3+
import { computed, Injectable, signal } from "@angular/core";
4+
import { AsyncState, initial, isLoading } from "../../../../../domain/shared/async-state";
55

66
@Injectable()
77
export class OnboardingUIInfoService {
8-
readonly stageSubmitting = signal<AsyncState<void>>(initial());
9-
readonly skipSubmitting = signal<AsyncState<void>>(initial());
8+
readonly stageSubmitting$ = signal<AsyncState<void>>(initial());
9+
readonly stageSubmitting = computed(() => isLoading(this.stageSubmitting$()));
10+
11+
readonly skipSubmitting$ = signal<AsyncState<void>>(initial());
12+
readonly skipSubmitting = computed(() => isLoading(this.skipSubmitting$()));
13+
1014
readonly stageTouched = signal<boolean>(false);
1115
}

0 commit comments

Comments
 (0)