Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion backend/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ Flask==3.0.0
Flask-CORS==4.0.0
Flask-SQLAlchemy==3.1.1
python-dotenv==1.0.0
SQLAlchemy==2.0.23
SQLAlchemy==2.0.35
68 changes: 68 additions & 0 deletions backend/routes.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from flask import Blueprint, request, jsonify
from database import db
from sqlalchemy import inspect
from models import StudySpot
from datetime import datetime
import logging
Expand Down Expand Up @@ -59,6 +60,73 @@ def health_check():

TIME_PATTERN = re.compile(r'^([01]\d|2[0-3]):([0-5]\d)$')

@api_bp.route('/schema', methods=['GET'])
def get_schema():
"""Return database schema information."""
try:
Comment on lines +63 to +66
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The /api/schema endpoint exposes internal database structure (table/column names, types, defaults) and is currently publicly accessible with permissive CORS. This is sensitive information that can aid attackers; consider removing this from the public API or gating it behind admin auth / an environment flag (e.g., only enabled in development).

Copilot uses AI. Check for mistakes.
# Get all tables
inspector = inspect(db.engine)
tables = inspector.get_table_names()

schema = {}
for table in tables:
columns = inspector.get_columns(table)
schema[table] = {
'columns': [
{
'name': col['name'],
'type': str(col['type']),
'nullable': col['nullable'],
'default': str(col['default']) if col['default'] is not None else None
}
for col in columns
]
}

return jsonify(schema), 200
except Exception as e:
logger.error(f"Error getting schema: {str(e)}")
return jsonify({'error': 'Failed to get schema'}), 500

@api_bp.route('/study_spots/raw', methods=['GET'])
def get_raw_study_spots():
"""Return raw StudySpot data as-is from database."""
try:
spots = StudySpot.query.all()
return jsonify([s.to_dict() for s in spots]), 200
Comment on lines +91 to +96
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/study_spots/raw currently returns the same payload as the existing /study_spots endpoint (to_dict() for all rows), but adds another route that fetches the entire table without pagination. If this is meant for debugging/admin use, consider gating it (auth/env flag) or adding pagination/limits; otherwise it’s redundant and increases maintenance surface.

Copilot uses AI. Check for mistakes.
except Exception as e:
logger.error(f"Error fetching raw study spots: {str(e)}")
return jsonify({'error': 'Failed to fetch study spots'}), 500

@api_bp.route('/study_spots/distinct/<column>', methods=['GET'])
def get_distinct_values(column):
try:
allowed_columns = {'spot_type', 'noise_level', 'tags', 'access_hours'}
if column not in allowed_columns:
return jsonify({'error': 'Invalid column name'}), 400

if column == 'access_hours':
# For open now, return boolean options as JSON booleans
return jsonify([True, False]), 200
elif column in {'spot_type', 'tags'}:
# Handle JSON arrays
spots = StudySpot.query.all()
values = set()
Comment on lines +111 to +114
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For spot_type/tags, this endpoint loads all StudySpot rows into memory to compute distinct values. That won’t scale as the table grows; prefer doing this in the database (e.g., via JSON table-valued functions / json_each/jsonb_array_elements depending on the DB) or add a reasonable cap and/or caching strategy.

Copilot uses AI. Check for mistakes.
for spot in spots:
col_data = getattr(spot, column)
if col_data:
for item in col_data:
if item:
values.add(str(item))
else:
# Handle regular string columns
values = db.session.query(getattr(StudySpot, column)).distinct().all()
values = [v[0] for v in values if v[0] is not None]

return jsonify(sorted(list(values))), 200
except Exception as e:
logger.error(f"Error fetching distinct values for column {column}: {str(e)}")
return jsonify({'error': 'Failed to fetch distinct values'}), 500

def _normalize_access_hours(value):
"""
Expand Down