diff --git a/backend/routes/diary.py b/backend/routes/diary.py
index a3dee2b..6f6cd12 100644
--- a/backend/routes/diary.py
+++ b/backend/routes/diary.py
@@ -9,7 +9,7 @@ from auth import get_current_user, require_admin
import ki as KI
import httpx
import weather as weather_mod
-from media_utils import convert_media, extract_video_thumb, safe_media_path, validate_upload, extract_gps_from_exif, generate_preview, preview_url_from
+from media_utils import convert_media, extract_video_thumb, safe_media_path, validate_upload, extract_gps_from_exif, generate_preview, preview_url_from, get_image_size
from timeutils import safe_client_time
logger = logging.getLogger(__name__)
@@ -30,6 +30,7 @@ class DiaryCreate(BaseModel):
location_name: Optional[str] = None
is_milestone: bool = False
dog_ids: Optional[list[int]] = None # alle Hunde inkl. primär; None = nur primary
+ weather_json: Optional[str] = None # Client-seitig vorab geholtes Wetter (Fallback wenn kein GPS)
class DiaryUpdate(BaseModel):
@@ -350,6 +351,19 @@ async def create_diary(dog_id: int, data: DiaryCreate,
)
entry = conn.execute("SELECT * FROM diary WHERE id=?", (entry_id,)).fetchone()
+ elif data.weather_json:
+ # Client hat Wetter vorab geholt (kein GPS-Standort gesetzt) → direkt speichern
+ try:
+ json.loads(data.weather_json) # Validierung
+ with db() as conn:
+ conn.execute(
+ "UPDATE diary SET weather_json=? WHERE id=?",
+ (data.weather_json, entry_id)
+ )
+ entry = conn.execute("SELECT * FROM diary WHERE id=?", (entry_id,)).fetchone()
+ except Exception as exc:
+ logger.warning("Client-weather_json ungültig: %s", exc)
+
return _entry_dict(entry, dogs_map, media_map)
@@ -692,10 +706,12 @@ async def upload_media(dog_id: int, entry_id: int,
media_url = f"/media/diary/{filename}"
- # EXIF-GPS aus Bild extrahieren (nur bei Bilddateien)
- exif_gps = None
+ # Bildmaße + EXIF-GPS (nur bei Bilddateien)
+ exif_gps = None
+ img_size = None
if media_type == "image":
exif_gps = extract_gps_from_exif(raw_data)
+ img_size = get_image_size(raw_data)
with db() as conn:
# sort_order = nächste freie Position
@@ -706,8 +722,9 @@ async def upload_media(dog_id: int, entry_id: int,
# Erstes Item eines Eintrags wird automatisch Cover
is_cover = 1 if max_order == -1 else 0
conn.execute(
- "INSERT INTO diary_media (diary_id, url, media_type, sort_order, is_cover) VALUES (?,?,?,?,?)",
- (entry_id, media_url, media_type, max_order + 1, is_cover)
+ "INSERT INTO diary_media (diary_id, url, media_type, sort_order, is_cover, img_width, img_height) VALUES (?,?,?,?,?,?,?)",
+ (entry_id, media_url, media_type, max_order + 1, is_cover,
+ img_size[0] if img_size else None, img_size[1] if img_size else None)
)
new_id = conn.execute(
"SELECT id FROM diary_media WHERE diary_id=? ORDER BY id DESC LIMIT 1",
diff --git a/backend/static/js/app.js b/backend/static/js/app.js
index 393bf53..4b5071c 100644
--- a/backend/static/js/app.js
+++ b/backend/static/js/app.js
@@ -3,7 +3,7 @@
Router, State-Management, Navigation, Initialisierung.
============================================================ */
-const APP_VER = '694'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
+const APP_VER = '695'; // ← bei jedem Deploy mit Frontend-Änderungen erhöhen
const APP_VERSION = '1.3.0'; // ← semantische Version, wird bei make release gesetzt
const IS_STAGING = location.hostname === 'staging.banyaro.app';
diff --git a/backend/static/js/pages/diary.js b/backend/static/js/pages/diary.js
index a39eccd..66d9150 100644
--- a/backend/static/js/pages/diary.js
+++ b/backend/static/js/pages/diary.js
@@ -868,9 +868,9 @@ window.Page_diary = (() => {
if (e.weather_json) {
try {
const w = typeof e.weather_json === 'string' ? JSON.parse(e.weather_json) : e.weather_json;
- const temp = w?.temperature_2m ?? w?.temp_c;
+ const temp = w?.temp_c ?? w?.temperature_2m;
if (temp != null) {
- metaParts.push(`${_weatherEmoji(w.weather_code ?? w.weathercode, w.is_day)} ${Math.round(temp)}°`);
+ metaParts.push(`${_weatherEmoji(w.weathercode ?? w.weather_code, w.is_day)} ${Math.round(temp)}°`);
}
} catch (_) {}
}
@@ -1073,15 +1073,14 @@ window.Page_diary = (() => {
if (entry.weather_json) {
try {
const w = typeof entry.weather_json === 'string' ? JSON.parse(entry.weather_json) : entry.weather_json;
- const temp = w?.temperature_2m ?? w?.temp_c;
+ const temp = w?.temp_c ?? w?.temperature_2m;
if (w && temp != null) {
- const feels = w.apparent_temperature ?? w.feels_like_c;
- const wind = w.wind_speed_10m ?? w.wind_kmh;
+ const wind = w.wind_kmh ?? w.wind_speed_10m;
+ const precip = w.precip_prob;
const parts = [
- `${_weatherEmoji(w.weather_code ?? w.weathercode, w.is_day)} ${Math.round(temp)}°C`,
- feels != null ? `gefühlt ${Math.round(feels)}°` : null,
- wind != null ? `💨 ${Math.round(wind)} km/h` : null,
- w.relative_humidity_2m != null ? `💧 ${w.relative_humidity_2m}%` : null,
+ `${_weatherEmoji(w.weathercode ?? w.weather_code, w.is_day)} ${Math.round(temp)}°C`,
+ wind != null ? `${Math.round(wind)} km/h Wind` : null,
+ precip != null ? `${precip}% Regen` : null,
].filter(Boolean).join(' · ');
metaItems.push(`${parts}`);
}
@@ -1728,6 +1727,16 @@ window.Page_diary = (() => {
});
await UI.asyncButton(submitBtn, async () => {
+ // Auto-Wetter: nur bei neuem Eintrag ohne GPS-Standort
+ let _clientWeather = null;
+ if (!isEdit && _locLat == null) {
+ try {
+ const pos = await API.getLocation();
+ const wd = await API.weather.get(pos.lat, pos.lon);
+ if (wd && wd.temp_c != null) _clientWeather = JSON.stringify(wd);
+ } catch (_) { /* GPS oder Wetter nicht verfügbar → kein Problem */ }
+ }
+
const payload = {
datum: fd.datum || null,
typ: fd.typ,
@@ -1739,6 +1748,7 @@ window.Page_diary = (() => {
gps_lon: _locLon,
location_name: _locName,
client_time: API.clientNow(),
+ weather_json: _clientWeather,
};
async function _uploadNewFiles(entryId) {
diff --git a/backend/static/sw.js b/backend/static/sw.js
index 7ce0992..28edf7a 100644
--- a/backend/static/sw.js
+++ b/backend/static/sw.js
@@ -3,7 +3,7 @@
Offline-Cache + Push Notifications + Tile-Cache
============================================================ */
-const CACHE_VERSION = 'by-v694';
+const CACHE_VERSION = 'by-v695';
const CACHE_STATIC = `${CACHE_VERSION}-static`;
const CACHE_TILES = 'ban-yaro-tiles-v1'; // bleibt über SW-Updates erhalten
const CACHE_API = 'ban-yaro-api-v1'; // API-Response-Cache