220 lines
6.2 KiB
Python
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))
|