Files
kashilo/js/services/listings.js

235 lines
6.7 KiB
JavaScript

/**
* Listings Service - Higher-level API for listing operations
* Wraps directus.js with business logic and convenience methods
*/
import { directus } from './directus.js'
import { currencyService } from './currency.js'
class ListingsService {
constructor() {
this.cache = new Map()
this.cacheTimeout = 5 * 60 * 1000 // 5 minutes
}
async getFeaturedListings(limit = 8) {
return directus.getListings({
filter: {
status: { _eq: 'published' }
},
sort: ['-views', '-date_created'],
limit
})
}
async getRecentListings(limit = 12) {
return directus.getListings({
filter: { status: { _eq: 'published' } },
sort: ['-date_created'],
limit
})
}
async getListingWithPriceConversion(id, targetCurrency = 'EUR') {
const listing = await directus.getListing(id)
if (!listing) return null
if (listing.price && listing.price_mode === 'xmr') {
const fiatPrice = await currencyService.xmrToFiat(listing.price, targetCurrency)
listing.price_converted = fiatPrice
listing.price_converted_currency = targetCurrency
} else if (listing.price && listing.currency === 'XMR') {
const fiatPrice = await currencyService.xmrToFiat(listing.price, targetCurrency)
listing.price_converted = fiatPrice
listing.price_converted_currency = targetCurrency
}
return listing
}
async getListingsWithFilters(filters = {}) {
const directusFilter = { status: { _eq: 'published' } }
if (filters.category) {
directusFilter.category = { _eq: filters.category }
}
if (filters.location) {
directusFilter.location = { _eq: filters.location }
}
if (filters.minPrice !== undefined) {
directusFilter.price = directusFilter.price || {}
directusFilter.price._gte = filters.minPrice
}
if (filters.maxPrice !== undefined) {
directusFilter.price = directusFilter.price || {}
directusFilter.price._lte = filters.maxPrice
}
if (filters.condition) {
directusFilter.condition = { _eq: filters.condition }
}
if (filters.shipping === true) {
directusFilter.shipping = { _eq: true }
}
if (filters.priceType) {
directusFilter.price_type = { _eq: filters.priceType }
}
const sortMap = {
'newest': ['-date_created'],
'oldest': ['date_created'],
'price_asc': ['price'],
'price_desc': ['-price'],
'views': ['-views']
}
return directus.getListings({
filter: directusFilter,
sort: sortMap[filters.sort] || ['-date_created'],
limit: filters.limit || 20,
page: filters.page || 1,
search: filters.search
})
}
async getSimilarListings(listing, limit = 4) {
if (!listing) return []
const response = await directus.getListings({
filter: {
status: { _eq: 'published' },
id: { _neq: listing.id },
category: { _eq: listing.category?.id || listing.category }
},
limit
})
return response.items || []
}
async getUserListings(userId) {
const response = await directus.get('/items/listings', {
fields: ['*', 'images.directus_files_id.id', 'category.name', 'location.name'],
filter: { user_created: { _eq: userId } },
sort: ['-date_created']
})
return response.data
}
async createListingWithImages(data, imageFiles) {
let images = []
if (imageFiles && imageFiles.length > 0) {
const uploadedFiles = await directus.uploadMultipleFiles(imageFiles)
images = uploadedFiles.map((file, index) => ({
directus_files_id: file.id,
sort: index
}))
}
const listingData = {
...data,
status: 'draft',
images: images.length > 0 ? { create: images } : undefined
}
return directus.createListing(listingData)
}
async publishListing(id) {
return directus.updateListing(id, { status: 'published' })
}
async unpublishListing(id) {
return directus.updateListing(id, { status: 'draft' })
}
async archiveListing(id) {
return directus.updateListing(id, { status: 'archived' })
}
formatPrice(listing, locale = 'de-DE') {
if (!listing || listing.price === null || listing.price === undefined) {
return null
}
if (listing.price_type === 'negotiable') {
return `${this.formatAmount(listing.price, listing.currency, locale)} VB`
}
if (listing.price_type === 'free') {
return 'Gratis'
}
return this.formatAmount(listing.price, listing.currency, locale)
}
formatAmount(amount, currency = 'EUR', locale = 'de-DE') {
if (currency === 'XMR') {
return `${parseFloat(amount).toFixed(4)} XMR`
}
return new Intl.NumberFormat(locale, {
style: 'currency',
currency: currency
}).format(amount)
}
getConditionLabel(condition, lang = 'de') {
const labels = {
de: {
new: 'Neu',
like_new: 'Wie neu',
good: 'Gut',
fair: 'Akzeptabel',
poor: 'Stark gebraucht'
},
en: {
new: 'New',
like_new: 'Like new',
good: 'Good',
fair: 'Fair',
poor: 'Poor'
},
fr: {
new: 'Neuf',
like_new: 'Comme neuf',
good: 'Bon',
fair: 'Acceptable',
poor: 'Usagé'
}
}
return labels[lang]?.[condition] || condition
}
getPriceTypeLabel(priceType, lang = 'de') {
const labels = {
de: {
fixed: 'Festpreis',
negotiable: 'Verhandelbar',
free: 'Zu verschenken'
},
en: {
fixed: 'Fixed price',
negotiable: 'Negotiable',
free: 'Free'
},
fr: {
fixed: 'Prix fixe',
negotiable: 'Négociable',
free: 'Gratuit'
}
}
return labels[lang]?.[priceType] || priceType
}
}
export const listingsService = new ListingsService()