optimize firebase reads
This commit is contained in:
@@ -2,13 +2,21 @@ import {Injectable} from '@angular/core';
|
||||
import {DbService} from './db.service';
|
||||
import {firstValueFrom, Observable} from 'rxjs';
|
||||
import {Config} from './config';
|
||||
import {shareReplay} from 'rxjs/operators';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class ConfigService {
|
||||
private readonly config$ = this.db.doc$<Config>('global/config').pipe(
|
||||
shareReplay({
|
||||
bufferSize: 1,
|
||||
refCount: true,
|
||||
})
|
||||
);
|
||||
|
||||
public constructor(private db: DbService) {}
|
||||
|
||||
public get$ = (): Observable<Config | null> => this.db.doc$<Config>('global/config');
|
||||
public get$ = (): Observable<Config | null> => this.config$;
|
||||
public get = (): Promise<Config | null> => firstValueFrom(this.get$());
|
||||
}
|
||||
|
||||
@@ -8,15 +8,17 @@ import {shareReplay} from 'rxjs/operators';
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class GlobalSettingsService {
|
||||
private readonly settings$ = this.db.doc$<GlobalSettings>('global/static').pipe(
|
||||
shareReplay({
|
||||
bufferSize: 1,
|
||||
refCount: true,
|
||||
})
|
||||
);
|
||||
|
||||
public constructor(private db: DbService) {}
|
||||
|
||||
public get get$(): Observable<GlobalSettings | null> {
|
||||
return this.db.doc$<GlobalSettings>('global/static').pipe(
|
||||
shareReplay({
|
||||
bufferSize: 1,
|
||||
refCount: true,
|
||||
})
|
||||
);
|
||||
return this.settings$;
|
||||
}
|
||||
|
||||
public async set(data: Partial<GlobalSettings>): Promise<void> {
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {AngularFireAuth} from '@angular/fire/compat/auth';
|
||||
import {BehaviorSubject, firstValueFrom, Observable} from 'rxjs';
|
||||
import {filter, map, shareReplay, switchMap, tap} from 'rxjs/operators';
|
||||
import {filter, map, shareReplay, switchMap, take, tap} from 'rxjs/operators';
|
||||
import {User} from './user';
|
||||
import {DbService} from '../db.service';
|
||||
import {environment} from '../../../environments/environment';
|
||||
import {Router} from '@angular/router';
|
||||
import {ShowDataService} from '../../modules/shows/services/show-data.service';
|
||||
import {ShowSongDataService} from '../../modules/shows/services/show-song-data.service';
|
||||
import firebase from 'firebase/compat/app';
|
||||
|
||||
export interface SongUsageMigrationResult {
|
||||
usersProcessed: number;
|
||||
showsProcessed: number;
|
||||
showSongsProcessed: number;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
@@ -51,6 +58,7 @@ export class UserService {
|
||||
const aUser = await this.afAuth.signInWithEmailAndPassword(user, password);
|
||||
if (!aUser.user) return null;
|
||||
const dUser = await this.readUser(aUser.user.uid);
|
||||
if (!dUser) return null;
|
||||
await this.initSongUsage(dUser);
|
||||
this.iUser$.next(dUser);
|
||||
this.iUserId$.next(aUser.user.uid);
|
||||
@@ -81,8 +89,9 @@ export class UserService {
|
||||
const aUser = await this.afAuth.createUserWithEmailAndPassword(user, password);
|
||||
if (!aUser.user) return;
|
||||
const userId = aUser.user.uid;
|
||||
await this.db.doc('users/' + userId).set({name, chordMode: 'onlyFirst'});
|
||||
await this.db.doc('users/' + userId).set({name, chordMode: 'onlyFirst', songUsage: {}});
|
||||
const dUser = await this.readUser(aUser.user.uid);
|
||||
if (!dUser) return;
|
||||
this.iUser$.next(dUser);
|
||||
await this.router.navigateByUrl('/brand/new-user');
|
||||
}
|
||||
@@ -90,35 +99,71 @@ export class UserService {
|
||||
public incSongCount = (songId: string) => this.updateSongUsage(songId, 1);
|
||||
public decSongCount = (songId: string) => this.updateSongUsage(songId, -1);
|
||||
|
||||
public async rebuildSongUsage(): Promise<SongUsageMigrationResult> {
|
||||
const currentUser = await firstValueFrom(this.iUser$.pipe(take(1)));
|
||||
if (!currentUser || !this.hasAdminRole(currentUser.role)) {
|
||||
throw new Error('Admin role required to rebuild songUsage.');
|
||||
}
|
||||
|
||||
const [users, shows] = await Promise.all([firstValueFrom(this.users$), firstValueFrom(this.showDataService.listRaw$())]);
|
||||
const songUsageByUserId: Record<string, Record<string, number>> = {};
|
||||
|
||||
users.forEach(user => {
|
||||
songUsageByUserId[user.id] = {};
|
||||
});
|
||||
|
||||
let showSongsProcessed = 0;
|
||||
for (const show of shows) {
|
||||
const ownerId = show.owner;
|
||||
if (!ownerId) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const showSongs = await firstValueFrom(this.showSongDataService.list$(show.id));
|
||||
const usage = songUsageByUserId[ownerId] ?? {};
|
||||
songUsageByUserId[ownerId] = usage;
|
||||
|
||||
for (const showSong of showSongs) {
|
||||
showSongsProcessed += 1;
|
||||
usage[showSong.songId] = (usage[showSong.songId] ?? 0) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(
|
||||
users.map(user =>
|
||||
this.update$(user.id, {
|
||||
songUsage: songUsageByUserId[user.id] ?? {},
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
return {
|
||||
usersProcessed: users.length,
|
||||
showsProcessed: shows.length,
|
||||
showSongsProcessed,
|
||||
};
|
||||
}
|
||||
|
||||
private async updateSongUsage(songId: string, direction: number) {
|
||||
const user = await firstValueFrom(this.user$);
|
||||
if (!user) return null;
|
||||
|
||||
const songUsage = user?.songUsage ?? {};
|
||||
let currentSongCount = songUsage[songId];
|
||||
if (currentSongCount === null || currentSongCount === undefined) currentSongCount = 0;
|
||||
else currentSongCount = currentSongCount + direction;
|
||||
songUsage[songId] = Math.max(0, currentSongCount);
|
||||
|
||||
await this.update$(user.id, {songUsage});
|
||||
await this.db.doc<User>('users/' + user.id).update({
|
||||
[`songUsage.${songId}`]: firebase.firestore.FieldValue.increment(direction),
|
||||
});
|
||||
}
|
||||
|
||||
private async initSongUsage(user: User) {
|
||||
if (user.songUsage) return;
|
||||
await this.update$(user.id, {songUsage: {}});
|
||||
}
|
||||
|
||||
const shows = await firstValueFrom(this.showDataService.listRaw$());
|
||||
const myShows = shows.filter(show => show.owner === user.id);
|
||||
const songUsage: {[songId: string]: number} = {};
|
||||
for (const show of myShows) {
|
||||
const showSongs = await firstValueFrom(this.showSongDataService.list$(show.id));
|
||||
for (const showSong of showSongs) {
|
||||
const current = songUsage[showSong.songId] ?? 0;
|
||||
songUsage[showSong.songId] = current + 1;
|
||||
}
|
||||
private hasAdminRole(role: string | null | undefined): boolean {
|
||||
if (!role) {
|
||||
return false;
|
||||
}
|
||||
|
||||
await this.update$(user.id, {songUsage});
|
||||
return;
|
||||
return role.split(';').includes('admin');
|
||||
}
|
||||
|
||||
private readUser$ = (uid: string) => this.db.doc$<User>('users/' + uid);
|
||||
|
||||
Reference in New Issue
Block a user