spotnotes.app/blueprints/search.py

79 lines
2.8 KiB
Python
Raw Normal View History

2026-05-12 23:58:18 +00:00
from flask import Blueprint, request, jsonify, session
from sqlalchemy import or_, func
2026-05-12 23:58:18 +00:00
from extensions import db
from models.note import Note
from models.notepad import Notepad
from models.favorite import Favorite
from models.collaborator import Collaborator
2026-05-12 23:58:18 +00:00
import urllib.request
import urllib.parse
import json
search_bp = Blueprint('search', __name__)
@search_bp.route('/api/search')
def search_notes():
query = request.args.get('q', '')
if not query:
return jsonify([])
user_id = session.get('user_id')
# Server searches public unencrypted notes.
# Private notes are encrypted client-side and searched locally in browser for zero-knowledge privacy.
2026-05-12 23:58:18 +00:00
sql_query = db.session.query(Note, Notepad).join(Notepad, Note.notepad_id == Notepad.id).filter(
Notepad.is_public == True,
Note.is_encrypted == False,
Note.is_hidden == False,
2026-05-12 23:58:18 +00:00
or_(
Note.title.ilike(f'%{query}%'),
Note.content.ilike(f'%{query}%')
)
)
results = sql_query.all()
# Get user favorites if logged in
fav_ids = set()
if user_id:
favs = Favorite.query.filter_by(user_id=user_id).all()
fav_ids = {f.notepad_id for f in favs}
# Eagerly fetch note counts and permissions to prevent N+1 queries
note_counts = dict(db.session.query(Note.notepad_id, func.count(Note.id)).filter(Note.is_hidden == False).group_by(Note.notepad_id).all())
collab_write_pad_ids = set()
if user_id:
collab_write_pad_ids = {c.notepad_id for c in Collaborator.query.filter_by(user_id=user_id, permission='write').all()}
2026-05-12 23:58:18 +00:00
# Construct response with extra info for frontend (owner_id, is_public)
output = []
for note, pad in results:
n_dict = note.to_dict()
p_dict = pad.to_dict(note_count=note_counts.get(pad.id, 0))
2026-05-12 23:58:18 +00:00
p_dict['is_favorite'] = pad.id in fav_ids
p_dict['can_write'] = (pad.user_id == user_id) or (pad.id in collab_write_pad_ids)
2026-05-12 23:58:18 +00:00
n_dict['notepad'] = p_dict
n_dict['owner_id'] = pad.user_id # Keep for map marker compatibility
n_dict['is_public'] = pad.is_public # Keep for map marker compatibility
output.append(n_dict)
return jsonify(output)
@search_bp.route('/api/geocode')
def geocode():
q = request.args.get('q', '')
if not q:
return jsonify([])
try:
# Proxy request to Nominatim with required User-Agent
url = f"https://nominatim.openstreetmap.org/search?format=json&q={urllib.parse.quote(q)}"
req = urllib.request.Request(url, headers={'User-Agent': 'SpotNotesApp/1.0'})
with urllib.request.urlopen(req) as response:
data = json.loads(response.read().decode())
return jsonify(data)
except Exception as e:
print(f"Geocode error: {e}")
return jsonify({'error': str(e)}), 500