diff --git a/README.md b/README.md index 00c4b9e..cab95cb 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,180 @@ Datumsrechner Live: [https://date.elpatron.me](https://date.elpatron.me) - Start-/Enddatum einer Kalenderwoche eines Jahres - Statistik-Dashboard mit Passwortschutz unter `/stats` +## REST API + +Alle Datumsfunktionen stehen auch als REST-API zur Verfügung. Die API akzeptiert und liefert JSON. +**Basis-URL:** `http://localhost:5000/api/` + +**Hinweis:** Die Nutzung der REST API wird im Statistik-Dashboard ausgewertet und als Diagramm angezeigt. + +### Endpunkte und Beispiele + +#### 1. Tage/Werktage zwischen zwei Daten + +**POST** `/api/tage_werktage` + +```json +{ + "start": "2024-06-01", + "end": "2024-06-10", + "werktage": true +} +``` + +**Mit curl:** +```bash +curl -X POST http://localhost:5000/api/tage_werktage \ + -H "Content-Type: application/json" \ + -d '{"start": "2024-06-01", "end": "2024-06-10", "werktage": true}' +``` + +**Antwort:** +```json +{ "result": 7 } +``` + +#### 2. Wochentag zu einem Datum + +**POST** `/api/wochentag` + +```json +{ "datum": "2024-06-10" } +``` + +**Mit curl:** +```bash +curl -X POST http://localhost:5000/api/wochentag \ + -H "Content-Type: application/json" \ + -d '{"datum": "2024-06-10"}' +``` + +**Antwort:** +```json +{ "result": "Montag" } +``` + +#### 3. Kalenderwoche zu Datum + +**POST** `/api/kw_berechnen` + +```json +{ "datum": "2024-06-10" } +``` + +**Mit curl:** +```bash +curl -X POST http://localhost:5000/api/kw_berechnen \ + -H "Content-Type: application/json" \ + -d '{"datum": "2024-06-10"}' +``` + +**Antwort:** +```json +{ "result": "KW 24 (2024)", "kw": 24, "jahr": 2024 } +``` + +#### 4. Start-/Enddatum einer Kalenderwoche + +**POST** `/api/kw_datum` + +```json +{ "jahr": 2024, "kw": 24 } +``` + +**Mit curl:** +```bash +curl -X POST http://localhost:5000/api/kw_datum \ + -H "Content-Type: application/json" \ + -d '{"jahr": 2024, "kw": 24}' +``` + +**Antwort:** +```json +{ + "result": "10.06.2024 bis 16.06.2024", + "start": "2024-06-10", + "end": "2024-06-16" +} +``` + +#### 5. Datum plus/minus Tage, Wochen, Monate + +**POST** `/api/plusminus` + +```json +{ + "datum": "2024-06-10", + "anzahl": 5, + "einheit": "tage", + "richtung": "add", + "werktage": false +} +``` + +**Mit curl:** +```bash +curl -X POST http://localhost:5000/api/plusminus \ + -H "Content-Type: application/json" \ + -d '{"datum": "2024-06-10", "anzahl": 5, "einheit": "tage", "richtung": "add", "werktage": false}' +``` + +**Antwort:** +```json +{ "result": "2024-06-15" } +``` + +**Hinweis:** +- `"einheit"`: `"tage"`, `"wochen"` oder `"monate"` +- `"richtung"`: `"add"` (plus) oder `"sub"` (minus) +- `"werktage"`: `true` für Werktage, sonst `false` (nur bei `"tage"` unterstützt) + +#### 6. Statistik + +**GET** `/api/stats` + +**Mit curl:** +```bash +curl http://localhost:5000/api/stats +``` + +**Antwort:** +```json +{ + "pageviews": 42, + "func_counts": { "plusminus": 10, "tage_werktage": 5 }, + "impressions_per_day": { "2024-06-10": 7 } +} +``` + +#### 7. Monitoring & Healthcheck + +**GET** `/api/monitor` + +**Mit curl:** +```bash +curl http://localhost:5000/api/monitor +``` + +**Antwort:** +```json +{ + "status": "ok", + "message": "App running", + "time": "2025-07-24T13:37:00.123456", + "uptime_seconds": 12345, + "pageviews_last_7_days": 42 +} +``` + +--- + +**Fehlerfälle** liefern immer einen HTTP-Statuscode 400 und ein JSON mit `"error"`-Feld, z.B.: + +```json +{ "error": "Ungültige Eingabe", "details": "..." } +``` + ## Installation (lokal) 1. Python 3.8+ installieren diff --git a/app.py b/app.py index 3a149a8..3ac2de2 100644 --- a/app.py +++ b/app.py @@ -148,6 +148,7 @@ def stats(): pageviews = 0 func_counts = {} impressions_per_day = {} + api_counts = {} if os.path.exists(log_path): with open(log_path, encoding='utf-8') as f: for line in f: @@ -163,7 +164,10 @@ def stats(): elif 'FUNC:' in line: func = line.split('FUNC:')[1].strip() func_counts[func] = func_counts.get(func, 0) + 1 - return render_template('stats_dashboard.html', pageviews=pageviews, func_counts=func_counts, impressions_per_day=impressions_per_day) + elif 'FUNC_API:' in line: + api = line.split('FUNC_API:')[1].strip() + api_counts[api] = api_counts.get(api, 0) + 1 + return render_template('stats_dashboard.html', pageviews=pageviews, func_counts=func_counts, impressions_per_day=impressions_per_day, api_counts=api_counts) @app.route('/monitor') @@ -184,6 +188,157 @@ def monitor(): "pageviews_last_7_days": pageviews }) +# --- REST API --- +def log_api_usage(api_name): + log_dir = 'log' + os.makedirs(log_dir, exist_ok=True) + log_path = os.path.join(log_dir, 'pageviews.log') + from datetime import datetime as dt + with open(log_path, 'a', encoding='utf-8') as f: + f.write(f"{dt.now().isoformat()} FUNC_API: {api_name}\n") + +@app.route('/api/tage_werktage', methods=['POST']) +def api_tage_werktage(): + log_api_usage('tage_werktage') + data = request.get_json() + start = data.get('start') + end = data.get('end') + is_werktage = data.get('werktage', False) + try: + d1 = datetime.strptime(start, '%Y-%m-%d') + d2 = datetime.strptime(end, '%Y-%m-%d') + if is_werktage: + if d1 > d2: + d1, d2 = d2, d1 + tage = int(np.busday_count(d1.date(), (d2 + timedelta(days=1)).date())) + else: + tage = abs((d2 - d1).days) + return jsonify({'result': tage}) + except Exception as e: + return jsonify({'error': 'Ungültige Eingabe', 'details': str(e)}), 400 + +@app.route('/api/wochentag', methods=['POST']) +def api_wochentag(): + log_api_usage('wochentag') + data = request.get_json() + datum = data.get('datum') + try: + d = datetime.strptime(datum, '%Y-%m-%d') + wochentag = WOCHENTAGE[d.weekday()] + return jsonify({'result': wochentag}) + except Exception as e: + return jsonify({'error': 'Ungültige Eingabe', 'details': str(e)}), 400 + +@app.route('/api/kw_berechnen', methods=['POST']) +def api_kw_berechnen(): + log_api_usage('kw_berechnen') + data = request.get_json() + datum = data.get('datum') + try: + d = datetime.strptime(datum, '%Y-%m-%d') + kw = d.isocalendar().week + return jsonify({'result': f"KW {kw} ({d.year})", 'kw': kw, 'jahr': d.year}) + except Exception as e: + return jsonify({'error': 'Ungültige Eingabe', 'details': str(e)}), 400 + +@app.route('/api/kw_datum', methods=['POST']) +def api_kw_datum(): + log_api_usage('kw_datum') + data = request.get_json() + jahr = data.get('jahr') + kw = data.get('kw') + try: + jahr = int(jahr) + kw = int(kw) + start = datetime.fromisocalendar(jahr, kw, 1) + end = datetime.fromisocalendar(jahr, kw, 7) + return jsonify({'result': f"{start.strftime('%d.%m.%Y')} bis {end.strftime('%d.%m.%Y')}", 'start': start.strftime('%Y-%m-%d'), 'end': end.strftime('%Y-%m-%d')}) + except Exception as e: + return jsonify({'error': 'Ungültige Eingabe', 'details': str(e)}), 400 + +@app.route('/api/plusminus', methods=['POST']) +def api_plusminus(): + log_api_usage('plusminus') + data = request.get_json() + datum = data.get('datum') + anzahl = data.get('anzahl') + einheit = data.get('einheit') + richtung = data.get('richtung', 'add') + is_werktage = data.get('werktage', False) + try: + d = datetime.strptime(datum, '%Y-%m-%d') + anzahl_int = int(anzahl) + if richtung == 'sub': + anzahl_int = -anzahl_int + if einheit == 'tage': + if is_werktage: + result = np.busday_offset(d.date(), anzahl_int, roll='forward') + result_dt = datetime.strptime(str(result), '%Y-%m-%d') + return jsonify({'result': result_dt.strftime('%Y-%m-%d')}) + else: + result = d + timedelta(days=anzahl_int) + return jsonify({'result': result.strftime('%Y-%m-%d')}) + elif einheit == 'wochen': + if is_werktage: + return jsonify({'error': 'Nicht unterstützt: Werktage + Wochen.'}), 400 + else: + result = d + timedelta(weeks=anzahl_int) + return jsonify({'result': result.strftime('%Y-%m-%d')}) + elif einheit == 'monate': + if is_werktage: + return jsonify({'error': 'Nicht unterstützt: Werktage + Monate.'}), 400 + else: + result = d + relativedelta(months=anzahl_int) + return jsonify({'result': result.strftime('%Y-%m-%d')}) + else: + return jsonify({'error': 'Ungültige Einheit'}), 400 + except Exception as e: + return jsonify({'error': 'Ungültige Eingabe', 'details': str(e)}), 400 + +@app.route('/api/stats', methods=['GET']) +def api_stats(): + log_path = os.path.join('log', 'pageviews.log') + pageviews = 0 + func_counts = {} + impressions_per_day = {} + api_counts = {} + if os.path.exists(log_path): + with open(log_path, encoding='utf-8') as f: + for line in f: + if 'PAGEVIEW' in line: + pageviews += 1 + try: + date = line[:10] + if len(date) == 10 and date[4] == '-' and date[7] == '-': + impressions_per_day[date] = impressions_per_day.get(date, 0) + 1 + except Exception: + pass + elif 'FUNC:' in line: + func = line.split('FUNC:')[1].strip() + func_counts[func] = func_counts.get(func, 0) + 1 + elif 'FUNC_API:' in line: + api = line.split('FUNC_API:')[1].strip() + api_counts[api] = api_counts.get(api, 0) + 1 + return render_template('stats_dashboard.html', pageviews=pageviews, func_counts=func_counts, impressions_per_day=impressions_per_day, api_counts=api_counts) + +@app.route('/api/monitor', methods=['GET']) +def api_monitor(): + log_path = os.path.join('log', 'pageviews.log') + pageviews = 0 + if os.path.exists(log_path): + with open(log_path, encoding='utf-8') as f: + for line in f: + if 'PAGEVIEW' in line: + pageviews += 1 + uptime = int(time.time() - app_start_time) + return jsonify({ + "status": "ok", + "message": "App running", + "time": datetime.now().isoformat(), + "uptime_seconds": uptime, + "pageviews_last_7_days": pageviews + }) + if __name__ == '__main__': app.run(debug=True, host="0.0.0.0") \ No newline at end of file diff --git a/templates/stats_dashboard.html b/templates/stats_dashboard.html index fa68e0e..b596f01 100644 --- a/templates/stats_dashboard.html +++ b/templates/stats_dashboard.html @@ -27,9 +27,16 @@
API-Counts: {{ api_counts|tojson }}Zurück zur App -