JavaScript · 7729 bytes Raw Blame History
1 const sqlite3 = require('sqlite3').verbose();
2 const path = require('path');
3
4 // Database file location
5 const DB_PATH = process.env.NODE_ENV === 'production'
6 ? '/data/localtoast.db' // Railway persistent volume
7 : path.join(__dirname, 'db', 'localtoast.db');
8
9 // Create database connection
10 const db = new sqlite3.Database(DB_PATH, (err) => {
11 if (err) {
12 console.error('Error opening database:', err);
13 } else {
14 console.log('Connected to SQLite database at:', DB_PATH);
15 }
16 });
17
18 // Enable foreign keys
19 db.run('PRAGMA foreign_keys = ON');
20
21 // Initialize database schema
22 function initializeDatabase() {
23 return new Promise((resolve, reject) => {
24 db.serialize(() => {
25 // Create restaurants table
26 db.run(`
27 CREATE TABLE IF NOT EXISTS restaurants (
28 id INTEGER PRIMARY KEY AUTOINCREMENT,
29 place_id TEXT UNIQUE NOT NULL,
30 name TEXT NOT NULL,
31 address TEXT NOT NULL,
32 latitude REAL NOT NULL,
33 longitude REAL NOT NULL,
34 created_at DATETIME DEFAULT CURRENT_TIMESTAMP
35 )
36 `, (err) => {
37 if (err) console.error('Error creating restaurants table:', err);
38 });
39
40 // Create ratings table
41 db.run(`
42 CREATE TABLE IF NOT EXISTS ratings (
43 id INTEGER PRIMARY KEY AUTOINCREMENT,
44 restaurant_id INTEGER NOT NULL,
45 rating INTEGER NOT NULL CHECK (rating >= 1 AND rating <= 5),
46 review TEXT,
47 created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
48 FOREIGN KEY (restaurant_id) REFERENCES restaurants (id)
49 )
50 `, (err) => {
51 if (err) console.error('Error creating ratings table:', err);
52 });
53
54 // Create indexes for better performance
55 db.run(`
56 CREATE INDEX IF NOT EXISTS idx_restaurants_location
57 ON restaurants(latitude, longitude)
58 `);
59
60 db.run(`
61 CREATE INDEX IF NOT EXISTS idx_ratings_restaurant
62 ON ratings(restaurant_id)
63 `, (err) => {
64 if (err) {
65 reject(err);
66 } else {
67 console.log('Database initialized successfully');
68 resolve();
69 }
70 });
71 });
72 });
73 }
74
75 // Database helper functions
76 const dbHelpers = {
77 // Get all restaurants with ratings
78 getAllRestaurants: () => {
79 return new Promise((resolve, reject) => {
80 const query = `
81 SELECT
82 r.*,
83 AVG(rt.rating) as average_rating,
84 COUNT(rt.id) as total_ratings
85 FROM restaurants r
86 LEFT JOIN ratings rt ON r.id = rt.restaurant_id
87 GROUP BY r.id
88 `;
89
90 db.all(query, (err, rows) => {
91 if (err) reject(err);
92 else resolve(rows);
93 });
94 });
95 },
96
97 // Get restaurants within radius
98 getNearbyRestaurants: (lat, lng, radiusKm = 5) => {
99 return new Promise((resolve, reject) => {
100 // SQLite doesn't have built-in geospatial functions, so we'll use a bounding box
101 // This is an approximation but works well for small distances
102 const latDiff = radiusKm / 111; // 1 degree latitude ≈ 111 km
103 const lngDiff = radiusKm / (111 * Math.cos(lat * Math.PI / 180));
104
105 const query = `
106 SELECT
107 r.*,
108 AVG(rt.rating) as average_rating,
109 COUNT(rt.id) as total_ratings
110 FROM restaurants r
111 LEFT JOIN ratings rt ON r.id = rt.restaurant_id
112 WHERE
113 r.latitude BETWEEN ? AND ?
114 AND r.longitude BETWEEN ? AND ?
115 GROUP BY r.id
116 `;
117
118 db.all(query, [
119 lat - latDiff, lat + latDiff,
120 lng - lngDiff, lng + lngDiff
121 ], (err, rows) => {
122 if (err) reject(err);
123 else resolve(rows);
124 });
125 });
126 },
127
128 // Get restaurant by place_id
129 getRestaurantByPlaceId: (placeId) => {
130 return new Promise((resolve, reject) => {
131 db.get(
132 'SELECT * FROM restaurants WHERE place_id = ?',
133 [placeId],
134 (err, row) => {
135 if (err) reject(err);
136 else resolve(row);
137 }
138 );
139 });
140 },
141
142 // Add new restaurant
143 addRestaurant: (restaurant) => {
144 return new Promise((resolve, reject) => {
145 const { place_id, name, address, latitude, longitude } = restaurant;
146
147 db.run(
148 `INSERT INTO restaurants (place_id, name, address, latitude, longitude)
149 VALUES (?, ?, ?, ?, ?)`,
150 [place_id, name, address, latitude, longitude],
151 function(err) {
152 if (err) {
153 reject(err);
154 } else {
155 // Get the inserted restaurant
156 db.get(
157 'SELECT * FROM restaurants WHERE id = ?',
158 [this.lastID],
159 (err, row) => {
160 if (err) reject(err);
161 else resolve(row);
162 }
163 );
164 }
165 }
166 );
167 });
168 },
169
170 // Add rating
171 addRating: (restaurantId, rating, review) => {
172 return new Promise((resolve, reject) => {
173 db.run(
174 `INSERT INTO ratings (restaurant_id, rating, review)
175 VALUES (?, ?, ?)`,
176 [restaurantId, rating, review],
177 function(err) {
178 if (err) {
179 reject(err);
180 } else {
181 resolve({
182 id: this.lastID,
183 message: 'Toast rating added successfully! 🍞'
184 });
185 }
186 }
187 );
188 });
189 },
190
191 // Get ratings for a restaurant
192 getRestaurantRatings: (restaurantId) => {
193 return new Promise((resolve, reject) => {
194 db.all(
195 `SELECT * FROM ratings
196 WHERE restaurant_id = ?
197 ORDER BY created_at DESC`,
198 [restaurantId],
199 (err, rows) => {
200 if (err) reject(err);
201 else resolve(rows);
202 }
203 );
204 });
205 },
206
207 // Migrate from JSON files (one-time migration)
208 migrateFromJSON: async () => {
209 const fs = require('fs').promises;
210 const oldRestaurantsPath = path.join(__dirname, 'db', 'restaurants.json');
211 const oldRatingsPath = path.join(__dirname, 'db', 'ratings.json');
212
213 try {
214 // Check if JSON files exist
215 const restaurantsData = await fs.readFile(oldRestaurantsPath, 'utf8').catch(() => '[]');
216 const ratingsData = await fs.readFile(oldRatingsPath, 'utf8').catch(() => '[]');
217
218 const restaurants = JSON.parse(restaurantsData);
219 const ratings = JSON.parse(ratingsData);
220
221 console.log(`Migrating ${restaurants.length} restaurants and ${ratings.length} ratings...`);
222
223 // Migrate restaurants
224 for (const restaurant of restaurants) {
225 try {
226 await dbHelpers.addRestaurant(restaurant);
227 console.log(`Migrated restaurant: ${restaurant.name}`);
228 } catch (err) {
229 if (err.message.includes('UNIQUE constraint failed')) {
230 console.log(`Restaurant already exists: ${restaurant.name}`);
231 } else {
232 console.error(`Failed to migrate restaurant ${restaurant.name}:`, err);
233 }
234 }
235 }
236
237 // Migrate ratings
238 for (const rating of ratings) {
239 try {
240 await dbHelpers.addRating(rating.restaurant_id, rating.rating, rating.review);
241 console.log(`Migrated rating for restaurant ${rating.restaurant_id}`);
242 } catch (err) {
243 console.error(`Failed to migrate rating:`, err);
244 }
245 }
246
247 console.log('Migration completed!');
248
249 // Rename old files to .backup
250 await fs.rename(oldRestaurantsPath, oldRestaurantsPath + '.backup').catch(() => {});
251 await fs.rename(oldRatingsPath, oldRatingsPath + '.backup').catch(() => {});
252
253 } catch (error) {
254 console.error('Migration error:', error);
255 }
256 }
257 };
258
259 module.exports = {
260 db,
261 initializeDatabase,
262 ...dbHelpers
263 };