| 36 | |
| 37 | @Injectable({providedIn: 'root'}) |
| 38 | export class BlockService { |
| 39 | /** Firebase functions instance, provided from the root. */ |
| 40 | private functions = inject(Functions); |
| 41 | /** Snackbar for displaying failure alerts. */ |
| 42 | private snackBar = inject(MatSnackBar); |
| 43 | /** Subject to trigger refreshing the blocked users list. */ |
| 44 | private refreshBlockedUsers$ = new BehaviorSubject<void>(undefined); |
| 45 | |
| 46 | /** Request all blocked users. */ |
| 47 | getBlockedUsers = this.asCallable<void, BlockedUserFromFirestore[]>('getBlockedUsers', true); |
| 48 | |
| 49 | /** All blocked users current blocked by the blocking service. */ |
| 50 | readonly blockedUsers = this.refreshBlockedUsers$.pipe( |
| 51 | switchMap(() => from(this.getBlockedUsers())), |
| 52 | map((blockedUsers) => |
| 53 | blockedUsers |
| 54 | .map((user) => ({ |
| 55 | ...user, |
| 56 | blockUntil: user.blockUntil === false ? false : new Date(user.blockUntil), |
| 57 | blockedOn: new Date(user.blockedOn), |
| 58 | })) |
| 59 | .sort((a, b) => (a.username.toLowerCase() > b.username.toLowerCase() ? 1 : -1)), |
| 60 | ), |
| 61 | shareReplay(1), |
| 62 | ); |
| 63 | |
| 64 | /** Request a user to be blocked. */ |
| 65 | block = this.asCallable<BlockUserParams, void>('blockUser'); |
| 66 | |
| 67 | /** Request a user to be unblocked. */ |
| 68 | unblock = this.asCallable<UnblockUserParams, void>('unblockUser'); |
| 69 | |
| 70 | /** Request a sync of all blocked users with the current Github blockings. */ |
| 71 | syncUsersFromGithub = this.asCallable<void, void>('syncUsersFromGithub'); |
| 72 | |
| 73 | /** Update the metadata for a blocked user. */ |
| 74 | update = this.asCallable<{username: string; data: Partial<BlockedUser>}, void>('updateUser'); |
| 75 | |
| 76 | /** |
| 77 | * Helper function to create a callable function that automatically refreshes the blocked users list. |
| 78 | * @param callableName The name of the callable function to create. |
| 79 | * @returns A function that can be called to invoke the callable function. |
| 80 | */ |
| 81 | private asCallable<T, R>( |
| 82 | callableName: string, |
| 83 | skipRefresh = false, |
| 84 | ): (callableArg: T) => Promise<R> { |
| 85 | return async (callableArg: T) => { |
| 86 | try { |
| 87 | const result = await httpsCallable<T, R>(this.functions, callableName)(callableArg); |
| 88 | if (!skipRefresh) { |
| 89 | this.refreshBlockedUsers$.next(); |
| 90 | } |
| 91 | return result.data; |
| 92 | } catch (error) { |
| 93 | const message = error instanceof Error ? error.message : 'Unknown error'; |
| 94 | this.snackBar.open(`Failed to execute ${callableName}: ${message}`, 'Dismiss', { |
| 95 | duration: 5000, |
nothing calls this directly
no test coverage detected