import { directus } from './directus.js' import { cryptoService } from './crypto.js' const CACHE_TTL = 5 * 60 * 1000 class ReputationService { constructor() { this.cache = new Map() } async getUserHash() { await cryptoService.ready const publicKey = cryptoService.getPublicKey() const encoder = new TextEncoder() const data = encoder.encode(publicKey) const hash = await crypto.subtle.digest('SHA-256', data) return Array.from(new Uint8Array(hash)) .map(b => b.toString(16).padStart(2, '0')) .join('') } // ── Deals ── async createDeal(conversation) { const userHash = await this.getUserHash() // participant_hash_1 = buyer (initiator), participant_hash_2 = seller const sellerHash = conversation.participant_hash_2 const buyerHash = conversation.participant_hash_1 const isSeller = sellerHash === userHash const response = await directus.post('/items/deals', { conversation: conversation.id, listing: conversation.listing_id, seller_hash: sellerHash, buyer_hash: buyerHash, status: 'pending', seller_confirmed: isSeller, buyer_confirmed: !isSeller }) return response.data || response } async confirmDeal(dealId) { const userHash = await this.getUserHash() const deal = await this.getDeal(dealId) if (!deal) throw new Error('Deal not found') const isSeller = deal.seller_hash === userHash const isBuyer = deal.buyer_hash === userHash if (!isSeller && !isBuyer) throw new Error('Not a participant of this deal') const update = {} if (isSeller) update.seller_confirmed = true if (isBuyer) update.buyer_confirmed = true const sellerConfirmed = isSeller ? true : deal.seller_confirmed const buyerConfirmed = isBuyer ? true : deal.buyer_confirmed if (sellerConfirmed && buyerConfirmed) { update.status = 'confirmed' update.date_confirmed = new Date().toISOString() } const response = await directus.patch(`/items/deals/${dealId}`, update) this.clearCache() return response.data || response } async getDeal(dealId) { const response = await directus.get(`/items/deals/${dealId}`) return response.data || response } async getDealsForConversation(conversationId) { const response = await directus.get('/items/deals', { filter: { conversation: { _eq: conversationId } }, sort: ['-date_created'] }) return response.data || [] } async getDealsForUser(userHash) { const response = await directus.get('/items/deals', { filter: { status: { _eq: 'confirmed' }, _or: [ { seller_hash: { _eq: userHash } }, { buyer_hash: { _eq: userHash } } ] }, sort: ['-date_confirmed'] }) return response.data || [] } // ── Ratings ── async rateDeal(dealId, score) { if (score < 1 || score > 5) throw new Error('Score must be between 1 and 5') const deal = await this.getDeal(dealId) if (!deal) throw new Error('Deal not found') if (deal.status !== 'confirmed') throw new Error('Deal must be confirmed before rating') const userHash = await this.getUserHash() const isSeller = deal.seller_hash === userHash const isBuyer = deal.buyer_hash === userHash if (!isSeller && !isBuyer) throw new Error('Not a participant of this deal') const ratedUserHash = isSeller ? deal.buyer_hash : deal.seller_hash const existing = await directus.get('/items/ratings', { filter: { deal: { _eq: dealId }, rater_hash: { _eq: userHash } }, limit: 1 }) if (existing.data?.length > 0) throw new Error('Already rated this deal') const response = await directus.post('/items/ratings', { deal: dealId, rater_hash: userHash, rated_hash: ratedUserHash, score, date_created: new Date().toISOString() }) this.clearCache() return response.data || response } async getRatingsForUser(userHash) { const response = await directus.get('/items/ratings', { filter: { rated_hash: { _eq: userHash } }, sort: ['-date_created'] }) return response.data || [] } // ── Reputation ── async getReputation(userHash) { const cached = this.cache.get(userHash) if (cached && Date.now() - cached.timestamp < CACHE_TTL) { return cached.value } const [deals, ratings] = await Promise.all([ this.getDealsForUser(userHash), this.getRatingsForUser(userHash) ]) const dealsCompleted = deals.length const avgRating = ratings.length > 0 ? ratings.reduce((sum, r) => sum + r.score, 0) / ratings.length : 0 let accountAgeDays = 0 const dates = deals.map(d => d.date_confirmed || d.date_created).filter(Boolean) if (dates.length > 0) { const oldest = dates .map(d => new Date(d).getTime()) .reduce((min, t) => Math.min(min, t), Infinity) accountAgeDays = Math.floor((Date.now() - oldest) / (1000 * 60 * 60 * 24)) } let level = 'new' if (dealsCompleted >= 30 && avgRating >= 4.5) { level = 'power' } else if (dealsCompleted >= 10 && avgRating >= 4.0) { level = 'trusted' } else if (dealsCompleted >= 3 && accountAgeDays >= 30) { level = 'active' } const reputation = { deals_completed: dealsCompleted, avg_rating: Math.round(avgRating * 100) / 100, account_age_days: accountAgeDays, level } this.cache.set(userHash, { value: reputation, timestamp: Date.now() }) return reputation } getLevelInfo(level) { const levels = { new: { badge: '⚪', i18nKey: 'reputation.level.new' }, active: { badge: '🔵', i18nKey: 'reputation.level.active' }, trusted: { badge: '🟢', i18nKey: 'reputation.level.trusted' }, power: { badge: '🟣', i18nKey: 'reputation.level.power' } } return levels[level] || levels.new } clearCache() { this.cache.clear() } } export const reputationService = new ReputationService()