mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-30 18:46:00 +00:00
d1e024277f7f337829129cb0a8e17643130cb347
660 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
172cff57a2 |
fix(airtrail): import departure/arrival times for manually-entered flights (#1336)
The mapper read only `departureScheduled`/`arrivalScheduled`, but those columns are optional in AirTrail and stay null for manually-entered flights — where `departure`/`arrival` are the only times set. So the import dropped the departure clock (date-only) and the whole arrival (no date, no time), exactly as reported. AirTrail's own rule is "use departure if available, otherwise fall back to departureScheduled". Mirror that: prefer the scheduled instant, fall back to the primary departure/arrival, in mapFlightToReservation, normalizeFlight, and the sync hash. Hashing the resolved instant means flights already imported without a scheduled time re-sync once and pick up their clock automatically; flights that do have scheduled times are unaffected (no spurious re-sync). Tests: 3 new mapper cases (fallback mapping, picker preview, hash tracking); two existing cases that asserted the scheduled-only behaviour updated to the "neither time set" case. Full server suite green (4085). |
||
|
|
6996a67670 |
fix(extract): don't let the day-clamp fallback break reservation resync (#1288)
This branch added a clamp-to-nearest-day fallback to resolveDayIdFromTime so an imported booking whose exact date has no day row still lands on a day. After rebasing onto dev, that collided with #1288's resyncReservationDays, which relies on the original "null when no exact day" semantics to leave a booking whose date now falls outside the range untouched — instead it snapped to an edge day (TRIP-SVC-019 failed: expected day_id kept, got the clamped one). Make clampToNearest an opt-in parameter (default true, preserving the import behaviour for create/update) and have resyncReservationDays pass false, so out-of-range bookings keep their day_id. Full server suite green (4082). |
||
|
|
c3b3c278b8 |
test(llm-parse): cover the extraction router, client factory and import jobs
The new LLM extraction router shipped with little branch coverage, dropping src/nest below the 80% gate. Add unit tests for routeExtraction (flights/single/union/error paths, deterministic booking-wide fill), the native Ollama format client, the provider factory, the local-router service path with its type-aware text cap, the flat->schema.org mapper's remaining reservation types, and the background import-jobs runner. Also remove the now-unused validate.ts (only its FlatLike type was still referenced; moved to flat-schemas). |
||
|
|
574c54c16c |
perf(extract): cap single-booking text tighter; require rental company
A long single-booking PDF (e.g. an 11-page rental voucher) spent ~200s on CPU prompt-eval at the 16k cap, though its data sits in the first ~2k. Cap non-flight docs at 6k (flights keep 16k for all legs). Also make the rental operator a required field so the car gets a real title. |
||
|
|
76447f4a73 |
fix(extract): require the hotel address and ask for the rental company
After dropping the vendor templates, the model skipped the (often unlabeled) Expedia-style hotel address — making address a required schema field forces it to emit the street-address line, restoring the booking's location/place. Also hint the rental company so a car booking gets a real title instead of the generic fallback. |
||
|
|
55ff5c03dd |
refactor(extract): drop vendor templates, let the model drive with deterministic backfill
Now that a capable instruct model (Qwen3-8B, thinking off) reads name/address/dates/legs reliably across formats, the per-vendor template short-circuit distorted more than it fixed: brittle on layout variations and overriding the better model output. Remove the template layer; the model extracts the structure and Schicht 2 backfills the confirmation/total and takes the currency from the document's own symbol (correcting model misreads like ¥→$). Per-type prompts now also ask for address and price/currency. |
||
|
|
d95d26e493 |
fix(extract): disable model thinking for grammar-constrained extraction
Hybrid/reasoning models (Qwen3 and similar) default to emitting reasoning tokens, which collide with Ollama's format-grammar constraint — on CPU this produced null/unparseable output and blew the latency budget (qwen3:8b: null or 300s timeouts vs ~20s with thinking off). Send think:false on the /api/chat call; Ollama ignores it for non-thinking models (verified on qwen2.5:7b), so it's safe and unlocks the stronger Qwen3 family. |
||
|
|
7bac753ff3 |
refactor(extract): dedupe currency/day helpers, drop redundant casts, support JPY vouchers
Code-audit clean-ups: share one normCurrency between the router and the templates, lift the duplicated nearest-day resolver into formatters.resolveDayId, drop two needless as-unknown-as casts at the fillBookingWideFields call sites, restore routeExtraction's doc comment, and give the broker template readable names. Plus recognise ¥/JPY and fall back to a standalone symbol amount, so a Klook-style voucher whose price sits far from any label still yields a cost. |
||
|
|
b3fa87bdd6 |
fix(reservations): skip un-geocoded endpoints instead of failing the save
reservation_endpoints.lat/lng are NOT NULL, so saving a reviewed transport whose pick-up/return couldn't be geocoded threw a 500 and lost the whole booking (dates, linked cost). Skip those rows; the dates still persist on reservation_time/reservation_end_time. |
||
|
|
c1d61c98f0 |
fix(extract): backfill booking code/total and harden the reference match
Apply the deterministic confirmation-code and total fill to vendor-template results too (not just model output), and require the captured reference to contain a digit so a bare 'Confirmation'/'Reference' label no longer grabs the next prose word. |
||
|
|
c7f5694f63 |
feat(extract): add Expedia and rental-broker booking templates
Pull the hotel/rental fields these vendors print in a stable text layout (name, address, stay/pickup dates, price, reference) deterministically, so the import stops depending on the local model for them. Handles German long/abbreviated months and English dates incl. 12-hour and comma forms. |
||
|
|
1c81e8b959 |
feat(import): parse bookings in the background with a progress widget
Parsing a booking can take a while on a CPU host, so don't hold the upload modal open for it. The async import endpoint returns a job id right away; the parse runs server-side (one at a time per user) and pushes progress over the user's WebSocket, and a small widget in the bottom corner tracks it while the user keeps navigating and editing. A finished job opens the per-item review from the widget. |
||
|
|
8f1c99a07a |
feat(extract): drive local parsing through a layered extraction router
The single-shot prompt was unreliable on multi-leg flights and longer documents, and slow on a CPU host. For the local provider, run a small router instead: - deterministic vendor templates first, with no model call at all - exactly one grammar-enforced call per document via Ollama's native `format` (flights as a flat array of legs, everything else as one flat reservation, the type picked from keywords or a union schema) - booking-wide fields (booking reference, total price, the overnight arrival day) filled deterministically from the text afterwards, and dates coerced to ISO so a natural-language date can't slip through Recommend qwen2.5 in the AI-parsing settings instead of NuExtract. |
||
|
|
8640100312 |
feat(extract): drive NuExtract with its native template
NuExtract isn't an instruct model — fed a plain chat prompt it just echoes the schema back. Detect a NuExtract model by id and talk to it the way the model cards document: the JSON template inlined in a single user message, no system prompt, no json_schema, temperature 0. Its flat result is mapped back to the same KiReservation shape the rest of the pipeline already uses, so nothing downstream changes; every other model keeps the generic prompt. Money is taken as a verbatim string and parsed locally (German "1.580,22 €" otherwise comes back as 1.49772), a rental car's pickup/return ride the from/to fields so a stray form label doesn't become the location, and a lodging with no name falls back to its address instead of being dropped. |
||
|
|
aa72d527c9 |
feat(extract): create a linked cost from the booking price on import
When a confirmation carries a total price, record it as a real expense linked to the reservation (in the matching Costs category) instead of leaving the amount in metadata only. Gated on the Costs addon. |
||
|
|
684ac3b442 |
feat(extract): capture seat, class, platform, price + event venue contact
Request and map root-level seat/class/platform and a total price/currency into reservation metadata (shown on the card; price reuses the existing label). Read both the root and reservationFor and tolerate common field-name aliases (priceAmount, priceCurrencyISO4217Code, fareClass, ...) since models name these inconsistently. Also capture event/attraction venue telephone + url onto the auto-created place, matching lodging/restaurant. |
||
|
|
f049229e25 |
perf(extract): cap LLM input at 4000 chars for CPU-only speed
On a GPU-less host the model's prompt-eval time scales with input length and dominates total latency. Booking details sit at the top of a confirmation, so capping the extracted text at 4000 chars (was 8000) roughly halves extraction time (~50s warm for a capable local 7B model) with no loss of fields on real hotel/rental confirmations. Tunable if a long multi-segment itinerary needs more. |
||
|
|
38565c3c6d |
feat(extract): fill transport/booking fields, geocode endpoints, assign days
- rental car: request+map dropoffLocation, emit pickup->return from/to endpoints, set a location string (G1/G2/G3). - geocode endpoints (stations/stops/terminals/rental desks) on confirm via Nominatim; mapper now emits coordless named endpoints and confirm persists only the geocoded ones (G6). - assign every dated booking to the nearest trip day so it still shows when slightly out of range, and keep hotel accommodation from vanishing when a check date misses (G5/G10). - fix bus mislabelled as train + add bus_number metadata (G7/G8), flag malformed boats (G9), accept root start/end time for events (G11). - raise the local-LLM timeout to 300s for CPU-only Ollama. |
||
|
|
a1cbc11169 |
fix(extract): make AI imports reliable and fast on local models
client: the import call inherited the global 8s axios timeout and aborted long LLM extractions even though the server finished it; remove the timeout. server: raise the OpenAI-compatible LLM timeout 60s->180s (a cold Ollama model can take ~45s to first token). server: cap extracted text to 8000 chars before the LLM - multi-page T&C tails (30k+ chars) overflowed the context window, truncating the relevant head and making CPU inference crawl; booking details sit at the top. |
||
|
|
b859ae8b00 |
fix(extract): auto-run the AI fallback when the addon is enabled
Booking import only fell back to the LLM when each user flipped an 'always retry with AI' toggle, so by default files kitinerary returned nothing for just failed. Run the fallback automatically whenever the AI Parsing addon is on (fallback-on-empty); drop the now-redundant per-user toggle and its setting. |
||
|
|
ae14a6c860 | feat(extract): extract data using LLM | ||
|
|
41c541828f |
fix(setup): warn when ADMIN_EMAIL/ADMIN_PASSWORD are ignored, ship reset-admin
The first-run seeder only applies ADMIN_EMAIL/ADMIN_PASSWORD on an empty database and then silently ignores them. People add the vars after the first boot, or pull a fresh image without clearing ./data, restart, and cannot log in with no hint why (#1339). The default is a generated password (not the .env.example placeholder), printed once in the first-run box. Now: warn loudly when the vars are set but a user already exists, and warn on a partial (one-of-two) config instead of quietly falling back. Also ship the reset-admin recovery script in the image -- it was never COPYed in despite the wiki referencing it. node server/reset-admin.js resets/creates admin@trek.local with a generated password (RESET_ADMIN_EMAIL/RESET_ADMIN_PASSWORD overridable), picks a free username so it cannot trip UNIQUE(username), and sets must_change_password. |
||
|
|
0631e34a79 | chore: bump version to 3.1.3 [skip ci] | ||
|
|
7c3440f139 |
Revert "chore: bump version to 3.1.3 [skip ci]"
This reverts commit
|
||
|
|
4ceea09e31 | chore: bump version to 3.1.3 [skip ci] | ||
|
|
03cdb4d276 |
fix(files): reject cross-trip reservation/place/assignment links
A member of one trip could point a file at a reservation, place or day-assignment belonging to another, private trip — on upload, on a metadata update, or through the file-link endpoint. The reservation join in the file list and the links list then returned that trip's reservation title, disclosing it across the trip boundary and letting an attacker enumerate foreign reservation titles by their id. The file already had to belong to the caller's trip; now the linked reservation/place/assignment must too. findForeignLinkTarget checks each supplied id against the trip (assignments via day -> trip) and the upload, update and link handlers reject a cross-trip reference with 400 before it is stored. Same-trip links and clearing a link are unchanged. |
||
|
|
f0877a2e7d |
Replace the 3.0 upgrade notices with a thank-you / support modal
The 3.0 "what's new" notices have served their purpose, so swap them for a single thank-you notice that comes back once on every fresh install and version bump. It carries Buy Me a Coffee and Ko-fi buttons and only shows on desktop. Adds a per-version recurring mode (new dismissed_app_version column) plus external-link CTAs to support it; the 3.0.14 whitespace-collision admin notice stays active. |
||
|
|
aa91f009ad |
fix(costs): freeze the FX rate so settled expenses don't reopen when rates drift (#1335)
Settle-up transfers are stored as fixed amounts, but a foreign-currency expense was re-converted with live rates on every settlement calc. When the rate drifted, the fixed transfer no longer cancelled the re-valued expense and a few-cent residual re-opened the settled position. Foreign-currency expenses now freeze the live rate at entry time into the existing budget_items.exchange_rate column, and the settlement converts with that frozen rate when working in the trip currency. Legacy rows (exchange_rate = 1) keep using live rates, so historical data is unchanged until re-edited; rate fetch failures fall back to live rates. |
||
|
|
2277f28a57 |
fix(airtrail): import the airline name, not the ICAO code (#1334)
AirTrail returns each airline as {icao, iata, name}, but the import reduced it to the ICAO/IATA code, so an imported flight showed e.g. 'EWG' instead of 'Eurowings'. The picker and the stored reservation now use the airline name (falling back to the code when AirTrail has none). The raw code is kept in metadata.airline_code so the writeback to AirTrail still sends a code, not a name (#1240), and the change-detection snapshot hash stays on the code so existing flights don't spuriously re-sync.
|
||
|
|
1ec2d62b1c |
fix(reservations): keep dated bookings on their date when the trip range shifts (#1288)
Changing a trip's start date positionally re-dates the day rows (keeping their ids), so a dated booking's day_id stayed glued to a now-re-dated day and the booking visually shifted by the offset — until you re-opened and saved it. After a date-range change, non-hotel bookings are now re-anchored to the day matching their absolute reservation_time (the same derivation create/update already use). Bookings whose date falls outside the new range are left untouched; hotels and the relative positional shift of places/notes are unaffected. |
||
|
|
4e91fbca48 |
fix(places): guide single-place links to the right importer (#1304)
Pasting a single-place Google Maps share link (.../maps/place/...) into the list import failed with a cryptic 'Could not extract list ID from URL'. When the link is a single place it now returns a clear message telling the user to paste it into the place search box instead; other unrecognised URLs keep the existing list-link message. |
||
|
|
4cb9b18cc6 |
fix(atlas): assign border places by polygon, not just bounding box (#1331)
getCountryFromCoords picked the country with the smallest bounding box containing the point, so a place just across a border (e.g. Strasbourg, which sits inside both the FR and DE boxes) landed in the wrong, smaller-box country. When more than one country box matches, it now disambiguates with the real admin0 polygon via point-in-polygon, smallest-box-first; a micro-territory with no admin0 polygon (HK, MO, SM, VA, ...) keeps the smallest-box win, and an unmatched point falls back to the old behaviour. The common single-candidate case is unchanged. |
||
|
|
005e0c109d |
fix(maps): make Overpass endpoints configurable and harden the POI search (#1309)
Builds on @Hardik-369's instance-specific User-Agent idea and reworks the rest of the #1309 fix: - keep the unique User-Agent (buildUserAgent) — a shared UA gets the public Overpass mirrors to rate-limit harder; it appends the configured instance URL and is applied to every Nominatim/Overpass/Wikimedia call - add OVERPASS_URL so an operator behind locked-down egress (e.g. a Kubernetes cluster) can point the explore search at an internal/self-hosted Overpass instance instead of the public mirrors - keep the per-endpoint timeout default at 12s but make it tunable via OVERPASS_TIMEOUT_MS for slow self-hosted instances; non-positive/invalid values fall back to the default rather than 502-ing every search at a 0ms cap - log each endpoint's failure reason before the 502 so blocked egress is diagnosable instead of a bare "Overpass request failed" Adds unit tests for the User-Agent, endpoint and timeout resolution plus the all-mirrors-down path, and documents the two new env vars in .env.example, the wiki and the Helm chart. |
||
|
|
e54ea2f17d |
fix: memoize User-Agent and prevent localhost leak; bump timeout to 30s
- Memoize USER_AGENT via IIFE so it's computed once, not per-request - Only append instance URL when APP_URL or ALLOWED_ORIGINS is explicitly configured; skip the getAppUrl() localhost fallback - Bump OVERPASS_TIMEOUT_MS to 30000 (above the observed 25.7s TTFB) |
||
|
|
544a76d2da |
fix(maps): increase Overpass timeout and add instance-specific User-Agent
The POI search endpoint (/api/maps/pois) returned 502 errors because: 1. OVERPASS_TIMEOUT_MS (12s) was shorter than mirror response times (kumi.systems takes ~25.7s to first byte). Increased to 25s to match the [timeout:20] query timeout. 2. The static User-Agent string was indistinguishable between instances, making rate-limiting and throttling more likely. The new userAgent() function appends the instance's APP_URL so each deployment identifies itself uniquely, following Overpass API best practices. |
||
|
|
0b2780ead2 |
test: make the Google Maps ftid path honest + cover the URL helper
The Places API googleMapsUri is a cid-style URL with no ftid, so the search/getPlaceDetails fixtures had stored a fabricated ftid. Switch them to real cid URLs and assert google_ftid is null — the precise query_place_id link still fixes the wrong-spot bug — and document the behaviour on googleFtidFromMapsUrl. - add a direct googleFtidFromMapsUrl test: extracts a real /place ftid, returns null for a cid URL, rejects malformed/hostile values - add placeGoogleMaps.test.ts covering the whole fallback chain (ftid -> place_id -> details URL -> coords) and the hostile-ftid rejection - PlaceInspector: use a freshly-fetched ftid when the place hasn't stored one |
||
|
|
91fcaa50f6 | Use Google Maps feature IDs for place map links | ||
|
|
9669642c62 |
feat(maps): add MapLibre OpenFreeMap support (#1317)
Adds MapLibre GL with OpenFreeMap as a tokenless third map provider alongside Leaflet and Mapbox: a provider abstraction with style presets, CSP + service-worker entries for tiles.openfreemap.org, and the map_provider allow-list entry. Mapbox-only APIs stay gated behind the mapbox provider, and existing Mapbox/Leaflet users are unaffected. Maintainer review follow-ups folded in: the new map-settings strings are translated across all locales; the GL engine is lazy-loaded so Leaflet-only installs don't download it; MapLibre gets its own maplibre_style slot so switching providers no longer overwrites a custom Mapbox style; and the MapLibre render path plus the OpenFreeMap style-guards are covered by tests. |
||
|
|
3d65bb0c12 |
fix: address review feedback on the distance unit setting
- server: allow distance_unit as an admin default (+ value validation) so the Admin "Default User Settings" toggle persists instead of returning 400 - i18n: add settings.distance to all 20 locales and translate the labels through t() instead of hardcoding "Distance" - route legs: include the unit in the OSRM cache key and recompute on a unit switch, so map and sidebar distances refresh and never mix units - keep wind speed tied to the temperature unit — a distance setting must not silently flip existing Fahrenheit users from mph to km/h - restore the sub-1km metres reading for metric, convert GPX elevation to feet for imperial, and format distances with a '.' decimal in every locale - add units.test.ts |
||
|
|
4cd4c9c8d8 | chore: bump version to 3.1.2 [skip ci] | ||
|
|
328d1c9468 |
fix(auth): keep the last admin when OIDC claims would demote it (#1274)
On OIDC-only instances the bootstrap admin (first SSO user) rarely carries the configured admin claim, so a forced re-login — e.g. after a JWT-secret rotation — re-derived its role purely from claims and demoted it to user, locking the instance out with no recovery. The OIDC login role sync now skips a downgrade that would strip the last remaining admin, and the admin user-update endpoint guards the same case. |
||
|
|
e40465ba1f |
test(days): bump over-long-time assertion to the new 250 limit (#1252)
Follow-up to raising the day-note time cap to 250: the unit test still sent 151 chars expecting a 400, which now passes validation and fell through to an unmocked service call. |
||
|
|
a2c552f04d |
fix(days): align note time limit to 250 and keep toasts above modal blur (#1252)
The day-note 'time' field capped at 150 server-side while the dialog and shared schema allow 250, so 151-250 char notes 400'd with a confusing 'time must be 150...' message. Raise the controller and MCP limits to 250. Also lift the toast container above modal overlays so the error toast isn't rendered behind the modal's backdrop blur. |
||
|
|
27762458e6 |
fix(dashboard): count archived trips in travel stats (#1264)
The trips/days widgets filtered out archived trips while places, countries and flight distance did not, so archiving a trip zeroed only those two. Drop the is_archived filter so all stats stay consistent. |
||
|
|
adbe15abc4 |
fix(security): allow same-origin PDF previews under CSP (#1253)
Firefox/Chrome enforce object-src, so object-src 'none' blocked the inline <object> PDF preview (worked only in Safari). Relax to 'self' for same-origin file previews. |
||
|
|
6a797a39ae |
fix(atlas): gzip-compress responses so large country GeoJSON loads behind reverse proxies (#1262)
The admin-0 country GeoJSON served at /api/addons/atlas/countries/geo is ~30 MB uncompressed. With no compression in the request pipeline the transfer aborts (~8s, net::ERR_FAILED despite a 200) behind reverse proxies / Cloudflare Tunnel, so the Atlas map never colours visited countries. LAN is unaffected. Add the `compression` middleware to the shared applyGlobalMiddleware pipeline (gzip brings ~30 MB down to ~4 MB). text/event-stream is excluded so the /mcp StreamableHTTP (SSE) transport is not buffered. Adds BOOT-008 asserting content-encoding: gzip on the geo endpoint. Fixes #1254 Co-authored-by: pai <pai@stabpablo.eu> |
||
|
|
438d4fc400 | chore: bump version to 3.1.1 [skip ci] | ||
|
|
d152f9d02b |
v3.1.1 bug fixes (#1228)
* fix(shared-view): render each leg of multi-leg flights correctly The read-only shared view showed the overall trip start/end airports and the first leg's flight number on every leg of a multi-leg flight. The Day Plan already expands legs (each carries __leg), but the renderer ignored it and read flat top-level metadata; the Bookings tab had the same bug. - Day Plan: use __leg for per-leg airline/flight number/route, plus dep-arr time - Bookings tab: list each leg via getFlightLegs() - unique React keys for multi-leg rows Closes #1219 * feat(pdf): add legs to pdf export * fix(demo): skip first-run admin seed in demo mode When DEMO_MODE is on, the demo seeder creates its own admin (admin@trek.app, username "admin") right after the generic seeds run. The first-run admin bootstrap was grabbing username "admin" first, so the demo seeder hit the UNIQUE(username) constraint and aborted before the demo user was ever created - which surfaced as a 500 "Demo user not found" on demo-login. Skip the generic admin bootstrap when demo mode owns the admin account. * fix(docker): ship the encryption-key migration script in the image The production image only copied server/dist, so the documented rotation command `node --import tsx scripts/migrate-encryption.ts` failed inside the container with a module-not-found error - the raw .ts was never present. The script runs via tsx straight from source and only pulls node builtins plus better-sqlite3 (both prod deps), so copying the single file into /app/server/scripts is enough to make the rotation work again. * fix(vacay): keep the mode toolbar above the mobile bottom nav The floating Vacation/Company toolbar was pinned at bottom-3 with z-30, so on mobile it landed in the same band as the fixed bottom nav (z-60) and got hidden behind it - and could scroll out of reach entirely. Pin it above the nav with the shared --bottom-nav-h variable (0px on desktop, so nothing changes there) and reserve matching space below the calendar grid so it never gets swallowed. * fix(dashboard): show the correct reservation date regardless of timezone The upcoming-reservations widget built the date with new Date(reservation_time) .toISOString(), which reinterprets the stored naive local time as UTC and can roll the displayed day forward in non-UTC timezones (e.g. a 23:30 reservation showing the next day). Read the date and time straight from the stored string parts via splitReservationDateTime, and format the time with the shared formatTime helper so it also honours the user's 12h/24h preference. * fix(atlas): cursor-following tooltips and removing countries from search Two related Atlas fixes: - Country tooltips were bound with sticky:false, which anchors them at the feature's bounds centre. For countries with overseas territories (e.g. France) that centre sits far out in the ocean, so the tooltip popped up nowhere near the area being hovered. Make them sticky so they track the cursor. - Selecting an already-visited country from the search bar always opened the "Mark / Bucket" dialog, with no way to remove it. Tiny countries like Vatican City or Singapore are hard to hit on the map, so search was the only way in. Mirror the map-click behaviour: a manually-marked country opens the Remove confirmation, a trip/place-backed one opens its detail. * fix(oidc): keep dots in generated usernames The OIDC username sanitizer stripped dots because they were missing from the allowed character class, so a name claim like "first.last" became "firstlast". Dots are valid usernames (the profile validator already allows ^[a-zA-Z0-9_.-]+$), so add the dot to the sanitizer. * fix(collab): show poll option labels in the UI The poll API formatted each option as { label, voters }, but the React poll component renders opt.text - so every option button came out blank. Emit text alongside label (kept for any other consumer) so options render again. * feat(backup): make the upload size limit configurable The restore upload was capped at a hard-coded 500 MB, so instances whose backup archive (uploads/ included) grew past that got a 413 "File too large" with no way to raise it. Add a BACKUP_UPLOAD_LIMIT_MB env var (default 500, invalid values warn and fall back), documented in .env.example. * feat(costs): create an expense from a booking, fix editing total-only items Replace the inline price + budget-category fields in the Transport and Reservation booking modals with a "Create expense" flow: the modal saves the booking, then opens the full Costs editor prefilled (name + category mapped from the booking type) and linked to the reservation. A booking with a linked expense shows it inline with edit / remove. Also fix the Costs editor so an expense with a recorded total but no payers (transport-derived or pre-rework items) opens with its amount, lets you set the currency, and saves - it previously showed 0 everywhere and could not be saved. Legacy / localized categories now map to the fixed keys, and changing a booking's type keeps its linked expense category in sync (unless it was manually set). - shared: reservation_id on budget create, typeToCostCategory helper, i18n keys - server: createBudgetItem stores reservation_id; keep total_price for payerless items; a booking update no longer wipes its linked expense and syncs the category on type change - client: shared BookingCostsSection, exported ExpenseModal with prefill and an editable total, page-level save-then-open wiring * test(reservations): align syncBudgetOnUpdate unit tests with no-wipe + type-sync The service now leaves a linked expense alone when no budget entry is on the payload (only an explicit total_price 0 deletes it) and syncs the category on a booking type change. Update the unit tests accordingly - the old "price cleared" case passed entry: undefined, which is now a no-op and left a mocked return queued that leaked into the next test. * fix(planner): keep a reservation on its day when edited (#1237) Editing a booking forced its day_id to the globally selected day, which is null when editing from the Book tab - so the booking lost its day and vanished from the Plan. Preserve the reservation own day_id on edit instead. * fix(planner): derive a booking day from its date when none is set (#1237) The client always sends day_id on a reservation update, so the server only derived it from reservation_time when the field was absent. A non-transport booking saved without a selected day (Book tab) therefore got day_id null and vanished from the Plan, even though its date matched a day. Derive the day from reservation_time whenever day_id is null, mirroring create. * fix(planner): let a booking's day follow its date when edited (#1237) Preserving the old day_id on edit left a re-dated booking on its previous start day while end_day_id followed the new date, so it spanned both. Stop sending day_id from the edit modal entirely - the server derives both ends from the booking's date (and keeps the current day when there is no date), so a re-dated booking moves cleanly to the matching day. * fix(atlas): keep the continent breakdown in sync on mark/unmark (#1225) The optimistic mark/unmark updates bumped the country total but never the per-continent counts, so the continent column froze until a full reload. Move the country to continent map into @trek/shared (single source for server and client) and adjust the matching continent count at every optimistic site: the country confirm flow plus the choose / region mark and region unmark handlers. * feat(admin): let admins set a default currency for new users Adds a currency picker to Admin > User Defaults. Stored as the default_currency user-default, so users who have not picked their own currency inherit it in Costs. * fix(atlas): give every sub-national region a distinct code (#1217) geoBoundaries fills shapeISO with the bare country code for some countries (every Spanish region got "ESP", every Chinese "CHN", also Chile/Oman), so marking one region lit up the whole country. build-atlas-geo.mjs now keeps shapeISO only when it is a real "XX-..." subdivision code and otherwise synthesizes a unique per-country id from the region name. Regenerated admin1.geojson.gz: Spain/China/ Chile/Oman now carry distinct region codes (countries with real codes, e.g. Germany, are unchanged). * fix(dashboard): never crash on a malformed reservation date A reservation with an invalid date blanked the whole My Trips page: the old Upcoming widget did new Date(value).toISOString(), which throws "Invalid time value" (fixed in #1222 by reading the string parts). Also guard splitDate so a bad date renders a dash instead of "Invalid Date" or throwing. * fix(airtrail): gate airtrail update behind a user setting, on airtrail update: rebuild payload from fresh data to prevent any data loss * fix(airtrail): add back missing tests * fix(costs): rework the cost panel UX wise and apply prettier on the shared package * chore(prettier) prettier this file * fix(airtrail): don't use cabin class as seat on import When an AirTrail flight has a cabin class but no seat number, the mapper fell back to the class for metadata.seat, so reservations showed e.g. "economy" as the seat. Use only the seat number; leave the seat blank otherwise. The class is still surfaced separately in the import picker. Closes #1246 * fix(airtrail): import scheduled flight times instead of actual AirTrail exposes both scheduled (departureScheduled/arrivalScheduled) and actual (departure/arrival) times. TREK read the actual times, so a delayed or early flight imported the wrong time for planning. Read the scheduled times on import and on poll-sync (both go through mapFlightToReservation); when a flight has no scheduled time, leave the clock blank (date preserved) rather than fabricating 00:00 or falling back to actual. The change-detection hash now tracks the scheduled values, so existing linked reservations re-sync once on the next poll. The opt-in writeback mirrors the read, pushing TREK edits to the scheduled fields so they round-trip. * fix(planner): hydrate per-assignment times when editing a place from the pool Times live per day-assignment, not on the pool place, so reopening a place from the Places panel / inspector showed empty Start/End fields (#1247). The editor now resolves a place's lone assignment when no day is in context and hydrates the fields from it; ambiguous (0 or 2+ days) edits hide the fields instead of showing non-persisting inputs. * fix(mcp): make write tools return client-valid, hydrated entities Audit of all write tools under server/src/mcp/tools (issue #1244 anchor). S1 (broken): - create_budget_item / create_budget_item_with_members now default the split to all trip members when member_ids omitted, so the entry passes the client save-gate instead of being member-less (#1244). - create_transport / update_transport backfill lat/lng/timezone for code-only flight endpoints (NOT NULL columns) and return a clean error for unresolvable endpoints instead of crashing. S2 (under-hydration): set_budget_item_members, create_journey, create_journey_entry, create_packing_bag, bulk_import_packing and update_vacay_plan now return the hydrated shape the matching read/REST route returns; bulk_import widened to accept bag/weight_grams/checked. S3 (parity): check_in_end added to accommodation tools; atlas mark_region_visited echoes the client shape; update_journey_entry/ update_journey_preferences, set_bag_members, set_packing_category_assignees, apply_packing_template return hydrated payloads; set_vacay_color echoes the color. Auth: save_packing_template now requires admin, matching the REST gate. Also refactors server/src/config.ts (JWT-secret handling). Adds getBudgetItem hydrated getter, exports EndpointInput, and MCP regression tests (incl. new tools-transports and tools-journey suites). * fix(mcp): fix ICS/maps/accommodation bugs, add settlement & template tools Bugs: - export_trip_ics: include flights that store times per-endpoint (local_date/local_time) instead of a top-level reservation_time - resolve_maps_url: follow redirects for cid=/share links and fall back to parsing the page body, all SSRF-guarded - link_hotel_accommodation: normalize accommodation_id (TEXT column) to an integer in the reservation read paths so it no longer returns "14.0" Gaps: - packing: save_packing_template returns the new template id; add list_packing_templates (read) and delete_packing_template (admin) - budget: update_budget_item accepts payers/member_ids; clarify create/ update/members descriptions to ask which members share the expense and who paid - budget: add settlement tools — get_settlement_summary, list_settlements, create/update/delete_settlement (budget_edit, mirrors REST + WS events) * chore: bump nodemailer * chore: bump multer --------- Co-authored-by: Maurice <mauriceboe@icloud.com> |
||
|
|
f6af1d67a2 | chore: bump version to 3.1.0 [skip ci] | ||
|
|
ad893eb1cc |
Release 3.1.0 (#1185)
* Phase 0 — NestJS + Zod foundation harness (F1–F8) (#1050) Co-hosted NestJS app behind the existing Express server via a strangler-fig dispatcher, sharing the same better-sqlite3 connection and JWT httpOnly cookie. Additive and dormant: default routing stays on Express, Nest only serves its own /api/_nest diagnostics until a module opts in. F1 @trek/shared Zod contract package; F2 Nest bootstrap co-hosted (fall-through, single Dockerfile/port); F3 shared better-sqlite3 provider; F4 JWT cookie auth guard (+ @CurrentUser, admin guard); F5 Zod validation pipe + error-envelope parity; F6 Nest test + coverage gates; F7 per-prefix strangler toggle (env, default Express); F8 CI build/typecheck/test/coverage. Remaining F4/F6/F8 checklist items (trip-access + permission levels + MFA policy, e2e harness/seed + 80% gate, Nest↔Express parity test, Playwright PR-comment workflow) are tracked on the first consuming module cards (L1/A1/C1). * feat(weather): migrate /api/weather to the NestJS pilot module (L1) (#1053) First strangler migration (L1): /api/weather is served by a NestJS module. - @trek/shared/weather Zod contract; Nest controller byte-identical to the legacy Express route (paths, query params, status codes, { error } bodies, lang default, ApiError/500 passthrough). Service reuses getWeather/getDetailedWeather (+ shared cache; MCP tools unchanged). - Strangler routes /api/weather to Nest by default; the legacy Express route + its migration-time parity test were decommissioned in this PR. - Frontend (FE2): weatherApi typed against the @trek/shared WeatherResult contract. - Harness: reusable Nest-vs-Express parity harness, e2e harness (temp SQLite + seed/cookie helpers, real JwtAuthGuard), src/nest coverage gate raised to >=80%, src/nest test guide. - Verified end-to-end on a prod mirror (dev1): 401/400/200 via Nest with real Open-Meteo data, Express route gone. * fix(packing): multiply item weight by quantity in bag/total weight calcs (#898) Quantity now counts toward bag and total weights. Generalised to an itemWeight() helper used by every weight sum (bag totals + max, unassigned, grand total; sidebar + bag modal) with unit tests. * feat(i18n): add Korean (ko) translation (#977) Korean translation by @ppuassi, topped up to full en.ts key parity. Language registration follows separately. * feat(i18n): add Japanese (ja) translation (#829) Japanese translation by @soma3978, at full en.ts key parity, registered in supportedLanguages + TranslationContext. * Add Turkish (tr) translation + language registry (#1029) Turkish translation by @SkyLostTR, at full en.ts key parity, registered in supportedLanguages + TranslationContext. * i18n: register Korean + add Ukrainian translation (#1055) Korean translation by @ppuassi (#977) — now registered. Ukrainian by @JeffyOLOLO (#902) — lifted onto a clean branch. Both at full en.ts key parity (2258 keys). * chore: fix monorepo build pipeline and migrate shared to built package (#1056) * chore: fix monorepo build pipeline and migrate shared to built package - Root package.json: add workspace scripts (dev, build, test, test:cov, test:e2e) that delegate to actual scripts in shared/server/client workspaces - shared: add tsup build step (CJS + ESM dual output, .d.ts); consumers now import from the built dist instead of raw TS source via path aliases - server: replace tsc-alias with tsconfig-paths (tsc-alias mangled node_modules paths); fix MCP SDK path aliases to point to root node_modules (../node_modules) - server/scripts/dev.mjs: delay node --watch until tsc -w signals first-pass done, eliminating the spurious restart on every dev startup - client/vite.config.js + vitest.config.ts: remove @trek/shared path alias (no longer needed now that shared is a proper package) - Consolidate package-lock.json at the workspace root; drop per-workspace lock files * chore: fix test script to reflect root package.json * chore: add missing lint and prettier script in root package.json * fix(ci): build shared before tests; fix vitest MCP SDK alias paths vitest.config.ts aliases pointed at ./node_modules/ (server-local) but packages are hoisted to the root node_modules/ in the npm workspace — changed to ../node_modules/. CI jobs now install and build shared before running server/client tests so that @trek/shared's dist/ exists when vitest resolves the package. * fix(docker): update Dockerfile and CI for monorepo workspace structure Dockerfile: - Add shared-builder stage that produces @trek/shared dist before client and server stages need it - Each build stage carries root package.json + package-lock.json so npm can resolve @trek/shared as a workspace dependency - Production stage installs via workspace context (npm ci --workspace=server --omit=dev) so node_modules/@trek/shared symlinks to shared/dist correctly - Copy server/tsconfig.json into the image so tsconfig-paths/register can find the MCP SDK path aliases at runtime - CMD cds into /app/server before starting node so tsconfig-paths baseUrl resolves and ../node_modules points to /app/node_modules - Remove mkdir for /app/server (now a real dir); keep symlinks for uploads/data docker.yml version-bump: - Replace manual per-workspace cd+npm-version calls with single: npm version --workspaces --include-workspace-root --no-git-tag-version (mirrors the version:* scripts in root package.json) - git add now references root package-lock.json; adds shared/package.json .dockerignore: add shared/dist package.json: fix version:prerelease preid (alpha → pre) * fix(tests): use in-memory SQLite per worker in test mode vitest pool:forks spawns parallel worker processes that all called initDb() on the same data/travel.db, causing SQLite "database is locked" and "duplicate column name" races. When NODE_ENV=test each fork now gets an isolated :memory: DB so migrations run independently with no file contention. * chore(ci): add ACT guards to skip DockerHub steps in local act runs act sets ACT=true automatically. Guards added: - docker login: if: ${{ !env.ACT }} - build outputs: type=docker (local load) when ACT, push-by-digest when CI - digest export/upload: if: ${{ !env.ACT }} - merge job: if: ${{ !env.ACT }} - release-helm job (docker.yml): if: ${{ !env.ACT }} - version-bump git push (docker.yml): wrapped in [ -z "$ACT" ] shell guard Run locally with: ./bin/act -j build -W .github/workflows/docker.yml \ -P ubuntu-latest=catthehacker/ubuntu:act-latest * fix(ci): move ACT guards to step level; add guards to security.yml env context is invalid in job-level if conditions — moved all ACT guards down to individual steps. Also guards docker login + scout in security.yml so act can run the build-only part of that workflow. * fix(ci): skip git fetch and tag logic in act (no remote access in local containers) * Revert "fix(ci): skip git fetch and tag logic in act (no remote access in local containers)" This reverts commit |