Skip to content

Commit 689ac41

Browse files
daisychangmnfq2
andauthored
Add Route Reporting Functionality (#353)
* Added web scrapper files for library and printer locations. * Created endpoint that returns closest bus on route * Added endpoint route and fixed function calls * Added route for fetching all libraries and printers and populated SQLite database * Wrote code to store route reports in sqlite database * Added route to get reports by vehicle Id * Added insert report logic * Fixed server restart issue * Remove print statements * Fixed parameter bug * Removed unneccessary function * Removed python files * Removed null return value for closest bus --------- Co-authored-by: Nicole Qiu <nfq2@cornell.edu>
1 parent b8b2046 commit 689ac41

8 files changed

Lines changed: 246 additions & 40 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"main": "index.js",
66
"type": "module",
77
"scripts": {
8-
"start:dev": "nodemon src/index.js",
8+
"start:dev": "nodemon --ignore src/data/notifRequests.json src/index.js",
99
"start": "node src/index.js"
1010
},
1111
"keywords": [],

src/controllers/RouteReportingController.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,32 @@ router.get('/closestBus', async (req, res) => {
1919
}
2020
});
2121

22+
router.post('/reports', async (req, res) => {
23+
try {
24+
const {
25+
vehicleId,
26+
congestionLevel,
27+
deviceToken,
28+
timestamp
29+
} = req.body;
30+
31+
const report = await RouteReportingUtils.insertReport(vehicleId, congestionLevel, deviceToken, timestamp);
32+
res.status(200).json(report);
33+
} catch (error) {
34+
LogUtils.logErr(error, req.body, 'Error inserting report');
35+
res.status(500).json({ error: 'Failed to insert report' });
36+
}
37+
});
38+
39+
router.get('/reports/:vehicleId', async (req, res) => {
40+
try {
41+
const { vehicleId } = req.params;
42+
const reports = await RouteReportingUtils.fetchReportsByBus(vehicleId);
43+
res.status(200).json(reports);
44+
} catch (error) {
45+
LogUtils.logErr(error, req.params, 'Error fetching reports by vehicleId');
46+
res.status(500).json({ error: 'Failed to fetch reports by vehicleId' });
47+
}
48+
});
49+
2250
export default router;

src/data/db/database.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,35 +2,48 @@
22
import os
33

44
# Get the absolute path of transit.db
5-
BASE_DIR = os.path.dirname(os.path.abspath(__file__)) # Gets the directory of the script
6-
DB_PATH = os.path.join(BASE_DIR, '..', 'transit.db') # Moves up one level to store db in /data/
5+
BASE_DIR = os.path.dirname(
6+
os.path.abspath(__file__)
7+
) # Gets the directory of the script
8+
DB_PATH = os.path.join(
9+
BASE_DIR, "..", "transit.db"
10+
) # Moves up one level to store db in /data/
11+
712

813
def get_db_connection():
914
"""Establish and return an SQLite connection."""
1015
return sqlite3.connect(DB_PATH)
1116

17+
1218
def insert_library(location, address, latitude, longitude):
1319
"""Insert a library into the database."""
1420
conn = get_db_connection()
1521
cursor = conn.cursor()
1622

17-
cursor.execute('''
23+
cursor.execute(
24+
"""
1825
INSERT OR IGNORE INTO libraries (location, address, latitude, longitude)
1926
VALUES (?, ?, ?, ?)
20-
''', (location, address, latitude, longitude))
27+
""",
28+
(location, address, latitude, longitude),
29+
)
2130

2231
conn.commit()
2332
conn.close()
2433

34+
2535
def insert_printer(location, description, latitude, longitude):
2636
"""Insert a printer into the database."""
2737
conn = get_db_connection()
2838
cursor = conn.cursor()
2939

30-
cursor.execute('''
40+
cursor.execute(
41+
"""
3142
INSERT OR IGNORE INTO printers (location, description, latitude, longitude)
3243
VALUES (?, ?, ?, ?)
33-
''', (location, description, latitude, longitude))
44+
""",
45+
(location, description, latitude, longitude),
46+
)
3447

3548
conn.commit()
36-
conn.close()
49+
conn.close()

src/data/db/models.py

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,36 +2,58 @@
22
import os
33

44
# Get the absolute path of transit.db
5-
BASE_DIR = os.path.dirname(os.path.abspath(__file__)) # Gets the directory of the script
6-
DB_PATH = os.path.join(BASE_DIR, '..', 'transit.db') # Moves up one level to store db in /data/
5+
BASE_DIR = os.path.dirname(
6+
os.path.abspath(__file__)
7+
) # Gets the directory of the script
8+
DB_PATH = os.path.join(
9+
BASE_DIR, "..", "transit.db"
10+
) # Moves up one level to store db in /data/
11+
712

813
def create_tables():
914
"""Creates tables for storing locations in SQLite."""
1015
conn = sqlite3.connect(DB_PATH)
1116
cursor = conn.cursor()
1217

13-
cursor.execute('''
18+
cursor.execute(
19+
"""
1420
CREATE TABLE IF NOT EXISTS libraries (
1521
id INTEGER PRIMARY KEY AUTOINCREMENT,
1622
location TEXT UNIQUE,
1723
address TEXT,
1824
latitude REAL,
1925
longitude REAL
2026
)
21-
''')
27+
"""
28+
)
2229

23-
cursor.execute('''
30+
cursor.execute(
31+
"""
2432
CREATE TABLE IF NOT EXISTS printers (
2533
id INTEGER PRIMARY KEY AUTOINCREMENT,
2634
location TEXT UNIQUE,
2735
description TEXT,
2836
latitude REAL,
2937
longitude REAL
3038
)
31-
''')
39+
"""
40+
)
41+
42+
cursor.execute(
43+
"""
44+
CREATE TABLE IF NOT EXISTS reports (
45+
id INTEGER PRIMARY KEY AUTOINCREMENT,
46+
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
47+
vehicleID TEXT,
48+
congestionLevel TEXT,
49+
deviceToken TEXT
50+
)
51+
"""
52+
)
3253

3354
conn.commit()
3455
conn.close()
3556

57+
3658
if __name__ == "__main__":
37-
create_tables()
59+
create_tables()
1.06 KB
Binary file not shown.
1.11 KB
Binary file not shown.

src/data/transit.db

4 KB
Binary file not shown.

src/utils/RouteReportingUtils.js

Lines changed: 168 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,206 @@
1-
import LogUtils from "./LogUtils.js";
2-
import RealtimeFeedUtilsV3 from "./RealtimeFeedUtilsV3.js";
3-
import ParseRouteUtilsV3 from "./ParseRouteUtilsV3.js";
1+
import LogUtils from './LogUtils.js';
2+
import RealtimeFeedUtilsV3 from './RealtimeFeedUtilsV3.js';
3+
import ParseRouteUtilsV3 from './ParseRouteUtilsV3.js';
4+
import sqlite3 from "sqlite3";
5+
import { fileURLToPath } from 'url';
6+
import path from "path";
7+
8+
const __filename = fileURLToPath(import.meta.url);
9+
const __dirname = path.dirname(__filename);
10+
const dbPath = path.join(__dirname, '..', 'data', 'transit.db');
11+
12+
const nullBus = {
13+
bearing: 0,
14+
congestionLevel: 0,
15+
currentStatus: 0,
16+
currentStopSequence: 0,
17+
latitude: 0,
18+
longitude: 0,
19+
occupancyStatus: 0,
20+
routeId: "",
21+
speed: 0,
22+
stopId: "",
23+
timestamp: {},
24+
tripId: "",
25+
vehicleId: "",
26+
}
427

528
/**
629
* Returns the closest bus on a given routeId and start position.
30+
*
731
* @param routeId
832
* @param start
933
* @returns {Object}
1034
*/
1135
async function getClosestBus(routeId, start) {
12-
LogUtils.log({ message: "getClosestBus: entering function", routeId });
36+
LogUtils.log({ message: 'getClosestBus: entering function', routeId });
1337
const vehicles = await RealtimeFeedUtilsV3.getVehicleData();
1438

1539
if (!vehicles) {
16-
LogUtils.log({ message: "No vehicle data available" });
17-
return null;
40+
LogUtils.log({ message: 'No vehicle data available' });
41+
return nullBus;
1842
}
19-
43+
2044
const routeVehicles = Object.values(vehicles).filter(
21-
(vehicle) => vehicle.routeId == routeId
22-
);
23-
45+
v => v.routeId === routeId );
46+
2447
if (routeVehicles.length === 0) {
25-
LogUtils.log({ message: "No vehicles found for given route", routeId });
26-
return null;
48+
LogUtils.log({ message: 'No vehicles found for given route', routeId });
49+
return nullBus;
2750
}
2851

29-
const startPointList = start.split(",");
52+
const startPointList = start.split(',');
3053
const startPoint = { lat: startPointList[0], long: startPointList[1] };
31-
32-
let closestVehicle = null;
54+
55+
let closestVehicle = nullBus;
3356
let minDistance = Infinity;
34-
57+
3558
for (const vehicle of routeVehicles) {
3659
const vehiclePosition = {
3760
lat: vehicle.latitude,
3861
long: vehicle.longitude,
3962
};
40-
63+
4164
const distance = ParseRouteUtilsV3.distanceBetweenPointsMiles(
4265
vehiclePosition,
43-
startPoint
66+
startPoint,
4467
);
45-
68+
4669
if (distance < minDistance) {
4770
minDistance = distance;
4871
closestVehicle = vehicle;
4972
}
5073
}
74+
75+
return closestVehicle;
76+
}
5177

52-
if (!closestVehicle) {
53-
LogUtils.log({ message: "No closest vehicle found", routeId });
54-
return null;
55-
}
5678

57-
LogUtils.log({ message: "Closest bus found", closestVehicle });
58-
return closestVehicle;
79+
80+
/**
81+
* Fetches a report from the database by its id.
82+
*
83+
* @param reportId
84+
* @returns {Promise<Object>}
85+
*/
86+
function fetchReportById(reportId) {
87+
return new Promise((resolve, reject) => {
88+
const db = new sqlite3.Database(dbPath, (err) => {
89+
if (err) {
90+
console.error(err.message);
91+
return reject(err);
92+
}
93+
console.log('Connected to the SQLite database.');
94+
});
95+
96+
db.get('SELECT * FROM reports WHERE id = ?', [reportId], (err, row) => {
97+
if (err) {
98+
console.error(err.message);
99+
return reject(err);
100+
}
101+
db.close((err) => {
102+
if (err) console.error(err.message);
103+
console.log('Closed the database connection.');
104+
});
105+
resolve(row);
106+
});
107+
});
59108
}
60109

110+
111+
/**
112+
* Inserts a report into the database. If a timestamp is not provided, the current time will be used.
113+
*
114+
* @param vehicleId
115+
* @param routeId
116+
* @param reportText
117+
* @param deviceToken
118+
* @param timestamp Optional
119+
* @returns {Promise<Object>}
120+
*/
121+
function insertReport(vehicleId, congestionLevel, deviceToken, timestamp = null) {
122+
return new Promise((resolve, reject) => {
123+
const db = new sqlite3.Database(dbPath, (err) => {
124+
if (err) {
125+
console.error('Error opening database:', err.message);
126+
return reject(err);
127+
}
128+
console.log('Connected to the SQLite database.');
129+
});
130+
131+
let query, values;
132+
133+
if (timestamp) {
134+
query = `
135+
INSERT INTO reports (timestamp, vehicleID, congestionLevel, deviceToken)
136+
VALUES (?, ?, ?, ?)
137+
`;
138+
values = [timestamp, vehicleId, congestionLevel, deviceToken];
139+
} else {
140+
query = `
141+
INSERT INTO reports (vehicleId, congestionLevel, deviceToken)
142+
VALUES (?, ?, ?)
143+
`;
144+
values = [vehicleId, congestionLevel, deviceToken];
145+
}
146+
147+
db.run(query, values, async function (err) {
148+
if (err) {
149+
console.error('Error inserting report:', err.message);
150+
return reject(err);
151+
}
152+
153+
const insertedId = this.lastID;
154+
db.close((err) => {
155+
if (err) console.error('Error closing database after insert:', err.message);
156+
console.log('Closed the database connection after insert.');
157+
});
158+
159+
try {
160+
const report = await fetchReportById(insertedId);
161+
resolve(report);
162+
} catch (fetchErr) {
163+
reject(fetchErr);
164+
}
165+
});
166+
});
167+
}
168+
169+
170+
171+
/**
172+
* Fetches all reports from the database by vehicle ID.
173+
*
174+
* @param vehicleId
175+
* @returns {Promise<Array<Object>>}
176+
*/
177+
function fetchReportsByBus(vehicleId) {
178+
return new Promise((resolve, reject) => {
179+
const db = new sqlite3.Database(dbPath, (err) => {
180+
if (err) {
181+
console.error(err.message);
182+
return reject(err);
183+
}
184+
});
185+
186+
db.all('SELECT * FROM reports WHERE vehicleID = ?', [vehicleId], (err, rows) => {
187+
if (err) {
188+
console.error(err.message);
189+
return reject(err);
190+
}
191+
192+
db.close((err) => {
193+
if (err) console.error(err.message);
194+
});
195+
196+
resolve(rows);
197+
})
198+
})
199+
}
200+
61201
export default {
62202
getClosestBus,
203+
insertReport,
204+
fetchReportsByBus
63205
};
206+

0 commit comments

Comments
 (0)