salvagedb_web/app.py
2024-12-01 18:06:42 +03:00

693 lines
27 KiB
Python

from flask import Flask, render_template, send_from_directory, make_response, request, redirect, session, g, json, send_file, abort
import os
import random
import socket
import oracledb
import traceback
import requests
from logging.config import dictConfig
import secrets
from werkzeug.middleware.proxy_fix import ProxyFix
import io
import json
from expiring_dict import ExpiringDict
capcha_site = '6LcJpHMgAAAAAMQLNY_g8J2Kv_qmCGureRN_lbGl'
capcha_site_sec = '6LcJpHMgAAAAAIUf4Jg_7NvawQKZoLoVypDU6-d8'
capcha_site_url='https://www.google.com/recaptcha/api/siteverify'
site = 'salvagedb.com'
app_path = os.path.dirname(os.path.realpath(__file__))
app = Flask(__name__)
app_debug : bool = os.environ.get('APP_DEBUG',False)
app.debug = os.environ.get('APP_DEBUG',False)
app.secret_key = secrets.token_hex()
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_prefix=1)
os.environ['NLS_LANG'] = 'American_America.AL32UTF8'
#Cache
app.cache = ExpiringDict(60*60*24)
try:
gips = open('gips.txt').read().split('\n')
except:
gips=[]
if app_debug:
print(gips)
def save_request(request):
req_data = {}
req_data['endpoint'] = request.endpoint
req_data['method'] = request.method
req_data['cookies'] = request.cookies
req_data['data'] = request.data
req_data['headers'] = dict(request.headers)
req_data['headers'].pop('Cookie', None)
req_data['args'] = request.args
req_data['form'] = request.form
req_data['remote_addr'] = request.remote_addr
return req_data
dictConfig(
{
"version": 1,
"formatters": {
"default": {
"format": "[%(asctime)s] [%(levelname)s | %(module)s] %(message)s",
"datefmt": " %d/%m/%Y %H:%M:%S"
},
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"formatter": "default",
},
"file": {
"class": "logging.handlers.TimedRotatingFileHandler",
"filename": app_path + "/logs/app.log",
"when": "D",
"interval": 10,
"backupCount": 5,
"formatter": "default",
},
},
"root": {"level": "DEBUG", "handlers": ["console", "file"]},
}
)
@app.after_request
def after_request(response):
if request.cookies.get('user_id', None) == None:
response.set_cookie('user_id', str(random.randint(0, 10000000000)), max_age=60 * 60 * 24 * 365 * 10)
return response
def init_session(connection, requestedTag_ignored):
pass
# cursor = connection.cursor()
# cursor.execute("""
# ALTER SESSION SET
# TIME_ZONE = 'UTC'
# NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI'""")
def start_pool():
pool_min = 4
pool_max = 4
pool_inc = 0
if app_debug:
print('Connecting to {}:{}/{}'.format(os.environ.get("DB_HOST"), os.environ.get("DB_PORT"), os.environ.get("DB_DBNAME")))
pool = oracledb.create_pool(
user=os.environ.get("DB_USERNAME"),
password=os.environ.get("DB_PASSWORD"),
dsn='{}:{}/{}'.format(os.environ.get("DB_HOST"), os.environ.get("DB_PORT"), os.environ.get("DB_DBNAME")),
#params=sample_env.get_pool_params(),
min=pool_min,
max=pool_max,
increment=pool_inc,
session_callback=init_session,
)
return pool
@app.route("/sitemap.xml")
def sitemap():
conn = pool.acquire()
cur = conn.cursor()
cur.execute('select distinct trunc(pg/40000)+1 nm from mv_pages');
nm = cur.fetchall()
return render_template('sitemap.xml', site=site, siten=nm)
@app.route("/sitemaps/<int:sitemap_id>.xml")
def sitemaps_xml(sitemap_id):
try:
id: int = int(sitemap_id)
conn = pool.acquire()
cur = conn.cursor()
cur.execute('select pg from mv_pages where sitemap_number = :p1', {'p1': id})
sitemap_pages = cur.fetchall()
return render_template('sitemaps.xml', site=site, sitemap_pages=sitemap_pages)
except:
app.logger.error(traceback.format_exc())
return 'bad request!', 500
@app.route("/")
def index_html():
try:
if 'maxnum' in app.cache:
if app_debug:
print('Find in cache max_num={}'.format(app.cache['maxnum']))
cnt = app.cache['maxnum']
else:
conn = pool.acquire()
cur = conn.cursor()
cur.execute('select max(num) from salvagedb')
cnt = "{:,}".format(cur.fetchone()[0])
app.cache['maxnum'] = cnt
return render_template('index.html', site=site, cnt=cnt ,capcha_site=capcha_site)
except:
app.logger.error(traceback.format_exc())
return 'bad request!', 500
@app.route("/privacy.html")
def privacy_html():
try:
return render_template('privacy.html', site=site)
except:
app.logger.error(traceback.format_exc())
return 'bad request!', 500
@app.route("/robots.txt")
def robot_txt():
try:
return render_template('robots.txt', site=site)
except:
app.logger.error(traceback.format_exc())
return 'bad request!', 500
@app.route("/decode", methods = ['POST'])
def decode():
try:
vin = request.form.get('q').strip()
g_respone = request.form['g-recaptcha-response']
capcha_check = requests.post(url=f'{capcha_site_url}?secret={capcha_site_sec}&response={g_respone}').json()
if capcha_check['success'] == False or capcha_check['score'] <0.5:
app.logger.info(f'Google reuest: {capcha_site_url}?secret={capcha_site_sec}&response={g_respone}')
app.logger.info(f'Bad google answer: {capcha_check}')
abort(401)
return redirect(f'/detail/{vin}.html', 301)
except:
app.logger.error(traceback.format_exc())
return 'bad request!', 500
@app.route("/decodevin.html")
def decodevin_html():
try:
return render_template('decodevin.html', site=site,capcha_site=capcha_site)
except:
app.logger.error(traceback.format_exc())
return 'bad request!', 500
@app.route("/database/page<int:page_num>.html")
def database_page_prc(page_num):
try:
pg = int(page_num)
conn = pool.acquire()
cur = conn.cursor()
user_ip = get_ip(request) ## определение ip клиента
try:
returnVal = cur.callfunc("checkip", int, [user_ip, request.headers.get("CF-IPCountry", 'None'), get_addr(user_ip), 1, 0, 0])
except:
print(request)
app.logger.error(traceback.format_exc())
cur.execute('select max(pg) from mv_pages')
max_page = int(cur.fetchall()[0][0])
cur.execute("""select rownum,s.vin,
COALESCE ((select value from m_JSONS_FROM_NHTSA v3 where v3.svin =s.svin and v3.variableid ='26'),(select val from vind2 where svin = substr(s.vin, 1, 8) || '*' || substr(s.vin, 10, 2) and varb = 'Make'),'UNKNOWN') make,
COALESCE ((select value from m_JSONS_FROM_NHTSA v3 where v3.svin =s.svin and v3.variableid ='28'),(select val from vind2 where svin = substr(s.vin, 1, 8) || '*' || substr(s.vin, 10, 2) and varb = 'Model'),'UNKNOWN') model,
COALESCE ((select value from m_JSONS_FROM_NHTSA v3 where v3.svin =s.svin and v3.variableid ='29'),(select val from vind2 where svin = substr(s.vin, 1, 8) || '*' || substr(s.vin, 10, 2) and varb = 'Model Year'),'UNKNOWN') year
from salvagedb s
where num between (select mi from mv_pages where pg = :p1) and (select ma from mv_pages where pg = :p1)""",
{'p1': pg})
pgf = cur.fetchall()
return render_template('database.html', site=site, cur_page=pg, max_page=max_page, pg=pgf)
except:
app.logger.error(traceback.format_exc())
return 'bad request!', 500
@app.route("/detail/<vin>.html")
def detail_vin(vin):
try:
conn = pool.acquire()
cur = conn.cursor()
user_ip = get_ip(request) ## определение ip клиента
try:
returnVal = cur.callfunc("checkip", int, [user_ip, request.headers.get("CF-IPCountry", 'None'), get_addr(user_ip), 0, 0, 1])
except:
print(request)
app.logger.error(traceback.format_exc())
cur.execute("""select 'None', COALESCE((select value from m_JSONS_FROM_NHTSA v3 where v3.svin =s.svin and v3.variableid ='26'),(select val from vind2 where svin = substr(s.vin, 1, 8) || '*' || substr(s.vin, 10, 2) and varb = 'Make'),'UNKNOWN') make,
COALESCE((select value from m_JSONS_FROM_NHTSA v3 where v3.svin =s.svin and v3.variableid ='28'),(select val from vind2 where svin = substr(s.vin, 1, 8) || '*' || substr(s.vin, 10, 2) and varb = 'Model'),'UNKNOWN') model,
COALESCE((select value from m_JSONS_FROM_NHTSA v3 where v3.svin =s.svin and v3.variableid ='29'),(select val from vind2 where svin = substr(s.vin, 1, 8) || '*' || substr(s.vin, 10, 2) and varb = 'Model Year'),'UNKNOWN') year,
COALESCE((select value from m_JSONS_FROM_NHTSA v3 where v3.svin =s.svin and v3.variableid ='5'),(select val from vind2 where svin = substr(s.vin, 1, 8) || '*' || substr(s.vin, 10, 2) and varb = 'Body Class'),'UNKNOWN') body,
COALESCE((select value from m_JSONS_FROM_NHTSA v3 where v3.svin =s.svin and v3.variableid ='13'),(select val from vind2 where svin = substr(s.vin, 1, 8) || '*' || substr(s.vin, 10, 2) and varb = 'Engine Model'),'UNKNOWN') engine,
COALESCE((select value from m_JSONS_FROM_NHTSA v3 where v3.svin =s.svin and v3.variableid ='9'),(select val from vind2 where svin = substr(s.vin, 1, 8) || '*' || substr(s.vin, 10, 2) and varb = 'Engine Number of Cylinders'),'UNKNOWN') celindr,
COALESCE((select value from m_JSONS_FROM_NHTSA v3 where v3.svin =s.svin and v3.variableid ='15'),(select val from vind2 where svin = substr(s.vin, 1, 8) || '*' || substr(s.vin, 10, 2) and varb = 'Drive Type'),'-') drive
from (select substr(:p1,1,10) svin, :p1 vin from dual) s """, {'p1': vin})
res = cur.fetchall()
return render_template('details.html', site=site, vin=vin, det=res, capcha_site=capcha_site)
except:
app.logger.error(traceback.format_exc())
return 'bad request!', 500
@app.route('/search', methods = ['POST'])
def search():
try:
user_ip = get_ip(request) ## определение ip клиента
vin = request.form.get('q')
ua = request.headers.get('User-Agent')
app.logger.info(f'AgeNt: {ua}')
gmedia = False
if ua == 'Mediapartners-Google':
app.logger.info(f'Find MediaPartner Aget {ua}')
if user_ip in gips:
gmedia = True
else:
gmedia = False
try:
f = open('ips.txt','a')
f.write(f'{user_ip}\n')
finally:
f.close()
g_respone = request.args.get('g-recaptcha-response')
capcha_check = requests.post(url=f'{capcha_site_url}?secret={capcha_site_sec}&response={g_respone}').json()
if capcha_check['success'] == False or capcha_check['score'] <0.5:
app.logger.info(f'Google reuest: {capcha_site_url}?secret={capcha_site_sec}&response={g_respone}')
app.logger.info(f'Bad google answer: {capcha_check}')
if gmedia ==False and app_debug==True:
req_data = save_request(request)
app.logger.info(json.dumps(req_data, indent=4, default=str))
return 'bad req', 401
if len(vin) != 17:
return 'bad vin!', 500
conn = pool.acquire()
cur = conn.cursor()
returnVal = cur.callfunc("checkip", int, [user_ip, request.headers.get("CF-IPCountry", 'None'), get_addr(user_ip), 0, 1, 0])
cur.execute("""select 'None', COALESCE((select value from m_JSONS_FROM_NHTSA v3 where v3.svin =s.svin and v3.variableid ='26'),(select val from vind2 where svin = substr(s.vin, 1, 8) || '*' || substr(s.vin, 10, 2) and varb = 'Make'),'UNKNOWN') make,
COALESCE((select value from m_JSONS_FROM_NHTSA v3 where v3.svin =s.svin and v3.variableid ='28'),(select val from vind2 where svin = substr(s.vin, 1, 8) || '*' || substr(s.vin, 10, 2) and varb = 'Model'),'UNKNOWN') model,
COALESCE((select value from m_JSONS_FROM_NHTSA v3 where v3.svin =s.svin and v3.variableid ='29'),(select val from vind2 where svin = substr(s.vin, 1, 8) || '*' || substr(s.vin, 10, 2) and varb = 'Model Year'),'UNKNOWN') year,
COALESCE((select value from m_JSONS_FROM_NHTSA v3 where v3.svin =s.svin and v3.variableid ='5'),(select val from vind2 where svin = substr(s.vin, 1, 8) || '*' || substr(s.vin, 10, 2) and varb = 'Body Class'),'UNKNOWN') body,
COALESCE((select value from m_JSONS_FROM_NHTSA v3 where v3.svin =s.svin and v3.variableid ='13'),(select val from vind2 where svin = substr(s.vin, 1, 8) || '*' || substr(s.vin, 10, 2) and varb = 'Engine Model'),'UNKNOWN') engine,
COALESCE((select value from m_JSONS_FROM_NHTSA v3 where v3.svin =s.svin and v3.variableid ='9'),(select val from vind2 where svin = substr(s.vin, 1, 8) || '*' || substr(s.vin, 10, 2) and varb = 'Engine Number of Cylinders'),'UNKNOWN') celindr,
COALESCE((select value from m_JSONS_FROM_NHTSA v3 where v3.svin =s.svin and v3.variableid ='15'),(select val from vind2 where svin = substr(s.vin, 1, 8) || '*' || substr(s.vin, 10, 2) and varb = 'Drive Type'),'-') drive
from (select substr(:p1,1,10) svin, :p1 vin from dual) s """, {'p1': vin})
res = cur.fetchall()
cur.execute("""select rownum, t.vin, t.title, t.odo, t.odos, t.dem1, t.dem2, t.year||'/'||t.month from salvagedb t where vin = :p1 and svin = substr(:p1,1,10) """, {'p1': vin})
his = cur.fetchall()
return render_template('search.html', site=site, vin=vin, det=res, his=his)
except:
app.logger.error(traceback.format_exc())
return 'bad request!', 500
## API
@app.route("/api/search")
def api_search():
try:
access_code = request.args.get('access_code', None)
vin = request.args.get('vin', None)
user_ip = get_ip(request) ## определение ip клиента
# базовые проверки входящих аргументов
if len(vin) != 17 or vin is None:
ret = {'status': 'incorrect vin'}
response = app.response_class(
response=json.dumps(ret),
status=200,
mimetype='application/json'
)
if app.debug:
app.logger.debug(json.dumps(ret))
return response
if len(access_code) > 16 or access_code is None:
ret = {'status': 'incorrect access_code'}
response = app.response_class(
response=json.dumps(ret),
status=200,
mimetype='application/json'
)
if app.debug:
app.logger.debug(json.dumps(ret))
return response
conn = pool.acquire()
cur = conn.cursor()
## проверяем access_code
cur.execute(
'select t.summ, t.found_price, t.notfound_price, t.decode_price from restfact t where access_code = :p1',
{'p1': str(access_code)})
res = cur.fetchone()
summ = res[0]
found_price = res[1]
notfound_price = res[2]
decode_price = res[3]
### не достаточно средств
if summ <= 0:
ret = {'status': 'You have insufficient balance.'}
response = app.response_class(
response=json.dumps(ret),
status=200,
mimetype='application/json'
)
if app.debug:
app.logger.debug(json.dumps(ret))
return response
## ищем в истории
cur.execute('select t.odo,t.odos,t.title,t.dem1,t.dem2,t.year,t.month from salvagedb t where vin =:p1 and svin = :p2',
{'p1': vin.upper(), 'p2': vin.upper()[:10]})
res = cur.fetchall()
if len(res) > 0:
# found
dat = []
for it in res:
dat.append({
'odometer': it[0],
'odometer_status': it[1],
'title': it[2],
'damage1': it[3],
'damage2': it[4],
'add_to_db': '{}-{}'.format(it[5], it[6])
})
ret = {
'status': 'found',
'vin': vin.upper(),
'cost': found_price,
'records': dat
}
response = app.response_class(
response=json.dumps(ret),
status=200,
mimetype='application/json'
)
if app.debug:
app.logger.debug(json.dumps(ret))
cur.execute("insert into billing(access_code, vin, ip, status, dt, cost) values (:p1, :p2, :p3, :p4, CAST(SYSTIMESTAMP AT TIME ZONE 'UTC' AS DATE), :p5)",
{'p1': str(access_code), 'p2': str(vin), 'p3': str(user_ip), 'p4': 'FOUND', 'p5': found_price})
conn.commit()
return response
else:
# nor found
ret = {'status': 'NOT FOUND', 'cost': notfound_price}
if app.debug:
app.logger.debug(json.dumps(ret))
response = app.response_class(
response=json.dumps(ret),
status=200,
mimetype='application/json'
)
cur.execute("insert into billing(access_code, vin, ip, status, dt, cost) values (:p1, :p2, :p3, :p4, CAST(SYSTIMESTAMP AT TIME ZONE 'UTC' AS DATE), :p5)",
{'p1': str(access_code), 'p2': str(vin), 'p3': str(user_ip), 'p4': 'NOT FOUND', 'p5': notfound_price})
conn.commit()
return response
except:
app.logger.error(traceback.format_exc())
return 'bad request!', 500
@app.route("/api/restfact")
def api_restfact():
access_code = request.args.get('access_code', None)
if len(access_code) > 16 or access_code is None:
ret = {'status': 'incorrect access_code'}
response = app.response_class(
response=json.dumps(ret),
status=200,
mimetype='application/json'
)
if app.debug:
app.logger.debug(json.dumps(ret))
return response
conn = pool.acquire()
cur = conn.cursor()
cur.callproc('make_billing')
cur.execute(
'select t.summ, t.found_price, t.notfound_price, t.decode_price from restfact t where access_code = :p1',
{'p1': str(access_code)})
res = cur.fetchone()
summ = res[0]
if len(res) <= 0:
ret = {'status': 'incorrect access_code'}
response = app.response_class(
response=json.dumps(ret),
status=200,
mimetype='application/json'
)
if app.debug:
app.logger.debug(json.dumps(ret))
return response
else:
ret = {
'access_code': access_code,
'restfact': summ
}
response = app.response_class(
response=json.dumps(ret),
status=200,
mimetype='application/json'
)
if app.debug:
app.logger.debug(json.dumps(ret))
return response
@app.route("/api/restfact_detail")
def restfact_detail():
access_code = request.args.get('access_code', None)
if len(access_code) > 16 or access_code is None:
ret = {'status': 'incorrect access_code'}
response = app.response_class(
response=json.dumps(ret),
status=200,
mimetype='application/json'
)
if app.debug:
app.logger.debug(json.dumps(ret))
return response
conn = pool.acquire()
cur = conn.cursor()
cur.callproc('make_billing')
cur.execute('select rownum from restfact t where access_code = :p1', {'p1': str(access_code)})
res = cur.fetchall()
if len(res) <= 0:
ret = {'status': 'incorrect access_code'}
response = app.response_class(
response=json.dumps(ret),
status=200,
mimetype='application/json'
)
if app.debug:
app.logger.debug(json.dumps(ret))
return response
cur.execute("select vin, ip, status, dt, cost from billing where access_code = :p1 and dt > CAST(SYSTIMESTAMP AT TIME ZONE 'UTC' AS DATE) - 45 and made = 1 order by id", {'p1': str(access_code)})
res = cur.fetchall()
if len(res) <= 0:
ret = {'status': 'billing not found'}
response = app.response_class(
response=json.dumps(ret),
status=200,
mimetype='application/json'
)
if app.debug:
app.logger.debug(json.dumps(ret))
return response
else:
dat = []
for it in res:
dat.append(
{
'VIN': it[0],
'IP': it[1],
'STATUS': it[2],
'DATETIME': it[3].isoformat(),
'COST': it[4]
}
)
ret = {
'access_code': access_code,
'report': dat
}
response = app.response_class(
response=json.dumps(ret),
status=200,
mimetype='application/json'
)
if app.debug:
app.logger.debug(json.dumps(ret))
return response
## API V2
@app.route("/api/v2/search")
def api_search_v2():
try:
access_code = request.args.get('access_code', None)
vin = request.args.get('vin', None)
user_ip = get_ip(request) ## определение ip клиента
# базовые проверки входящих аргументов
if len(vin) != 17 or vin is None:
ret = {'status': 'incorrect vin'}
response = app.response_class(
response=json.dumps(ret),
status=200,
mimetype='application/json'
)
if app.debug:
app.logger.debug(json.dumps(ret))
return response
if len(access_code) > 16 or access_code is None:
ret = {'status': 'incorrect access_code'}
response = app.response_class(
response=json.dumps(ret),
status=200,
mimetype='application/json'
)
if app.debug:
app.logger.debug(json.dumps(ret))
return response
conn = pool.acquire()
cur = conn.cursor()
## проверяем access_code
cur.execute(
'select t.summ, t.found_price, t.notfound_price, t.decode_price from restfact t where access_code = :p1',
{'p1': str(access_code)})
res = cur.fetchone()
summ = res[0]
found_price = res[1]
notfound_price = res[2]
decode_price = res[3]
### не достаточно средств
if summ <= 0:
ret = {'status': 'You have insufficient balance.'}
response = app.response_class(
response=json.dumps(ret),
status=200,
mimetype='application/json'
)
if app.debug:
app.logger.debug(json.dumps(ret))
return response
## ищем в истории
cur.execute('''select s.odo,s.odos,s.title,s.dem1,s.dem2,s.year,s.month, json_value(i.jdata, '$.Runs_Drive') RD_Status, json_value(i.jdata, '$.Locate') Sale_Location, json_value(i.jdata, '$.RepCost') as Repear_cost,
(select count(1) from salvage_images si where si.vin = s.vin) image_count from salvagedb.salvagedb s left join addinfo i on s.num = i.numid where vin =:p1 and svin = :p2''',
{'p1': vin.upper(), 'p2': vin.upper()[:10]})
res = cur.fetchall()
if len(res) > 0:
# found
dat = []
for it in res:
dat.append({
'odometer': it[0],
'odometer_status': it[1],
'title': it[2],
'damage1': it[3],
'damage2': it[4],
'add_to_db': '{}-{}'.format(it[5], it[6]),
'RD_Status':it[7],
'Sale_Location':it[8],
'Repear_Cost':it[9],
'Image_Count':it[10]
})
ret = {
'status': 'found',
'vin': vin.upper(),
'cost': found_price,
'records': dat
}
response = app.response_class(
response=json.dumps(ret),
status=200,
mimetype='application/json'
)
if app.debug:
app.logger.debug(json.dumps(ret))
cur.execute("insert into billing(access_code, vin, ip, status, dt, cost) values (:p1, :p2, :p3, :p4, CAST(SYSTIMESTAMP AT TIME ZONE 'UTC' AS DATE), :p5)",
{'p1': str(access_code), 'p2': str(vin), 'p3': str(user_ip), 'p4': 'FOUND', 'p5': found_price})
conn.commit()
return response
else:
# nor found
ret = {'status': 'NOT FOUND', 'cost': notfound_price}
if app.debug:
app.logger.debug(json.dumps(ret))
response = app.response_class(
response=json.dumps(ret),
status=200,
mimetype='application/json'
)
cur.execute("insert into billing(access_code, vin, ip, status, dt, cost) values (:p1, :p2, :p3, :p4, CAST(SYSTIMESTAMP AT TIME ZONE 'UTC' AS DATE), :p5)",
{'p1': str(access_code), 'p2': str(vin), 'p3': str(user_ip), 'p4': 'NOT FOUND', 'p5': notfound_price})
conn.commit()
return response
except:
app.logger.error(traceback.format_exc())
return 'bad request!', 500
@app.route("/api/v2/reqimage")
def api_reqimage():
pass
@app.route("/ads.txt")
def ads_txt():
try:
return render_template('ads.txt')
except:
app.logger.error(traceback.format_exc())
return 'bad request!', 500
@app.route('/favicon.ico')
def logo():
return send_file(app_path+"/static/favicon.ico")
def get_ip(req) -> str:
if 'X-Forwarded-For' in req.headers:
proxy_data = req.headers['X-Forwarded-For']
ip_list = proxy_data.split(',')
user_ip = req.headers.get("Cf-Connecting-Ip") or ip_list[0] # first address in list is User IP
else:
user_ip = req.headers.get("Cf-Connecting-Ip") or req.remote_addr
return user_ip
def get_addr(req) -> str:
try:
return socket.gethostbyaddr(req)[0]
except:
return req
if __name__ == '__main__':
# Start a pool of connections
pool = start_pool()
app.run(port=int(os.environ.get('PORT', '8080')))