furmeet-geoquery/backend_code/app.py

220 lines
6.2 KiB
Python

#!/usr/bin/env flask
# -*- encoding: utf-8 -*-
from flask import Flask, Response, request, render_template, redirect, send_from_directory, jsonify
from flask_sqlalchemy import SQLAlchemy
from flask_cors import CORS
from flask_wtf import FlaskForm
from pathlib import Path
import datetime
import sqlite3
import wtforms
import json
COOKIE_BASE_DOMAIN = '.furmeet.app'
SITE_NAME = 'FurmeetApp'
sensible_info = Path('creds.txt').read_text().splitlines()
app = Flask(__name__)
cors = CORS(app, origins='*')
app.secret_key = sensible_info[0]
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SQLALCHEMY_DATABASE_URI'] = sensible_info[1]
db = SQLAlchemy(app)
class Country(db.Model):
__tablename__ = 'countries'
id = db.Column('rowid', db.Integer, primary_key=True)
name = db.Column(db.String())
iso2 = db.Column(db.String())
iso3 = db.Column(db.String())
capital = db.Column(db.String())
capital_id = db.Column(db.Integer())
lat = db.Column(db.Float())
lng = db.Column(db.Float())
@property
def serialize(self):
return {
'id': self.id,
'name': self.name,
'iso2': self.iso2,
'iso3': self.iso3,
'lat': self.lat,
'lng': self.lng,
'capital': self.capital,
'capital_id': self.capital_id,
}
class State(db.Model):
__tablename__ = 'states'
id = db.Column('rowid', db.Integer, primary_key=True)
parent_id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String())
capital = db.Column(db.String())
capital_id = db.Column(db.Integer())
lat = db.Column(db.Float())
lng = db.Column(db.Float())
@property
def serialize(self):
return {
'id': self.id,
'name': self.name,
'lat': self.lat,
'lng': self.lng,
'capital': self.capital,
'capital_id': self.capital_id,
'parent_id': self.parent_id,
'parent': Country.query.filter_by(id=self.parent_id).first().serialize,
}
class City(db.Model):
__tablename__ = 'cities'
id = db.Column('rowid', db.Integer, primary_key=True)
parent_id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String())
name_ascii = db.Column(db.String())
capital_of = db.Column(db.String())
capital_of_no = db.Column(db.Integer())
lat = db.Column(db.Float())
lng = db.Column(db.Float())
@property
def serialize(self):
return {
'id': self.id,
'name': self.name,
'name_ascii': self.name_ascii,
'lat': self.lat,
'lng': self.lng,
'capital_of': self.capital_of,
'capital_of_no': self.capital_of_no,
'parent_id': self.parent_id,
'parent': State.query.filter_by(id=self.parent_id).first().serialize,
}
HIDDEN_ROUTES = [
'static',
'search',
'index',
]
class UnsafeFlaskForm(FlaskForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **{'csrf_enabled': False}, **kwargs)
def JsonResponse(obj, kind='any[]', human_readable=False, http_code=200):
return Response(json.dumps(
{'kind': kind, 'data': obj},
indent=(4 if human_readable else None)
), mimetype='application/json', status=http_code)
@app.route("/")
def index():
return redirect("/search.html")
@app.route("/search.html")
def search():
return send_from_directory("cached", "search.html")
@app.route("/routes")
def routes():
entries = [{
'name': rule.endpoint,
'route': rule.rule,
'methods': sorted(rule.methods),
} for rule in app.url_map.iter_rules() if rule.endpoint not in HIDDEN_ROUTES]
return JsonResponse(entries, 'List[Route]', True)
def score_country(country, matching, score=0):
if country['name'] in matching:
score += 1
if country['iso2'] in matching:
score += 1
if country['iso3'] in matching:
score += 1
return score
def score_state(state, matching, score=0):
if state['name'] in matching:
score += 1
return score_country(state['parent'], matching, score)
def score_city(city, matching, score=0):
if city['name'] in matching:
score += 1
if city['name_ascii'] in matching:
score += 1
return score_state(city['parent'], matching, score)
@app.route("/query", methods=['POST'])
def query():
place = request.form.get('place', '')
human_readable = request.form.get('human_readable', '')
if len(place) <= 0:
return JsonResponse(list(), 'List[Geolocated]')
GEONAMES = json.loads(
Path('simplemaps_worldcities_basic_names.json').read_text())
place_lwr = place.lower()
place_parts = [
GEONAMES[i]
for i, nm in enumerate(GEONAMES)
if nm.lower() in place_lwr and nm != ""
]
locations = dict()
locations['country'] = Country.query.filter(
Country.name.in_(place_parts) |
Country.iso2.in_(place_parts) |
Country.iso3.in_(place_parts)
).all()
locations['state'] = State.query.filter(
State.name.in_(place_parts)
).all()
locations['city'] = City.query.filter(
City.name.in_(place_parts) |
City.name_ascii.in_(place_parts)
).all()
for i in ['country', 'state', 'city']:
locations[i] = list(map(
lambda a: a.serialize,
locations[i]
))
final = [
{
'kind': x[5],
'score': -x[0],
'data': x[4],
}
for x in sorted(
[
(-score_city(city, place_parts),
-len(city['name']), city['name'], city['id'], city, 'City')
for city in locations['city']
] + [
(-score_state(state, place_parts),
-len(state['name']), state['name'], state['id'], state, 'State')
for state in locations['state']
] + [
(-score_country(country, place_parts),
-len(country['name']), country['name'], country['id'], country, 'Country')
for country in locations['country']
]
)
]
return JsonResponse(final, 'List[Geolocated]', len(human_readable))