190 lines
5.3 KiB
TypeScript
190 lines
5.3 KiB
TypeScript
/**
|
|
* @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<void> {
|
|
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<Station | null> {
|
|
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<SavedStation> {
|
|
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<SavedStation | null> {
|
|
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<SavedStation[]> {
|
|
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<boolean> {
|
|
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
|
|
};
|
|
}
|
|
}
|