From 0954fae39953ad559e20f778dacb6c3ac202234d Mon Sep 17 00:00:00 2001 From: Milad Raeisi Date: Thu, 31 Oct 2024 08:27:44 +0400 Subject: [PATCH] Add multi-user support to BookmarkService --- .../components/bookmark/bookmark.component.ts | 56 +++---- .../components/explore/explore.component.ts | 15 +- src/app/services/bookmark.service.ts | 141 +++++++++++++++--- 3 files changed, 150 insertions(+), 62 deletions(-) diff --git a/src/app/components/bookmark/bookmark.component.ts b/src/app/components/bookmark/bookmark.component.ts index 762691d..536308e 100644 --- a/src/app/components/bookmark/bookmark.component.ts +++ b/src/app/components/bookmark/bookmark.component.ts @@ -16,7 +16,7 @@ import { RouterLink } from '@angular/router'; import { BookmarkService } from 'app/services/bookmark.service'; import { Project } from 'app/interface/project.interface'; import { StorageService } from 'app/services/storage.service'; -import { Observable, Subject, Subscription, takeUntil } from 'rxjs'; +import { Observable, Subject, takeUntil } from 'rxjs'; @Component({ selector: 'app-bookmark', @@ -39,59 +39,52 @@ import { Observable, Subject, Subscription, takeUntil } from 'rxjs'; PercentPipe, I18nPluralPipe, CommonModule, - -], + ], templateUrl: './bookmark.component.html', - styleUrl: './bookmark.component.scss' + styleUrls: ['./bookmark.component.scss'] }) export class BookmarkComponent implements OnInit, OnDestroy { savedProjects: Project[] = []; bookmarks$: Observable; - private _unsubscribeAll: Subject = new Subject(); + private _unsubscribeAll = new Subject(); constructor( private _bookmarkService: BookmarkService, private _storageService: StorageService, - private _changeDetectorRef: ChangeDetectorRef ) { this.bookmarks$ = this._bookmarkService.bookmarks$; } - ngOnInit(): void { - this.loadBookmarkedProjects(); + async ngOnInit(): Promise { + await this._bookmarkService.initializeForCurrentUser(); // Clear previous bookmarks and load current user's bookmarks + await this.loadBookmarkedProjects(); this.subscribeToBookmarkChanges(); } - - private loadBookmarkedProjects(): void { - const bookmarkIds = this._bookmarkService.getBookmarks(); - this._storageService.getProjectsByIds(bookmarkIds).then((projects: Project[]) => { - this.savedProjects = projects; - this.fetchMetadataForProjects(this.savedProjects); - }); - } - - trackByFn(index: number, item: Project): string | number { return item.projectIdentifier || index; } + private async loadBookmarkedProjects(): Promise { + const bookmarkIds = await this._bookmarkService.getBookmarks(); + const projects = await this._storageService.getProjectsByIds(bookmarkIds); + this.savedProjects = projects; + this.fetchMetadataForProjects(this.savedProjects); // Fetch metadata for loaded projects + } + private subscribeToBookmarkChanges(): void { - this.bookmarks$.pipe(takeUntil(this._unsubscribeAll)).subscribe((bookmarkIds: string[]) => { - this._storageService.getProjectsByIds(bookmarkIds).then((projects: Project[]) => { - this.savedProjects = projects; - this.fetchMetadataForProjects(this.savedProjects); - }); + this.bookmarks$.pipe(takeUntil(this._unsubscribeAll)).subscribe(async (bookmarkIds) => { + const projects = await this._storageService.getProjectsByIds(bookmarkIds); + this.savedProjects = projects; + this.fetchMetadataForProjects(this.savedProjects); // Fetch metadata for updated projects }); } - private fetchMetadataForProjects(projects: Project[]): void { projects.forEach(project => { this._storageService.getProfile(project.nostrPubKey).then(profileMetadata => { if (profileMetadata) { this.updateProjectMetadata(project, profileMetadata); - this._changeDetectorRef.detectChanges(); } }); }); @@ -104,18 +97,15 @@ export class BookmarkComponent implements OnInit, OnDestroy { project.banner = metadata.banner || project.banner; } - toggleBookmark(projectId: string): void { - if (this._bookmarkService.isBookmarked(projectId)) { - this._bookmarkService.removeBookmark(projectId); + async toggleBookmark(projectId: string): Promise { + const isBookmarked = await this._bookmarkService.isBookmarked(projectId); + if (isBookmarked) { + await this._bookmarkService.removeBookmark(projectId); } else { - this._bookmarkService.addBookmark(projectId); + await this._bookmarkService.addBookmark(projectId); } } - isProjectBookmarked(projectId: string): boolean { - return this._bookmarkService.isBookmarked(projectId); - } - ngOnDestroy(): void { this._unsubscribeAll.next(null); this._unsubscribeAll.complete(); diff --git a/src/app/components/explore/explore.component.ts b/src/app/components/explore/explore.component.ts index f421315..863afbb 100644 --- a/src/app/components/explore/explore.component.ts +++ b/src/app/components/explore/explore.component.ts @@ -264,18 +264,19 @@ export class ExploreComponent implements OnInit, OnDestroy { this.showCloseSearchButton = false; } - toggleBookmark(projectId: string): void { - if (this._bookmarkService.isBookmarked(projectId)) { - this._bookmarkService.removeBookmark(projectId); + async toggleBookmark(projectId: string): Promise { + const isBookmarked = await this._bookmarkService.isBookmarked(projectId); + if (isBookmarked) { + await this._bookmarkService.removeBookmark(projectId); } else { - this._bookmarkService.addBookmark(projectId); + await this._bookmarkService.addBookmark(projectId); } } - isProjectBookmarked(projectId: string): boolean { - return this._bookmarkService.isBookmarked(projectId); + async isProjectBookmarked(projectId: string): Promise { + return await this._bookmarkService.isBookmarked(projectId); } - + ngOnDestroy(): void { this._unsubscribeAll.next(null); this._unsubscribeAll.complete(); diff --git a/src/app/services/bookmark.service.ts b/src/app/services/bookmark.service.ts index 0dc34c5..c5ff199 100644 --- a/src/app/services/bookmark.service.ts +++ b/src/app/services/bookmark.service.ts @@ -1,51 +1,148 @@ import { Injectable } from '@angular/core'; import { BehaviorSubject, Observable } from 'rxjs'; +import { SignerService } from './signer.service'; @Injectable({ providedIn: 'root', }) export class BookmarkService { - private readonly STORAGE_KEY = 'bookmarkedProjects'; + private readonly STORAGE_KEY = 'userBookmarkedProjects'; + private bookmarksSubject = new BehaviorSubject([]); + public bookmarks$ = this.bookmarksSubject.asObservable(); + private currentUserPubKey: string | null = null; - - private bookmarksSubject: BehaviorSubject = new BehaviorSubject(this.getBookmarks()); - public bookmarks$: Observable = this.bookmarksSubject.asObservable(); - - constructor() { + constructor(private _signerService: SignerService) { + // Listen to storage changes to keep bookmarks updated across tabs window.addEventListener('storage', (event) => { if (event.key === this.STORAGE_KEY) { - - this.bookmarksSubject.next(this.getBookmarks()); + this.refreshBookmarksForCurrentUser(); } }); } + // Initialize bookmarks for the current user + async initializeForCurrentUser(): Promise { + this.clearBookmarks(); // Clear any previous bookmarks + this.currentUserPubKey = await this._signerService.getPublicKey(); // Get current user's public key + + if (this.currentUserPubKey) { + await this.loadBookmarksForCurrentUser(); + } + } + + // Clear current bookmarks to prevent showing previous user's data + private clearBookmarks(): void { + this.bookmarksSubject.next([]); + } + + // Load bookmarks for the current user based on their public key + private async loadBookmarksForCurrentUser(): Promise { + if (!this.currentUserPubKey) return; - getBookmarks(): string[] { + const allBookmarks = this.getUserBookmarks(); + const userBookmarks = allBookmarks[this.currentUserPubKey] || []; + this.bookmarksSubject.next(userBookmarks); // Set bookmarks for current user + } + + // Fetch all user bookmarks from localStorage + private getUserBookmarks(): { [pubKey: string]: string[] } { const bookmarks = localStorage.getItem(this.STORAGE_KEY); - return bookmarks ? JSON.parse(bookmarks) : []; + return bookmarks ? JSON.parse(bookmarks) : {}; + } + + // Save bookmarks for the current user to localStorage + private saveUserBookmarks(bookmarks: { [pubKey: string]: string[] }): void { + localStorage.setItem(this.STORAGE_KEY, JSON.stringify(bookmarks)); } + // Add a bookmark for the current user + async addBookmark(projectId: string): Promise { + if (!this.currentUserPubKey) { + this.currentUserPubKey = await this._signerService.getPublicKey(); + if (!this.currentUserPubKey) { + console.warn('No public key found for the current user.'); + return; + } + } - addBookmark(projectId: string): void { - const bookmarks = this.getBookmarks(); - if (!bookmarks.includes(projectId)) { - bookmarks.push(projectId); - localStorage.setItem(this.STORAGE_KEY, JSON.stringify(bookmarks)); - this.bookmarksSubject.next(bookmarks); + const allBookmarks = this.getUserBookmarks(); + const userBookmarks = allBookmarks[this.currentUserPubKey] || []; + + if (!userBookmarks.includes(projectId)) { + userBookmarks.push(projectId); + allBookmarks[this.currentUserPubKey] = userBookmarks; + this.saveUserBookmarks(allBookmarks); + this.bookmarksSubject.next(userBookmarks); } } + // Remove a bookmark for the current user + async removeBookmark(projectId: string): Promise { + if (!this.currentUserPubKey) { + this.currentUserPubKey = await this._signerService.getPublicKey(); + if (!this.currentUserPubKey) { + console.warn('No public key found for the current user.'); + return; + } + } + + const allBookmarks = this.getUserBookmarks(); + const userBookmarks = allBookmarks[this.currentUserPubKey] || []; + const updatedBookmarks = userBookmarks.filter(id => id !== projectId); - removeBookmark(projectId: string): void { - const bookmarks = this.getBookmarks(); - const updatedBookmarks = bookmarks.filter(id => id !== projectId); - localStorage.setItem(this.STORAGE_KEY, JSON.stringify(updatedBookmarks)); + allBookmarks[this.currentUserPubKey] = updatedBookmarks; + this.saveUserBookmarks(allBookmarks); this.bookmarksSubject.next(updatedBookmarks); } + // Check if a project is bookmarked by the current user + async isBookmarked(projectId: string): Promise { + if (!this.currentUserPubKey) { + this.currentUserPubKey = await this._signerService.getPublicKey(); + if (!this.currentUserPubKey) { + console.warn('No public key found for the current user.'); + return false; + } + } + + const userBookmarks = this.getUserBookmarks()[this.currentUserPubKey] || []; + return userBookmarks.includes(projectId); + } + + // Retrieve all bookmarks for the current user + async getBookmarks(): Promise { + if (!this.currentUserPubKey) { + this.currentUserPubKey = await this._signerService.getPublicKey(); + if (!this.currentUserPubKey) { + console.warn('No public key found for the current user.'); + return []; + } + } - isBookmarked(projectId: string): boolean { - return this.getBookmarks().includes(projectId); + const allBookmarks = this.getUserBookmarks(); + return allBookmarks[this.currentUserPubKey] || []; + } + + // Remove all bookmarks for the current user + async removeAllBookmarks(): Promise { + if (!this.currentUserPubKey) { + this.currentUserPubKey = await this._signerService.getPublicKey(); + if (!this.currentUserPubKey) { + console.warn('No public key found for the current user.'); + return; + } + } + + const allBookmarks = this.getUserBookmarks(); + allBookmarks[this.currentUserPubKey] = []; + this.saveUserBookmarks(allBookmarks); + this.bookmarksSubject.next([]); + } + + // Refresh bookmarks for the current user when the storage event occurs + private refreshBookmarksForCurrentUser(): void { + if (this.currentUserPubKey) { + this.loadBookmarksForCurrentUser(); + } } }