Merge branch 'feature/bootstrap5-migration' into 'main'

feat: обновление API для поиска автомобиля по VIN - изменен маршрут на...

See merge request root/salvagedb!1
This commit is contained in:
Administrator 2025-05-14 17:29:09 +00:00
commit 04ad3163d4
35 changed files with 1754 additions and 682 deletions

6
.cursorignore Normal file
View File

@ -0,0 +1,6 @@
gips.txt
ips.txt
killapp.sh
refresh_ip.sh
sync_cloud_nginx.sh
watchdog.sh

6
.gitignore vendored
View File

@ -1,2 +1,4 @@
__pycache__
logs
__pycache__/
*.pyc
logs
node_modules/

View File

@ -77,10 +77,10 @@ paths:
type: string
description: Error description
/decode:
/v2/search:
post:
summary: Decode VIN
description: Decode VIN number to get vehicle information
summary: Search vehicle by VIN (v2)
description: Search for vehicle information with full history records
parameters:
- name: access_code
in: query
@ -101,21 +101,77 @@ paths:
example: "1HGCM82633A123456"
responses:
'200':
description: Successful decoding
description: Successful search
content:
application/json:
schema:
type: object
properties:
make:
type: string
description: Vehicle make
model:
type: string
description: Vehicle model
year:
type: integer
description: Manufacturing year
found:
type: boolean
description: Whether the vehicle was found
details:
type: object
description: Vehicle details
properties:
make:
type: string
description: Vehicle manufacturer
model:
type: string
description: Vehicle model
year:
type: integer
description: Manufacturing year
body:
type: string
description: Vehicle body type
engine:
type: string
description: Engine model
cylinders:
type: string
description: Number of engine cylinders
drive:
type: string
description: Drive type (FWD/RWD/AWD)
records:
type: array
description: Vehicle history records
items:
type: object
properties:
odometer:
type: integer
description: Odometer reading in miles
odometer_status:
type: string
description: Odometer status (ACTUAL, EXEMPT, NOT ACTUAL)
title:
type: string
description: Title status (CLEAR, SALVAGE, REBUILT)
damage1:
type: string
description: Primary damage type
damage2:
type: string
description: Secondary damage type
add_to_db:
type: string
format: date
description: Date added to database
RD_Status:
type: string
description: Vehicle status
Sale_Location:
type: string
description: Sale location
Repear_Cost:
type: number
description: Repair cost in USD
Photo_Count:
type: integer
description: Number of available photos
'400':
description: Invalid VIN or access code
content:
@ -127,57 +183,57 @@ paths:
type: string
description: Error description
/detail/{vin}:
get:
summary: Get detailed information
description: Get detailed information about a vehicle by VIN
/v2/reqphoto:
post:
summary: Get vehicle photos
description: Get photos of the vehicle by VIN
parameters:
- name: vin
in: path
required: true
schema:
type: string
description: Vehicle Identification Number
- name: access_code
in: query
required: true
schema:
type: string
description: API access code for authentication
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
vin:
type: string
description: Vehicle Identification Number
example: "1HGCM82633A123456"
responses:
'200':
description: Successful information retrieval
description: Successful photo retrieval
content:
application/json:
schema:
type: object
properties:
vin:
type: string
description: VIN number
make:
type: string
description: Vehicle make
model:
type: string
description: Vehicle model
year:
type: integer
description: Manufacturing year
history:
found:
type: boolean
description: Whether photos were found
photos:
type: array
description: List of photo information
items:
type: object
properties:
url:
type: string
description: URL of the photo
type:
type: string
description: Type of photo (FRONT, REAR, SIDE, INTERIOR, etc.)
date:
type: string
format: date
description: Event date
event:
type: string
description: Event description
'404':
description: Vehicle not found
description: Date when photo was taken
'400':
description: Invalid VIN or access code
content:
application/json:
schema:
@ -275,4 +331,4 @@ paths:
properties:
error:
type: string
description: Error description
description: Error description

584
app.py
View File

@ -13,16 +13,29 @@ import json
from expiring_dict import ExpiringDict
import uuid
from flask_swagger_ui import get_swaggerui_blueprint
import datetime
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.platypus import SimpleDocTemplate, Table, TableStyle, Paragraph, Spacer, Image
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import inch, mm
from reportlab.pdfgen.canvas import Canvas
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT
from reportlab.platypus import Frame
import sys
import logging
capcha_score: float = 0.1
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)
@ -32,7 +45,7 @@ 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)
# Swagger UI
@ -68,34 +81,104 @@ def save_request(request):
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"]},
}
)
# Создаем директорию для логов если её нет
log_dir = os.path.join(app_path, 'logs')
if not os.path.exists(log_dir):
os.makedirs(log_dir)
# Определяем уровень логирования в зависимости от окружения
log_level = 'DEBUG' if app_debug else 'INFO'
dictConfig({
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"default": {
"format": "[%(asctime)s] [%(levelname)s] [%(name)s:%(lineno)d] %(message)s",
"datefmt": "%Y-%m-%d %H:%M:%S"
},
"error": {
"format": "[%(asctime)s] [%(levelname)s] [%(name)s:%(lineno)d] [%(pathname)s] %(message)s",
"datefmt": "%Y-%m-%d %H:%M:%S"
}
},
"handlers": {
"console": {
"class": "logging.StreamHandler",
"formatter": "default",
"stream": "ext://sys.stdout"
},
"file": {
"class": "logging.handlers.RotatingFileHandler",
"formatter": "default",
"filename": os.path.join(log_dir, "app.log"),
"maxBytes": 10485760, # 10MB
"backupCount": 10,
"encoding": "utf8"
},
"error_file": {
"class": "logging.handlers.RotatingFileHandler",
"formatter": "error",
"filename": os.path.join(log_dir, "error.log"),
"maxBytes": 10485760, # 10MB
"backupCount": 10,
"encoding": "utf8",
"level": "ERROR"
}
},
"loggers": {
"werkzeug": {
"handlers": ["console", "file"],
"level": log_level,
"propagate": False
},
"flask": {
"handlers": ["console", "file"],
"level": log_level,
"propagate": False
},
"app": {
"handlers": ["console", "file", "error_file"],
"level": log_level,
"propagate": False
}
},
"root": {
"level": log_level,
"handlers": ["console", "file", "error_file"]
}
})
# Создаем логгер для приложения
logger = logging.getLogger("app")
class OverlayCanvas(Canvas):
def __init__(self, *args, **kwargs):
Canvas.__init__(self, *args, **kwargs)
self._saved_page_states = []
def showPage(self):
self._saved_page_states.append(dict(self.__dict__))
self._startPage()
def save(self):
for state in self._saved_page_states:
self.__dict__.update(state)
self.draw_overlay() # теперь рисуем поверх
Canvas.showPage(self)
Canvas.save(self)
def draw_overlay(self):
stamp_path = os.path.join(app_path, 'static', 'stamp256.png')
if os.path.exists(stamp_path):
self.drawImage(
stamp_path,
x=400, # настроить по ширине
y=400, # настроить по высоте
width=100,
height=100,
mask='auto'
)
@app.after_request
def after_request(response):
@ -151,7 +234,7 @@ def sitemaps_xml(sitemap_id):
sitemap_pages = cur.fetchall()
return render_template('sitemaps.xml', site=site, sitemap_pages=sitemap_pages)
except:
app.logger.error(traceback.format_exc())
logger.error(traceback.format_exc())
return 'bad request!', 500
@app.route("/")
@ -169,7 +252,7 @@ def index_html():
app.cache['maxnum'] = cnt
return render_template('index.html', site=site, cnt=cnt ,capcha_site=capcha_site)
except:
app.logger.error(traceback.format_exc())
logger.error(traceback.format_exc())
return 'bad request!', 500
@app.route("/privacy.html")
@ -178,7 +261,7 @@ def privacy_html():
return render_template('privacy.html', site=site)
except:
app.logger.error(traceback.format_exc())
logger.error(traceback.format_exc())
return 'bad request!', 500
@app.route("/robots.txt")
@ -186,7 +269,7 @@ def robot_txt():
try:
return render_template('robots.txt', site=site)
except:
app.logger.error(traceback.format_exc())
logger.error(traceback.format_exc())
return 'bad request!', 500
@app.route("/decode", methods = ['POST'])
@ -195,13 +278,13 @@ def decode():
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}')
if capcha_check['success'] == False or capcha_check['score'] <capcha_score:
logger.info(f'Google reuest: {capcha_site_url}?secret={capcha_site_sec}&response={g_respone}')
logger.info(f'Bad google answer: {capcha_check}')
abort(401)
return redirect(f'/detail/{vin}.html', 301)
except:
app.logger.error(traceback.format_exc())
logger.error(traceback.format_exc())
return 'bad request!', 500
@ -210,7 +293,7 @@ def decodevin_html():
try:
return render_template('decodevin.html', site=site,capcha_site=capcha_site)
except:
app.logger.error(traceback.format_exc())
logger.error(traceback.format_exc())
return 'bad request!', 500
@app.route("/database/page<int:page_num>.html")
@ -224,7 +307,7 @@ def database_page_prc(page_num):
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())
logger.error(traceback.format_exc())
cur.execute('select max(pg) from mv_pages')
max_page = int(cur.fetchall()[0][0])
@ -238,7 +321,7 @@ def database_page_prc(page_num):
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())
logger.error(traceback.format_exc())
return 'bad request!', 500
@app.route("/detail/<vin>.html")
@ -251,7 +334,7 @@ def detail_vin(vin):
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())
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,
@ -264,7 +347,7 @@ def detail_vin(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())
logger.error(traceback.format_exc())
return 'bad request!', 500
@ -274,17 +357,17 @@ def search():
user_ip = get_ip(request) ## определение ip клиента
vin = request.form.get('q')
ua = request.headers.get('User-Agent')
app.logger.info(f'AgeNt: {ua}')
logger.info(f'AgeNt: {ua}')
g_respone = request.form.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 capcha_check['success'] == False or capcha_check['score'] <capcha_score:
logger.info(f'Google reuest: {capcha_site_url}?secret={capcha_site_sec}&response={g_respone}')
logger.info(f'Bad google answer: {capcha_check}')
if app_debug==True:
req_data = save_request(request)
app.logger.info(json.dumps(req_data, indent=4, default=str))
logger.info(json.dumps(req_data, indent=4, default=str))
return 'google recaptcha req low score', 401
@ -305,9 +388,14 @@ def search():
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()
# Сохраняем VIN в сессии
session['last_searched_vin'] = vin
session['last_search_time'] = datetime.datetime.now().timestamp()
return render_template('search.html', site=site, vin=vin, det=res, his=his)
except:
app.logger.error(traceback.format_exc())
logger.error(traceback.format_exc())
return 'bad request!', 500
## API
@ -328,7 +416,7 @@ def api_search():
mimetype='application/json'
)
if app.debug:
app.logger.debug(json.dumps(ret))
logger.debug(json.dumps(ret))
return response
if len(access_code) > 16 or access_code is None:
ret = {'status': 'incorrect access_code'}
@ -338,7 +426,7 @@ def api_search():
mimetype='application/json'
)
if app.debug:
app.logger.debug(json.dumps(ret))
logger.debug(json.dumps(ret))
return response
conn = pool.acquire()
@ -363,7 +451,7 @@ def api_search():
mimetype='application/json'
)
if app.debug:
app.logger.debug(json.dumps(ret))
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',
@ -394,7 +482,7 @@ def api_search():
mimetype='application/json'
)
if app.debug:
app.logger.debug(json.dumps(ret))
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()
@ -404,7 +492,7 @@ def api_search():
# nor found
ret = {'status': 'NOT FOUND', 'cost': notfound_price}
if app.debug:
app.logger.debug(json.dumps(ret))
logger.debug(json.dumps(ret))
response = app.response_class(
response=json.dumps(ret),
status=200,
@ -416,7 +504,7 @@ def api_search():
return response
except:
app.logger.error(traceback.format_exc())
logger.error(traceback.format_exc())
return 'bad request!', 500
@ -431,7 +519,7 @@ def api_restfact():
mimetype='application/json'
)
if app.debug:
app.logger.debug(json.dumps(ret))
logger.debug(json.dumps(ret))
return response
conn = pool.acquire()
@ -450,7 +538,7 @@ def api_restfact():
mimetype='application/json'
)
if app.debug:
app.logger.debug(json.dumps(ret))
logger.debug(json.dumps(ret))
return response
else:
ret = {
@ -463,7 +551,7 @@ def api_restfact():
mimetype='application/json'
)
if app.debug:
app.logger.debug(json.dumps(ret))
logger.debug(json.dumps(ret))
return response
@app.route("/api/restfact_detail")
@ -477,7 +565,7 @@ def restfact_detail():
mimetype='application/json'
)
if app.debug:
app.logger.debug(json.dumps(ret))
logger.debug(json.dumps(ret))
return response
conn = pool.acquire()
@ -493,7 +581,7 @@ def restfact_detail():
mimetype='application/json'
)
if app.debug:
app.logger.debug(json.dumps(ret))
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()
@ -505,7 +593,7 @@ def restfact_detail():
mimetype='application/json'
)
if app.debug:
app.logger.debug(json.dumps(ret))
logger.debug(json.dumps(ret))
return response
else:
dat = []
@ -529,7 +617,7 @@ def restfact_detail():
mimetype='application/json'
)
if app.debug:
app.logger.debug(json.dumps(ret))
logger.debug(json.dumps(ret))
return response
## API V2
@ -551,7 +639,7 @@ def api_search_v2():
mimetype='application/json'
)
if app.debug:
app.logger.debug(json.dumps(ret))
logger.debug(json.dumps(ret))
return response
if len(access_code) > 16 or access_code is None:
ret = {'status': 'incorrect access_code'}
@ -561,7 +649,7 @@ def api_search_v2():
mimetype='application/json'
)
if app.debug:
app.logger.debug(json.dumps(ret))
logger.debug(json.dumps(ret))
return response
conn = pool.acquire()
@ -586,7 +674,7 @@ def api_search_v2():
mimetype='application/json'
)
if app.debug:
app.logger.debug(json.dumps(ret))
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,
@ -622,7 +710,7 @@ def api_search_v2():
mimetype='application/json'
)
if app.debug:
app.logger.debug(json.dumps(ret))
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()
@ -632,7 +720,7 @@ def api_search_v2():
# nor found
ret = {'status': 'NOT FOUND', 'cost': notfound_price}
if app.debug:
app.logger.debug(json.dumps(ret))
logger.debug(json.dumps(ret))
response = app.response_class(
response=json.dumps(ret),
status=200,
@ -644,7 +732,7 @@ def api_search_v2():
return response
except:
app.logger.error(traceback.format_exc())
logger.error(traceback.format_exc())
return 'bad request!', 500
@app.route("/api/v2/reqphoto")
@ -664,7 +752,7 @@ def api_reqimage():
mimetype='application/json'
)
if app.debug:
app.logger.debug(json.dumps(ret))
logger.debug(json.dumps(ret))
return response
if len(access_code) > 16 or access_code is None:
ret = {'status': 'incorrect access_code'}
@ -674,7 +762,7 @@ def api_reqimage():
mimetype='application/json'
)
if app.debug:
app.logger.debug(json.dumps(ret))
logger.debug(json.dumps(ret))
return response
conn = pool.acquire()
@ -698,10 +786,10 @@ def api_reqimage():
mimetype='application/json'
)
if app.debug:
app.logger.debug(json.dumps(ret))
logger.debug(json.dumps(ret))
return response
cur.execute('select count(*) from salvagedb.salvage_images where vin = :p1 and fnd = 1' , {'p1':vin.upper()})
cur.execute('select count(*) from salvagedb.salvage_images where vin = :p1 and fn = 1' , {'p1':vin.upper()})
res = cur.fetchone()
img_count = int(res[0])
if img_count<1:
@ -712,22 +800,20 @@ def api_reqimage():
mimetype='application/json'
)
if app.debug:
app.logger.debug(json.dumps(ret))
logger.debug(json.dumps(ret))
return response
else:
#req_id = uuid.uuid4().hex
#cur.execute('select rownum,ipath from salvagedb.salvage_images where vin = :p1 and fnd = 1',{'p1':vin.upper()})
cur.execute("""select GenImages('{}','{}') uid from dual""".format(access_code, vin.upper()))
cur.execute("""select GenImages('{}','{}') as ui from dual""".format(access_code, vin.upper()))
res = cur.fetchall()
req_id = res[0]
cur.execute("""select fake_name from salvage_images_req where req_id = '{}}'""".format(req_id))
req_id = res[0][0]
cur.execute('select fake_name from salvage_images_req where req_id = :p1', {'p1':req_id})
res = cur.fetchall()
images = []
nm = 0
for it in res:
images.append({
'num':nm,
'url':'https://salimages.salvagedb/{}/{}'.format(req_id, it)
'url':'https://salimages.salvagedb/{}/{}'.format(req_id, it[0])
})
nm = nm + 1
ret = {'status': 'Image_found', 'Images':images}
@ -736,6 +822,7 @@ def api_reqimage():
status=200,
mimetype='application/json'
)
return response
@ -744,25 +831,34 @@ def api_reqimage():
except:
app.logger.error(traceback.format_exc())
logger.error(traceback.format_exc())
return 'bad request!', 500
@app.route("/ads.txt")
def ads_txt():
try:
return render_template('ads.txt')
except:
app.logger.error(traceback.format_exc())
logger.error(traceback.format_exc())
return 'bad request!', 500
@app.route('/favicon.ico')
def logo():
return send_file(app_path+"/static/favicon.ico")
@app.route('/limit')
def rate_limit():
try:
return render_template('rate_limit.html', site=site)
except:
logger.error(traceback.format_exc())
return 'bad request!', 500
@app.route('/donate')
def donate():
return render_template('donate.html')
def get_ip(req) -> str:
if 'X-Forwarded-For' in req.headers:
@ -783,6 +879,328 @@ def get_addr(req) -> str:
def swagger_yaml():
return send_from_directory('api', 'swagger.yaml')
@app.route('/static/<path:filename>')
def serve_static(filename):
try:
# Проверка расширения файла
allowed_extensions = {'.css', '.js', '.png', '.jpg', '.jpeg', '.gif', '.ico', '.svg', '.json'}
file_ext = os.path.splitext(filename)[1].lower()
if file_ext not in allowed_extensions:
logger.warning(f'Attempt to access forbidden file type: {filename}')
return 'Access denied', 403
# Проверка пути на directory traversal
safe_path = os.path.normpath(os.path.join('static', filename))
if not safe_path.startswith('static'):
logger.warning(f'Attempt to access file outside static directory: {filename}')
return 'Access denied', 403
# Определение MIME-типа
mime_types = {
'.css': 'text/css',
'.js': 'application/javascript',
'.json': 'application/json',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.gif': 'image/gif',
'.ico': 'image/x-icon',
'.svg': 'image/svg+xml'
}
mime_type = mime_types.get(file_ext, 'application/octet-stream')
# Логирование доступа
logger.info(f'Access to static file: {filename}')
response = make_response(send_from_directory('static', filename))
response.headers['Content-Type'] = mime_type
# Специальные заголовки для PWA файлов
if filename == 'manifest.json':
response.headers['Content-Type'] = 'application/manifest+json'
elif filename == 'sw.js':
response.headers['Service-Worker-Allowed'] = '/'
return response
except Exception as e:
logger.error(f'Error accessing file {filename}: {str(e)}')
return 'File not found', 404
@app.route("/salvagereport/<string:vin>")
def generate_pdf_report(vin):
try:
# Проверяем наличие VIN в сессии и время последнего поиска
if 'last_searched_vin' not in session or session['last_searched_vin'] != vin:
logger.warning(f'Direct access attempt to report generation for VIN: {vin}')
return 'Access denied', 403
# Проверяем время последнего поиска (не более 5 минут)
if datetime.datetime.now().timestamp() - session['last_search_time'] > 300:
logger.warning(f'Report generation attempt expired for VIN: {vin}')
return 'Access denied', 403
conn = pool.acquire()
cur = conn.cursor()
user_ip = get_ip(request)
try:
returnVal = cur.callfunc("checkip", int, [user_ip, request.headers.get("CF-IPCountry", 'None'), get_addr(user_ip), 1, 0, 0])
except:
logger.error(traceback.format_exc())
# Get vehicle details
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})
det = cur.fetchall()
# Get salvage history
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()
# Generate current date and report ID
now = datetime.datetime.now()
report_date = now.strftime("%Y-%m-%d") # Только дата без времени
report_id = uuid.uuid4().hex[:8].upper()
current_year = now.year
try:
# Создаем буфер для PDF
buffer = io.BytesIO()
# Создаем PDF документ
doc = SimpleDocTemplate(
buffer,
pagesize=A4,
rightMargin=20*mm,
leftMargin=20*mm,
topMargin=15*mm,
bottomMargin=20*mm
)
# Создаем стили для текста
styles = getSampleStyleSheet()
styles.add(ParagraphStyle(
name='ReportTitle',
parent=styles['Heading1'],
fontSize=16,
alignment=TA_CENTER
))
styles.add(ParagraphStyle(
name='ReportSubtitle',
parent=styles['Normal'],
fontSize=12,
alignment=TA_CENTER,
textColor=colors.HexColor('#0066CC') # Синий цвет для акцента
))
styles.add(ParagraphStyle(
name='Right',
parent=styles['Normal'],
fontSize=10,
alignment=TA_RIGHT
))
styles.add(ParagraphStyle(
name='Disclaimer',
parent=styles['Normal'],
fontSize=8,
textColor=colors.gray
))
styles.add(ParagraphStyle(
name='SalvageDBFooter',
parent=styles['Normal'],
fontSize=9,
alignment=TA_CENTER,
textColor=colors.HexColor('#0066CC'), # Синий цвет для акцента
fontName='Helvetica-Bold'
))
# Список элементов для PDF
elements = []
# Путь к логотипу
# logo_path = os.path.join(app_path, 'static', 'icons', 'icon-144x144.png')
# Добавляем логотип, если файл существует
# if os.path.exists(logo_path):
# logo_img = Image(logo_path, width=30*mm, height=30*mm)
# logo_img.hAlign = 'CENTER'
# elements.append(logo_img)
# elements.append(Spacer(1, 5*mm))
# Заголовок отчета с акцентом на SALVAGEDB.COM
elements.append(Paragraph("Vehicle History Report", styles['ReportTitle']))
elements.append(Paragraph("SALVAGEDB.COM - Comprehensive Vehicle History Check", styles['ReportSubtitle']))
elements.append(Spacer(1, 10*mm))
# Информация об отчете
elements.append(Paragraph(f"Report Date: {report_date}", styles['Right']))
elements.append(Paragraph(f"Report ID: {report_id}", styles['Right']))
elements.append(Spacer(1, 15*mm))
# Информация о транспортном средстве
elements.append(Paragraph("Vehicle Information", styles['Heading2']))
# Создаем таблицу с информацией о транспортном средстве
vehicle_data = [
["VIN", vin],
["Make", det[0][1]],
["Model", det[0][2]],
["Year", det[0][3]],
["Body Style", det[0][4]],
["Engine", det[0][5]],
["Cylinders", det[0][6]],
["Drive", det[0][7]]
]
vehicle_table = Table(vehicle_data, colWidths=[100, 350])
vehicle_table.setStyle(TableStyle([
('BACKGROUND', (0, 0), (0, -1), colors.lightgrey),
('TEXTCOLOR', (0, 0), (0, -1), colors.black),
('ALIGN', (0, 0), (0, -1), 'LEFT'),
('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'),
('FONTSIZE', (0, 0), (0, -1), 10),
('BOTTOMPADDING', (0, 0), (-1, -1), 6),
('BACKGROUND', (1, 0), (-1, -1), colors.white),
('GRID', (0, 0), (-1, -1), 0.5, colors.black),
]))
elements.append(vehicle_table)
elements.append(Spacer(1, 10*mm))
# Информация об истории
if his:
elements.append(Paragraph("Salvage History", styles['Heading2']))
# Создаем стиль для ячеек с переносом текста
table_style = TableStyle([
('BACKGROUND', (0, 0), (-1, 0), colors.lightgrey),
('TEXTCOLOR', (0, 0), (-1, 0), colors.black),
('ALIGN', (0, 0), (-1, 0), 'CENTER'),
('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'),
('FONTSIZE', (0, 0), (-1, 0), 9),
('BOTTOMPADDING', (0, 0), (-1, 0), 6),
('BACKGROUND', (0, 1), (-1, -1), colors.white),
('GRID', (0, 0), (-1, -1), 0.5, colors.black),
('VALIGN', (0, 0), (-1, -1), 'TOP'),
('LEFTPADDING', (0, 0), (-1, -1), 3),
('RIGHTPADDING', (0, 0), (-1, -1), 3),
('FONTSIZE', (0, 1), (-1, -1), 8),
('WORDWRAP', (0, 0), (-1, -1), True),
])
# Оптимизированные заголовки без VIN
history_headers = ["Title", "Mileage", "Odometer Status", "Primary\nDamage", "Secondary\nDamage"]
# Формируем данные для таблицы истории
history_data = [history_headers]
for item in his:
title_value = item[2]
primary_damage = item[5] if item[5] else "N/A"
secondary_damage = item[6] if item[6] else "N/A"
# Добавляем строку с данными, убрав VIN
history_data.append([
title_value,
str(item[3]),
item[4],
primary_damage,
secondary_damage
])
# Устанавливаем ширину колонок оптимально для A4
available_width = doc.width # Доступная ширина страницы
col_widths = [
available_width * 0.20, # 20% для Title
available_width * 0.12, # 12% для Mileage
available_width * 0.18, # 18% для Odometer Status
available_width * 0.25, # 25% для Primary Damage
available_width * 0.25 # 25% для Secondary Damage
]
# Создаем таблицу с правильными размерами
history_table = Table(history_data, colWidths=col_widths, repeatRows=1)
history_table.setStyle(table_style)
elements.append(history_table)
else:
elements.append(Paragraph("Salvage History", styles['Heading2']))
elements.append(Paragraph("Salvage history not found.", styles['Normal']))
elements.append(Spacer(1, 15*mm))
# Добавляем штамп поверх таблицы
# stamp_path = os.path.join(app_path, 'static', 'stamp256.png')
# if os.path.exists(stamp_path):
# stamp_img = Image(stamp_path, width=100, height=100)
# stamp_img.hAlign = 'RIGHT'
# stamp_img.vAlign = 'TOP'
# # Размещаем штамп поверх таблицы
# elements.append(stamp_img)
# elements.append(Spacer(1, 15*mm))
# Дисклеймер
disclaimer_text = """
Disclaimer: This report provides information about salvage or junk vehicles; damage from hail, flood or fire;
mileage discrepancies or odometer rollback; and gray market vehicles. We do not claim that the car got in our
databank has salvage title, but the fact that it has been damaged for sure. Our site helps people avoid buying
a damaged vehicle in the past.
"""
elements.append(Paragraph(disclaimer_text, styles['Disclaimer']))
# Футер с акцентом на SALVAGEDB.COM
elements.append(Spacer(1, 15*mm))
elements.append(Paragraph(f"© {current_year} SALVAGEDB.COM - All Rights Reserved", styles['SalvageDBFooter']))
elements.append(Paragraph("Visit SALVAGEDB.COM for more information about vehicle history", styles['SalvageDBFooter']))
elements.append(Paragraph('"This report is provided as is without any guarantees or warranty."', styles['Disclaimer']))
# Строим PDF документ
# doc.build(elements, onFirstPage=add_stamp_overlay, onLaterPages=add_stamp_overlay)
doc.build(elements, canvasmaker=OverlayCanvas)
# Получаем содержимое буфера
pdf_data = buffer.getvalue()
buffer.close()
# Создаем HTTP-ответ с PDF
response = make_response(pdf_data)
response.headers['Content-Type'] = 'application/pdf'
response.headers['Content-Disposition'] = f'attachment; filename=vehicle_report_{vin}.pdf'
return response
except Exception as pdf_error:
# Если возникла ошибка при генерации PDF, логируем ее
app.logger.error(f"PDF generation error: {str(pdf_error)}")
app.logger.error(traceback.format_exc())
# Показываем печатную версию в браузере
return render_template(
'report_printable.html',
vin=vin,
det=det,
his=his,
report_date=report_date,
report_id=report_id,
current_year=current_year,
error_message=str(pdf_error)
)
except Exception as e:
app.logger.error(traceback.format_exc())
return 'Report generation failed: ' + str(e), 500
if __name__ == '__main__':
# Start a pool of connections
pool = start_pool()

View File

@ -1,5 +0,0 @@
1799493 (HEAD -> main) feat: адаптация шаблонов для мобильных устройств
c2d60d9 (origin/main, origin/HEAD) Развиваем API v2: +search Поправил favicon
11f6d56 Перевод search на post
97e85ec remove readme.md
12f0b91 Init

9
package.json Normal file
View File

@ -0,0 +1,9 @@
{
"name": "salvagedb-web",
"version": "1.0.0",
"description": "SalvageDB Web Application",
"dependencies": {
"bootstrap": "^5.3.2",
"@popperjs/core": "^2.11.8"
}
}

View File

@ -1,3 +1,4 @@
flask
oracledb
cacheing
cacheing
reportlab

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

BIN
static/icons/icon-72x72.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

BIN
static/icons/icon-96x96.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

BIN
static/logo2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

51
static/manifest.json Normal file
View File

@ -0,0 +1,51 @@
{
"name": "Salvagedb",
"short_name": "Salvagedb",
"description": "Check vehicle history and salvage information",
"start_url": "/",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#0d6efd",
"icons": [
{
"src": "/static/icons/icon-72x72.png",
"sizes": "72x72",
"type": "image/png"
},
{
"src": "/static/icons/icon-96x96.png",
"sizes": "96x96",
"type": "image/png"
},
{
"src": "/static/icons/icon-128x128.png",
"sizes": "128x128",
"type": "image/png"
},
{
"src": "/static/icons/icon-144x144.png",
"sizes": "144x144",
"type": "image/png"
},
{
"src": "/static/icons/icon-152x152.png",
"sizes": "152x152",
"type": "image/png"
},
{
"src": "/static/icons/icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/static/icons/icon-384x384.png",
"sizes": "384x384",
"type": "image/png"
},
{
"src": "/static/icons/icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}

BIN
static/salvagedblogo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

BIN
static/stamp256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

BIN
static/stamp_big.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

82
static/sw.js Normal file
View File

@ -0,0 +1,82 @@
const CACHE_NAME = 'salvagedb-v1';
const urlsToCache = [
'/',
'/static/styles.css',
'/static/styles__ltr.css',
'/static/privacy.css',
'/static/slogo.png',
'/static/logo2.png',
'/static/salvagedblogo.png',
'/static/favicon.ico',
'/static/faviconV2.png',
'/static/curved-arrow.png',
'/static/vin-position1.gif',
'/static/manifest.json',
'/static/icons/icon-72x72.png',
'/static/icons/icon-96x96.png',
'/static/icons/icon-128x128.png',
'/static/icons/icon-144x144.png',
'/static/icons/icon-152x152.png',
'/static/icons/icon-192x192.png',
'/static/icons/icon-384x384.png',
'/static/icons/icon-512x512.png',
'https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css',
'https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js',
'https://www.google.com/recaptcha/api.js'
];
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => {
console.log('Opened cache');
return Promise.all(
urlsToCache.map(url =>
fetch(url)
.then(response => {
if (!response.ok) {
throw new Error(`Failed to fetch ${url}`);
}
return cache.put(url, response);
})
.catch(error => {
console.warn(`Failed to cache ${url}:`, error);
})
)
);
})
.catch(error => {
console.error('Cache failed:', error);
})
);
});
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
if (response) {
return response;
}
return fetch(event.request)
.then(response => {
if (!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
const responseToCache = response.clone();
caches.open(CACHE_NAME)
.then(cache => {
cache.put(event.request, responseToCache);
});
return response;
})
.catch(error => {
console.error('Fetch failed:', error);
return new Response('Network error happened', {
status: 408,
headers: { 'Content-Type': 'text/plain' },
});
});
})
);
});

39
templates/base.html Normal file
View File

@ -0,0 +1,39 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}{{ site }}{% endblock %}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
{% block head %}{% endblock %}
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container">
<a class="navbar-brand" href="{{ url_for('index_html') }}">{{ site }}</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link" href="{{ url_for('index_html') }}">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('decodevin_html') }}">Decode VIN</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('donate') }}">Support Us</a>
</li>
</ul>
</div>
</div>
</nav>
{% block content %}{% endblock %}
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
{% block scripts %}{% endblock %}
</body>
</html>

View File

@ -1,35 +1,34 @@
{% extends "head.html" %}
{% block title %}Salvage Vehicles Database{% endblock %}
{% block content %}
<div class="container">
<div class="hero-unit">
<h1>Buying a Used Car? Check it!</h1>
<br>
<p><a href="https://www.salvagedb.com/" title="Salvagedb.com" rel="nofollow">Salvagedb.com</a> provides
<div class="container-lg py-4">
<div class="p-5 mb-4 bg-light rounded-3">
<h1 class="display-4">Buying a Used Car? Check it!</h1>
<p class="lead">
<a href="https://www.salvagedb.com/" title="Salvagedb.com" rel="nofollow">Salvagedb.com</a> provides
information about salvage or junk vehicles; damage from hail, flood or fire; mileage discrepancies or
odometer rollback; and gray market vehicles. We do not claim that the car got in our databank has salvage
title, but the fact that it has been damaged for sure. Our site helps people avoid buying a damaged vehicle
in the past.
</p>
</div>
<br><br>
<div class="table-container">
<table id="hor-minimalist-a" summary="Salvage cars;Database page 1">
<thead>
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead class="table-light">
<tr>
<th>#</th>
<th>VIN</th>
<th>Make</th>
<th>Model</th>
<th>Year</th>
<th scope="col">VIN</th>
<th scope="col">Make</th>
<th scope="col">Model</th>
<th scope="col">Year</th>
</tr>
</thead>
<tbody>
{% for row in pg %}
<tr>
<td>{{ row[0] }}</td>
<td><a href="/detail/{{ row[1] }}.html">{{ row[1] }}</a></td>
<td><a href="/detail/{{ row[1] }}.html" class="text-decoration-none">{{ row[1] }}</a></td>
<td>{{ row[2] }}</td>
<td>{{ row[3] }}</td>
<td>{{ row[4] }}</td>
@ -39,16 +38,21 @@
</table>
</div>
<br><br>
<div class="pagination-container">
<div class="pagination">
<nav aria-label="Page navigation" class="my-4">
<ul class="pagination justify-content-center">
{% if cur_page > 1 %}
<a class="btn" href="/database/page1.html">First</a>
<a class="btn" href="/database/page{{cur_page-1}}.html">Prev</a>
<li class="page-item">
<a class="page-link" href="/database/page1.html">First</a>
</li>
<li class="page-item">
<a class="page-link" href="/database/page{{cur_page-1}}.html">Prev</a>
</li>
{% endif %}
{% if cur_page > 3 %}
<span class="btn disabled">...</span>
<li class="page-item disabled">
<span class="page-link">...</span>
</li>
{% endif %}
{% set start_page = cur_page - 2 %}
@ -62,25 +66,32 @@
{% endif %}
{% for page in range(start_page, end_page + 1) %}
{% if page == cur_page %}
<button class="btn active">{{page}}</button>
{% else %}
<a class="btn" href="/database/page{{page}}.html">{{page}}</a>
{% endif %}
<li class="page-item {% if page == cur_page %}active{% endif %}">
{% if page == cur_page %}
<span class="page-link">{{page}}</span>
{% else %}
<a class="page-link" href="/database/page{{page}}.html">{{page}}</a>
{% endif %}
</li>
{% endfor %}
{% if cur_page < max_page-2 %}
<span class="btn disabled">...</span>
<li class="page-item disabled">
<span class="page-link">...</span>
</li>
{% endif %}
{% if cur_page < max_page %}
<a class="btn" href="/database/page{{cur_page+1}}.html">Next</a>
<a class="btn" href="/database/page{{max_page}}.html">Last</a>
<li class="page-item">
<a class="page-link" href="/database/page{{cur_page+1}}.html">Next</a>
</li>
<li class="page-item">
<a class="page-link" href="/database/page{{max_page}}.html">Last</a>
</li>
{% endif %}
</div>
</div>
</ul>
</nav>
</div>
<br>
<hr>
<hr class="my-4">
{% endblock %}

View File

@ -1,45 +1,54 @@
{% extends "head.html" %}
{% block title %}Decode VIN Number{% endblock %}
{% block content %}
<div class="container">
<div class="hero-unit">
<h1>Buying a Used Car? Check it!</h1>
<br>
<p><a href="https://www.salvagedb.com/" title="Salvagedb.com" rel="nofollow">Salvagedb.com</a> provides
<div class="container-lg py-4">
<div class="p-5 mb-4 bg-light rounded-3">
<h1 class="display-4">Buying a Used Car? Check it!</h1>
<p class="lead">
<a href="https://www.salvagedb.com/" title="Salvagedb.com" rel="nofollow">Salvagedb.com</a> provides
information about salvage or junk vehicles; damage from hail, flood or fire; mileage discrepancies or
odometer rollback; and gray market vehicles. We do not claim that the car got in our databank has salvage
title, but the fact that it has been damaged for sure. Our site helps people avoid buying a damaged vehicle
in the past.
</p>
</div>
<br><br>
<div class="container-fluid">
<div class="row-fluid">
<center>
<form id="search_bar" action="/decode" method="post">
<div class="search-container">
<div class="search-title">
<h2 id="find_your_car">ENTER <a href="https://en.wikipedia.org/wiki/Vehicle_Identification_Number" target="_blank">VIN</a> YOU CAR</h2>
<div class="container-lg">
<div class="row justify-content-center">
<div class="col-md-8">
<form id="search_bar" action="/decode" method="post" class="mb-4">
<div class="card">
<div class="card-header text-center">
<h2 class="h4 mb-0">ENTER <a href="https://en.wikipedia.org/wiki/Vehicle_Identification_Number" target="_blank" class="text-decoration-none">VIN</a> OF YOUR CAR</h2>
</div>
<div class="search-wrapper">
<input type="text" id="vininput" name="q" class="search_box ui-autocomplete-input"
autocomplete="off" value="" role="textbox" autofocus required
validate="length_between,17,17">
<button class="g-recaptcha btn btn-primary" type="submit"
data-sitekey="{{capcha_site}}" id="go_button"
data-callback='onSubmit' data-action='submit'>Check It</button>
<div class="card-body">
<div class="input-group">
<input type="text" id="vininput" name="q"
class="form-control"
autocomplete="off"
placeholder="Enter VIN number"
autofocus required
validate="length_between,17,17">
<button class="btn btn-primary g-recaptcha d-flex align-items-center justify-content-center"
type="submit"
data-sitekey="{{capcha_site}}"
id="go_button"
data-callback='onSubmit'
data-action='submit'>Check It</button>
</div>
</div>
</div>
</form>
</center>
</div>
</div>
</div>
<script>
function onSubmit(token) {
document.getElementById("search_bar").submit();
}
</script>
</div>
<script>
function onSubmit(token) {
document.getElementById("search_bar").submit();
}
</script>
{% endblock %}

View File

@ -1,11 +1,13 @@
{% extends "head.html" %}
{% block title %}Vehicle Details{% endblock %}
{% block content %}
<div class="container">
<div class="hero-unit">
<h1>Buying a Used Car? Check it!</h1>
<br>
<p><a href="https://www.salvagedb.com/" title="Salvagedb.com" rel="nofollow">Salvagedb.com</a> provides
<div class="container-lg py-4">
<div class="p-5 mb-4 bg-light rounded-3">
<h1 class="display-4">Buying a Used Car? Check it!</h1>
<p class="lead">
<a href="https://www.salvagedb.com/" title="Salvagedb.com" rel="nofollow">Salvagedb.com</a> provides
information about salvage or junk vehicles; damage from hail, flood or fire; mileage discrepancies or
odometer rollback; and gray market vehicles. We do not claim that the car got in our databank has salvage
title, but the fact that it has been damaged for sure. Our site helps people avoid buying a damaged vehicle
@ -13,59 +15,73 @@
</p>
</div>
<div class="details-container">
<div class="car-details">
<table id="one-column-emphasis">
<tbody>
<tr>
<td>Make</td>
<td>{{det[0][1]}}</td>
</tr>
<tr>
<td>Model</td>
<td>{{det[0][2]}}</td>
</tr>
<tr>
<td>Year</td>
<td>{{det[0][3]}}</td>
</tr>
<tr>
<td>Body Style</td>
<td>{{det[0][4]}}</td>
</tr>
<tr>
<td>Engine</td>
<td>{{det[0][5]}}</td>
</tr>
<tr>
<td>Cylinders</td>
<td>{{det[0][6]}}</td>
</tr>
<tr>
<td>Drive</td>
<td>{{det[0][7]}}</td>
</tr>
</tbody>
</table>
<div class="row">
<div class="col-md-6 mb-4">
<div class="card">
<div class="card-header">
<h2 class="h4 mb-0">Vehicle Details</h2>
</div>
<div class="card-body">
<table class="table table-striped">
<tbody>
<tr>
<th scope="row">Make</th>
<td>{{det[0][1]}}</td>
</tr>
<tr>
<th scope="row">Model</th>
<td>{{det[0][2]}}</td>
</tr>
<tr>
<th scope="row">Year</th>
<td>{{det[0][3]}}</td>
</tr>
<tr>
<th scope="row">Body Style</th>
<td>{{det[0][4]}}</td>
</tr>
<tr>
<th scope="row">Engine</th>
<td>{{det[0][5]}}</td>
</tr>
<tr>
<th scope="row">Cylinders</th>
<td>{{det[0][6]}}</td>
</tr>
<tr>
<th scope="row">Drive</th>
<td>{{det[0][7]}}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="search-container">
<h2>Search salvage history?</h2>
<form name="search_bar" id="search_bar" action="/search" method="post">
<div class="search-wrapper">
<input id="make_model" class="search_box ui-autocomplete-input" name="q"
value="{{vin}}" autocomplete="off" spellcheck="false" type="text">
<button class="g-recaptcha btn btn-primary" data-sitekey="{{capcha_site}}"
id="go_button" data-callback='onSubmit' data-action='submit'>Search</button>
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h2 class="h4 mb-0">Search salvage history?</h2>
</div>
</form>
<div class="card-body">
<form name="search_bar" id="search_bar" action="/search" method="post">
<div class="input-group">
<input id="make_model" class="form-control" name="q"
value="{{vin}}" autocomplete="off" spellcheck="false" type="text"
placeholder="Enter VIN number">
<button class="g-recaptcha btn btn-primary d-flex align-items-center justify-content-center" data-sitekey="{{capcha_site}}"
id="go_button" data-callback='onSubmit' data-action='submit'>Search</button>
</div>
</form>
</div>
</div>
</div>
</div>
<script>
function onSubmit(token) {
document.getElementById("search_bar").submit();
}
</script>
</div>
<script>
function onSubmit(token) {
document.getElementById("search_bar").submit();
}
</script>
{% endblock %}

67
templates/donate.html Normal file
View File

@ -0,0 +1,67 @@
{% extends "head.html" %}
{% block content %}
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card shadow">
<div class="card-body text-center">
<h1 class="card-title mb-4">Support SalvageDB</h1>
<div class="mb-5">
<p class="lead">
For 15 years, we have been providing free access to vehicle history information,
helping people make informed decisions when purchasing used vehicles.
</p>
<p>
Our database contains information about damaged, salvaged, and rebuilt
vehicles, helping to avoid purchasing a car with hidden issues.
</p>
</div>
<div class="mb-5">
<h2 class="h4 mb-3">Support Our Project</h2>
<p>
Your support helps us continue providing quality service and developing
our database. We accept donations in cryptocurrency.
</p>
</div>
<div class="row">
<div class="col-md-6 mb-4">
<div class="card h-100">
<div class="card-body">
<h3 class="h5 mb-3">Bitcoin</h3>
<img src="https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=16uL5x6qx8yF7k4uxyQVfz5N7yLE9F3iCZ"
alt="Bitcoin QR Code" class="img-fluid mb-3">
<p class="mb-2">Wallet Address:</p>
<code class="d-block mb-3">16uL5x6qx8yF7k4uxyQVfz5N7yLE9F3iCZ</code>
</div>
</div>
</div>
<div class="col-md-6 mb-4">
<div class="card h-100">
<div class="card-body">
<h3 class="h5 mb-3">TonCoin</h3>
<img src="https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=UQDi0mgMaDqXORp7UzV6n7E5WFC0yre0on63BSO2tIa7umFe"
alt="TonCoin QR Code" class="img-fluid mb-3">
<p class="mb-2">Wallet Address:</p>
<code class="d-block mb-3">UQDi0mgMaDqXORp7UzV6n7E5WFC0yre0on63BSO2tIa7umFe</code>
</div>
</div>
</div>
</div>
<div class="mt-4">
<p class="text-muted">
Thank you for your support! Every donation helps us improve our service
and provide better information to our users.
</p>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -15,15 +15,14 @@
<meta name="google-site-verification" content="NBTledQfL0wfsD78Ro4s-mdYTEMhXUQGBpsy4aLor9M">
<meta property="fb:admins" content="100000893732418">
<meta http-equiv="Content-Language" content="en">
<title>Vehicle history check. Check your car VIN. For FREE!</title>
<link href="https://storage.googleapis.com/static.salvagedb.com/favicon.ico" rel="shortcut icon"
type="image/x-icon">
<link href="/static/bootstrap.min.css" rel="stylesheet">
<link href="/static/bootstrap-responsive.min.css" rel="stylesheet">
<link href="/static/plus.css" rel="stylesheet">
<link href="/static/plus2.css" rel="stylesheet">
<title>{% block title %}{% endblock %} - {{ site }}</title>
<link href="/static/icons/icon-72x72.png" rel="shortcut icon"
type="image/png">
<link rel="manifest" href="/static/manifest.json">
<meta name="theme-color" content="#0d6efd">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="/static/styles.css" rel="stylesheet">
<script src="/static/bootstrap.bundle.min.js"></script>
<!-- Yatomo -->
<script type="text/javascript" >
@ -44,7 +43,6 @@
<!-- End Matomo Code -->
<script src="https://www.google.com/recaptcha/api.js"></script>
<script src="/static/bootstrap.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
var btnNavbar = document.querySelector('.btn-navbar');
@ -67,29 +65,52 @@
});
</script>
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/static/sw.js')
.then(registration => {
console.log('ServiceWorker registration successful');
})
.catch(err => {
console.log('ServiceWorker registration failed: ', err);
});
});
}
</script>
<link rel="stylesheet"
href="data:text/css;charset=utf-8;base64,LyogU2V0IHRoZSBhcHAgYXR0cmlidXRlIHRvIHlvdXIgYXBwJ3MgZGFzaC1kZWxpbWl0ZWQgYWxpYXMuICovCmNsb3VkZmxhcmUtYXBwW2FwcD0ieW91ci1hcHAtbmFtZSJdIHsKICBkaXNwbGF5OiBibG9jazsKICBtYXJnaW46IDAuNWVtIDAuMmVtOwogIG91dGxpbmU6IDFweCBkb3R0ZWQ7CiAgcGFkZGluZzogMC41ZW07Cn0KCi8qIFVzZSBuYXRpdmUgZWxlbWVudHMgd2hlbiB5b3UnZCBsaWtlIHRvIGluaGVyaXQgc29tZSBzdHlsZXMgZnJvbSB0aGUgcGFnZS4gKi8KY2xvdWRmbGFyZS1hcHBbYXBwPSJ5b3VyLWFwcC1uYW1lIl0gcCB7CiAgdGV4dC1pbmRlbnQ6IDA7Cn0KCi8qIFVzZSBlbSB1bml0cyB0byBzY2FsZSBlbGVtZW50cyBmb3IgZGlmZmVyZW50IGluaGVyaXRlZCBzdHlsZXMuICovCmNsb3VkZmxhcmUtYXBwW2FwcD0ieW91ci1hcHAtbmFtZSJdIGgxIHsKICBmb250LXNpemU6IDEuOGVtOwogIGZvbnQtd2VpZ2h0OiBib2xkOwp9CgovKiBVc2UgY3VzdG9tIGVsZW1lbnRzIHRvIGF2b2lkIHN0eWxlIGluaGVyaXRhbmNlLiAqLwpjbG91ZGZsYXJlLWFwcFthcHA9InlvdXItYXBwLW5hbWUiXSBleGFtcGxlLWJ1dHRvbiB7CiAgYm9yZGVyOiAxcHggc29saWQ7Cn0KCi8qIFByZWZpeCBjbGFzc2VzIHdpdGggeW91ciBhcHAncyBhbGlhcyB0byBhdm9pZCBzdHlsZSBjb2xsaXNpb25zLiAqLwpjbG91ZGZsYXJlLWFwcFthcHA9InlvdXItYXBwLW5hbWUiXSAuZXhhbXBsZS1oaWdobGlnaHRlZCB7CiAgYmFja2dyb3VuZC1jb2xvcjogeWVsbG93OwogIG91dGxpbmU6IDAuMmVtIHNvbGlkIHllbGxvdzsKfQo=">
{% block head %}{% endblock %}
</head>
<body data-new-gr-c-s-check-loaded="14.982.0">
<div class="navbar navbar-fixed-top">
<div class="navbar-inner">
<div class="container">
<a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</a>
<a class="brand" href="https://www.salvagedb.com/">
<img src="/static/slogo.png" height="28">
Salvagedb.com</a>
<div class="nav-collapse">
<ul class="nav">
<li class="active"><a href="/">Home</a></li>
<li class="no2"><a href="/database/page1.html">Salvage vehicles</a></li>
<li class="no3"><a href="/decodevin.html">Decode VIN</a></li>
<li class="no6"><a href="mailto:info@salvagedb.com">Contact</a></li>
</ul>
</div>
<div class="navbar navbar-expand-lg navbar-light bg-light fixed-top">
<div class="container-lg">
<a class="navbar-brand" href="https://www.salvagedb.com/">
<img src="/static/slogo.png" height="28" alt="Salvagedb">
Salvagedb.com
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav">
<li class="nav-item">
<a class="nav-link active" href="/">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/database/page1.html">Vehicle History</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/decodevin.html">Decode VIN</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('donate') }}">Support Us</a>
</li>
<li class="nav-item">
<a class="nav-link" href="mailto:info@salvagedb.com">Contact</a>
</li>
</ul>
</div>
</div>
</div>
@ -104,5 +125,8 @@
</footer>
</div>
<!-- Bootstrap 5 Bundle with Popper -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
{% block scripts %}{% endblock %}
</body>
</html>

View File

@ -1,47 +1,55 @@
{% extends "head.html" %}
{% block title %}Buying a Used Car? Check it!{% endblock %}
{% block content %}
<div class="container">
<div class="hero-unit">
<h1>Buying a Used Car? Check it!</h1>
<br>
<p><a href="https://www.salvagedb.com/" title="Salvagedb.com" rel="nofollow">Salvagedb.com</a> provides
<div class="container-lg py-4">
<div class="p-5 mb-4 bg-light rounded-3">
<h1 class="display-4">Buying a Used Car? Check it!</h1>
<p class="lead">
<a href="https://www.salvagedb.com/" title="Salvagedb.com" rel="nofollow">Salvagedb.com</a> provides
information about salvage or junk vehicles; damage from hail, flood or fire; mileage discrepancies or
odometer rollback; and gray market vehicles. We do not claim that the car got in our databank has salvage
title, but the fact that it has been damaged for sure. Our site helps people avoid buying a damaged vehicle
in the past.
</p>
</div>
<br><br>
<div class="container-fluid">
<div class="row-fluid">
<center>
<form id="search_bar" action="/decode" method="post">
<div class="search-container">
<div class="search-title">
<h2 id="find_your_car">ENTER <a href="https://en.wikipedia.org/wiki/Vehicle_Identification_Number" target="_blank">VIN</a> YOU CAR</h2>
</div>
<div class="search-wrapper">
<input type="text" id="vininput" name="q" class="search_box ui-autocomplete-input"
autocomplete="off" value="" role="textbox" autofocus required
<div class="row justify-content-center">
<div class="col-md-8">
<form id="search_bar" action="/decode" method="post" class="mb-4">
<div class="card">
<div class="card-header text-center">
<h2 class="h4 mb-0">ENTER <a href="https://en.wikipedia.org/wiki/Vehicle_Identification_Number" target="_blank" class="text-decoration-none">VIN</a> OF YOUR CAR</h2>
</div>
<div class="card-body">
<div class="input-group">
<input type="text" id="vininput" name="q"
class="form-control"
style="width: 80%;"
autocomplete="off"
placeholder="Enter VIN number"
autofocus required
validate="length_between,17,17">
<button class="g-recaptcha btn btn-primary" type="submit"
data-sitekey="{{capcha_site}}" id="go_button"
data-callback='onSubmit' data-action='submit'>Check It</button>
<button class="btn btn-primary g-recaptcha d-flex align-items-center justify-content-center"
type="submit"
data-sitekey="{{capcha_site}}"
id="go_button"
data-callback='onSubmit'
data-action='submit'>Check It</button>
</div>
</div>
</form>
</center>
</div>
</form>
</div>
</div>
<h2 style="text-align: center;">Over {{ cnt }} salvage vehicles added.</h2>
<br>
<center>
<img src="static/vin-position1.gif" class="vin-image">
</center>
<br>
<h2 class="text-center mb-4">Over {{ cnt }} salvage vehicles added.</h2>
<div class="text-center">
<img src="static/vin-position1.gif" class="img-fluid" alt="VIN position diagram">
</div>
</div>
<script>
function onSubmit(token) {
document.getElementById("search_bar").submit();

View File

@ -1,290 +1,89 @@
{% raw %}
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html id="iubenda_policy" class="iubenda_fixed_policy">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Privacy Policy of www.salvagedb.com</title>
<meta content="privacy policy" name="keywords">
<meta http-equiv="Content-Language" content="en">
<meta name="robots" content="noindex, follow">
{% extends "head.html" %}
<meta property="og:title" content="Privacy Policy of www.salvagedb.com">
<meta property="og:type" content="website">
<meta name="viewport" content="width=device-width">
<link href="static/privacy.css" media="screen" rel="stylesheet" type="text/css">
<style>@media print {#ghostery-purple-box {display:none !important}}</style>
<link rel="stylesheet"
href="data:text/css;charset=utf-8;base64,LyogU2V0IHRoZSBhcHAgYXR0cmlidXRlIHRvIHlvdXIgYXBwJ3MgZGFzaC1kZWxpbWl0ZWQgYWxpYXMuICovCmNsb3VkZmxhcmUtYXBwW2FwcD0ieW91ci1hcHAtbmFtZSJdIHsKICBkaXNwbGF5OiBibG9jazsKICBtYXJnaW46IDAuNWVtIDAuMmVtOwogIG91dGxpbmU6IDFweCBkb3R0ZWQ7CiAgcGFkZGluZzogMC41ZW07Cn0KCi8qIFVzZSBuYXRpdmUgZWxlbWVudHMgd2hlbiB5b3UnZCBsaWtlIHRvIGluaGVyaXQgc29tZSBzdHlsZXMgZnJvbSB0aGUgcGFnZS4gKi8KY2xvdWRmbGFyZS1hcHBbYXBwPSJ5b3VyLWFwcC1uYW1lIl0gcCB7CiAgdGV4dC1pbmRlbnQ6IDA7Cn0KCi8qIFVzZSBlbSB1bml0cyB0byBzY2FsZSBlbGVtZW50cyBmb3IgZGlmZmVyZW50IGluaGVyaXRlZCBzdHlsZXMuICovCmNsb3VkZmxhcmUtYXBwW2FwcD0ieW91ci1hcHAtbmFtZSJdIGgxIHsKICBmb250LXNpemU6IDEuOGVtOwogIGZvbnQtd2VpZ2h0OiBib2xkOwp9CgovKiBVc2UgY3VzdG9tIGVsZW1lbnRzIHRvIGF2b2lkIHN0eWxlIGluaGVyaXRhbmNlLiAqLwpjbG91ZGZsYXJlLWFwcFthcHA9InlvdXItYXBwLW5hbWUiXSBleGFtcGxlLWJ1dHRvbiB7CiAgYm9yZGVyOiAxcHggc29saWQ7Cn0KCi8qIFByZWZpeCBjbGFzc2VzIHdpdGggeW91ciBhcHAncyBhbGlhcyB0byBhdm9pZCBzdHlsZSBjb2xsaXNpb25zLiAqLwpjbG91ZGZsYXJlLWFwcFthcHA9InlvdXItYXBwLW5hbWUiXSAuZXhhbXBsZS1oaWdobGlnaHRlZCB7CiAgYmFja2dyb3VuZC1jb2xvcjogeWVsbG93OwogIG91dGxpbmU6IDAuMmVtIHNvbGlkIHllbGxvdzsKfQo=">
</head>
<body>
<div id="wbars_all">
<div class="iub_container iub_base_container">
<div id="wbars">
<div class="iub_content legal_pp">
<div class="iub_header">
<h1>Privacy Policy of <strong>www.salvagedb.com</strong></h1>
<p>In order to receive information about your Personal Data, the purposes and the parties the Data
is shared with, contact the Owner.</p>
</div>
<div class="one_line_col">
<h2 id="owner_of_the_data">Owner and Data Controller</h2>
</div>
<div class="one_line_col">
<h2 id="types_of_data">Types of Data collected</h2>
<p>The owner does not provide a list of Personal Data types collected.</p>
<p>Complete details on each type of Personal Data collected are provided in the dedicated sections
of this privacy policy or by specific explanation texts displayed prior to the Data
collection.<br>Personal Data may be freely provided by the User, or, in case of Usage Data,
collected automatically when using this Application.<br>Unless specified otherwise, all Data
requested by this Application is mandatory and failure to provide this Data may make it
impossible for this Application to provide its services. In cases where this Application
specifically states that some Data is not mandatory, Users are free not to communicate this Data
without consequences to the availability or the functioning of the Service.<br>Users who are
uncertain about which Personal Data is mandatory are welcome to contact the Owner.<br>Any use of
Cookies <20> or of other tracking tools <20> by this Application or by the owners of third-party
services used by this Application serves the purpose of providing the Service required by the
User, in addition to any other purposes described in the present document and in the Cookie
Policy, if available.</p>
<p>Users are responsible for any third-party Personal Data obtained, published or shared through
this Application and confirm that they have the third party's consent to provide the Data to the
Owner.</p>
</div>
<div class="one_line_col">
<h2 id="place_of_processing">Mode and place of processing the Data</h2>
<h3>Methods of processing</h3>
<p>The Owner takes appropriate security measures to prevent unauthorized access, disclosure,
modification, or unauthorized destruction of the Data.<br>The Data processing is carried out
using computers and/or IT enabled tools, following organizational procedures and modes strictly
related to the purposes indicated. In addition to the Owner, in some cases, the Data may be
accessible to certain types of persons in charge, involved with the operation of this
Application (administration, sales, marketing, legal, system administration) or external parties
(such as third-party technical service providers, mail carriers, hosting providers, IT
companies, communications agencies) appointed, if necessary, as Data Processors by the Owner.
The updated list of these parties may be requested from the Owner at any time.</p>
<h3>Legal basis of processing</h3>
<p>The Owner may process Personal Data relating to Users if one of the following applies:</p>
<ul>
<li>Users have given their consent for one or more specific purposes. Note: Under some
legislations the Owner may be allowed to process Personal Data until the User objects to
such processing (<28>opt-out<75>), without having to rely on consent or any other of the following
legal bases. This, however, does not apply, whenever the processing of Personal Data is
subject to European data protection law;
</li>
<li>provision of Data is necessary for the performance of an agreement with the User and/or for
any pre-contractual obligations thereof;
</li>
<li>processing is necessary for compliance with a legal obligation to which the Owner is
subject;
</li>
<li>processing is related to a task that is carried out in the public interest or in the
exercise of official authority vested in the Owner;
</li>
<li>processing is necessary for the purposes of the legitimate interests pursued by the Owner or
by a third party.
</li>
</ul>
<p>In any case, the Owner will gladly help to clarify the specific legal basis that applies to the
processing, and in particular whether the provision of Personal Data is a statutory or
contractual requirement, or a requirement necessary to enter into a contract. </p>
<h3>Place</h3>
<p>The Data is processed at the Owner's operating offices and in any other places where the parties
involved in the processing are located.<br><br>
Depending on the User's location, data transfers may involve transferring the User's Data to a
country other than their own. To find out more about the place of processing of such transferred
Data, Users can check the section containing details about the processing of Personal Data.</p>
<p>Users are also entitled to learn about the legal basis of Data transfers to a country outside the
European Union or to any international organization governed by public international law or set
up by two or more countries, such as the UN, and about the security measures taken by the Owner
to safeguard their Data.<br><br>
If any such transfer takes place, Users can find out more by checking the relevant sections of
this document or inquire with the Owner using the information provided in the contact section.
</p>
<h3>Retention time</h3>
<p>Personal Data shall be processed and stored for as long as required by the purpose they have been
collected for.</p>
<p>Therefore:</p>
<ul>
<li>Personal Data collected for purposes related to the performance of a contract between the
Owner and the User shall be retained until such contract has been fully performed.
</li>
<li>Personal Data collected for the purposes of the Owner<65>s legitimate interests shall be
retained as long as needed to fulfill such purposes. Users may find specific information
regarding the legitimate interests pursued by the Owner within the relevant sections of this
document or by contacting the Owner.
</li>
</ul>
<p>The Owner may be allowed to retain Personal Data for a longer period whenever the User has given
consent to such processing, as long as such consent is not withdrawn. Furthermore, the Owner may
be obliged to retain Personal Data for a longer period whenever required to do so for the
performance of a legal obligation or upon order of an authority.<br><br>
Once the retention period expires, Personal Data shall be deleted. Therefore, the right to
access, the right to erasure, the right to rectification and the right to data portability
cannot be enforced after expiration of the retention period.</p>
</div>
<div class="one_line_col">
<h2>The rights of Users</h2>
<p>Users may exercise certain rights regarding their Data processed by the Owner.</p>
<p>In particular, Users have the right to do the following:</p>
<ul>
<li><b>Withdraw their consent at any time.</b> Users have the right to withdraw consent where
they have previously given their consent to the processing of their Personal Data.
</li>
<li><b>Object to processing of their Data.</b> Users have the right to object to the processing
of their Data if the processing is carried out on a legal basis other than consent. Further
details are provided in the dedicated section below.
</li>
<li><b>Access their Data.</b> Users have the right to learn if Data is being processed by the
Owner, obtain disclosure regarding certain aspects of the processing and obtain a copy of
the Data undergoing processing.
</li>
<li><b>Verify and seek rectification.</b> Users have the right to verify the accuracy of their
Data and ask for it to be updated or corrected.
</li>
<li><b>Restrict the processing of their Data.</b> Users have the right, under certain
circumstances, to restrict the processing of their Data. In this case, the Owner will not
process their Data for any purpose other than storing it.
</li>
<li><b>Have their Personal Data deleted or otherwise removed.</b> Users have the right, under
certain circumstances, to obtain the erasure of their Data from the Owner.
</li>
<li><b>Receive their Data and have it transferred to another controller.</b> Users have the
right to receive their Data in a structured, commonly used and machine readable format and,
if technically feasible, to have it transmitted to another controller without any hindrance.
This provision is applicable provided that the Data is processed by automated means and that
the processing is based on the User's consent, on a contract which the User is part of or on
pre-contractual obligations thereof.
</li>
<li><b>Lodge a complaint.</b> Users have the right to bring a claim before their competent data
protection authority.
</li>
</ul>
<h3>Details about the right to object to processing</h3>
<p>Where Personal Data is processed for a public interest, in the exercise of an official authority
vested in the Owner or for the purposes of the legitimate interests pursued by the Owner, Users
may object to such processing by providing a ground related to their particular situation to
justify the objection.</p>
<p>Users must know that, however, should their Personal Data be processed for direct marketing
purposes, they can object to that processing at any time without providing any justification. To
learn, whether the Owner is processing Personal Data for direct marketing purposes, Users may
refer to the relevant sections of this document. </p>
<h3>How to exercise these rights</h3>
<p>Any requests to exercise User rights can be directed to the Owner through the contact details
provided in this document. These requests can be exercised free of charge and will be addressed
by the Owner as early as possible and always within one month.</p>
</div>
<div class="one_line_col">
<h2 id="further_data_processing_info">Additional information about Data collection and
processing</h2>
<h3>Legal action</h3>
<p>
The User's Personal Data may be used for legal purposes by the Owner in Court or in the stages
leading to possible legal action arising from improper use of this Application or the related
Services.<br>The User declares to be aware that the Owner may be required to reveal personal
data upon request of public authorities.
</p>
<h3>Additional information about User's Personal Data</h3>
<p>
In addition to the information contained in this privacy policy, this Application may provide
the User with additional and contextual information concerning particular Services or the
collection and processing of Personal Data upon request.
</p>
<h3>System logs and maintenance</h3>
<p>
For operation and maintenance purposes, this Application and any third-party services may
collect files that record interaction with this Application (System logs) use other Personal
Data (such as the IP Address) for this purpose.
</p>
<h3>Information not contained in this policy</h3>
<p>
More details concerning the collection or processing of Personal Data may be requested from the
Owner at any time. Please see the contact information at the beginning of this document.
</p>
<h3>How <20>Do Not Track<63> requests are handled</h3>
<p>
This Application does not support <20>Do Not Track<63> requests.<br>To determine whether any of the
third-party services it uses honor the <20>Do Not Track<63> requests, please read their privacy
policies.
</p>
<h3>Changes to this privacy policy</h3>
<p>
The Owner reserves the right to make changes to this privacy policy at any time by giving notice
to its Users on this page and possibly within this Application and/or - as far as technically
and legally feasible - sending a notice to Users via any contact information available to the
Owner. It is strongly recommended to check this page often, referring to the date of the last
modification listed at the bottom. <br><br>
Should the changes affect processing activities performed on the basis of the User<65>s consent,
the Owner shall collect new consent from the User, where required.
</p>
</div>
<div class="one_line_col">
<div class="box_primary box_10 definitions expand">
<h3 class="expand-click w_icon_24 icon_ribbon">
Definitions and legal references
</h3>
<div class="expand-content">
<h4>Personal Data (or Data)</h4>
<p>Any information that directly, indirectly, or in connection with other information <20>
including a personal identification number <20> allows for the identification or
identifiability of a natural person.</p>
<h4>Usage Data</h4>
<p>Information collected automatically through this Application (or third-party services
employed in this Application), which can include: the IP addresses or domain names of
the computers utilized by the Users who use this Application, the URI addresses (Uniform
Resource Identifier), the time of the request, the method utilized to submit the request
to the server, the size of the file received in response, the numerical code indicating
the status of the server's answer (successful outcome, error, etc.), the country of
origin, the features of the browser and the operating system utilized by the User, the
various time details per visit (e.g., the time spent on each page within the
Application) and the details about the path followed within the Application with special
reference to the sequence of pages visited, and other parameters about the device
operating system and/or the User's IT environment.</p>
<h4>User</h4>
<p>The individual using this Application who, unless otherwise specified, coincides with the
Data Subject.</p>
<h4>Data Subject</h4>
<p>The natural person to whom the Personal Data refers.</p>
<h4>Data Processor (or Data Supervisor)</h4>
<p>The natural or legal person, public authority, agency or other body which processes
Personal Data on behalf of the Controller, as described in this privacy policy.</p>
<h4>Data Controller (or Owner)</h4>
<p>The natural or legal person, public authority, agency or other body which, alone or
jointly with others, determines the purposes and means of the processing of Personal
Data, including the security measures concerning the operation and use of this
Application. The Data Controller, unless otherwise specified, is the Owner of this
Application.</p>
<h4>
This Application
</h4>
<p>The means by which the Personal Data of the User is collected and processed.</p>
<h4>Service</h4>
<p>The service provided by this Application as described in the relative terms (if
available) and on this site/application.</p>
<h4>European Union (or EU)
</h4>
<p>Unless otherwise specified, all references made within this document to the European
Union include all current member states to the European Union and the European Economic
Area.
</p>
<hr>
<h4>Legal information</h4>
<p>This privacy statement has been prepared based on provisions of multiple legislations,
including Art. 13/14 of Regulation (EU) 2016/679 (General Data Protection
Regulation).</p>
<p>This privacy policy relates solely to this Application, if not stated otherwise within
this document.</p>
</div>
</div>
</div>
<div class="iub_footer">
<p>
Latest update: May 13, 2018
</p>
</div>
{% block title %}Privacy Policy{% endblock %}
{% block content %}
<div class="container-lg py-4">
<div class="card">
<div class="card-body">
<h1 class="display-4 mb-4">Privacy Policy of <strong>www.salvagedb.com</strong></h1>
<p class="lead">In order to receive information about your Personal Data, the purposes and the parties the Data is shared with, contact the Owner.</p>
<h2 class="h3 mt-4" id="owner_of_the_data">Owner and Data Controller</h2>
<h2 class="h3 mt-4" id="types_of_data">Types of Data collected</h2>
<p>The owner does not provide a list of Personal Data types collected.</p>
<p>Complete details on each type of Personal Data collected are provided in the dedicated sections of this privacy policy or by specific explanation texts displayed prior to the Data collection.<br>Personal Data may be freely provided by the User, or, in case of Usage Data, collected automatically when using this Application.<br>Unless specified otherwise, all Data requested by this Application is mandatory and failure to provide this Data may make it impossible for this Application to provide its services. In cases where this Application specifically states that some Data is not mandatory, Users are free not to communicate this Data without consequences to the availability or the functioning of the Service.<br>Users who are uncertain about which Personal Data is mandatory are welcome to contact the Owner.<br>Any use of Cookies or of other tracking tools by this Application or by the owners of third-party services used by this Application serves the purpose of providing the Service required by the User, in addition to any other purposes described in the present document and in the Cookie Policy, if available.</p>
<p>Users are responsible for any third-party Personal Data obtained, published or shared through this Application and confirm that they have the third party's consent to provide the Data to the Owner.</p>
<h2 class="h3 mt-4" id="place_of_processing">Mode and place of processing the Data</h2>
<h3 class="h4">Methods of processing</h3>
<p>The Owner takes appropriate security measures to prevent unauthorized access, disclosure, modification, or unauthorized destruction of the Data.<br>The Data processing is carried out using computers and/or IT enabled tools, following organizational procedures and modes strictly related to the purposes indicated. In addition to the Owner, in some cases, the Data may be accessible to certain types of persons in charge, involved with the operation of this Application (administration, sales, marketing, legal, system administration) or external parties (such as third-party technical service providers, mail carriers, hosting providers, IT companies, communications agencies) appointed, if necessary, as Data Processors by the Owner. The updated list of these parties may be requested from the Owner at any time.</p>
<h3 class="h4">Legal basis of processing</h3>
<p>The Owner may process Personal Data relating to Users if one of the following applies:</p>
<ul class="list-unstyled">
<li class="mb-2">• Users have given their consent for one or more specific purposes. Note: Under some legislations the Owner may be allowed to process Personal Data until the User objects to such processing ("opt-out"), without having to rely on consent or any other of the following legal bases. This, however, does not apply, whenever the processing of Personal Data is subject to European data protection law;</li>
<li class="mb-2">• provision of Data is necessary for the performance of an agreement with the User and/or for any pre-contractual obligations thereof;</li>
<li class="mb-2">• processing is necessary for compliance with a legal obligation to which the Owner is subject;</li>
<li class="mb-2">• processing is related to a task that is carried out in the public interest or in the exercise of official authority vested in the Owner;</li>
<li class="mb-2">• processing is necessary for the purposes of the legitimate interests pursued by the Owner or by a third party.</li>
</ul>
<h2 class="h3 mt-4">The rights of Users</h2>
<p>Users may exercise certain rights regarding their Data processed by the Owner.</p>
<p>In particular, Users have the right to do the following:</p>
<ul class="list-unstyled">
<li class="mb-2"><strong>Withdraw their consent at any time.</strong> Users have the right to withdraw consent where they have previously given their consent to the processing of their Personal Data.</li>
<li class="mb-2"><strong>Object to processing of their Data.</strong> Users have the right to object to the processing of their Data if the processing is carried out on a legal basis other than consent. Further details are provided in the dedicated section below.</li>
<li class="mb-2"><strong>Access their Data.</strong> Users have the right to learn if Data is being processed by the Owner, obtain disclosure regarding certain aspects of the processing and obtain a copy of the Data undergoing processing.</li>
<li class="mb-2"><strong>Verify and seek rectification.</strong> Users have the right to verify the accuracy of their Data and ask for it to be updated or corrected.</li>
<li class="mb-2"><strong>Restrict the processing of their Data.</strong> Users have the right, under certain circumstances, to restrict the processing of their Data. In this case, the Owner will not process their Data for any purpose other than storing it.</li>
<li class="mb-2"><strong>Have their Personal Data deleted or otherwise removed.</strong> Users have the right, under certain circumstances, to obtain the erasure of their Data from the Owner.</li>
<li class="mb-2"><strong>Receive their Data and have it transferred to another controller.</strong> Users have the right to receive their Data in a structured, commonly used and machine readable format and, if technically feasible, to have it transmitted to another controller without any hindrance. This provision is applicable provided that the Data is processed by automated means and that the processing is based on the User's consent, on a contract which the User is part of or on pre-contractual obligations thereof.</li>
<li class="mb-2"><strong>Lodge a complaint.</strong> Users have the right to bring a claim before their competent data protection authority.</li>
</ul>
<h2 class="h3 mt-4" id="further_data_processing_info">Additional information about Data collection and processing</h2>
<h3 class="h4">Legal action</h3>
<p>The User's Personal Data may be used for legal purposes by the Owner in Court or in the stages leading to possible legal action arising from improper use of this Application or the related Services.<br>The User declares to be aware that the Owner may be required to reveal personal data upon request of public authorities.</p>
<h3 class="h4">Additional information about User's Personal Data</h3>
<p>In addition to the information contained in this privacy policy, this Application may provide the User with additional and contextual information concerning particular Services or the collection and processing of Personal Data upon request.</p>
<h3 class="h4">System logs and maintenance</h3>
<p>For operation and maintenance purposes, this Application and any third-party services may collect files that record interaction with this Application (System logs) use other Personal Data (such as the IP Address) for this purpose.</p>
<h3 class="h4">Information not contained in this policy</h3>
<p>More details concerning the collection or processing of Personal Data may be requested from the Owner at any time. Please see the contact information at the beginning of this document.</p>
<h3 class="h4">How "Do Not Track" requests are handled</h3>
<p>This Application does not support "Do Not Track" requests.<br>To determine whether any of the third-party services it uses honor the "Do Not Track" requests, please read their privacy policies.</p>
<h3 class="h4">Changes to this privacy policy</h3>
<p>The Owner reserves the right to make changes to this privacy policy at any time by giving notice to its Users on this page and possibly within this Application and/or - as far as technically and legally feasible - sending a notice to Users via any contact information available to the Owner. It is strongly recommended to check this page often, referring to the date of the last modification listed at the bottom. <br><br>Should the changes affect processing activities performed on the basis of the User's consent, the Owner shall collect new consent from the User, where required.</p>
<div class="mt-4">
<h3 class="h4">Definitions and legal references</h3>
<h4 class="h5">Personal Data (or Data)</h4>
<p>Any information that directly, indirectly, or in connection with other information including a personal identification number allows for the identification or identifiability of a natural person.</p>
<h4 class="h5">Usage Data</h4>
<p>Information collected automatically through this Application (or third-party services employed in this Application), which can include: the IP addresses or domain names of the computers utilized by the Users who use this Application, the URI addresses (Uniform Resource Identifier), the time of the request, the method utilized to submit the request to the server, the size of the file received in response, the numerical code indicating the status of the server's answer (successful outcome, error, etc.), the country of origin, the features of the browser and the operating system utilized by the User, the various time details per visit (e.g., the time spent on each page within the Application) and the details about the path followed within the Application with special reference to the sequence of pages visited, and other parameters about the device operating system and/or the User's IT environment.</p>
<h4 class="h5">User</h4>
<p>The individual using this Application who, unless otherwise specified, coincides with the Data Subject.</p>
<h4 class="h5">Data Subject</h4>
<p>The natural person to whom the Personal Data refers.</p>
<h4 class="h5">Data Processor (or Data Supervisor)</h4>
<p>The natural or legal person, public authority, agency or other body which processes Personal Data on behalf of the Controller, as described in this privacy policy.</p>
<h4 class="h5">Data Controller (or Owner)</h4>
<p>The natural or legal person, public authority, agency or other body which, alone or jointly with others, determines the purposes and means of the processing of Personal Data, including the security measures concerning the operation and use of this Application. The Data Controller, unless otherwise specified, is the Owner of this Application.</p>
</div>
</div>
</div>
</div>
</body>
</html>
{% endraw %}
{% endblock %}

23
templates/rate_limit.html Normal file
View File

@ -0,0 +1,23 @@
{% extends "head.html" %}
{% block title %}Rate Limit Exceeded{% endblock %}
{% block content %}
<div class="container-lg py-4">
<div class="p-5 mb-4 bg-light rounded-3">
<div class="text-center">
<i class="fas fa-exclamation-triangle text-warning" style="font-size: 4rem;"></i>
<h1 class="display-4 mt-3">Rate Limit Exceeded</h1>
<p class="lead">
We've noticed that you're using our service very frequently. Such frequent checks are typically associated with business usage, which contradicts our principles of providing free vehicle history checks for individual users.
</p>
<p class="lead">
Please try again later or contact us if you need a business solution.
</p>
<div class="mt-4">
<a href="/" class="btn btn-primary">Return to Homepage</a>
</div>
</div>
</div>
</div>
{% endblock %}

178
templates/report_pdf.html Normal file
View File

@ -0,0 +1,178 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vehicle History Report</title>
<style>
body {
font-family: Arial, sans-serif;
line-height: 1.6;
color: #333;
margin: 0;
padding: 0;
}
.container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.header {
text-align: center;
margin-bottom: 30px;
border-bottom: 2px solid #333;
padding-bottom: 20px;
}
.header h1 {
font-size: 24px;
margin-bottom: 10px;
}
.header p {
font-size: 14px;
color: #666;
}
.report-date {
text-align: right;
font-size: 12px;
margin-bottom: 20px;
}
.section {
margin-bottom: 30px;
}
.section-title {
font-size: 18px;
margin-bottom: 15px;
padding-bottom: 5px;
border-bottom: 1px solid #ddd;
}
table {
width: 100%;
border-collapse: collapse;
margin-bottom: 20px;
}
table, th, td {
border: 1px solid #ddd;
}
th, td {
padding: 10px;
text-align: left;
}
th {
background-color: #f2f2f2;
}
.footer {
margin-top: 50px;
text-align: center;
font-size: 12px;
color: #666;
border-top: 1px solid #ddd;
padding-top: 20px;
}
.disclaimer {
font-size: 10px;
margin-top: 20px;
padding: 10px;
background-color: #f9f9f9;
border: 1px solid #ddd;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>Vehicle History Report</h1>
<p>SALVAGEDB.COM - Comprehensive Vehicle History Check</p>
</div>
<div class="report-date">
<p>Report Date: {{report_date}}</p>
<p>Report ID: {{report_id}}</p>
</div>
<div class="section">
<h2 class="section-title">Vehicle Information</h2>
<table>
<tbody>
<tr>
<td><strong>VIN</strong></td>
<td>{{vin}}</td>
</tr>
<tr>
<td><strong>Make</strong></td>
<td>{{det[0][1]}}</td>
</tr>
<tr>
<td><strong>Model</strong></td>
<td>{{det[0][2]}}</td>
</tr>
<tr>
<td><strong>Year</strong></td>
<td>{{det[0][3]}}</td>
</tr>
<tr>
<td><strong>Body Style</strong></td>
<td>{{det[0][4]}}</td>
</tr>
<tr>
<td><strong>Engine</strong></td>
<td>{{det[0][5]}}</td>
</tr>
<tr>
<td><strong>Cylinders</strong></td>
<td>{{det[0][6]}}</td>
</tr>
<tr>
<td><strong>Drive</strong></td>
<td>{{det[0][7]}}</td>
</tr>
</tbody>
</table>
</div>
{% if his %}
<div class="section">
<h2 class="section-title">Salvage History</h2>
<table>
<thead>
<tr>
<th>VIN</th>
<th>Title</th>
<th>Odometer</th>
<th>Odometer Status</th>
<th>Primary Damage</th>
<th>Secondary Damage</th>
</tr>
</thead>
<tbody>
{% for it in his %}
<tr>
<td>{{it[1]}}</td>
<td>{{it[2]}}</td>
<td>{{it[3]}}</td>
<td>{{it[4]}}</td>
<td>{{it[5]}}</td>
<td>{{it[6]}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="section">
<div class="alert-warning" style="padding: 15px; background-color: #fcf8e3; border: 1px solid #faebcc; color: #8a6d3b;">
<h2 style="font-size: 16px; margin: 0;">Salvage history not found.</h2>
</div>
</div>
{% endif %}
<div class="disclaimer">
<p><strong>Disclaimer:</strong> This report provides information about salvage or junk vehicles; damage from hail, flood or fire; mileage discrepancies or odometer rollback; and gray market vehicles. We do not claim that the car got in our databank has salvage title, but the fact that it has been damaged for sure. Our site helps people avoid buying a damaged vehicle in the past.</p>
</div>
<div class="footer">
<p>&copy; {{current_year}} SalvageDB.com - All Rights Reserved</p>
<p>This report is provided as is without any guarantees or warranty. For additional assistance, please contact us.</p>
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,274 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vehicle History Report - {{report_id}}</title>
<style>
@media print {
body {
font-family: Arial, sans-serif;
line-height: 1.6;
color: #333;
margin: 0;
padding: 0;
}
.no-print {
display: none !important;
}
@page {
size: A4;
margin: 10mm;
}
}
body {
font-family: Arial, sans-serif;
line-height: 1.6;
color: #333;
margin: 0;
padding: 0;
}
.container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.header {
text-align: center;
margin-bottom: 30px;
border-bottom: 2px solid #333;
padding-bottom: 20px;
}
.header h1 {
font-size: 24px;
margin-bottom: 10px;
}
.header p {
font-size: 14px;
color: #666;
}
.report-date {
text-align: right;
font-size: 12px;
margin-bottom: 20px;
}
.section {
margin-bottom: 30px;
}
.section-title {
font-size: 18px;
margin-bottom: 15px;
padding-bottom: 5px;
border-bottom: 1px solid #ddd;
}
table {
width: 100%;
border-collapse: collapse;
margin-bottom: 20px;
}
table, th, td {
border: 1px solid #ddd;
}
th, td {
padding: 10px;
text-align: left;
}
th {
background-color: #f2f2f2;
}
.footer {
margin-top: 50px;
text-align: center;
font-size: 12px;
color: #666;
border-top: 1px solid #ddd;
padding-top: 20px;
}
.disclaimer {
font-size: 10px;
margin-top: 20px;
padding: 10px;
background-color: #f9f9f9;
border: 1px solid #ddd;
}
.print-controls {
background-color: #f5f5f5;
padding: 15px;
margin-bottom: 20px;
text-align: center;
border-radius: 5px;
}
.btn {
display: inline-block;
font-weight: 400;
text-align: center;
white-space: nowrap;
vertical-align: middle;
user-select: none;
border: 1px solid transparent;
padding: 0.375rem 0.75rem;
font-size: 1rem;
line-height: 1.5;
border-radius: 0.25rem;
text-decoration: none;
margin-right: 10px;
cursor: pointer;
}
.btn-primary {
color: #fff;
background-color: #007bff;
border-color: #007bff;
}
.btn-secondary {
color: #fff;
background-color: #6c757d;
border-color: #6c757d;
}
.error-message {
background-color: #f8d7da;
color: #721c24;
padding: 10px;
margin-bottom: 20px;
border-radius: 5px;
border: 1px solid #f5c6cb;
}
.error-details {
font-family: monospace;
font-size: 12px;
background: #f8f9fa;
padding: 10px;
border: 1px solid #ddd;
max-height: 200px;
overflow: auto;
margin-top: 10px;
}
</style>
<script>
function printReport() {
window.print();
}
function generatePDF() {
// Уведомляем пользователя, что используем встроенную функцию браузера
alert("Используйте функцию 'Сохранить как PDF' в диалоговом окне печати вашего браузера.");
window.print();
}
</script>
</head>
<body>
<div class="container">
<div class="print-controls no-print">
<button class="btn btn-primary" onclick="printReport()">Print Report</button>
<button class="btn btn-secondary" onclick="generatePDF()">Save as PDF</button>
<a class="btn btn-secondary" href="javascript:history.back()">Back to Vehicle Details</a>
</div>
{% if error_message %}
<div class="error-message no-print">
<h3>PDF Generation Error</h3>
<p>The system encountered an error while generating the PDF. You can print this page or save it as PDF using your browser's print function.</p>
<div class="error-details">
{{ error_message }}
</div>
<p>Note: To install WeasyPrint dependencies on Windows, see the <a href="https://doc.courtbouillon.org/weasyprint/stable/first_steps.html#windows" target="_blank">installation instructions</a>.</p>
</div>
{% endif %}
<div class="header">
<h1>Vehicle History Report</h1>
<p>SALVAGEDB.COM - Comprehensive Vehicle History Check</p>
</div>
<div class="report-date">
<p>Report Date: {{report_date}}</p>
<p>Report ID: {{report_id}}</p>
</div>
<div class="section">
<h2 class="section-title">Vehicle Information</h2>
<table>
<tbody>
<tr>
<td><strong>VIN</strong></td>
<td>{{vin}}</td>
</tr>
<tr>
<td><strong>Make</strong></td>
<td>{{det[0][1]}}</td>
</tr>
<tr>
<td><strong>Model</strong></td>
<td>{{det[0][2]}}</td>
</tr>
<tr>
<td><strong>Year</strong></td>
<td>{{det[0][3]}}</td>
</tr>
<tr>
<td><strong>Body Style</strong></td>
<td>{{det[0][4]}}</td>
</tr>
<tr>
<td><strong>Engine</strong></td>
<td>{{det[0][5]}}</td>
</tr>
<tr>
<td><strong>Cylinders</strong></td>
<td>{{det[0][6]}}</td>
</tr>
<tr>
<td><strong>Drive</strong></td>
<td>{{det[0][7]}}</td>
</tr>
</tbody>
</table>
</div>
{% if his %}
<div class="section">
<h2 class="section-title">Salvage History</h2>
<table>
<thead>
<tr>
<th>VIN</th>
<th>Title</th>
<th>Odometer</th>
<th>Odometer Status</th>
<th>Primary Damage</th>
<th>Secondary Damage</th>
</tr>
</thead>
<tbody>
{% for it in his %}
<tr>
<td>{{it[1]}}</td>
<td>{{it[2]}}</td>
<td>{{it[3]}}</td>
<td>{{it[4]}}</td>
<td>{{it[5]}}</td>
<td>{{it[6]}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<div class="section">
<div class="alert-warning" style="padding: 15px; background-color: #fcf8e3; border: 1px solid #faebcc; color: #8a6d3b;">
<h2 style="font-size: 16px; margin: 0;">Salvage history not found.</h2>
</div>
</div>
{% endif %}
<div class="disclaimer">
<p><strong>Disclaimer:</strong> This report provides information about salvage or junk vehicles; damage from hail, flood or fire; mileage discrepancies or odometer rollback; and gray market vehicles. We do not claim that the car got in our databank has salvage title, but the fact that it has been damaged for sure. Our site helps people avoid buying a damaged vehicle in the past.</p>
</div>
<div class="footer">
<p>&copy; {{current_year}} SalvageDB.com - All Rights Reserved</p>
<p>This report is provided as is without any guarantees or warranty. For additional assistance, please contact us.</p>
</div>
</div>
</body>
</html>

View File

@ -1,11 +1,13 @@
{% extends "head.html" %}
{% block title %}Vehicle History Check{% endblock %}
{% block content %}
<div class="container">
<div class="hero-unit">
<h1>Buying a Used Car? Check it!</h1>
<br>
<p><a href="https://www.salvagedb.com/" title="Salvagedb.com" rel="nofollow">Salvagedb.com</a> provides
<div class="container-lg py-4">
<div class="p-5 mb-4 bg-light rounded-3">
<h1 class="display-4">Buying a Used Car? Check it!</h1>
<p class="lead">
<a href="https://www.salvagedb.com/" title="Salvagedb.com" rel="nofollow">Salvagedb.com</a> provides
information about salvage or junk vehicles; damage from hail, flood or fire; mileage discrepancies or
odometer rollback; and gray market vehicles. We do not claim that the car got in our databank has salvage
title, but the fact that it has been damaged for sure. Our site helps people avoid buying a damaged vehicle
@ -13,74 +15,97 @@
</p>
</div>
<div class="details-container">
<div class="car-details">
<table id="one-column-emphasis">
<tbody>
<tr>
<td>Make</td>
<td>{{det[0][1]}}</td>
</tr>
<tr>
<td>Model</td>
<td>{{det[0][2]}}</td>
</tr>
<tr>
<td>Year</td>
<td>{{det[0][3]}}</td>
</tr>
<tr>
<td>Body Style</td>
<td>{{det[0][4]}}</td>
</tr>
<tr>
<td>Engine</td>
<td>{{det[0][5]}}</td>
</tr>
<tr>
<td>Cylinders</td>
<td>{{det[0][6]}}</td>
</tr>
<tr>
<td>Drive</td>
<td>{{det[0][7]}}</td>
</tr>
</tbody>
</table>
<div class="row">
<div class="col-md-4">
<div class="card mb-4">
<div class="card-header">
<h2 class="h5 mb-0">Vehicle Details</h2>
</div>
<div class="card-body">
<table class="table table-sm">
<tbody>
<tr>
<td>Make</td>
<td>{{det[0][1]}}</td>
</tr>
<tr>
<td>Model</td>
<td>{{det[0][2]}}</td>
</tr>
<tr>
<td>Year</td>
<td>{{det[0][3]}}</td>
</tr>
<tr>
<td>Body Style</td>
<td>{{det[0][4]}}</td>
</tr>
<tr>
<td>Engine</td>
<td>{{det[0][5]}}</td>
</tr>
<tr>
<td>Cylinders</td>
<td>{{det[0][6]}}</td>
</tr>
<tr>
<td>Drive</td>
<td>{{det[0][7]}}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
{% if his %}
<div class="history-container">
<table id="hor-minimalist-a">
<thead>
<tr>
<th scope="col">VIN</th>
<th scope="col">Title</th>
<th scope="col">Odometer</th>
<th scope="col">Odometer Status</th>
<th scope="col">Primary Damage</th>
<th scope="col">Secondary Damage</th>
</tr>
</thead>
<tbody>
{% for it in his%}
<tr>
<td>{{it[1]}}</td>
<td>{{it[2]}}</td>
<td>{{it[3]}}</td>
<td>{{it[4]}}</td>
<td>{{it[5]}}</td>
<td>{{it[6]}}</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="col-md-8">
{% if his %}
<div class="card">
<div class="card-header">
<h2 class="h5 mb-0">Salvage History</h2>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-hover align-middle">
<thead class="table-light">
<tr>
<th scope="col">VIN</th>
<th scope="col">Title</th>
<th scope="col">Odometer</th>
<th scope="col">Odometer Status</th>
<th scope="col">Primary Damage</th>
<th scope="col">Secondary Damage</th>
</tr>
</thead>
<tbody>
{% for it in his%}
<tr>
<td>{{it[1]}}</td>
<td>{{it[2]}}</td>
<td>{{it[3]}}</td>
<td>{{it[4]}}</td>
<td>{{it[5]}}</td>
<td>{{it[6]}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- PDF Report Download Button -->
<div class="mt-3">
<a href="/salvagereport/{{vin}}" class="btn btn-primary w-100">
<i class="fas fa-file-pdf me-2"></i> Download PDF Report
</a>
</div>
</div>
</div>
{% else %}
<div class="alert alert-warning">
<h2 class="h5 mb-0">Salvage history not found.</h2>
</div>
{% endif %}
</div>
{% else %}
<div class="not-found">
<h2>Salvage history not found.</h2>
</div>
{% endif %}
</div>
</div>
{% endblock %}