mirror of
https://git.nolog.cz/mdivecky/PID-odjezdy.git
synced 2025-01-31 05:03:35 +01:00
init
This commit is contained in:
commit
6f25cb4691
13 changed files with 327 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
*/__pycache__
|
||||
.env
|
||||
*.pyc
|
16
README.md
Normal file
16
README.md
Normal file
|
@ -0,0 +1,16 @@
|
|||
|
||||
Aktuálně jsou hardcodované dvě zastávky v proměnné na začátku kódu, dalo by se hodit do proměné třeba jako json.
|
||||
Ryšánka - směr Budějovická (C) - U632Z3P
|
||||
Na Vrstevnici - směr Přístaviště (A) - U436Z1P
|
||||
|
||||
Název zastávky a její stanoviště (A,B,C,D...) je možné najít na [webu PID](https://pid.cz/zastavkova-tabla/?stop=Na+Vrstevnici&stanoviste=&tab=3)
|
||||
Název je pak možné hodit do [Swaggeru od Golemia](https://api.golemio.cz/pid/docs/openapi/#/%F0%9F%A7%BE%20GTFS%20Static/get_gtfs_stops) (get_gtfs_stops) a ve výstupu najít odpovídající ID zastávky (`stop_id`)
|
||||
|
||||
Před prvním spuštěním je potřeba dostat API key od Golemia, jde to zdarma [zde](https://api.golemio.cz/api-keys/auth/sign-in). Ten pak vložit do souboru .env jako proměnnou `API_KEY`.
|
||||
|
||||
"Dockerizováno" primárně podle návodu [tady](https://github.com/docker/awesome-compose/blob/master/nginx-wsgi-flask/compose.yaml)
|
||||
|
||||
## Todo
|
||||
* Auto refresh?
|
||||
* Cache pro data z Golemia?
|
||||
* Přesun IDs zastávek do proměnné
|
31
docker-compose.yml
Normal file
31
docker-compose.yml
Normal file
|
@ -0,0 +1,31 @@
|
|||
version: "3.3"
|
||||
|
||||
services:
|
||||
nginx-proxy:
|
||||
build: nginx
|
||||
restart: always
|
||||
volumes:
|
||||
- ./nginx/default.conf:/tmp/default.conf
|
||||
environment:
|
||||
- FLASK_SERVER_ADDR=flask-app:8000
|
||||
ports:
|
||||
- "127.0.0.1:7880:80"
|
||||
depends_on:
|
||||
- flask-app
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl --silent --fail localhost:80/health-check || exit 1"]
|
||||
interval: 10s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
command: /app/start.sh
|
||||
flask-app:
|
||||
build: flask
|
||||
restart: always
|
||||
environment:
|
||||
- API_KEY=${API_KEY}
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl --silent --fail localhost:8000/flask-health-check || exit 1"]
|
||||
interval: 10s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
command: gunicorn -w 3 -t 60 -b 0.0.0.0:8000 app:app
|
1
env.sample
Normal file
1
env.sample
Normal file
|
@ -0,0 +1 @@
|
|||
API_KEY=
|
32
flask/Dockerfile
Normal file
32
flask/Dockerfile
Normal file
|
@ -0,0 +1,32 @@
|
|||
FROM python:alpine
|
||||
|
||||
# upgrade pip
|
||||
RUN pip install --upgrade pip
|
||||
|
||||
# get curl for healthchecks
|
||||
RUN apk add curl
|
||||
|
||||
# permissions and nonroot user for tightened security
|
||||
RUN adduser -D nonroot
|
||||
RUN mkdir /home/app/ && chown -R nonroot:nonroot /home/app
|
||||
RUN mkdir -p /var/log/flask-app && touch /var/log/flask-app/flask-app.err.log && touch /var/log/flask-app/flask-app.out.log
|
||||
RUN chown -R nonroot:nonroot /var/log/flask-app
|
||||
WORKDIR /home/app
|
||||
USER nonroot
|
||||
|
||||
# copy all the files to the container
|
||||
COPY --chown=nonroot:nonroot . .
|
||||
|
||||
# venv
|
||||
ENV VIRTUAL_ENV=/home/app/venv
|
||||
|
||||
# python setup
|
||||
RUN python -m venv $VIRTUAL_ENV
|
||||
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
|
||||
RUN export FLASK_APP=app.py
|
||||
RUN pip install -r requirements.txt
|
||||
|
||||
# define the port number the container should expose
|
||||
EXPOSE 5000
|
||||
|
||||
CMD ["python", "app.py"]
|
79
flask/app.py
Normal file
79
flask/app.py
Normal file
|
@ -0,0 +1,79 @@
|
|||
import os
|
||||
import requests
|
||||
from pprint import pprint
|
||||
from flask import Flask, render_template
|
||||
import datetime
|
||||
|
||||
|
||||
API_KEY = os.environ['API_KEY']
|
||||
|
||||
API_URL = "https://api.golemio.cz/v2/pid/departureboards"
|
||||
|
||||
STOPS = ["U632Z3P", "U436Z1P"]
|
||||
|
||||
#STOPS = ["U632Z3P"]
|
||||
|
||||
CAR_AMOUNT = 10
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
def get_departures(api, key, stop):
|
||||
headers = {"accept": "application/json", "X-Access-Token": key}
|
||||
params = {"ids": stop, "limit": CAR_AMOUNT}
|
||||
r = requests.get(api, params=params, headers=headers)
|
||||
return r.json()
|
||||
|
||||
|
||||
def get_cars(stops):
|
||||
cars = []
|
||||
for stop in stops:
|
||||
deps = get_departures(API_URL, API_KEY, stop)
|
||||
stop_name = deps["stops"][0]["stop_name"]
|
||||
stop_platform = deps["stops"][0]["platform_code"]
|
||||
stop_display = stop_name #+ " (" + stop_platform + ")"
|
||||
|
||||
stop_cars = []
|
||||
|
||||
for car in deps["departures"]:
|
||||
# print(car)
|
||||
car_id = car["route"]["short_name"]
|
||||
car_dir = car["trip"]["headsign"]
|
||||
car_ac = car["trip"]["is_air_conditioned"]
|
||||
car_departure = car["departure_timestamp"]["minutes"]
|
||||
if car["delay"]["is_available"]:
|
||||
# car_ahead = "+ "
|
||||
car_delay_sec = (car["delay"]["minutes"] * 60) + car["delay"]["seconds"]
|
||||
# if car_delay_sec < 0:
|
||||
# car_ahead = "- "
|
||||
# elif car_delay_sec == 0:
|
||||
# car_ahead = ""
|
||||
|
||||
|
||||
# m, s = divmod(car_delay_sec, 60)
|
||||
# car_delay = car_ahead + str(m) + "min, " + str(s) + "sec"
|
||||
car_delay = car_delay_sec
|
||||
else:
|
||||
car_delay = "?"
|
||||
|
||||
car_data = {
|
||||
"id": car_id,
|
||||
"dir": car_dir,
|
||||
"ac": car_ac,
|
||||
"departure": car_departure,
|
||||
"delay": car_delay,
|
||||
}
|
||||
|
||||
stop_cars.append(car_data)
|
||||
|
||||
cars.append({"id": stop, "name": stop_display, "cars": stop_cars})
|
||||
return cars
|
||||
|
||||
|
||||
@app.route("/")
|
||||
def hello_world():
|
||||
return render_template('index.html', data=get_cars(STOPS))
|
||||
|
||||
@app.route('/flask-health-check')
|
||||
def flask_health_check():
|
||||
return "success"
|
3
flask/requirements.txt
Normal file
3
flask/requirements.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
requests
|
||||
gunicorn
|
||||
Flask
|
55
flask/templates/index.html
Normal file
55
flask/templates/index.html
Normal file
|
@ -0,0 +1,55 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<!-- Required meta tags -->
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<!-- Bootstrap CSS -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.1/font/bootstrap-icons.css">
|
||||
<title>Hello, world!</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="container">
|
||||
|
||||
{% for stop in data %}
|
||||
|
||||
<p style="font-size: 24px">{{ stop.name }}</p>
|
||||
|
||||
<table class="table table-striped table-sm">
|
||||
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Linka</th>
|
||||
<th scope="col">Odjezd za</th>
|
||||
<!-- <th scope="col">Směr</th> -->
|
||||
<th scope="col">Zpoždění</th>
|
||||
<th scope="col">AC</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for car in stop.cars %}
|
||||
<tr>
|
||||
<th scope="row">{{ car.id }}</th>
|
||||
<td>{{ car.departure }} min</td>
|
||||
<!-- <td>{{ car.dir }}</td> -->
|
||||
<td>{{ car.delay }} sec</td>
|
||||
<td>{% if car.ac %} <i class="bi bi-snow"></i> {% endif %} </td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{% endfor %}
|
||||
<caption>Odjezdy jsou včetně zpoždění</caption>
|
||||
</div>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
|
5
flask/wsgi.py
Normal file
5
flask/wsgi.py
Normal file
|
@ -0,0 +1,5 @@
|
|||
from app import app
|
||||
import os
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(host='0.0.0.0', port=os.environ.get("FLASK_SERVER_PORT"), debug=True)
|
32
nginx/Dockerfile
Normal file
32
nginx/Dockerfile
Normal file
|
@ -0,0 +1,32 @@
|
|||
FROM nginx:1.25.2-alpine
|
||||
|
||||
# Add bash for boot cmd
|
||||
RUN apk add bash
|
||||
|
||||
# Add nginx.conf to container
|
||||
COPY --chown=nginx:nginx nginx.conf /etc/nginx/nginx.conf
|
||||
COPY --chown=nginx:nginx start.sh /app/start.sh
|
||||
|
||||
# set workdir
|
||||
WORKDIR /app
|
||||
|
||||
# permissions and nginx user for tightened security
|
||||
RUN chown -R nginx:nginx /app && chmod -R 755 /app && \
|
||||
chown -R nginx:nginx /var/cache/nginx && \
|
||||
chown -R nginx:nginx /var/log/nginx && \
|
||||
chmod -R 755 /var/log/nginx; \
|
||||
chown -R nginx:nginx /etc/nginx/conf.d
|
||||
RUN touch /var/run/nginx.pid && chown -R nginx:nginx /var/run/nginx.pid
|
||||
|
||||
# # Uncomment to keep the nginx logs inside the container - Leave commented for logging to stdout and stderr
|
||||
# RUN mkdir -p /var/log/nginx
|
||||
# RUN unlink /var/log/nginx/access.log \
|
||||
# && unlink /var/log/nginx/error.log \
|
||||
# && touch /var/log/nginx/access.log \
|
||||
# && touch /var/log/nginx/error.log \
|
||||
# && chown nginx /var/log/nginx/*log \
|
||||
# && chmod 644 /var/log/nginx/*log
|
||||
|
||||
USER nginx
|
||||
|
||||
CMD ["nginx", "-g", "'daemon off;'"]
|
18
nginx/default.conf
Normal file
18
nginx/default.conf
Normal file
|
@ -0,0 +1,18 @@
|
|||
proxy_cache_path /tmp/cache levels=1:2 keys_zone=cache:10m max_size=500m inactive=60m use_temp_path=off;
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
|
||||
location / {
|
||||
proxy_pass http://$FLASK_SERVER_ADDR;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
}
|
||||
|
||||
location /health-check {
|
||||
add_header Content-Type text/plain;
|
||||
return 200 "success";
|
||||
}
|
||||
|
||||
}
|
50
nginx/nginx.conf
Normal file
50
nginx/nginx.conf
Normal file
|
@ -0,0 +1,50 @@
|
|||
worker_processes auto;
|
||||
pid /var/run/nginx.pid;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
# Define the format of log messages.
|
||||
log_format main_ext '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
'"$http_user_agent" "$http_x_forwarded_for" '
|
||||
'"$host" sn="$server_name" '
|
||||
'rt=$request_time '
|
||||
'ua="$upstream_addr" us="$upstream_status" '
|
||||
'ut="$upstream_response_time" ul="$upstream_response_length" '
|
||||
'cs=$upstream_cache_status' ;
|
||||
|
||||
access_log /var/log/nginx/access.log main_ext;
|
||||
error_log /var/log/nginx/error.log warn;
|
||||
|
||||
sendfile on;
|
||||
|
||||
keepalive_timeout 65;
|
||||
|
||||
# Enable Compression
|
||||
gzip on;
|
||||
|
||||
# Disable Display of NGINX Version
|
||||
server_tokens off;
|
||||
|
||||
# Size Limits
|
||||
client_body_buffer_size 10K;
|
||||
client_header_buffer_size 1k;
|
||||
client_max_body_size 8m;
|
||||
large_client_header_buffers 2 1k;
|
||||
|
||||
# # SSL / TLS Settings - Suggested for Security
|
||||
# ssl_protocols TLSv1.2 TLSv1.3;
|
||||
# ssl_session_timeout 15m;
|
||||
# ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
|
||||
# ssl_prefer_server_ciphers on;
|
||||
# ssl_session_tickets off;
|
||||
|
||||
include /etc/nginx/conf.d/*.conf;
|
||||
|
||||
}
|
2
nginx/start.sh
Normal file
2
nginx/start.sh
Normal file
|
@ -0,0 +1,2 @@
|
|||
#!/bin/bash
|
||||
envsubst '$FLASK_SERVER_ADDR' < /tmp/default.conf > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'
|
Loading…
Reference in a new issue