soma improvements

This commit is contained in:
Mateusz Gruszczyński
2026-07-14 13:18:35 +02:00
parent 1f4d459a64
commit 5980a61a7e
11 changed files with 62 additions and 27 deletions
+3 -3
View File
@@ -53,11 +53,11 @@ def create_app(test_config=None):
if request.endpoint == "static": if request.endpoint == "static":
filename = request.view_args.get("filename", "") if request.view_args else "" filename = request.view_args.get("filename", "") if request.view_args else ""
if filename.endswith(".css"): if filename.endswith(".css"):
response.headers["Cache-Control"] = "public, max-age=2592000, immutable" response.headers["Cache-Control"] = "public, max-age=1800, immutable"
else: else:
response.headers["Cache-Control"] = "public, max-age=2592000, immutable" response.headers["Cache-Control"] = "public, max-age=1800, immutable"
elif response.mimetype == "text/html": elif response.mimetype == "text/html":
response.headers["Cache-Control"] = "no-store, private, must-revalidate" response.headers["Cache-Control"] = "private, no-store"
return response return response
@login_manager.user_loader @login_manager.user_loader
+1 -1
View File
@@ -35,7 +35,7 @@ class Config:
SECRET_KEY = env("SECRET_KEY", "dev-secret") SECRET_KEY = env("SECRET_KEY", "dev-secret")
SQLALCHEMY_DATABASE_URI = env("DATABASE_URL", "sqlite:////app/instance/fueltrack.db") SQLALCHEMY_DATABASE_URI = env("DATABASE_URL", "sqlite:////app/instance/fueltrack.db")
SQLALCHEMY_TRACK_MODIFICATIONS = False SQLALCHEMY_TRACK_MODIFICATIONS = False
API_TOKEN_MAX_AGE_SECONDS = env_int("API_TOKEN_MAX_AGE_SECONDS", 28800) API_TOKEN_MAX_AGE_SECONDS = env_int("API_TOKEN_MAX_AGE_SECONDS", 928800)
HTTP_TIMEOUT_SECONDS = env_float("HTTP_TIMEOUT_SECONDS", 30) HTTP_TIMEOUT_SECONDS = env_float("HTTP_TIMEOUT_SECONDS", 30)
ORLEN_TIMEOUT_SECONDS = env_float("ORLEN_TIMEOUT_SECONDS", 15) ORLEN_TIMEOUT_SECONDS = env_float("ORLEN_TIMEOUT_SECONDS", 15)
-3
View File
@@ -1,11 +1,8 @@
from __future__ import annotations from __future__ import annotations
import fcntl import fcntl
from contextlib import contextmanager from contextlib import contextmanager
from pathlib import Path from pathlib import Path
from flask import Flask from flask import Flask
from .database_init import initialize_database from .database_init import initialize_database
from .database_migrations import migrate_database from .database_migrations import migrate_database
-2
View File
@@ -1,7 +1,5 @@
from __future__ import annotations from __future__ import annotations
from sqlalchemy import inspect from sqlalchemy import inspect
from .extensions import db from .extensions import db
from .models import AppSetting, CompanySettings, User from .models import AppSetting, CompanySettings, User
-3
View File
@@ -1,10 +1,7 @@
from __future__ import annotations from __future__ import annotations
from collections.abc import Callable from collections.abc import Callable
from dataclasses import dataclass from dataclasses import dataclass
from sqlalchemy import inspect, text from sqlalchemy import inspect, text
from .extensions import db from .extensions import db
-1
View File
@@ -1,4 +1,3 @@
"""Compatibility facade. New code lives in domain/ and integrations/."""
from .domain.costs import calculate_costs, money from .domain.costs import calculate_costs, money
from .domain.invoices import invoice_period from .domain.invoices import invoice_period
from .integrations.orlen import fetch_orlen_price, fetch_orlen_range, POLISH_REGIONS, PRODUCT_IDS from .integrations.orlen import fetch_orlen_price, fetch_orlen_range, POLISH_REGIONS, PRODUCT_IDS
+1 -1
View File
@@ -1 +1 @@
(()=>{const F=FuelTrack;F.replaceMain=async(url=location.href,{push=false}={})=>{const r=await fetch(url,{headers:{'X-Requested-With':'XMLHttpRequest'}});if(!r.ok)throw new Error('Nie udało się odświeżyć widoku.');const doc=new DOMParser().parseFromString(await r.text(),'text/html');const incoming=F.qs('main',doc),current=F.qs('main');if(!incoming||!current)return;current.replaceWith(incoming);document.documentElement.dataset.bsTheme=doc.documentElement.dataset.bsTheme||'light';const next=F.qs('#bootstrap-theme',doc),theme=F.qs('#bootstrap-theme');if(next&&theme)theme.href=next.href;if(push)history.pushState({},'',url);F.initPage();F.qsa('script:not([src])',doc).forEach(x=>{if(x.textContent.includes('FuelTrack.'))try{Function(x.textContent)()}catch(e){console.error(e)}})};F.submitAjax=async form=>{const b=F.qs('[type=submit]',form),old=b?.innerHTML;if(b){b.disabled=true;b.innerHTML='Zapisywanie…'}try{const r=await fetch(form.action||location.href,{method:(form.dataset.method||form.getAttribute('method')||'POST').toUpperCase(),body:new FormData(form),headers:{'X-Requested-With':'XMLHttpRequest','Accept':'application/json'}});const d=await r.json();if(!r.ok||!d.ok)throw new Error(d.error||d.message||'Operacja nie powiodła się.');F.notify(d.message||'Zapisano');bootstrap.Modal.getInstance(form.closest('.modal'))?.hide();await F.replaceMain((d.data||{}).redirect||location.href,{push:Boolean((d.data||{}).redirect)})}catch(e){F.notify(e.message,'danger')}finally{if(b){b.disabled=false;b.innerHTML=old}}};F.bindAjaxForms=()=>{F.qsa('form.ajax-form').forEach(f=>{if(f.dataset.bound)return;f.dataset.bound='1';f.addEventListener('submit',e=>{e.preventDefault();F.submitAjax(f)})});F.qsa('form.ajax-nav-form').forEach(f=>{if(f.dataset.bound)return;f.dataset.bound='1';f.addEventListener('submit',e=>{e.preventDefault();F.replaceMain(`${f.action||location.pathname}?${new URLSearchParams(new FormData(f))}`,{push:true}).catch(x=>F.notify(x.message,'danger'))})});F.qsa('a.ajax-nav-link').forEach(a=>{if(a.dataset.bound)return;a.dataset.bound='1';a.addEventListener('click',e=>{e.preventDefault();F.replaceMain(a.href,{push:true}).catch(x=>F.notify(x.message,'danger'))})})}})(); (()=>{const F=FuelTrack;F.replaceMain=async(url=location.href,{push=false}={})=>{const r=await fetch(url,{headers:{'X-Requested-With':'XMLHttpRequest'}});if(!r.ok)throw new Error('Nie udało się odświeżyć widoku.');const doc=new DOMParser().parseFromString(await r.text(),'text/html');const incoming=F.qs('main',doc),current=F.qs('main');if(!incoming||!current)return;current.replaceWith(incoming);document.documentElement.dataset.bsTheme=doc.documentElement.dataset.bsTheme||'light';const next=F.qs('#bootstrap-theme',doc),theme=F.qs('#bootstrap-theme');if(next&&theme)theme.href=next.href;if(push)history.pushState({},'',url);F.initPage();F.qsa('script:not([src])',doc).forEach(x=>{if(x.textContent.includes('FuelTrack.'))try{Function(x.textContent)()}catch(e){console.error(e)}})};F.submitAjax=async form=>{const b=F.qs('[type=submit]',form),old=b?.innerHTML;if(b){b.disabled=true;b.innerHTML='Zapisywanie…'}try{const r=await fetch(form.action||location.href,{method:(form.dataset.method||form.getAttribute('method')||'POST').toUpperCase(),body:new FormData(form),headers:{'X-Requested-With':'XMLHttpRequest','Accept':'application/json'}});const d=await r.json();if(!r.ok||!d.ok)throw new Error(d.error||d.message||'Operacja nie powiodła się.');F.notify(d.message||'Zapisano');bootstrap.Modal.getInstance(form.closest('.modal'))?.hide();await F.replaceMain((d.data||{}).redirect||location.href,{push:Boolean((d.data||{}).redirect)})}catch(e){F.notify(e.message,'danger')}finally{if(b){b.disabled=false;b.innerHTML=old}}};F.bindAjaxForms=()=>{F.qsa('form.ajax-form').forEach(f=>{if(f.dataset.bound)return;f.dataset.bound='1';f.addEventListener('submit',e=>{e.preventDefault();F.submitAjax(f)})});F.qsa('form.ajax-nav-form').forEach(f=>{if(f.dataset.bound)return;f.dataset.bound='1';f.addEventListener('submit',e=>{e.preventDefault();(()=>{const url=new URL(f.getAttribute('action')||location.pathname,location.origin);url.search=new URLSearchParams(new FormData(f)).toString();return F.replaceMain(url.toString(),{push:true})})().catch(x=>F.notify(x.message,'danger'))})});F.qsa('a.ajax-nav-link').forEach(a=>{if(a.dataset.bound)return;a.dataset.bound='1';a.addEventListener('click',e=>{e.preventDefault();F.replaceMain(a.href,{push:true}).catch(x=>F.notify(x.message,'danger'))})})}})();
+1 -1
View File
@@ -47,6 +47,6 @@
<div class="card mb-4"><div class="card-body"><div class="section-heading"><div><h2 class="h5 mb-1">{% if mode == 'compare' %}Porównanie: {{compare_fuel}}{% else %}Trend ceny{% endif %} {% if include_vat %}brutto{% else %}netto{% endif %}</h2><p class="small text-body-secondary mb-0">Cena za litr w złotych.</p></div></div><div class="chart-wrap"><canvas id="orlen-chart"></canvas><div id="orlen-chart-empty" class="chart-empty d-none">Brak danych dla wybranych filtrów.</div></div></div></div> <div class="card mb-4"><div class="card-body"><div class="section-heading"><div><h2 class="h5 mb-1">{% if mode == 'compare' %}Porównanie: {{compare_fuel}}{% else %}Trend ceny{% endif %} {% if include_vat %}brutto{% else %}netto{% endif %}</h2><p class="small text-body-secondary mb-0">Cena za litr w złotych.</p></div></div><div class="chart-wrap"><canvas id="orlen-chart"></canvas><div id="orlen-chart-empty" class="chart-empty d-none">Brak danych dla wybranych filtrów.</div></div></div></div>
<div class="card"><div class="card-body"><div class="table-responsive"><table class="table table-hover align-middle"><thead><tr><th>Data</th><th>Paliwo</th><th>Cena netto zł/l</th><th>Region</th><th>Pobrano</th></tr></thead><tbody>{% for r in rows|reverse %}<tr><td>{{r.effective_date}}</td><td>{{r.product_name}}</td><td>{{'%.4f'|format(r.price_per_liter|float)}}</td><td>{{r.region or '—'}}</td><td>{{r.fetched_at.strftime('%Y-%m-%d %H:%M')}}</td></tr>{% else %}<tr><td colspan="5" class="text-center text-body-secondary py-4">Brak danych w bazie.</td></tr>{% endfor %}</tbody></table></div></div></div> {% if mode == 'history' %}<div class="card"><div class="card-body"><div class="table-responsive"><table class="table table-hover align-middle"><thead><tr><th>Data</th><th>Paliwo</th><th>Cena netto zł/l</th><th>Region</th><th>Pobrano</th></tr></thead><tbody>{% for r in rows|reverse %}<tr><td>{{r.effective_date}}</td><td>{{r.product_name}}</td><td>{{'%.4f'|format(r.price_per_liter|float)}}</td><td>{{r.region or '—'}}</td><td>{{r.fetched_at.strftime('%Y-%m-%d %H:%M')}}</td></tr>{% else %}<tr><td colspan="5" class="text-center text-body-secondary py-4">Brak danych w bazie.</td></tr>{% endfor %}</tbody></table></div></div></div>{% endif %}
{% endblock %} {% endblock %}
{% block scripts %}<script>FuelTrack.renderMultiLineChart('orlen-chart', {{chart|tojson}}, {{include_vat|tojson}});FuelTrack.bindOrlenSync();FuelTrack.bindOrlenComparison();</script>{% endblock %} {% block scripts %}<script>FuelTrack.renderMultiLineChart('orlen-chart', {{chart|tojson}}, {{include_vat|tojson}});FuelTrack.bindOrlenSync();FuelTrack.bindOrlenComparison();</script>{% endblock %}
+30 -1
View File
@@ -16,10 +16,39 @@
<td>{{s.station_count}}</td> <td>{{s.station_count}}</td>
<td><div class="d-flex gap-1 flex-wrap">{% if s.has_petrol %}<span class="badge text-bg-secondary">benzyna</span>{% endif %}{% if s.has_diesel %}<span class="badge text-bg-secondary">diesel</span>{% endif %}{% if s.has_lpg %}<span class="badge text-bg-secondary">LPG</span>{% endif %}</div></td> <td><div class="d-flex gap-1 flex-wrap">{% if s.has_petrol %}<span class="badge text-bg-secondary">benzyna</span>{% endif %}{% if s.has_diesel %}<span class="badge text-bg-secondary">diesel</span>{% endif %}{% if s.has_lpg %}<span class="badge text-bg-secondary">LPG</span>{% endif %}</div></td>
<td><form method="post" action="/api/stations/{{s.id}}" data-method="PUT" class="ajax-form row g-2"><input type="hidden" name="company_id" value="{{ company.id }}"><div class="col-12"><div class="form-check"><input class="form-check-input" type="checkbox" name="active" id="active-{{s.id}}" {% if s.active %}checked{% endif %}><label class="form-check-label" for="active-{{s.id}}">Pokazuj w propozycjach</label></div><div class="form-check"><input class="form-check-input" type="checkbox" name="allowed" id="allowed-{{s.id}}" {% if s.id in allowed_ids %}checked{% endif %}><label class="form-check-label" for="allowed-{{s.id}}">Dodaj do zamkniętej listy firmy</label></div></div><div class="col-12"><button class="btn btn-primary btn-sm">Zapisz</button></div></form></td> <td><form method="post" action="/api/stations/{{s.id}}" data-method="PUT" class="ajax-form row g-2"><input type="hidden" name="company_id" value="{{ company.id }}"><div class="col-12"><div class="form-check"><input class="form-check-input" type="checkbox" name="active" id="active-{{s.id}}" {% if s.active %}checked{% endif %}><label class="form-check-label" for="active-{{s.id}}">Pokazuj w propozycjach</label></div><div class="form-check"><input class="form-check-input" type="checkbox" name="allowed" id="allowed-{{s.id}}" {% if s.id in allowed_ids %}checked{% endif %}><label class="form-check-label" for="allowed-{{s.id}}">Dodaj do zamkniętej listy firmy</label></div></div><div class="col-12"><button class="btn btn-primary btn-sm">Zapisz</button></div></form></td>
<td>{% for card in fuel_cards %}{% set rule=card_rules.get((card.id,s.id)) %}<form action="/api/fuel-cards/{{card.id}}/stations/{{s.id}}" data-method="PUT" class="ajax-form border rounded p-2 mb-2"><div class="fw-semibold small mb-2">{{card.name}}</div><div class="row g-2"><div class="col-12"><div class="form-check"><input class="form-check-input" type="checkbox" name="allowed" id="rule-allowed-{{card.id}}-{{s.id}}" {% if not rule or rule.allowed %}checked{% endif %}><label class="form-check-label" for="rule-allowed-{{card.id}}-{{s.id}}">Dozwolona dla karty</label></div><div class="form-check"><input class="form-check-input" type="checkbox" name="use_orlen_last_price" id="rule-last-{{card.id}}-{{s.id}}" {% if rule and rule.use_orlen_last_price %}checked{% endif %}><label class="form-check-label" for="rule-last-{{card.id}}-{{s.id}}">Orlen last price</label></div></div><div class="col-6"><label class="form-label small">Rabat netto %</label><input class="form-control form-control-sm" type="number" step="0.001" name="discount_net_percent" value="{{rule.discount_net_percent if rule else 0}}"></div><div class="col-6"><label class="form-label small">Dopłata netto/l</label><input class="form-control form-control-sm" type="number" step="0.0001" name="surcharge_net_per_liter" value="{{rule.surcharge_net_per_liter if rule else 0}}"></div><div class="col-12"><button class="btn btn-primary btn-sm">Zapisz warunki</button></div></div></form>{% else %}<span class="text-body-secondary small">Najpierw dodaj kartę w ustawieniach firmy.</span>{% endfor %}</td> <td class="text-nowrap">{% if fuel_cards %}<div class="d-flex flex-column align-items-start gap-1"><span class="small text-body-secondary">{{ fuel_cards|length }} {% if fuel_cards|length == 1 %}karta{% elif fuel_cards|length in [2,3,4] %}karty{% else %}kart{% endif %}</span><button type="button" class="btn btn-outline-primary btn-sm" data-bs-toggle="modal" data-bs-target="#stationCardRulesModal-{{s.id}}">Edytuj warunki</button></div>{% else %}<span class="text-body-secondary small">Brak kart</span>{% endif %}</td>
</tr>{% else %}<tr><td colspan="5" class="text-center text-body-secondary py-5">Brak firm. Pobierz katalog z URE.</td></tr>{% endfor %} </tr>{% else %}<tr><td colspan="5" class="text-center text-body-secondary py-5">Brak firm. Pobierz katalog z URE.</td></tr>{% endfor %}
</tbody></table></div> </tbody></table></div>
{% if station_page.pages > 1 %}<nav><ul class="pagination mb-0">{% for p in station_page.iter_pages() %}{% if p %}<li class="page-item {% if p==station_page.page %}active{% endif %}"><a class="page-link ajax-nav-link" href="{{url_for('main.stations',company_id=company.id,page=p,q=q,sort=sort,direction=direction)}}">{{p}}</a></li>{% else %}<li class="page-item disabled"><span class="page-link"></span></li>{% endif %}{% endfor %}</ul></nav>{% endif %} {% if station_page.pages > 1 %}<nav><ul class="pagination mb-0">{% for p in station_page.iter_pages() %}{% if p %}<li class="page-item {% if p==station_page.page %}active{% endif %}"><a class="page-link ajax-nav-link" href="{{url_for('main.stations',company_id=company.id,page=p,q=q,sort=sort,direction=direction)}}">{{p}}</a></li>{% else %}<li class="page-item disabled"><span class="page-link"></span></li>{% endif %}{% endfor %}</ul></nav>{% endif %}
</div></div> </div></div>
{% for s in station_page.items %}{% if fuel_cards %}
<div class="modal fade" id="stationCardRulesModal-{{s.id}}" tabindex="-1" aria-labelledby="stationCardRulesTitle-{{s.id}}" aria-hidden="true">
<div class="modal-dialog modal-xl modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<div><h2 class="modal-title fs-5" id="stationCardRulesTitle-{{s.id}}">Warunki kart — {{s.display_name}}</h2><div class="small text-body-secondary">{{ fuel_cards|length }} {% if fuel_cards|length == 1 %}karta paliwowa{% elif fuel_cards|length in [2,3,4] %}karty paliwowe{% else %}kart paliwowych{% endif %}</div></div>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Zamknij"></button>
</div>
<div class="modal-body">
<div class="row g-3">
{% for card in fuel_cards %}{% set rule=card_rules.get((card.id,s.id)) %}
<div class="col-12 col-lg-6">
<form action="/api/fuel-cards/{{card.id}}/stations/{{s.id}}" data-method="PUT" class="ajax-form border rounded p-3 h-100">
<div class="d-flex justify-content-between align-items-start gap-2 mb-3"><div><div class="fw-semibold">{{card.name}}</div><div class="small text-body-secondary">{{card.provider}}</div></div>{% if not rule or rule.allowed %}<span class="badge text-bg-success">Dozwolona</span>{% else %}<span class="badge text-bg-secondary">Zablokowana</span>{% endif %}</div>
<div class="row g-3">
<div class="col-12"><div class="form-check"><input class="form-check-input" type="checkbox" name="allowed" id="rule-allowed-{{card.id}}-{{s.id}}" {% if not rule or rule.allowed %}checked{% endif %}><label class="form-check-label" for="rule-allowed-{{card.id}}-{{s.id}}">Dozwolona dla karty</label></div><div class="form-check"><input class="form-check-input" type="checkbox" name="use_orlen_last_price" id="rule-last-{{card.id}}-{{s.id}}" {% if rule and rule.use_orlen_last_price %}checked{% endif %}><label class="form-check-label" for="rule-last-{{card.id}}-{{s.id}}">Orlen last price</label></div></div>
<div class="col-sm-6"><label class="form-label small">Rabat netto %</label><input class="form-control form-control-sm" type="number" step="0.001" name="discount_net_percent" value="{{rule.discount_net_percent if rule else 0}}"></div>
<div class="col-sm-6"><label class="form-label small">Dopłata netto/l</label><input class="form-control form-control-sm" type="number" step="0.0001" name="surcharge_net_per_liter" value="{{rule.surcharge_net_per_liter if rule else 0}}"></div>
<div class="col-12"><button class="btn btn-primary btn-sm">Zapisz warunki</button></div>
</div>
</form>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
{% endif %}{% endfor %}
<div class="modal fade" id="stationPointsModal" tabindex="-1" aria-hidden="true"><div class="modal-dialog modal-xl modal-dialog-scrollable"><div class="modal-content"><div class="modal-header"><div><h2 class="modal-title fs-5" id="stationPointsTitle">Punkty stacji</h2><div class="small text-body-secondary" id="stationPointsCount"></div></div><button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Zamknij"></button></div><div class="modal-body"><div class="input-group mb-3"><span class="input-group-text">Szukaj</span><input class="form-control" id="stationPointsSearch" placeholder="Nazwa, ulica, miejscowość lub kod pocztowy"></div><div id="stationPointsLoading" class="text-center py-5 d-none"><div class="spinner-border" role="status"></div></div><div class="table-responsive"><table class="table table-hover align-middle mb-0"><thead><tr><th>Nazwa punktu</th><th>Adres</th><th>Województwo</th><th>Paliwa</th></tr></thead><tbody id="stationPointsRows"></tbody></table></div><div id="stationPointsEmpty" class="text-center text-body-secondary py-5 d-none">Brak punktów do wyświetlenia. Wykonaj ponowny import z URE.</div></div></div></div></div> <div class="modal fade" id="stationPointsModal" tabindex="-1" aria-hidden="true"><div class="modal-dialog modal-xl modal-dialog-scrollable"><div class="modal-content"><div class="modal-header"><div><h2 class="modal-title fs-5" id="stationPointsTitle">Punkty stacji</h2><div class="small text-body-secondary" id="stationPointsCount"></div></div><button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Zamknij"></button></div><div class="modal-body"><div class="input-group mb-3"><span class="input-group-text">Szukaj</span><input class="form-control" id="stationPointsSearch" placeholder="Nazwa, ulica, miejscowość lub kod pocztowy"></div><div id="stationPointsLoading" class="text-center py-5 d-none"><div class="spinner-border" role="status"></div></div><div class="table-responsive"><table class="table table-hover align-middle mb-0"><thead><tr><th>Nazwa punktu</th><th>Adres</th><th>Województwo</th><th>Paliwa</th></tr></thead><tbody id="stationPointsRows"></tbody></table></div><div id="stationPointsEmpty" class="text-center text-body-secondary py-5 d-none">Brak punktów do wyświetlenia. Wykonaj ponowny import z URE.</div></div></div></div></div>
{% endblock %} {% endblock %}
-1
View File
@@ -1,6 +1,5 @@
from datetime import date, datetime from datetime import date, datetime
from decimal import Decimal, InvalidOperation from decimal import Decimal, InvalidOperation
from .extensions import db from .extensions import db
from .models import AppSetting from .models import AppSetting
+25 -9
View File
@@ -1,31 +1,47 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
cd "$ROOT_DIR" APP_DIR="${APP_DIR:-$SCRIPT_DIR}"
INSTANCE_DIR="${INSTANCE_DIR:-$APP_DIR/instance}"
ENV_FILE="${ENV_FILE:-$APP_DIR/.env}"
DB_FILE="${DB_FILE:-$INSTANCE_DIR/fueltrack.db}"
if [[ -f .env ]]; then # Uruchamiaj aplikację zawsze z jej właściwego katalogu.
cd "$APP_DIR"
# Wczytaj .env niezależnie od katalogu wywołania skryptu.
if [[ -f "$ENV_FILE" ]]; then
set -a set -a
# shellcheck disable=SC1091 # shellcheck disable=SC1090
source .env source "$ENV_FILE"
set +a set +a
fi fi
mkdir -p instance mkdir -p "$INSTANCE_DIR"
mkdir -p "$(dirname -- "$DB_FILE")"
export FLASK_ENV="${FLASK_ENV:-development}" export FLASK_ENV="${FLASK_ENV:-development}"
export DATABASE_URL="${DATABASE_URL:-sqlite:///$ROOT_DIR/instance/fueltrack.db}"
export APP_HOST="${APP_HOST:-0.0.0.0}" export APP_HOST="${APP_HOST:-0.0.0.0}"
export APP_PORT="${APP_PORT:-8000}" export APP_PORT="${APP_PORT:-8000}"
export GUNICORN_WORKERS="${GUNICORN_WORKERS:-1}" export GUNICORN_WORKERS="${GUNICORN_WORKERS:-1}"
export GUNICORN_THREADS="${GUNICORN_THREADS:-8}" export GUNICORN_THREADS="${GUNICORN_THREADS:-8}"
export GUNICORN_TIMEOUT="${GUNICORN_TIMEOUT:-0}" export GUNICORN_TIMEOUT="${GUNICORN_TIMEOUT:-0}"
export GUNICORN_APP="${GUNICORN_APP:-wsgi:app}"
# Jeśli DATABASE_URL nie jest ustawione w .env, użyj bezwzględnej
# ścieżki do pliku SQLite.
if [[ -z "${DATABASE_URL:-}" ]]; then
export DATABASE_URL="sqlite:///$DB_FILE"
fi
echo "FuelTrack DEV" echo "FuelTrack DEV"
echo "Katalog aplikacji: $APP_DIR"
echo "Adres: http://localhost:${APP_PORT}" echo "Adres: http://localhost:${APP_PORT}"
echo "Baza: ${DATABASE_URL}" echo "Baza: $DATABASE_URL"
exec gunicorn \ exec gunicorn \
--chdir "$APP_DIR" \
--reload \ --reload \
--access-logfile - \ --access-logfile - \
--error-logfile - \ --error-logfile - \
@@ -34,4 +50,4 @@ exec gunicorn \
--threads "$GUNICORN_THREADS" \ --threads "$GUNICORN_THREADS" \
--timeout "$GUNICORN_TIMEOUT" \ --timeout "$GUNICORN_TIMEOUT" \
--bind "$APP_HOST:$APP_PORT" \ --bind "$APP_HOST:$APP_PORT" \
wsgi:app "$GUNICORN_APP"