|
| 1 | +import Service, { inject as service } from '@ember/service'; |
| 2 | +import LocalStorageService from 'codecrafters-frontend/services/local-storage'; |
| 3 | +import { tracked } from '@glimmer/tracking'; |
| 4 | +import { action } from '@ember/object'; |
| 5 | +import type AuthenticatorService from './authenticator'; |
| 6 | +import LeaderboardEntryModel from 'codecrafters-frontend/models/leaderboard-entry'; |
| 7 | +import type Store from '@ember-data/store'; |
| 8 | + |
| 9 | +class StoredData { |
| 10 | + languageSlugs: string[]; |
| 11 | + storedAt: Date; |
| 12 | + |
| 13 | + constructor(languageSlugs: string[], storedAt?: Date) { |
| 14 | + this.languageSlugs = languageSlugs; |
| 15 | + this.storedAt = storedAt || new Date(); |
| 16 | + } |
| 17 | + |
| 18 | + static fromJSON(json: string): StoredData { |
| 19 | + const { languageSlugs, storedAt } = JSON.parse(json); |
| 20 | + |
| 21 | + return new StoredData(languageSlugs, storedAt); |
| 22 | + } |
| 23 | + |
| 24 | + toJSON(): string { |
| 25 | + return JSON.stringify({ |
| 26 | + languageSlugs: this.languageSlugs, |
| 27 | + storedAt: this.storedAt, |
| 28 | + }); |
| 29 | + } |
| 30 | +} |
| 31 | + |
| 32 | +export default class PreferredLanguageLeaderboardService extends Service { |
| 33 | + static STORAGE_KEY = 'preferred-language-leaderboard-v1'; |
| 34 | + |
| 35 | + @service declare authenticator: AuthenticatorService; |
| 36 | + @service declare localStorage: LocalStorageService; |
| 37 | + @service declare store: Store; |
| 38 | + |
| 39 | + // We default to Rust since it's the first track in the catalog |
| 40 | + @tracked preferredLanguageSlugs: string[] = []; |
| 41 | + |
| 42 | + // This is used when a user clicks on the Leaderboard link in the header |
| 43 | + get defaultLanguageSlug(): string { |
| 44 | + return this.preferredLanguageSlugs[0] || 'rust'; |
| 45 | + } |
| 46 | + |
| 47 | + @action |
| 48 | + async onBoot(): Promise<void> { |
| 49 | + if (!this.authenticator.isAuthenticated) { |
| 50 | + return; |
| 51 | + } |
| 52 | + |
| 53 | + const serializedStoredData = this.localStorage.getItem(PreferredLanguageLeaderboardService.STORAGE_KEY); |
| 54 | + |
| 55 | + if (!serializedStoredData) { |
| 56 | + return; |
| 57 | + } |
| 58 | + |
| 59 | + // Let's use the latest value we have from |
| 60 | + const storedData = StoredData.fromJSON(serializedStoredData); |
| 61 | + this.preferredLanguageSlugs = storedData.languageSlugs; |
| 62 | + |
| 63 | + // Re-fetch if data is more than 6 hours old |
| 64 | + if (storedData.storedAt.getTime() < Date.now() - 1000 * 60 * 60 * 6) { |
| 65 | + await this.refresh(); |
| 66 | + } |
| 67 | + } |
| 68 | + |
| 69 | + async refresh(): Promise<void> { |
| 70 | + await this.authenticator.authenticate(); |
| 71 | + |
| 72 | + const userLeaderboardEntries = (await this.store |
| 73 | + .createRecord('leaderboard-entry') |
| 74 | + .fetchForCurrentUser({ include: 'leaderboard,leaderboard.language,user' })) as unknown as LeaderboardEntryModel[]; |
| 75 | + |
| 76 | + this.preferredLanguageSlugs = userLeaderboardEntries |
| 77 | + .filter((entry) => entry.score > 0) |
| 78 | + .sort((a, b) => b.score - a.score) |
| 79 | + .map((entry) => entry.leaderboard.language.slug) |
| 80 | + .slice(0, 3); |
| 81 | + |
| 82 | + this.localStorage.setItem(PreferredLanguageLeaderboardService.STORAGE_KEY, new StoredData(this.preferredLanguageSlugs, new Date()).toJSON()); |
| 83 | + } |
| 84 | +} |
0 commit comments