mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-20 05:41:47 +00:00
Compare commits
7 Commits
v3.0.0-pre.59
...
v3.0.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 523bca3a20 | |||
| d5be528d4b | |||
| 3ada075b1a | |||
| afce302b59 | |||
| 8e8433fa9d | |||
| ff42fa0b8c | |||
| ccea7f7a65 |
@@ -127,19 +127,23 @@ A self-hosted, real-time collaborative travel planner — with maps, budgets, pa
|
||||
|
||||
#### 🧩 Addons (admin-toggleable)
|
||||
|
||||
- **Lists** — packing lists + to-dos with templates, member assignments, optional bag tracking
|
||||
- **Budget** — expense tracker with splits, pie chart, multi-currency
|
||||
- **Documents** — file attachments on trips, places, and reservations
|
||||
- **Collab** — chat, notes, polls, day-by-day attendance
|
||||
- **Vacay** — personal vacation planner with calendar, 100+ country holidays, carry-over tracking
|
||||
- **Atlas** — world map of visited countries, bucket list, travel stats, streak tracking, liquid-glass UI
|
||||
- **Collab** — chat, notes, polls, day-by-day attendance
|
||||
- **Journey** — magazine-style travel journal with entries, photos, maps, moods
|
||||
- **Dashboard widgets** — currency converter and timezone clocks
|
||||
- **Journey** — magazine-style travel journal with entries, photos (Immich/Synology), maps, moods
|
||||
- **Naver List Import** — one-click import from shared Naver Maps lists
|
||||
- **MCP** — expose TREK to AI assistants via OAuth 2.1
|
||||
|
||||
</td>
|
||||
<td width="50%" valign="top">
|
||||
|
||||
#### 🤖 AI / MCP
|
||||
|
||||
- **Built-in MCP server** — OAuth 2.1 authenticated. 80+ tools, 27 resources
|
||||
- **Granular scopes** — 24 OAuth scopes across 13 permission groups
|
||||
- **Built-in MCP server** — OAuth 2.1 authenticated. 150+ tools, 30 resources
|
||||
- **Granular scopes** — 27 OAuth scopes across 13 permission groups
|
||||
- **Full automation** — AI can create trips, plan days, build packing lists, manage budgets, mark countries visited
|
||||
- **Pre-built prompts** — `trip-summary`, `packing-list`, `budget-overview`
|
||||
- **Addon-aware** — exposes Atlas, Collab, Vacay when those addons are on
|
||||
@@ -152,7 +156,7 @@ A self-hosted, real-time collaborative travel planner — with maps, budgets, pa
|
||||
#### ⚙️ Admin & customisation
|
||||
|
||||
- **Dashboard views** — card grid or compact list · **Dark mode** — full theme with matching status bar
|
||||
- **14 languages** — EN, DE, ES, FR, IT, NL, HU, RU, ZH, ZH-TW, PL, CS, AR (RTL), BR, ID
|
||||
- **15 languages** — EN, DE, ES, FR, IT, NL, HU, RU, ZH, ZH-TW, PL, CS, AR (RTL), BR, ID
|
||||
- **Admin panel** — users, invites, packing templates, categories, addons, API keys, backups, GitHub history
|
||||
- **Auto-backups** — scheduled with configurable retention · **Units** — °C/°F, 12h/24h, map tile sources, default coordinates
|
||||
|
||||
@@ -172,7 +176,7 @@ ENCRYPTION_KEY=$(openssl rand -hex 32) docker run -d -p 3000:3000 \
|
||||
-v ./data:/app/data -v ./uploads:/app/uploads mauriceboe/trek
|
||||
```
|
||||
|
||||
Open `http://localhost:3000`. The first user to register becomes admin.
|
||||
Open `http://localhost:3000`. On first boot TREK seeds an admin account — if you set `ADMIN_EMAIL`/`ADMIN_PASSWORD` those are used, otherwise the credentials are printed to the container log (`docker logs trek`).
|
||||
|
||||
<div align="center">
|
||||
|
||||
@@ -338,7 +342,8 @@ server {
|
||||
ssl_certificate /etc/ssl/fullchain.pem;
|
||||
ssl_certificate_key /etc/ssl/privkey.pem;
|
||||
|
||||
client_max_body_size 50m;
|
||||
# 500 MB covers backup-restore uploads (capped at 500 MB server-side).
|
||||
client_max_body_size 500m;
|
||||
|
||||
location / {
|
||||
proxy_pass http://localhost:3000;
|
||||
@@ -355,6 +360,7 @@ server {
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection "upgrade";
|
||||
proxy_set_header Host $host;
|
||||
proxy_read_timeout 86400;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
apiVersion: v2
|
||||
name: trek
|
||||
version: 2.9.14
|
||||
version: 3.0.0
|
||||
description: Minimal Helm chart for TREK app
|
||||
appVersion: "2.9.14"
|
||||
appVersion: "3.0.0"
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "trek-client",
|
||||
"version": "2.9.14",
|
||||
"version": "3.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "trek-client",
|
||||
"version": "2.9.14",
|
||||
"version": "3.0.0",
|
||||
"dependencies": {
|
||||
"@react-pdf/renderer": "^4.3.2",
|
||||
"axios": "^1.6.7",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "trek-client",
|
||||
"version": "2.9.14",
|
||||
"version": "3.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useState, useEffect, useMemo } from 'react'
|
||||
import { Plane, Train, Car, Ship } from 'lucide-react'
|
||||
import Modal from '../shared/Modal'
|
||||
import CustomSelect from '../shared/CustomSelect'
|
||||
@@ -7,6 +7,8 @@ import AirportSelect, { type Airport } from './AirportSelect'
|
||||
import LocationSelect, { type LocationPoint } from './LocationSelect'
|
||||
import { useTranslation } from '../../i18n'
|
||||
import { useToast } from '../shared/Toast'
|
||||
import { useTripStore } from '../../store/tripStore'
|
||||
import { useAddonStore } from '../../store/addonStore'
|
||||
import { formatDate } from '../../utils/formatters'
|
||||
import type { Day, Reservation, ReservationEndpoint } from '../../types'
|
||||
|
||||
@@ -75,6 +77,8 @@ const defaultForm = {
|
||||
arrival_time: '',
|
||||
confirmation_number: '',
|
||||
notes: '',
|
||||
price: '',
|
||||
budget_category: '',
|
||||
meta_airline: '',
|
||||
meta_flight_number: '',
|
||||
meta_train_number: '',
|
||||
@@ -94,6 +98,13 @@ interface TransportModalProps {
|
||||
export function TransportModal({ isOpen, onClose, onSave, reservation, days, selectedDayId }: TransportModalProps) {
|
||||
const { t, locale } = useTranslation()
|
||||
const toast = useToast()
|
||||
const isBudgetEnabled = useAddonStore(s => s.isEnabled('budget'))
|
||||
const budgetItems = useTripStore(s => s.budgetItems)
|
||||
const budgetCategories = useMemo(() => {
|
||||
const cats = new Set<string>()
|
||||
budgetItems.forEach(i => { if (i.category) cats.add(i.category) })
|
||||
return Array.from(cats).sort()
|
||||
}, [budgetItems])
|
||||
const [form, setForm] = useState({ ...defaultForm })
|
||||
const [isSaving, setIsSaving] = useState(false)
|
||||
const [fromPick, setFromPick] = useState<EndpointPick>({})
|
||||
@@ -126,6 +137,8 @@ export function TransportModal({ isOpen, onClose, onSave, reservation, days, sel
|
||||
meta_train_number: meta.train_number || '',
|
||||
meta_platform: meta.platform || '',
|
||||
meta_seat: meta.seat || '',
|
||||
price: meta.price || '',
|
||||
budget_category: (meta.budget_category && budgetItems.some(i => i.category === meta.budget_category)) ? meta.budget_category : '',
|
||||
})
|
||||
if (type === 'flight') {
|
||||
setFromPick({ airport: airportFromEndpoint(from) || undefined })
|
||||
@@ -139,7 +152,7 @@ export function TransportModal({ isOpen, onClose, onSave, reservation, days, sel
|
||||
setFromPick({})
|
||||
setToPick({})
|
||||
}
|
||||
}, [isOpen, reservation, selectedDayId])
|
||||
}, [isOpen, reservation, selectedDayId, budgetItems])
|
||||
|
||||
const set = (field: string, value: any) => setForm(prev => ({ ...prev, [field]: value }))
|
||||
|
||||
@@ -173,6 +186,10 @@ export function TransportModal({ isOpen, onClose, onSave, reservation, days, sel
|
||||
if (form.meta_platform) metadata.platform = form.meta_platform
|
||||
if (form.meta_seat) metadata.seat = form.meta_seat
|
||||
}
|
||||
if (isBudgetEnabled) {
|
||||
if (form.price) metadata.price = form.price
|
||||
if (form.budget_category) metadata.budget_category = form.budget_category
|
||||
}
|
||||
|
||||
const startDate = startDay?.date ?? null
|
||||
const endDate = (endDay ?? startDay)?.date ?? null
|
||||
@@ -200,6 +217,11 @@ export function TransportModal({ isOpen, onClose, onSave, reservation, days, sel
|
||||
endpoints,
|
||||
needs_review: false,
|
||||
}
|
||||
if (isBudgetEnabled) {
|
||||
(payload as any).create_budget_entry = form.price && parseFloat(form.price) > 0
|
||||
? { total_price: parseFloat(form.price), category: form.budget_category || t(`reservations.type.${form.type}`) || 'Other' }
|
||||
: { total_price: 0 }
|
||||
}
|
||||
await onSave(payload)
|
||||
} catch (err: unknown) {
|
||||
toast.error(err instanceof Error ? err.message : t('common.unknownError'))
|
||||
@@ -422,6 +444,40 @@ export function TransportModal({ isOpen, onClose, onSave, reservation, days, sel
|
||||
style={{ ...inputStyle, resize: 'none', lineHeight: 1.5 }} />
|
||||
</div>
|
||||
|
||||
{/* Price + Budget Category */}
|
||||
{isBudgetEnabled && (
|
||||
<>
|
||||
<div style={{ display: 'flex', gap: 8 }}>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<label style={labelStyle}>{t('reservations.price')}</label>
|
||||
<input type="text" inputMode="decimal" value={form.price}
|
||||
onChange={e => { const v = e.target.value; if (v === '' || /^\d*[.,]?\d{0,2}$/.test(v)) set('price', v.replace(',', '.')) }}
|
||||
onPaste={e => { e.preventDefault(); let txt = e.clipboardData.getData('text').trim().replace(/[^\d.,-]/g, ''); const lc = txt.lastIndexOf(','), ld = txt.lastIndexOf('.'), dp = Math.max(lc, ld); if (dp > -1) { txt = txt.substring(0, dp).replace(/[.,]/g, '') + '.' + txt.substring(dp + 1) } else { txt = txt.replace(/[.,]/g, '') } set('price', txt) }}
|
||||
placeholder="0.00"
|
||||
style={inputStyle} />
|
||||
</div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<label style={labelStyle}>{t('reservations.budgetCategory')}</label>
|
||||
<CustomSelect
|
||||
value={form.budget_category}
|
||||
onChange={v => set('budget_category', v)}
|
||||
options={[
|
||||
{ value: '', label: t('reservations.budgetCategoryAuto') },
|
||||
...budgetCategories.map(c => ({ value: c, label: c })),
|
||||
]}
|
||||
placeholder={t('reservations.budgetCategoryAuto')}
|
||||
size="sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{form.price && parseFloat(form.price) > 0 && (
|
||||
<div style={{ fontSize: 11, color: 'var(--text-faint)', marginTop: -4 }}>
|
||||
{t('reservations.budgetHint')}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
</form>
|
||||
</Modal>
|
||||
)
|
||||
|
||||
@@ -2941,6 +2941,7 @@ function JourneyShareSection({ journeyId }: { journeyId: number }) {
|
||||
{[
|
||||
{ key: 'share_timeline' as const, label: t('journey.share.timeline'), icon: List },
|
||||
{ key: 'share_gallery' as const, label: t('journey.share.gallery'), icon: Grid },
|
||||
{ key: 'share_map' as const, label: t('journey.share.map'), icon: MapPin },
|
||||
].map(({ key, label, icon: Icon }) => (
|
||||
<button
|
||||
key={key}
|
||||
|
||||
@@ -448,7 +448,7 @@ export default function JourneyPublicPage() {
|
||||
return (
|
||||
<div className="min-h-screen bg-zinc-50 dark:bg-zinc-950">
|
||||
{/* Hero */}
|
||||
<div className="relative text-center text-white" style={{ background: 'linear-gradient(135deg, #000 0%, #0f172a 50%, #1e293b 100%)', padding: '32px 20px 28px' }}>
|
||||
<div className="relative text-center text-white" style={{ background: 'linear-gradient(135deg, #000 0%, #0f172a 50%, #1e293b 100%)', padding: '32px 20px 28px', overflow: 'hidden' }}>
|
||||
{journey.cover_image && (
|
||||
<div style={{ position: 'absolute', inset: 0, backgroundImage: `url(/uploads/${journey.cover_image})`, backgroundSize: 'cover', backgroundPosition: 'center', opacity: 0.15 }} />
|
||||
)}
|
||||
|
||||
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "trek-server",
|
||||
"version": "2.9.14",
|
||||
"version": "3.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "trek-server",
|
||||
"version": "2.9.14",
|
||||
"version": "3.0.0",
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.28.0",
|
||||
"archiver": "^6.0.1",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "trek-server",
|
||||
"version": "2.9.14",
|
||||
"version": "3.0.0",
|
||||
"main": "src/index.ts",
|
||||
"scripts": {
|
||||
"start": "node --import tsx src/index.ts",
|
||||
|
||||
@@ -59,7 +59,7 @@ If a toggle fails (e.g., network error), it rolls back to its previous state.
|
||||
|
||||
Some addons require credentials or environment variables before they are functional:
|
||||
|
||||
- **Journey** — requires photo provider credentials (Immich or Synology Photos) configured per-user in their personal Settings. See [Photo-Providers](Photo-Providers).
|
||||
- **Journey** — works without any external integration. To embed photos from Immich or Synology Photos, enable the corresponding photo-provider toggle listed under Journey, then configure credentials per-user in **Settings → Integrations**. See [Photo-Providers](Photo-Providers).
|
||||
- **MCP** — requires `APP_URL` to be set so OAuth redirect URIs resolve correctly.
|
||||
|
||||
## Related pages
|
||||
|
||||
+13
-7
@@ -30,17 +30,23 @@ TREK is a self-hosted, real-time collaborative travel planner licensed under AGP
|
||||
- **Public Share Links** — share a read-only view of any trip
|
||||
|
||||
### Addons _(admin-toggleable)_
|
||||
- **Lists** — packing lists and to-dos with templates, member assignments, optional bag tracking
|
||||
- **Budget Planner** — expense tracker with category breakdown, splits, multi-currency
|
||||
- **Documents** — file manager for trips, places, and reservations
|
||||
- **Collab** — group chat, shared notes, polls, day-by-day attendance
|
||||
- **Vacay** — personal vacation day planner with calendar view, public holidays, and carry-over tracking
|
||||
- **Atlas** — interactive world map, bucket list, travel stats, continent breakdown
|
||||
- **Journey** — travel journal linking entries to trips, with contributor roles
|
||||
- **Memories** — photo-focused trip memories
|
||||
- **Collab** — group chat, shared notes, polls, and activity sign-ups
|
||||
- **Dashboard Widgets** — currency converter and timezone clock, toggled per user
|
||||
- **Journey** — magazine-style travel journal with entries, photos (via Immich/Synology Photos), maps, and moods
|
||||
- **Naver List Import** — import places from shared Naver Maps lists
|
||||
- **MCP** — expose TREK to AI assistants via the Model Context Protocol (OAuth 2.1)
|
||||
|
||||
> Dashboard widgets (currency converter and timezone clock) are per-user preferences, not an admin-toggleable addon — see [Dashboard-Widgets](Dashboard-Widgets).
|
||||
|
||||
### AI / MCP Integration
|
||||
- **MCP Server** — built-in Model Context Protocol server with OAuth 2.1 authentication
|
||||
- **80+ Tools** — create trips, plan itineraries, manage budgets, send messages, and more
|
||||
- **24 OAuth Scopes** — granular permissions across 13 permission groups
|
||||
- **150+ Tools** — create trips, plan itineraries, manage budgets, send messages, and more
|
||||
- **30 Resources** — read-only `trek://` URIs for trips, days, places, budget, packing, journeys, and more
|
||||
- **27 OAuth Scopes** — granular permissions across 13 permission groups
|
||||
- **Pre-built Prompts** — `trip-summary`, `packing-list`, and `budget-overview` context loaders
|
||||
|
||||
### Admin
|
||||
@@ -48,7 +54,7 @@ TREK is a self-hosted, real-time collaborative travel planner licensed under AGP
|
||||
- Addon management, API key storage, scheduled auto-backups
|
||||
- System notices for onboarding and announcements
|
||||
|
||||
> **Admin:** Most configuration lives in the Admin Panel. The first user to register becomes the admin automatically.
|
||||
> **Admin:** Most configuration lives in the Admin Panel. On first boot TREK seeds an admin account automatically — credentials come from `ADMIN_EMAIL` / `ADMIN_PASSWORD` if set, otherwise a random password is printed to the container log.
|
||||
|
||||
## Get Started
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
TREK can browse your personal photo library on Immich or Synology Photos and attach selected photos to trips. TREK never copies the original files — it stores only a reference (provider name + asset ID) and proxies all image streams through its own server, so your provider credentials are never sent to the browser.
|
||||
|
||||
> **Admin:** Two things must be enabled for photo providers to appear in Settings: the **Memories addon** and the **individual photo provider** (Immich or Synology Photos). Both are toggled separately in **Admin → Addons**. See [Admin-Addons](Admin-Addons). If your provider is on a local or private network, the server must be configured to allow internal network access. See [Internal-Network-Access](Internal-Network-Access).
|
||||
> **Admin:** Enable at least one photo provider (Immich or Synology Photos) in **Admin → Addons** — photo provider toggles appear as sub-items under the **Journey** addon. Once a provider is on, a Photo Providers section appears in each user's **Settings → Integrations**. If your provider runs on a local or private network, the server must be configured to allow internal network access. See [Admin-Addons](Admin-Addons) and [Internal-Network-Access](Internal-Network-Access).
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
# Tags and Categories
|
||||
|
||||
TREK has a labeling system: **Global Place Categories** (admin-managed, shared across all users).
|
||||
TREK has two independent labelling systems for places:
|
||||
|
||||
- **Global Place Categories** — admin-managed, shared across every user on the instance (e.g. `Restaurant`, `Museum`).
|
||||
- **Personal Tags** — user-scoped, private labels (e.g. `hidden gem`, `kid-friendly`).
|
||||
|
||||
<!-- TODO: screenshot: tag list on place detail -->
|
||||
|
||||
@@ -24,6 +26,23 @@ Categories appear in:
|
||||
|
||||
> **Admin:** Create and manage categories in [Admin-Categories](Admin-Categories). Only admins can create, edit, or delete categories. All users can read them.
|
||||
|
||||
## Personal Tags
|
||||
|
||||
Tags are private labels owned by each user. They attach to individual places via a many-to-many relationship (`place_tags` table), so the same tag can be applied to as many places as you like, and a single place can carry multiple tags.
|
||||
|
||||
**Fields per tag:**
|
||||
|
||||
- **Name** — free-form text.
|
||||
- **Color** — hex value displayed alongside the tag name. Default: `#10b981` (emerald).
|
||||
|
||||
Tags are scoped to their creator — other trip members do not see your tags, and different users can create tags with identical names without conflict. Deleting a tag automatically removes it from every place it was attached to.
|
||||
|
||||
### Where to manage them
|
||||
|
||||
At the moment tags are exposed primarily through the MCP API — AI assistants connected to your instance can list, create, update, and delete tags (`list_tags`, `create_tag`, `update_tag`, `delete_tag`) and attach them to places through the place endpoints. A dedicated web UI for tag management is not yet available; the filter `tag` parameter on the places API / MCP resource does support filtering places by a tag ID once one exists.
|
||||
|
||||
> **AI / MCP:** See [MCP-Tools-and-Resources](MCP-Tools-and-Resources) for the full tag tool list.
|
||||
|
||||
## When to use which
|
||||
|
||||
| Use case | Use |
|
||||
|
||||
Reference in New Issue
Block a user