Files
kashilo/js/services/favorites.js

187 lines
4.8 KiB
JavaScript

import { directus } from './directus.js'
import { auth } from './auth.js'
const ANON_KEY = 'kashilo_favorites'
class FavoritesService {
constructor() {
this.ids = new Set()
this.loaded = false
this.syncing = false
this.listeners = []
}
init() {
// Migrate old localStorage key
const oldFavs = localStorage.getItem('favorites')
if (oldFavs && !localStorage.getItem(ANON_KEY)) {
localStorage.setItem(ANON_KEY, oldFavs)
localStorage.removeItem('favorites')
}
this.ids = new Set(this.getAnonIds())
this.loaded = true
auth.subscribe((loggedIn) => {
if (loggedIn) {
this.mergeOnLogin()
} else {
this.saveAnonIds()
this.loaded = true
this.notify()
}
})
if (auth.isLoggedIn()) {
this.mergeOnLogin()
}
}
subscribe(cb) {
this.listeners.push(cb)
return () => {
this.listeners = this.listeners.filter(l => l !== cb)
}
}
notify() {
this.listeners.forEach(cb => cb(this.ids))
window.dispatchEvent(new CustomEvent('favorites-changed'))
}
// localStorage helpers
getAnonIds() {
try {
return JSON.parse(localStorage.getItem(ANON_KEY) || '[]')
} catch {
return []
}
}
saveAnonIds() {
localStorage.setItem(ANON_KEY, JSON.stringify([...this.ids]))
}
// Public API
isFavorite(id) {
return this.ids.has(id)
}
getAll() {
return [...this.ids]
}
async toggle(id) {
const wasFavorite = this.ids.has(id)
// Optimistic update
if (wasFavorite) {
this.ids.delete(id)
} else {
this.ids.add(id)
}
this.notify()
if (auth.isLoggedIn()) {
try {
if (wasFavorite) {
await this.removeRemote(id)
} else {
await this.addRemote(id)
}
} catch (e) {
// Revert on failure
if (wasFavorite) {
this.ids.add(id)
} else {
this.ids.delete(id)
}
this.notify()
console.error('Failed to sync favorite:', e)
}
} else {
this.saveAnonIds()
}
}
// Directus API
async addRemote(listingId) {
const user = await auth.getUser()
if (!user) return
const existing = await directus.get('/items/favorites', {
filter: { user: { _eq: user.id }, listing: { _eq: listingId } },
fields: ['id'],
limit: 1
})
if (existing.data?.length > 0) return
await directus.post('/items/favorites', {
user: user.id,
listing: listingId
})
}
async removeRemote(listingId) {
const user = await auth.getUser()
if (!user) return
const response = await directus.get('/items/favorites', {
filter: {
user: { _eq: user.id },
listing: { _eq: listingId }
},
fields: ['id'],
limit: 1
})
const item = response.data?.[0]
if (item) {
await directus.delete(`/items/favorites/${item.id}`)
}
}
async fetchRemote() {
const user = await auth.getUser()
if (!user) return []
const response = await directus.get('/items/favorites', {
filter: {
user: { _eq: user.id }
},
fields: ['listing'],
limit: 200
})
return (response.data || []).map(f => f.listing).filter(Boolean)
}
async mergeOnLogin() {
this.syncing = true
try {
const remoteIds = await this.fetchRemote()
const localIds = this.getAnonIds()
// Union merge
const merged = new Set([...remoteIds, ...localIds])
this.ids = merged
// Push local-only favorites to server
const remoteSet = new Set(remoteIds)
const toAdd = localIds.filter(id => !remoteSet.has(id))
for (const id of toAdd) {
try {
await this.addRemote(id)
} catch (e) {
console.error('Failed to sync favorite to server:', e)
}
}
localStorage.removeItem(ANON_KEY)
this.loaded = true
this.notify()
} catch (e) {
console.error('Failed to merge favorites:', e)
this.loaded = true
}
this.syncing = false
}
}
export const favoritesService = new FavoritesService()
export default favoritesService