232 lines
6.5 KiB
JavaScript
232 lines
6.5 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 currency 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' || listing.currency === 'XMR')) {
|
|
const rates = await currency.getXmrRates()
|
|
const fiatPrice = currency.convertFromXmr(listing.price, targetCurrency, rates)
|
|
listing.price_converted = fiatPrice
|
|
listing.price_converted_currency = targetCurrency
|
|
}
|
|
|
|
return listing
|
|
}
|
|
|
|
async getListingsWithFilters(filters = {}) {
|
|
const directusFilter = { status: { _eq: 'published' } }
|
|
|
|
if (filters.category) {
|
|
directusFilter.category = { slug: { _eq: filters.category } }
|
|
}
|
|
|
|
if (filters.location) {
|
|
directusFilter.location = { _eq: filters.location }
|
|
}
|
|
|
|
if (filters.minPrice != null) {
|
|
directusFilter.price = directusFilter.price || {}
|
|
directusFilter.price._gte = filters.minPrice
|
|
}
|
|
|
|
if (filters.maxPrice != null) {
|
|
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()
|