Commit Graph

1088 Commits

Author SHA1 Message Date
Dries Peeters 3f56a06ef0 feat(release): auto-trigger Render demo deploy after container push
Add trigger-demo-deploy job to cd-release workflow that POSTs to
Render deploy hook when TimeTrackerDemoRender org secret is set.
Runs after build-and-push; skips gracefully if secret is not
configured. Include demo deploy status in release summary.

Document in RENDER.md, CI_CD_DOCUMENTATION.md, and
GITHUB_ACTIONS_SETUP.md.
2026-02-17 20:23:46 +01:00
Dries Peeters 5593c9742f Redesign Log Time Manually page for clearer, more professional UI
- Group form into sections: Project & task, Date & time, Details (with headings and icons)
- Upgrade main card to rounded-xl and shadow-lg; add section borders and spacing
- Unify form labels and helper text to app design tokens
- Style primary Log Time and secondary Clear buttons to match dashboard
- Apply rounded-xl to duplicate-entry info banner
- Document change in CHANGELOG under [Unreleased]
2026-02-17 20:23:31 +01:00
Dries Peeters 07794987af fix(dashboard): remove duplicate const notesEl declaration
Reuse the existing notesEl variable when syncing Toast UI Editor notes
into the hidden textarea on form submit, fixing SyntaxError:
redeclaration of const notesEl in the start timer submit handler.
2026-02-17 20:16:11 +01:00
Dries Peeters 94ed0ae7d7 VErsion bump 2026-02-16 22:00:14 +01:00
Dries Peeters cd3b1b68ce Update Render 2026-02-16 21:49:48 +01:00
Dries Peeters 32f8a90ded Version bump 2026-02-16 21:28:44 +01:00
Dries Peeters 897c87dec8 fix: add merge migration for Alembic multiple heads (118 and 128)
Resolves 'Multiple head revisions' error during pre-deploy (flask db upgrade)
on Render. Two branches existed: 118_add_role_hidden_module_ids and
128_add_invoices_zugferd_pdf. New revision 129_merge_118_128_heads merges
them so 'alembic upgrade head' runs successfully.
2026-02-16 21:27:58 +01:00
Dries Peeters dbaec5c583 Update render.yaml 2026-02-16 21:11:34 +01:00
Dries Peeters 17dc6476b9 Update render.yaml 2026-02-16 20:58:19 +01:00
Dries Peeters c64149a783 Update render.yaml 2026-02-16 20:42:31 +01:00
Dries Peeters 5035ea8c66 Update render 2026-02-16 20:40:10 +01:00
Dries Peeters a3d362ec48 Version bump 2026-02-16 20:34:48 +01:00
Dries Peeters a6b60b16dd feat: Render deployment and demo mode for single-user demo
- Add render.yaml Blueprint: PostgreSQL + Python web service, auto-deploy on push
- Add demo mode (DEMO_MODE, DEMO_USERNAME, DEMO_PASSWORD): single fixed user only
- Login page shows demo credentials when demo mode is active
- Disable self-registration, admin user creation, and OIDC user creation in demo mode
- DB init creates demo user with password when DEMO_MODE is true
- Add docs/deploy/RENDER.md with deployment and demo mode instructions
2026-02-16 20:34:32 +01:00
Dries Peeters def875431c chore: bump version to 4.20.0 and update documentation 2026-02-16 09:34:15 +01:00
Dries Peeters 4f05c3d540 feat(header): group Chat, Timer, Help as aligned round buttons
- Group Chat, Timer, and Help in header as round icon buttons
- Vertically aligned, evenly spaced (gap-2), consistent w-10 h-10
- Header timer: one-click start/stop from any page via floating-timer-bar.js
- Fix timer manual entry URL (use /timer/manual, not /timer/manual_entry)
- Add Help button linking to help page
- Update FEATURES_COMPLETE (Header Quick Access, One-Click Timers)
- Update help page Time Tracking section with header timer tip
- Update CHANGELOG
2026-02-16 09:31:07 +01:00
Dries Peeters 7ae7de12d2 feat(setup): guided 6-step setup wizard for first-time configuration
Replace the single-page setup (telemetry + optional Google Calendar) with
a guided wizard that collects all base settings before completion.

Wizard steps:
1. Welcome - intro and Next
2. Region & time - timezone, date/time format, currency (Settings)
3. Company - name, address, email, optional phone/website (Settings)
4. System - allow self-registration, rounding minutes, single active
   timer, idle timeout (Settings)
5. Integrations (optional) - Google Calendar OAuth; can skip
6. Privacy & finish - telemetry opt-in; Complete Setup submits form

Backend (app/routes/setup.py):
- GET: pass settings and timezones to template for prefilling
- POST: validate timezone, date_format, currency, rounding_minutes,
  idle_timeout_minutes; persist all fields to Settings and
  mark_setup_complete(telemetry_enabled)
- Default timezone/currency to UTC/EUR when missing (keeps tests passing)

Frontend:
- initial_setup.html: 6 wizard steps, progress bar (Step X of 6),
  Back/Next and submit on last step
- setup-wizard.js: step navigation, progress update, optional
  client-side validation for step 2 (timezone, currency required)

Docs updated: TELEMETRY_QUICK_START.md, GETTING_STARTED.md,
TELEMETRY_IMPLEMENTATION_SUMMARY.md.
2026-02-16 08:02:33 +01:00
Dries Peeters d39c5a2f37 fix(pdf-layout): decorative image persistence and PDF preview (Issue #432)
Decorative images now survive save/load and no longer cause a black PDF preview:

- Sync imageUrl onto groups before generateCode() so template_json has
  correct source (invoice and quote layout editors).
- Inject name/imageUrl into design_json with position-based matching so
  reordering does not swap or drop URLs.
- Restore name and imageUrl from saved JSON onto canvas on load
  (synchronous) so Konva custom attrs are not required.
- Omit decorative image elements with empty source from template_json;
  placeholders stay visible in the editor but are not sent to ReportLab.
- ReportLab: explicitly skip decorative images with empty source; validate
  base64 data URI payload and decode in try/except to avoid bad PDF output.

Documentation: PDF_LAYOUT_CUSTOMIZATION.md and PDF_EDITOR_ENHANCED_FEATURES.md
updated with decorative image description, state persistence details, and
troubleshooting. CHANGELOG.md updated under [Unreleased] Fixed.
2026-02-16 07:37:59 +01:00
Dries Peeters b0809e2f90 feat(invoicing): add ZugFerd/Factur-X support and document Peppol & ZugFerd
- Add optional embedding of EN 16931 UBL XML in invoice PDFs (ZugFerd/Factur-X)
  when 'Embed EN 16931 XML in invoice PDFs' is enabled in Admin > Peppol e-Invoicing.
  Exported PDFs then contain ZUGFeRD-invoice.xml for hybrid human- and machine-readable
  invoices; same UBL as Peppol, usable via Peppol or email.
- New setting invoices_zugferd_pdf (migration 128), pikepdf dependency, and
  app.utils.zugferd helper (best-effort supplier/customer from Settings and client).
- Wire embed in export_invoice_pdf (and fallback path); admin checkbox and persistence.
- Docs: PEPPOL_EINVOICING.md retitled to 'Peppol and ZugFerd', new section for
  ZugFerd embedding; README and CHANGELOG updated; migration 128 noted.
- Tests: test_zugferd.py (embed adds attachment with expected XML; invalid PDF
  returns original bytes and error).
2026-02-16 07:36:49 +01:00
Dries Peeters ae9ee9dec1 feat: add subcontractor role with assigned clients (scope-restricted access)
- Add user_clients table and UserClient model for many-to-many user-client assignment
- Add 'subcontractor' system role; users with this role see only assigned clients and their projects
- User helpers: is_scope_restricted, get_allowed_client_ids(), get_allowed_project_ids()
- Admin user form: assign clients when role is Subcontractor (multi-select, JS toggle)
- Scope filtering: clients, projects, time entries, reports, invoices, timer, API v1
- Direct access to out-of-scope client/project returns 403 (web and API)
- Migration 127_add_user_clients_table; scope_filter utility and ProjectService scope_client_ids
- Docs: SUBCONTRACTOR_ROLE.md, ADVANCED_PERMISSIONS.md, RBAC, CLIENT_PORTAL, README, CHANGELOG

Addresses GitHub Discussion #476 (user with limited clients/projects).
2026-02-16 07:12:57 +01:00
Dries Peeters eeb3aeddf2 Fix BuildError when payment_gateways blueprint is not registered
Add has_endpoint() helper to check if a Flask endpoint exists before
rendering navigation links. When stripe is not installed, the
payment_gateways blueprint fails to register but is_module_enabled()
can still return true, causing url_for('payment_gateways.list_gateways')
to raise BuildError when rendering base.html.

Only show the Payment Gateways nav link when the endpoint is actually
registered. This fixes inventory route tests that failed when following
redirects to the movements list page in environments without stripe.

Related to issue #385 (stock item devaluation).
2026-02-16 06:58:19 +01:00
Dries Peeters 28e3be4666 Use Flatpickr date picker on Finance & Expenses and Reports
Add user-date-input class to all date inputs in:
- Reports: index, export form, builder, and report pages (user, task,
  project, time entries, unpaid hours)
- Expenses: form, dashboard, list
- Per diem: form, rate form, list
- Mileage: form, list
- Payments: create, list
- Invoices: edit (due date and linked expense dates)

Date fields now use the same Flatpickr-based picker as Time Tracking
and Log Entries, respecting user date format and first day of week
preferences (fixes inconsistency noted in #496).
2026-02-16 06:53:36 +01:00
Dries Peeters 6f4f1af887 Fix PDF invoice layout save and items display (Issues #503, #504)
- Fix #504: Save button no longer removes items/expenses table from PDF layout
  - Add i18n-aware table name inference when Konva fails to serialize custom attrs
  - Ensure table group names are set before generateCode on save
  - Extend load-time defensive fix to support localized headers (DE, FR, IT, NL)
  - Apply same fixes to Quote PDF layout for quote-items-table

- Fix #503: Invoice items (time entries, extra goods, expenses) now appear in PDF
  - Root cause was #504; tables are now persisted correctly on save

- Add collapsible Help sections to invoice and quote PDF layout pages
- Update README with PDF Invoice Layout subsection and doc links
- Add troubleshooting for 'Items/expenses table disappears after save'
- Clarify invoice.all_line_items usage in PDF_LAYOUT_CUSTOMIZATION.md
- Document Items Table element in INVOICE_EXTRA_GOODS_PDF_EXPORT.md
2026-02-16 06:53:21 +01:00
Dries Peeters de4abbd984 Version Bump 4.19.2 2026-02-14 22:16:57 +01:00
Dries Peeters 93d1111791 Promote Support & Purchase Key page across app and docs
Make https://timetracker.drytrix.com/support.html visible so users can purchase a key to hide donate/support UI (one key per instance, €25 one-time).

- Add SUPPORT_PURCHASE_URL config and support_purchase_url in template context
- Donate page: 'Remove Donation Messages' section and CTA, link in Other Ways to Help
- Admin Settings: Support visibility copy and 'Get key at Support & Purchase' button
- User Settings: line and link for admins to purchase a key
- Support banner: 'Purchase key to hide' link
- Dashboard widget: 'Want to hide this widget? Purchase a key'
- README: Support section bullet and intro line for support/purchase key
- SUPPORT_VISIBILITY.md: 'How to get a code' subsection, issuing codes note
- docs/README.md: Support visibility in Configuration with purchase link
- messages.po: add new translatable strings
2026-02-14 22:14:57 +01:00
Dries Peeters e68f231b91 feat: API v1 CRM/approvals, api_responses, templates, version & RBAC docs
- REST API v1: add deals, leads, contacts, time-entry-approvals (CRUD + approve/reject/cancel/bulk-approve). New scopes and /info entries.
- Standardize API errors: use error_response, forbidden_response, not_found_response in api_v1 (projects + new CRM/approval routes).
- Consolidate templates: move root templates/ into app/templates/, remove ChoiceLoader and legacy root files.
- Version: README/FEATURES_COMPLETE/CHANGELOG/mobile docs reference setup.py as single source (4.19.0); add [4.19.0] changelog entry.
- Docs: SERVICE_LAYER_AND_BASE_CRUD.md, RBAC_PERMISSION_MODEL.md; base_crud_service docstring points to service-layer doc.
- Mark projects_refactored_example, timer_refactored, invoices_refactored as REFERENCE ONLY in docstrings.
2026-02-13 21:43:09 +01:00
Dries Peeters 115db52b2b feat(time-approvals): complete time entry approval workflow and fix bugs
- Fix approve crash: make _mark_entry_approved a no-op (approval state from
  TimeEntryApproval only; TimeEntry has no metadata column).
- Fix PostgreSQL enum: use values_callable on ApprovalStatus so DB receives
  'pending' not 'PENDING' (matches approvalstatus enum).
- Fix approval templates: use requester/approver, request_comment,
  time_entry.notes; reject form field name 'reason'; add can_approve to
  view and pass from route.
- Filter get_pending_approvals by current approver (policy/project fallback).
- Add Time Approvals nav link under Work in base.html (module-enabled).
- Add Request approval in time entries list (icon) and view_timer (sidebar);
  redirect to list_approvals after request.
- Fix empty_state calls in approvals/list.html (use icon_class positional
  args to match components/ui.html macro).
2026-02-13 21:06:18 +01:00
Dries Peeters ab14e4352a feat: time entry requirements in API and timer; import skips requirements
- API: include time_entry_requirements in GET /users/me (require_task, require_description, min length)
- Timer: validate task/description requirements when starting timer; pass time_approvals_enabled and can_request_approval to view_timer; pass entry_ids_with_pending_approval to time_entries_overview
- Import: pass skip_entry_requirements=True when creating entries from CSV import
2026-02-13 20:57:54 +01:00
Dries Peeters 3ec139ef0f fix(reports): Issue #496 follow-up - expense reports, export form, Quick Actions
- Fix Expense.date -> expense_date in custom_report_service, QuickBooks, Xero
- Report Builder: add expenses and invoices data sources in generate_report_data()
- Export Report button: require date range and show alert when missing
- Quick Actions: both CSV and Excel open export form (format selector on form)
- Finance & Reports: standardize date inputs (form-input on expenses list/dashboard)
2026-02-13 20:56:14 +01:00
Dries Peeters 54533ec95e feat(reports): add Time Entries report with Excel/CSV export (Discussion #463)
- Add /reports/time-entries page listing all time entries (billed and unbilled)
- Columns: Date, Start, Stop, Duration, Project, Task, Notes, Billed, Client
- Filters: date range, user, project, client, task, billed (all/yes/no)
- Export to Excel and CSV with same filters; add Billed column to excel export
- Resolve client from entry.client or project.client in export
- Add Time Entries Report card to Reports index
2026-02-13 20:56:07 +01:00
Dries Peeters 7110194080 VErsion Bump 4.19.0 2026-02-09 21:38:55 +01:00
Dries Peeters bada0b68aa Fix report and time-entries CSV export 500 and error visibility
- Use Project.client_obj in joinedload (fixes loader strategy error on export).
- Null-safe user/project/client in CSV rows; client column uses Project.client.
- Try/except and logger.exception in both CSV routes; SQLAlchemy handler
  logs full traceback and writes to stderr for Docker logs.

Refs #496

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-09 21:33:51 +01:00
Dries Peeters 16eaf4c807 fix(timer): date selector in manual time log for dark mode
- Load Flatpickr dark theme when app is in dark mode; sync on theme toggle.
- Apply form-input class to Flatpickr alt input so visible date field gets dark styles.

Refs #

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-09 21:31:27 +01:00
Dries Peeters 743b7b9d4d Fix PDF invoice missing items (extra goods and expenses)
- Admin PDF preview: build all_line_items on invoice wrapper and resolve
  table data from element data source (invoice.all_line_items or
  invoice.items) so preview matches exported PDF.
- ReportLab: when template uses invoice.items, append both extra_goods
  and expenses to table data so all line types appear in PDF.
- Export PDF: explicitly load items, extra_goods, and expenses before
  generation so data is in session.
- Docs: recommend invoice.all_line_items for custom templates; document
  backward compatibility and preview behavior.

Refs #503

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-09 21:27:05 +01:00
Dries Peeters 045b3c1aa3 Fix PDF layout save dropping item and expenses tables
Persist items-table and expenses-table group names in design_json on save (Konva does not reliably serialize custom attrs on Groups). Add defensive restore of table group names from structure when loading saved layouts that lack names. Fix undefined hasForLoop in save handler.

Ref: #504
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-09 21:26:22 +01:00
Dries Peeters abc89f9633 Fix lead conversion, activity recording, and deal history
- Lead to Client: stop passing status into Client(); set after create (#514)
- Leads/Deals activity: parse datetime-local single string in timezone util (#513)
- Deal page: add audit tracking for Deal, entity history support, History section (#515)

Refs #513, #514, #515

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-09 20:44:23 +01:00
Dries Peeters cdb579ae38 feat(dashboard): show live elapsed time in Timer and Recent Entries
Add client-side 1s ticker so elapsed time updates in real time on the dashboard when a timer is running. Also update Today's/Week's/Month's hours live and show the running time entry on the calendar in a distinct color.

- Timer section: display live elapsed (HH:MM:SS) using active_timer.start_time
- Recent Entries: live-updating duration for the row that is the active timer
- Stats cards: Today/Week/Month hours include running timer and update every second
- Calendar: include active timer in time_entries with is_running and cyan color

Ref: Issue #516
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-09 20:43:02 +01:00
Dries Peeters 17b67cc578 feat(calendar): default view setting and remember last view
- Resolve calendar view from URL param, then user default, then session
- Add user preference calendar_default_view (day/week/month or unset)
- Persist last-used view in session when opening with ?view=
- Settings: Calendar default view dropdown (Remember last view / Day / Week / Month)
- PATCH /api/preferences supports calendar_default_view
- Migration 123: add calendar_default_view to users
- Tests for calendar view resolution and settings form/API

Ref #518

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-09 20:22:08 +01:00
Dries Peeters ef43eb858f Fix time entries showing as Event in calendar module
When the API returns a merged events list (single 'events' array with
item_type), the calendar module now splits by item_type so time entries
populate this.timeEntries and render with Time Entry category (clock
icon, time-entry color, correct modal and links).

- loadEvents(): detect merged response and split data.events by
  extendedProps.item_type into events, tasks, time_entries
- renderDayEvents(), renderWeekDayBlocks(), renderMonthCellEvents():
  use item_type for block/badge type and showEventDetails() so
  time entries show as Time Entry, not Event

Refs #517

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-09 20:21:52 +01:00
Dries Peeters a11a37c7df Version Bump 4.18.4 2026-02-08 21:29:39 +01:00
Dries Peeters 1bf13d2d2f Fix missing leads and deals templates (fixes #499, #509)
Add four templates that were referenced by routes but never created, which caused TemplateNotFound when using convert or activity flows:

- leads/convert_to_client.html: confirmation page for converting a lead to a client
- leads/convert_to_deal.html: form to convert a lead to a deal (name, client, stage, expected close date, probability)
- leads/activity_form.html: form to add an activity to a lead
- deals/activity_form.html: form to add an activity to a deal

In app/routes/leads.py, define PIPELINE_STAGES and pass pipeline_stages into the convert_to_deal template so the stage dropdown matches the deals module.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 21:24:42 +01:00
Dries Peeters 2c2833eccf feat: date/week preferences, time entry notes fix, PDF export wrapping (#497)
- Respect user date format and week start: Flatpickr on user-date-input
  fields, get_resolved_week_start_day for calendar/pickers, Week Starts On
  from My Settings. Update CALENDAR_FEATURES_README.
- Fix time entry notes not saving on edit: sync Toast UI editor to hidden
  textarea on all forms so the submitted form gets the current note (edit_timer).
- Time entries PDF export: wrap Task, Client, and Project columns like Notes
  so long text breaks across lines (Issue #489).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 21:01:53 +01:00
Dries Peeters 9e88835513 fix(dashboard): move Start button to Start Timer modal title bar
Put the Start submit button in the modal header next to Close so it is always visible without scrolling on mobile and small viewports. Removes the footer Start button and associates the header button with the form via form=startTimerForm. Fixes #506 (comment: better use of real estate).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 21:00:19 +01:00
Dries Peeters 699ee83e8b Update public file 2026-02-08 20:33:35 +01:00
Dries Peeters 4ce27b9334 ci(docker): inject donate-hide public key from secret in release and develop workflows
- Add optional step before Docker build: write DONATE_HIDE_PUBLIC_KEY_PEM secret to donate_hide_public.pem
- Image then contains key at /app/donate_hide_public.pem when secret is set; build still succeeds if unset

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 14:00:39 +01:00
Dries Peeters a7f2fec930 feat(support): system-wide support visibility (admin-only)
- Add Settings.donate_ui_hidden and migration 122; admin verify in Admin → Settings
- Support visibility section in admin settings: System ID, code input, Verify and hide for everyone
- New route POST /admin/settings/verify-donate-hide-code (Ed25519 or HMAC) sets donate_ui_hidden
- Templates (base, dashboard, about, help) hide donate UI when settings.donate_ui_hidden
- User settings: remove per-user code/System ID block; add note that admins configure in Admin → Settings
- Config: DONATE_HIDE_PUBLIC_KEY_PEM / _FILE and HMAC fallback; refuse private key PEM
- Dockerfile: set DONATE_HIDE_PUBLIC_KEY_FILE=/app/donate_hide_public.pem
- .gitignore: docs/internal/, scripts/generate_donate_hide_code.py, donate_hide_private.pem
- Update SUPPORT_VISIBILITY.md for system-wide flow and admin-only setup

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 14:00:30 +01:00
Dries Peeters 2814661cf1 Version Bump 4.18.3 2026-02-08 11:22:22 +01:00
Dries Peeters 91685bb9c6 feat: allow users to hide donate/support UI via verified code
- Add Support visibility in Settings: users see a stable System ID and can
  enter a code to permanently hide donate buttons, support banner, and
  donate widgets.
- Verification supports two modes:
  - Ed25519: server stores only public key; codes are signatures generated
    offline with the private key (no secret on server).
  - HMAC: server stores a secret; code = HMAC(secret, system_id).
- Add User.ui_show_donate and Settings.system_instance_id (migration 121).
- Add donate_hide_code utility (HMAC + Ed25519 verify) and config for
  DONATE_HIDE_PUBLIC_KEY(_FILE) and DONATE_HIDE_UNLOCK_SECRET(_FILE).
- Wrap all donate UI in base, dashboard, about, help with conditional.
- Add admin doc SUPPORT_VISIBILITY.md; ignore docs/internal/ and
  code-generation script in .gitignore.
- Add translations for new strings.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 11:11:03 +01:00
Dries Peeters 6dc935db3f fix(dashboard): make Start Timer modal scrollable on small viewports
- Add overflow-y-auto to modal overlay so content can scroll when tall
- Add max-h-[90vh] and overflow-y-auto to modal content box so the
  Start button is always reachable on mobile and short desktop windows

Fixes #506

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 11:08:15 +01:00
Dries Peeters a77eb2361e Version bump 4.18.2 2026-02-07 22:26:40 +01:00
Dries Peeters 5bc637cc6b fix(invoices): include extra goods and expenses in PDF invoice exports
PDF invoices were missing extra goods (and expenses) because the ReportLab
template renderer only used invoice.items as the table data source.

- Add invoice.all_line_items to template context: merged list of items,
  extra_goods, and expenses with normalized description/quantity/price fields
- Update default template schema to use invoice.all_line_items instead of
  invoice.items for the items table
- Add migration to update existing saved templates with the new data source
- Update PDF layout designer: add all_line_items and extra_goods loop options,
  default items table to all_line_items
- Add expenses to fallback ReportLab generator for consistency with
  pdf_default.html

Fixes #503

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-07 22:24:45 +01:00