187 lines
4.8 KiB
JavaScript
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
|