/** * @ai-summary Data access layer for stations */ import { Pool } from 'pg'; import { Station, SavedStation } from '../domain/stations.types'; export class StationsRepository { constructor(private pool: Pool) {} async cacheStation(station: Station): Promise { const query = ` INSERT INTO station_cache ( place_id, name, address, latitude, longitude, price_regular, price_premium, price_diesel, rating, photo_url ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) ON CONFLICT (place_id) DO UPDATE SET name = $2, address = $3, latitude = $4, longitude = $5, rating = $9, photo_url = $10, cached_at = NOW() `; await this.pool.query(query, [ station.placeId, station.name, station.address, station.latitude, station.longitude, station.priceRegular, station.pricePremium, station.priceDiesel, station.rating, station.photoUrl ]); } async getCachedStation(placeId: string): Promise { const query = 'SELECT * FROM station_cache WHERE place_id = $1'; const result = await this.pool.query(query, [placeId]); if (result.rows.length === 0) { return null; } return this.mapCacheRow(result.rows[0]); } async saveStation( userId: string, placeId: string, data?: { nickname?: string; notes?: string; isFavorite?: boolean; has93Octane?: boolean; has93OctaneEthanolFree?: boolean; } ): Promise { const query = ` INSERT INTO saved_stations ( user_id, place_id, nickname, notes, is_favorite, has_93_octane, has_93_octane_ethanol_free ) VALUES ($1, $2, $3, $4, $5, $6, $7) ON CONFLICT (user_id, place_id) DO UPDATE SET nickname = COALESCE($3, saved_stations.nickname), notes = COALESCE($4, saved_stations.notes), is_favorite = COALESCE($5, saved_stations.is_favorite), has_93_octane = COALESCE($6, saved_stations.has_93_octane), has_93_octane_ethanol_free = CASE WHEN $6 IS NOT NULL AND $6 = false THEN false ELSE COALESCE($7, saved_stations.has_93_octane_ethanol_free) END, updated_at = NOW() RETURNING * `; const result = await this.pool.query(query, [ userId, placeId, data?.nickname, data?.notes, data?.isFavorite ?? false, data?.has93Octane ?? false, data?.has93OctaneEthanolFree ?? false ]); return this.mapSavedRow(result.rows[0]); } async updateSavedStation( userId: string, placeId: string, data: { nickname?: string; notes?: string; isFavorite?: boolean; has93Octane?: boolean; has93OctaneEthanolFree?: boolean; } ): Promise { const query = ` UPDATE saved_stations SET nickname = COALESCE($3, nickname), notes = COALESCE($4, notes), is_favorite = COALESCE($5, is_favorite), has_93_octane = COALESCE($6, has_93_octane), has_93_octane_ethanol_free = CASE WHEN $6 IS NOT NULL AND $6 = false THEN false ELSE COALESCE($7, has_93_octane_ethanol_free) END, updated_at = NOW() WHERE user_id = $1 AND place_id = $2 RETURNING * `; const result = await this.pool.query(query, [ userId, placeId, data.nickname, data.notes, data.isFavorite, data.has93Octane, data.has93OctaneEthanolFree ]); if (result.rows.length === 0) { return null; } return this.mapSavedRow(result.rows[0]); } async getUserSavedStations(userId: string): Promise { const query = ` SELECT * FROM saved_stations WHERE user_id = $1 ORDER BY is_favorite DESC, created_at DESC `; const result = await this.pool.query(query, [userId]); return result.rows.map(row => this.mapSavedRow(row)); } async deleteSavedStation(userId: string, placeId: string): Promise { const query = 'DELETE FROM saved_stations WHERE user_id = $1 AND place_id = $2'; const result = await this.pool.query(query, [userId, placeId]); return (result.rowCount ?? 0) > 0; } private mapCacheRow(row: any): Station { return { id: row.id, placeId: row.place_id, name: row.name, address: row.address, latitude: parseFloat(row.latitude), longitude: parseFloat(row.longitude), priceRegular: row.price_regular ? parseFloat(row.price_regular) : undefined, pricePremium: row.price_premium ? parseFloat(row.price_premium) : undefined, priceDiesel: row.price_diesel ? parseFloat(row.price_diesel) : undefined, rating: row.rating ? parseFloat(row.rating) : undefined, photoUrl: row.photo_url, lastUpdated: row.cached_at }; } private mapSavedRow(row: any): SavedStation { return { id: row.id, userId: row.user_id, stationId: row.place_id, placeId: row.place_id, nickname: row.nickname, notes: row.notes, isFavorite: row.is_favorite, has93Octane: row.has_93_octane ?? false, has93OctaneEthanolFree: row.has_93_octane_ethanol_free ?? false, createdAt: row.created_at, updatedAt: row.updated_at }; } }