import { query } from '@/lib/database/config' import { Package, CreatePackageInput, UpdatePackageInput, PackageFilter } from '@/models/Package' export class PackageService { // Get packages with filtering and pagination static async getMany(filter: PackageFilter = {}): Promise<{ packages: Package[], total: number }> { const { platform_id, category_id, type, repository, search, limit = 50, offset = 0, sort_by = 'name', sort_order = 'asc' } = filter // Build WHERE clause const whereConditions = [] const values = [] let paramIndex = 1 if (platform_id) { whereConditions.push(`p.platform_id = $${paramIndex++}`) values.push(platform_id) } if (category_id) { whereConditions.push(`p.category_id = $${paramIndex++}`) values.push(category_id) } if (type) { whereConditions.push(`p.type = $${paramIndex++}`) values.push(type) } if (repository) { whereConditions.push(`p.repository = $${paramIndex++}`) values.push(repository) } if (search) { whereConditions.push(`( to_tsvector('english', p.name) @@ to_tsquery('english', $${paramIndex}) OR to_tsvector('english', p.description) @@ to_tsquery('english', $${paramIndex}) )`) values.push(search.split(' ').join(' & ')) paramIndex++ } whereConditions.push('p.is_active = true') const whereClause = whereConditions.length > 0 ? `WHERE ${whereConditions.join(' AND ')}` : '' // Build ORDER BY clause const validSortFields = ['name', 'popularity_score', 'last_updated', 'downloads_count'] const sortField = validSortFields.includes(sort_by || '') ? sort_by : 'name' const sortDirection = sort_order === 'desc' ? 'DESC' : 'ASC' const orderClause = `ORDER BY p.${sortField} ${sortDirection}` // Get total count const countQuery = ` SELECT COUNT(*) as total FROM packages p ${whereClause} ` const countResult = await query(countQuery, values) const total = parseInt(countResult.rows[0].total) // Get packages with joins const packagesQuery = ` SELECT p.*, pl.name as platform_name, pl.package_manager as platform_package_manager, pl.icon as platform_icon, c.name as category_name, l.name as license_name, l.url as license_url, COALESCE( ARRAY_AGG(DISTINCT pt.tag) FILTER (WHERE pt.tag IS NOT NULL), ARRAY[]::VARCHAR[] ) as tags FROM packages p LEFT JOIN platforms pl ON p.platform_id = pl.id LEFT JOIN categories c ON p.category_id = c.id LEFT JOIN licenses l ON p.license_id = l.id LEFT JOIN package_tags pt ON p.id = pt.package_id ${whereClause} GROUP BY p.id, pl.name, pl.package_manager, pl.icon, c.name, l.name, l.url ${orderClause} LIMIT $${paramIndex++} OFFSET $${paramIndex++} ` values.push(limit, offset) const packagesResult = await query(packagesQuery, values) // Transform the results const packages = packagesResult.rows.map((row: any) => ({ id: row.id, name: row.name, description: row.description, version: row.version, platform_id: row.platform_id, category_id: row.category_id, license_id: row.license_id, type: row.type, repository: row.repository, homepage_url: row.homepage_url, download_url: row.download_url, last_updated: row.last_updated, downloads_count: row.downloads_count, popularity_score: row.popularity_score, is_active: row.is_active, created_at: row.created_at, updated_at: row.updated_at, platform: row.platform_name ? { id: row.platform_id, name: row.platform_name, package_manager: row.platform_package_manager, icon: row.platform_icon } : undefined, category: row.category_name ? { id: row.category_id, name: row.category_name } : undefined, license: row.license_name ? { id: row.license_id, name: row.license_name, url: row.license_url } : undefined, tags: row.tags || [] })) return { packages, total } } // Get package by ID static async getById(id: string): Promise { const packageQuery = ` SELECT p.*, pl.name as platform_name, pl.package_manager as platform_package_manager, pl.icon as platform_icon, c.name as category_name, l.name as license_name, l.url as license_url, COALESCE( ARRAY_AGG(DISTINCT pt.tag) FILTER (WHERE pt.tag IS NOT NULL), ARRAY[]::VARCHAR[] ) as tags FROM packages p LEFT JOIN platforms pl ON p.platform_id = pl.id LEFT JOIN categories c ON p.category_id = c.id LEFT JOIN licenses l ON p.license_id = l.id LEFT JOIN package_tags pt ON p.id = pt.package_id WHERE p.id = $1 AND p.is_active = true GROUP BY p.id, pl.name, pl.package_manager, pl.icon, c.name, l.name, l.url ` const result = await query(packageQuery, [id]) if (result.rows.length === 0) { return null } const row = result.rows[0] return { id: row.id, name: row.name, description: row.description, version: row.version, platform_id: row.platform_id, category_id: row.category_id, license_id: row.license_id, type: row.type, repository: row.repository, homepage_url: row.homepage_url, download_url: row.download_url, last_updated: row.last_updated, downloads_count: row.downloads_count, popularity_score: row.popularity_score, is_active: row.is_active, created_at: row.created_at, updated_at: row.updated_at, platform: row.platform_name ? { id: row.platform_id, name: row.platform_name, package_manager: row.platform_package_manager, icon: row.platform_icon } : undefined, category: row.category_name ? { id: row.category_id, name: row.category_name } : undefined, license: row.license_name ? { id: row.license_id, name: row.license_name, url: row.license_url } : undefined, tags: row.tags || [] } } // Create new package static async create(data: CreatePackageInput): Promise { const { id, name, description, version, platform_id, category_id, license_id, type, repository, homepage_url, download_url, popularity_score } = data const result = await query( `INSERT INTO packages ( id, name, description, version, platform_id, category_id, license_id, type, repository, homepage_url, download_url, popularity_score ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) RETURNING *`, [id, name, description, version, platform_id, category_id, license_id, type, repository, homepage_url, download_url, popularity_score] ) const createdPackage = await this.getById(result.rows[0].id) if (!createdPackage) { throw new Error('Failed to create package') } return createdPackage } // Update package static async update(id: string, data: UpdatePackageInput): Promise { const fields = [] const values = [] let paramIndex = 1 for (const [key, value] of Object.entries(data)) { if (value !== undefined) { fields.push(`${key} = $${paramIndex++}`) values.push(value) } } if (fields.length === 0) { return this.getById(id) } values.push(id) await query( `UPDATE packages SET ${fields.join(', ')} WHERE id = $${paramIndex}`, values ) return this.getById(id) } // Delete package (soft delete) static async delete(id: string): Promise { const result = await query( 'UPDATE packages SET is_active = false WHERE id = $1', [id] ) return (result.rowCount ?? 0) > 0 } // Add tags to package static async addTags(packageId: string, tags: string[]): Promise { if (tags.length === 0) return const values = tags.map((tag, index) => `($1, $${index + 2})`).join(', ') const params = [packageId, ...tags] await query( `INSERT INTO package_tags (package_id, tag) VALUES ${values} ON CONFLICT DO NOTHING`, params ) } // Remove tags from package static async removeTags(packageId: string, tags: string[]): Promise { if (tags.length === 0) return await query( 'DELETE FROM package_tags WHERE package_id = $1 AND tag = ANY($2)', [packageId, tags] ) } // Get popular packages static async getPopular(limit: number = 10): Promise { const result = await this.getMany({ limit, sort_by: 'popularity_score', sort_order: 'desc' }) return result.packages } // Get recently updated packages static async getRecentlyUpdated(limit: number = 10): Promise { const result = await this.getMany({ limit, sort_by: 'last_updated', sort_order: 'desc' }) return result.packages } }