Skip to content

Add Meteor 3 + wormhole backend PoC with live DDP tickets#357

Merged
Dharp02 merged 136 commits into
mainfrom
meteor-is-back
Jul 6, 2026
Merged

Add Meteor 3 + wormhole backend PoC with live DDP tickets#357
Dharp02 merged 136 commits into
mainfrom
meteor-is-back

Conversation

@horner

@horner horner commented Jun 12, 2026

Copy link
Copy Markdown
Member

Meteor-to-Production Plan — Migration Complete

Tracking document for migrating TimeHuddle's backend from Fastify + Better Auth to Meteor 3 +
meteor-wormhole
(REST + OpenAPI + MCP from one
method definition), with DDP pub/sub replacing all hand-rolled WebSocket fan-out.

Branch / PR: meteor-is-backPR #357

Production URL: https://huddle.os.mieweb.org/

✅ PRODUCTION DEPLOYMENT COMPLETE (July 5, 2026)

The meteor-is-back branch needs to be merged and deployed to production.
The database migration is complete. The Meteor backend is now serving all
production traffic at https://huddle.os.mieweb.org/.


Architecture: Before & After

flowchart TB
    subgraph Before["❌ Before — Fastify + Better Auth"]
        direction TB
        Client1["Frontend\nVite + React 19\nPort 3000"]
        Fastify["Fastify Backend\nPort 4000"]
        BetterAuth["Better Auth\nSession + JWT + OAuth"]
        WS1["WS Hub: Clock"]
        WS2["WS Hub: Tickets"]
        WS3["WS Hub: Timers"]
        WS4["WS Hub: Teams"]
        WS5["WS Hub: Presence"]
        WS6["WS Hub: Notifications"]
        WS7["WS Hub: Messages\n+ Channels"]
        Mongo1[(MongoDB)]

        Client1 -->|REST API| Fastify
        Client1 -->|7 WebSocket connections| WS1 & WS2 & WS3 & WS4 & WS5 & WS6 & WS7
        Fastify --> BetterAuth
        Fastify --> Mongo1
        WS1 & WS2 & WS3 & WS4 & WS5 & WS6 & WS7 --> Mongo1
    end

    subgraph After["✅ After — Meteor 3 + Wormhole"]
        direction TB
        Client2["Frontend\nVite + React 19\nPort 3000"]
        Meteor["Meteor 3 Backend\nPort 3100"]
        Wormhole["meteor-wormhole\nREST + OpenAPI + MCP"]
        DDP["DDP Protocol\nSingle Connection"]
        MeteorAccounts["Meteor Accounts\nEmail/Password + OAuth"]
        Mongo2[(MongoDB)]

        Client2 -->|"wormholeCall() — REST"| Wormhole
        Client2 -->|"DDP — Real-time subs"| DDP
        Wormhole --> Meteor
        DDP --> Meteor
        Meteor --> MeteorAccounts
        Meteor --> Mongo2
    end

    Before -.->|"Migration\n~6 months"| After

    classDef removed fill:#fee,stroke:#c33,color:#900
    classDef added fill:#efe,stroke:#393,color:#060

    class Before removed
    class After added
Loading

Migration Principle

Each feature moved as one unit. Fastify kept serving everything not yet moved (shared Mongo, zero
big-bang cutover):

flowchart LR
    Methods["Port service logic\nto Meteor methods"] --> Pub["DDP publication\nreplaces WS hub"]
    Pub --> Expose["Wormhole expose\nREST + MCP"]
    Expose --> Cutover["Frontend cutover"]
    Cutover --> Delete["Delete Fastify route"]
Loading

Per-milestone gate: npm run lint && npm run typecheck && npm run format && npm test green,
browser smoke test, then commit to PR #357.


What Was Replaced

Authentication — Fastify + Better Auth → Meteor Accounts

Capability Fastify (Before) Meteor (After)
Email/password login Better Auth POST /api/auth/sign-in/email accounts-password DDP login handler
Signup Better Auth POST /api/auth/sign-up/email accounts.createUser Meteor method
Password reset Better Auth endpoints accounts.sendResetPasswordEmail + accounts.resetPassword Meteor methods (nodemailer)
Social OAuth (Google, GitHub, Apple, Authentik) Better Auth social providers Meteor Accounts OAuth + genericOAuth for Authentik
Session management Better Auth DB-backed sessions + JWT access tokens Meteor resume tokens via DDP
Token verification JWKS endpoint at /api/auth/jwks, Fastify require-auth.ts middleware Meteor resolveUser() in auth-bridge.js (resume token + PAT)
Personal Access Tokens Fastify tokens.ts route, SHA-256 hashed tokens.list/create/revoke Meteor methods, same SHA-256 scheme
CASL permissions backend/src/lib/permissions.ts meteor-backend/server/casl.js — same ability definitions
Inter-service auth N/A JWT + JWKS bridge during coexistence (retired post-migration)

Real-Time — 7 WebSocket Hubs → DDP Publications

Domain Fastify WebSocket Hub Meteor DDP Publication
Clock clock-ws.ts — hand-rolled fan-out clock.liveForTeams — oplog-backed cursor
Tickets tickets-ws.ts tickets.byTeam
Timers timers-ws.ts timers.liveForUser
Teams teams-ws.ts teams.byUser
Presence presence-ws.ts — polling presence.watch — in-memory + 75s timeout
Notifications notifications-ws.ts notifications.liveForUser (via subscribeNewNotifications)
Messages + Channels messages-ws.ts + channels-ws.ts messages.byThread + channelmessages.byChannel

Result: 7 separate WebSocket connections replaced by 1 DDP connection with multiplexed subscriptions.

REST API — Fastify Routes → Wormhole Methods

Every Fastify route was replaced by a Meteor method exposed via meteor-wormhole (auto-generated REST + OpenAPI + MCP):

Domain Fastify Routes Meteor Methods Frontend Call
Clock clock.ts (12 endpoints) clock.* (12 methods) wormholeCall()
Tickets tickets.ts (8 endpoints) tickets.* (8 methods) wormholeCall()
Timers timers.ts (12 endpoints) timers.* (12 methods) wormholeCall()
Teams teams.ts + teams-admin.ts (12 endpoints) teams.* (12 methods + join-request workflow) wormholeCall()
Messages messages.ts (2 endpoints) messages.* (2 methods) wormholeCall()
Channels channels.ts (4 endpoints) channels.* (4 methods) wormholeCall()
Huddle huddle.ts (7 endpoints) huddle.* (7 methods) wormholeCall() + DDP
Organizations org.ts + org-admin.ts (20 endpoints) orgs.* (20 methods) wormholeCall()
Enterprises enterprises.ts (9 endpoints) enterprises.* (9 methods) wormholeCall()
Users/Profiles users.ts (6 endpoints) users.* (6 methods) wormholeCall()
Activity activity.ts (3 endpoints) activity.* (3 methods) wormholeCall()
Work Summary work.ts (1 endpoint) timers.getUserWorkSummary wormholeCall()
Notifications notifications.ts (6 endpoints) notifications.* (mutations + testPush) wormholeCall()
PATs tokens.ts (3 endpoints) tokens.* (3 methods) wormholeCall()
Presence presence.ts (1 endpoint) presence.watch (DDP pub) DDP subscription

Uploads & Media — Fastify Multipart → Meteor HTTP Handlers

Feature Before After
Avatar/background upload Fastify multipart + static serve busboy on WebApp.connectHandlers (/api/me/avatar, /api/me/background)
Media library images Fastify media.ts multipart Meteor /api/media/upload + media.* methods
Video (PulseVault) Fastify TUS server Meteor TUS server (pulsevault.js) + pulsevault.reserve* methods
Attachments Fastify attachments.ts attachments.* Meteor methods (metadata-only)
Static file serving Fastify static plugin WebApp.connectHandlers at /uploads/*

Background Jobs — Same Engine, New Host

Feature Before After
Job engine agenda npm lib in Fastify Same agenda lib inside Meteor
Job collection agendajobs in MongoDB Same agendajobs collection
Shift reminders Fastify processor Meteor processor (METEOR_AGENDA_ENABLED=true)
Auto-clockout Fastify processor Meteor processor
Handover Flag-gated (FASTIFY_AGENDA_ENABLED / METEOR_AGENDA_ENABLED) Fastify removed, Meteor owns all jobs

Test Infrastructure

Aspect Before After
Auth in tests Fastify HTTP POST /api/auth/sign-in/email DDP WebSocket client, accounts.createUser + resume tokens
Test framework Vitest Vitest (unchanged)
Test count 64 integration tests across 5 suites
Test suites Tickets (10), Teams (29), Clock (9), Enterprises (4), Timers (12)

Completed Phases

Phase 1 — Proof of Concept

  • Mongo single-node replica set (oplog tailing)
  • Headless Meteor 3 app (meteor-backend/, port 3100, shared Mongo)
  • Wormhole vendored as submodule (vendor/meteor-wormhole, mieweb fork)
  • Better-auth session bridge (auth.bridge DDP method + REST token param)
  • First DDP publication: tickets.byTeam + clock.liveForTeams
  • Frontend DDP client (src/lib/ddp.ts) — dependency-free, EJSON decode, live hooks

M0 — Identity & Foundations

  • Wormhole invocation context: AsyncLocalStorage-based context so REST/MCP calls read Authorization header (no credentials in body)
  • Header auth: requireIdentity falls back to currentBearerToken(), wormholeCall() sends Authorization: Bearer
  • JWT + JWKS: better-auth issued JWTs (15-min TTL), JWKS at /api/auth/jwks, Meteor verified via jose key cache
  • Meteor Accounts login: emailPassword login handler with native bcrypt verify + Fastify fallback during coexistence, accounts.createUser for signup, password reset methods (nodemailer)
  • Social OAuth: Google, Apple, GitHub, Authentik (genericOAuth + OIDC discovery), env-gated server + client
  • CASL abilities: Permission system ported to Meteor method/publication guards
  • Agenda jobs: Same agenda lib + agendajobs collection, shift reminders + auto-clockout
  • Push service: web-push + FCM + APNs ported (plain npm libs)
  • Email service: nodemailer wrapper for transactional email

M1 — Core Time-Tracking

  • Clock: 12 methods (start/stop/pause/resume/status/active/events/timesheet/updateTimes/deleteEvent/createManual/agreeAutoClockout/respondShiftReminder) + clock.liveForTeams pub + coupled timer close-out
  • Tickets: 8 methods (list/create/update/delete/assign/batchStatus/updateStatus/shareWithTimeharbor) + tickets.byTeam pub + assignment notifications + activity log
  • Timers: 12 methods (createEntry/startSession/stopSession/updateEntry/deleteEntry/getRunning/getToday/getDay/getWeek/getTicketTotal/copyPrevious/getTeamRunningTimers) + timers.liveForUser pub
  • Notifications: inbox pub (notifications.liveForUser) + push notifications (web-push, FCM, APNs) + testPush method

M2 — Collaboration

  • Presence: presence.watch custom pub — in-memory tracking, 75s timeout, connection lifecycle
  • Activity log: activity.log, activity.userLog, activity.ticketActivity — cursor-paginated
  • Teams: 12 methods + join-request/approval workflow + teams.byUser pub + org auto-provisioning
  • Messages: messages.getThread + messages.send + messages.byThread pub — cursor-paginated, notifications
  • Channels: 4 methods + channelmessages.byChannel pub + #general auto-provisioning + notification fan-out
  • Huddle: 7 methods (createPost/updatePost/deletePost/toggleLike/getComments/addComment/deleteComment) + huddlePosts.byTeam pub + ticket-scoped feeds

M3 — Org & Profiles

  • Users/Profiles: 6 methods (get/getByUsername/batchGet/updateProfile/checkUsername/claimUsername)
  • Organizations: 20 methods — full CRUD, member management, CASL ability checks, slug validation, auto-join, search, reports-to, admin endpoints, public profiles
  • Enterprises: 9 methods — CRUD, member management, ownership transfer, installation flow (takeOwnership + installStatus)
  • PATs: 3 methods (list/create/revoke) — SHA-256 hashing, activity log, raw token shown once

M4 — HTTP Surfaces & Decommission

  • Uploads: Avatar/background multipart upload via busboy, static file serving at /uploads/*
  • Media library: Image upload (/api/media/upload), thumbnail upload, CRUD methods
  • PulseVault: TUS resumable video upload, reservation system, streaming playback
  • Attachments: Metadata-only methods (list/add/remove)
  • Test infrastructure: Migrated from Fastify auth to DDP WebSocket client — 64 tests, 5 suites
  • Fastify backend deleted: Commit 401bad5 (June 30, 2026) — entire backend/ directory removed
  • Production deployment: July 5, 2026 — merged, DB migrated, live

M5 — Post-Merge Drift Reconciliation


Final Architecture

Frontend (Vite + React 19, port 3000)
    ├── wormholeCall()  →  Meteor REST (via meteor-wormhole)
    └── DDP connection  →  Meteor DDP (real-time subscriptions)
                               │
                          Meteor 3 Backend (port 3100)
                               ├── Meteor Accounts (auth)
                               ├── Meteor Methods (business logic)
                               ├── DDP Publications (real-time)
                               ├── Wormhole (REST + OpenAPI + MCP)
                               ├── Agenda (background jobs)
                               ├── Push (APNs + FCM + web-push)
                               └── MongoDB (all 38 collections)

Final Stats

Metric Value
Migration timeline ~6 months
Production endpoints migrated 100% of critical features
Fastify routes deleted All (entire backend/ removed)
WebSocket hubs replaced 7 → 1 DDP connection
Meteor methods 100+ across 15 domains
DDP publications 10 live subscriptions
Integration tests 64 (5 suites)
Database collections 38 on timehuddle
Backend removal June 30, 2026 (commit 401bad5)
Production go-live July 5, 2026

Architecture Decisions

Decision Resolution
Auth model Meteor Accounts owns all interactive login (email/password, OAuth, resume tokens, password reset). Better Auth fully retired in production.
OIDC provider for TimeHarbor Open: build OIDC provider on Meteor or have TimeHarbor re-point elsewhere. Better Auth's OIDC provider was the only remaining dependency.
Background jobs Same agenda npm lib inside Meteor, same agendajobs collection — zero migration needed
Real-time DDP publications only; all 7 Fastify WS hubs retired
Credentials over wormhole Authorization: Bearer header via AsyncLocalStorage invocation context — never in the body
File uploads / TUS Raw HTTP handlers on WebApp.connectHandlers (not method-shaped); storage paths unchanged
Database Single MongoDB instance, database renamed from timeharbortimehuddle

horner added 6 commits June 12, 2026 19:40
- Vendor wreiske:meteor-wormhole as git submodule at vendor/meteor-wormhole
- Run MongoDB as single-node replica set (rs0) in docker-compose for oplog tailing
- Add headless Meteor 3 app at meteor-backend/ (port 3100) sharing the existing Mongo:
  - better-auth session bridge (auth.bridge method + per-connection identity)
  - Tickets methods (list/create/updateStatus) + tickets.byTeam publication
  - Clock methods (active/start/stop) + clock.liveForTeams publication
  - Wormhole REST (/api + Swagger docs) and MCP (/mcp) exposure of all 6 methods
- Add dependency-free DDP client (src/lib/ddp.ts) with EJSON decoding and live hooks
- TicketsPage: replace hand-rolled /v1/tickets/ws WebSocket with DDP subscription
- seed.ts: emit migrated shapes (assignedTo as array, teams with orgId)
- Ignore vendor/ and meteor-backend/ in ESLint and Prettier
- meteor-backend: add tickets.update/delete/assign/batchStatus, reviewed
  semantics, github param, creator auto-assign, and portable activity-log
  emission (shared 'activities' collection) matching the Fastify service
- expose new methods via wormhole REST/MCP; add CORS for the Vite origin
- frontend: ticket mutations (create/update/status/delete/batch) now go
  through wormhole REST; assignTicket stays on Fastify for notification
  fan-out
- clock UI cutover: TeamContext + WorkPage drop the /v1/clock/ws WebSocket
  for the oplog-backed clock.liveForTeams DDP publication
- DdpClient: auto-reconnect with backoff, re-auth, and sub restore
- vendor/meteor-wormhole → feat/invocation-context (mieweb/meteor-wormhole#4):
  AsyncLocalStorage per-invocation context exposing the caller's
  Authorization header to methods invoked over REST/MCP
- auth-bridge: requireIdentity resolves explicit sessionToken param (legacy),
  then currentBearerToken() from the header, then the DDP connection cache
- main.js: drop sessionTokenProp from all wormhole schemas — tokens no
  longer advertised in Swagger/MCP tool schemas
- wormholeCall: send Authorization: Bearer header instead of a sessionToken
  body field (methods still accept the body param for one release)
- add meteor-to-production-plan.md — trackable M0–M4 production plan

Validated: REST + MCP tool calls with header-only auth, UI ticket create,
legacy body-token path, unauthenticated rejection; lint/typecheck/format/53
tests green
- backend auth: enable better-auth jwt plugin (15m access tokens) and keep
  JWKS endpoint at /api/auth/jwks
- Fastify require-auth: accept JWT bearer tokens via jose + remote JWKS local
  verification (no DB token lookup path for JWTs)
- Meteor auth bridge: replace session-collection reads with JWT verification
  against JWKS, keep PAT validation path (th_pat_) via personal_access_tokens,
  and retain DDP connection identity cache
- frontend api: add getAccessToken() cache and use JWT bearer auth for
  wormholeCall
- frontend DDP: auth.bridge now uses JWTs and proactively re-bridges before exp
- update meteor-to-production-plan.md: mark M0.c checklist complete

Validated:
- JWT claims include sub/email/name/iss/aud/exp with 15m TTL
- Meteor REST + MCP tool calls work with JWT bearer
- Fastify /v1 routes accept JWT bearer
- Meteor PAT path works; legacy session bearer now rejected by Meteor
- Browser smoke test: sign in + create ticket via UI with JWT-backed flow
- lint/typecheck/format/tests green
Methods now resolve identity only from the Authorization: Bearer header
(REST/MCP) or the DDP connection cache (auth.bridge). Dropped the
sessionToken body param from all tickets.* and clock.* methods and the
requireIdentity(methodContext) signature; refreshed auth-bridge docs.
Completes the final M0.b overlap-cleanup checkbox.
Backend (Fastify/better-auth IdP only):
- auth.ts builds socialProviders from env (github/google/apple), each
  self-registering only when its *_CLIENT_ID is present.
- Authentik (any OIDC IdP) via the genericOAuth plugin, registered as
  providerId 'authentik' when AUTHENTIK_CLIENT_ID is set.
- server.ts: allow 'apple' in the sign-in/social + callback provider enums.

Frontend:
- socialProviders.ts registry gated by VITE_SOCIAL_PROVIDERS.
- LoginForm renders the configured provider buttons via @mieweb/ui Button.
- api.ts: signInWithSocial accepts apple; new signInWithOAuth2 for genericOAuth.

Docs/config: backend/README env vars; docker-compose VITE_SOCIAL_PROVIDERS.
horner and others added 10 commits June 12, 2026 22:17
Add meteor-backend/server/permissions.js — a faithful port of
backend/src/lib/permissions.ts. It builds the same CASL ability
(buildAbilityFor) and exposes buildTeamAbility, requireTeamMembership,
and requireTicketPermission, resolving org-role and enterprise-elevation
via the native driver.

Rewire tickets.js and clock.js to the shared guards and remove the
duplicated local requireTeamMembership/requireAuthorizedTicket helpers.
Per-ticket methods now authorize by CASL action (update/delete/assign),
bringing Meteor methods to authorization parity with Fastify — notably
only a ticket's creator (or an org/enterprise-elevated user) may delete.
Add meteor-backend/server/email.js — a nodemailer wrapper that mirrors
backend/src/lib/email.ts, reading the same SMTP_*/EMAIL_FROM env. Imported
from main.js so it is bundled and validated; a Meteor consumer follows in
M1 (clock/notification mail). Adds nodemailer to meteor-backend deps.
Add meteor-backend/server/push.js — port of backend/src/services/push.service.ts.
sendToUser fans a notification out to iOS (APNs), Android (FCM) and web
(VAPID) targets, reading the same pushsubscriptions/devicetokens collections
and the same VAPID_*/APNS_*/FIREBASE_SERVICE_ACCOUNT env. Imported from main.js
for bundling/validation; a Meteor consumer follows with notifications in M1.

firebase-admin's exports map is not honored by Meteor's bundler, so the app
API is taken from the package root and getMessaging from its concrete
lib/messaging path. Adds web-push, @parse/node-apn, firebase-admin deps.
…stence)

Extract shared break math into clock-core.js (DRY between clock.* methods
and the auto-clockout jobs), and add agenda.js porting the four clock jobs
from backend/src/services/agenda.service.ts: shift-4h-reminder,
shift-end-reminder, shift-auto-clockout, shift-missed-clockout.

The Agenda backend reuses Meteor's already-open MongoDB connection
(MongoBackend({ mongo: rawDb() })) against the shared agendajobs collection,
so it never opens a second client. The processor loop only starts when
METEOR_AGENDA_ENABLED=true, leaving Fastify the sole processor during
coexistence; M1 flips it on when Meteor becomes the clock writer.
Add meteor-backend/Dockerfile.dev (node:24-bookworm-slim + pinned Meteor
3.4.1; Alpine/musl can't run Meteor) and a docker-compose meteor-backend
service that bind-mounts the repo, installs deps on start, and runs
meteor run on port 3100. Wires container-internal MONGO_URL/MONGO_OPLOG_URL
to the rs0 mongodb service, METEOR_PACKAGE_DIRS for the vendored wormhole
package, and CORS_ORIGINS for the /api bridge. Adds VITE_METEOR_URL to the
frontend service so the SPA can reach the Meteor backend.

Validated: image builds and the container boots cleanly (agenda processor
gated off, /api/docs 200).
Add timers.liveForUser publication (user's running timers, endTime:null) and
register a Timers collection. WorkPage now subscribes via DDP and refetches the
day/week on any timers collection change, mirroring the clock.liveForTeams
cutover. Timer mutations and day/week reads stay on Fastify REST during
coexistence. Removes the now-unused timerApi.openLiveStream WS client.
Add notifications.liveForUser publication (user's inbox, newest 200) and a
Notifications collection. New shared DDP helper subscribeNewNotifications fires
for notifications delivered live (after the initial backlog snapshot). main.tsx,
NotificationsPage, and ShiftReminderContext now consume it instead of
notificationApi.openStream, which is removed. Fastify remains the notification
writer and owns push fan-out during coexistence; mutations stay on REST.
Port the entire Fastify ClockService onto Meteor as the clock-domain writer.

Backend (meteor-backend):
- clock.js: all clock.* methods (start/stop/pause/resume/status/active/
  activeForUser/events/timesheet/updateTimes/deleteEvent/createManual/
  agreeAutoClockout/respondShiftReminder) with the full side-effect pipeline.
- clock-core.js: rich toPublicClockEvent + break helpers (toBreakEntries,
  normalizeBreakEntries, computeTotalBreakSeconds, findBreaksForEvents).
- timer-core.js (new): coupled timer close-out/restart on pause/resume/stop.
- notify-core.js (new): createNotification + notifyClockAdmins (agenda.js now
  reuses createNotification, DRY).
- activity-core.js (new): emitActivity clock.in/clock.out logging.
- main.js: wormhole-expose all new clock methods.

Coexistence flip (C3): Meteor Agenda processor ON by default
(METEOR_AGENDA_ENABLED), Fastify processor gated OFF
(FASTIFY_AGENDA_ENABLED!=true) so the shared agendajobs collection is never
double-processed. docker-compose wires both flags.

Frontend:
- clockApi cut over from Fastify /v1/clock/* REST to wormhole method calls;
  dropped the dead clock WS openLiveStream (clock live state already via DDP).
- agreeClockout -> clock.agreeAutoClockout; ShiftReminderContext tolerant of
  the wormhole not-found/already-closed reason shape.

Validated: lint, typecheck, prettier, 53 frontend tests; live REST exercise of
the full clock lifecycle (start/pause/resume/stop/manual/timesheet/update/
delete) with confirmed accumulatedTime math, agenda job schedule+cancel,
activity log, and admin/self notifications.
- Add assignee notification fan-out + resolved assigneeName to the Meteor
  tickets.assign method (createNotification fires a single push per newly
  added assignee, fixing the Fastify double-push).
- Cut frontend ticketApi.assignTicket over to wormhole REST.
- Audit M1 Fastify route deletion: all four route files still serve live
  consumers (team dashboard, WorkPage reads, notification inbox/invites,
  ticket reads), so deletion is deferred to M2/M3. Documented in plan.
mfisher31 and others added 10 commits June 18, 2026 09:43
move: activity and presense features to Meteor
Port 18 Fastify REST endpoints + 3 WebSocket streams to Meteor methods
and DDP publications. Teams (12 methods + teams.byUser pub), Messages
(2 methods + messages.byThread pub), Channels (4 methods +
channelmessages.byChannel pub + ensureDefaultChannel helper).

Org auto-provisioning ported to org-helpers.js for team create/join.
Frontend teamApi, messageApi, channelApi cut over to wormholeCall.
TeamContext.tsx and MessagesPage.tsx cut over from WebSocket to DDP
subscriptions for real-time updates.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Port ~35 Fastify REST endpoints to Meteor methods:
- Users (6): profile get/update, username check/claim, batch lookup
- Organizations (20): CRUD, member management, CASL ability checks,
  admin/public default-org endpoints, slug validation, search
- Enterprises (7): CRUD, member role management, user search
- PATs (3): list, create (sha256 hash), revoke with activity log

Frontend userApi, usernameApi, orgApi, orgAdminApi, enterpriseApi,
tokenApi all cut over to wormholeCall. Avatar/background uploads and
GET /v1/me stay on Fastify (deferred to M4).

Add Vitest integration test suite for the Meteor wormhole REST API:
- tests/tickets.test.ts (10 tests), teams.test.ts (11), clock.test.ts (9)
- Test infrastructure: helpers.ts (auth + wormhole wrapper), setup.ts
- scripts/checks.sh gains `meteor` job for CI

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Port file uploads and media management from Fastify to Meteor:

- Static file serving via WebApp.connectHandlers at /uploads/*
- Avatar/background upload+delete via multipart handlers (busboy)
- Media library: image upload, thumbnail upload, CRUD methods
- Attachments: metadata-only wormhole methods (list, add, remove)

Frontend api.ts cut over: attachmentApi, mediaApi (upload + CRUD),
userApi avatar/background all point to Meteor. toAbsoluteUrl routes
/uploads/ paths through METEOR_BASE_URL.

Fix video thumbnail regeneration: fetch video with auth credentials
into a blob URL to avoid cross-origin canvas tainting.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Dharp02 added 18 commits July 2, 2026 19:40
- Update ddp.ts to subscribe to 'users' instead of 'user'
- Fix TeamContext user queries
- Update EnterprisePage and InstallerModal user references
- Migrate test helpers to use 'users' collection
- Update all Meteor backend tests to use correct user references
- Add bcrypt dependency for test password handling
- Simplify signup spec by removing redundant wait operations
- Update blocking tests to use cleaner assertions
- Improve test page objects (LoginPage, SignupPage)
- Remove unnecessary delays and improve test reliability
- Remove unnecessary wait operations from all realtime tests
- Update DDP subscriptions to use 'users' collection
- Improve assertion patterns for reactive data
- Simplify timer and timesheet test logic
- Refactor team invitation tests with cleaner assertions
- Simplify work summary tests
- Remove redundant waits and improve test flow
- Add global setup/teardown hooks for test stability
- Update Playwright configuration for better test isolation
- Bump required dependencies
- Add onboarding navigation fix documentation
- Add Meteor migration scripts
- Add password reset E2E test
- Add global test setup/teardown utilities
- Add onboarding E2E test suite
- Fix AdminTimesheetPanel.tsx: Move fetchData useCallback before DDP useEffect to prevent "Cannot access 'fetchData' before initialization" error. The useEffect was referencing fetchData before its const declaration, causing temporal dead zone violation.

- Fix dashboard test: Add { exact: true } to getByText('Open tickets') to avoid strict mode violation when multiple elements match the selector.

- Fix ticket-timers tests: Add handling for "Clock In Required" dialog that appears when starting a timer without being clocked in. Increased timeouts for real-time sync waits.

- Fix timesheet test: Add waitForLoadState('networkidle') before checking clock state. Implement graceful team-auto-selection wait. Increase test timeout to 60s to accommodate clock-in/out cycle. Wait for button to be enabled before clicking.

All 89 tests now pass (88 passed, 1 flaky that passes on retry).
- Add migrate-all-collections.js for data migration pipeline
- Add migrate-all-data.mjs for complete migration workflow
- Add migrate-complete.mjs for final migration verification
- Add migrate-to-meteor-accounts.js for account conversion
- Add set-test-passwords.mjs for test user provisioning
- Add verify-user-collections.js for migration validation
- Add run-migration.sh as main migration entry point
- Add comprehensive migration documentation in docs/
- Move scripts from meteor-backend/scripts to root scripts/
- Add scripts README for migration maintenance
- Add migration-login-handler.js for dual auth system support
- Update auth-bridge.js to handle both Better Auth and Meteor users
- Update clock.js to support migrated user authentication
- Update main.js to register migration login handler
- Enable seamless transition from Better Auth to Meteor accounts
- Maintain backward compatibility during migration period
- Add ReportIssueModal.test.tsx for feedback component testing
- Update ReportIssueModal.tsx to use @mieweb/ui components
- Update api.ts to handle Meteor authentication tokens
- Update LoginForm.tsx with Meteor login support
- Add provision-test-users.mjs for e2e test user setup
- Add auth-migration.spec.ts for migration flow testing
- Add better-auth-migration.spec.ts for comprehensive auth testing
- Update e2e test configuration for migration testing
- Update global-setup.ts for test environment preparation
- Update ticket-timers.spec.ts for migrated auth
- Update README.md with migration instructions
- Update .gitignore for migration artifacts
- Update package.json dependencies
…wn UX

- Add 7 new unit tests for tickets.assign endpoint covering edge cases:
  - Multiple user assignment
  - Unassignment (empty array)
  - Reassignment to different user
  - Invalid user ID validation
  - Non-array value rejection
  - Invalid user ID format rejection
  - Non-team member rejection

- Fix mobile dropdown menu viewport issues on TicketsPage:
  - Adjust z-index to prevent overlapping with other elements
  - Add overflow-visible classes for mobile screens
  - Constrain dropdown max-width on mobile devices
  - Ensure dropdown content stays within viewport boundaries

- Add E2E test for mobile dropdown rendering and positioning
- Clean up eslint-disable comments in global-setup and global-teardown
- Remove 'Unassigned' option from member filter (assignment now requires explicit user selection)
- Add METEOR_API_BASE for same-origin REST calls through Vite proxy
- Add /uploads and /v1 proxy routes to vite.config.ts
- Fix teams.getMembers to query both user collections (Meteor + Better Auth)
- All mobile REST calls now work in Capacitor WebView
- Organizations and member names now display correctly

Fixes mobile compatibility with Meteor backend on port 3100.
- Expose pushSubscribe/pushUnsubscribe wormhole methods
- Add Meteor methods for device token and web push subscription CRUD
- Update nativePush and pushNotificationsClient to route through Meteor API
- Add push notification permission status to settings page
- Update dev-ios and dev-android scripts
- Configure Vite VAPID key env variable
@Dharp02 Dharp02 self-assigned this Jul 6, 2026
@kadenhorner kadenhorner moved this from In Progress to Ready in Scrum Team Jerry Jul 6, 2026
Dharp02 and others added 8 commits July 6, 2026 19:35
… Apple)

- All three OAuth providers now use jose library for JWT signing
- Consistent 5-minute token expiration across all providers
- JWT includes userId, email, name, provider, and meteorToken claims
- Improved security with cryptographically signed tokens
- Better frontend compatibility with structured, verifiable tokens
- Remove backend workspace from package-lock.json
- Add .gitignore for data/videos and uploads directories
- Prepare for Meteor-only deployment
- Remove all Better Auth collection references (session, account, user, verification)
- Update auth-bridge.js: Remove session collection lookups, simplify whoami to use only Meteor users
- Update main.js: Remove Better Auth fallback lookups in token resolution
- Update clock.js: Remove legacy user collection image lookups
- Update teams.js: Replace Better Auth account password updates with Meteor Accounts API
- Add password reset flow for users without passwords (auto-redirect on login)
- Add migration scripts: remigrate-users.js, verify-migration.js, drop-legacy-collections.js
- All 42 users migrated, 270 legacy documents dropped
- Backup saved at /home/pdharamkar/backups/pre_remigration_2026-07-06T19-16-23/
Resolved conflicts and integrated features from main:
- Added privacy.html for App Store requirements
- Added user blocking migration (backend/migrations/20260627_120000_add-user-blocked-field.cjs)
- Updated .prettierignore to exclude meteor-backend
- Improved mobile UI for team join requests
- Better error handling in useSession

Note: Blocking functionality already exists in meteor-is-back
Note: Better Auth migration detection kept for remaining user migrations
- Disable backend checks workflow (Fastify backend removed, Meteor doesn't have lint/typecheck scripts)
- Fix 34 frontend lint errors (unused variables/imports)
  - Remove unused expect imports from page objects
  - Prefix unused test variables with underscore
  - Remove unused imports from dashboard tests
- Update playwright job to not depend on backend
- Fix MediaItem type in huddle video upload (remove duplicate/invalid fields)
- Fix getComments type casting for huddle and comments
- Filter out null roles when loading organizations
- Fix HuddlePost type casting in Huddle page
- Run prettier formatting fix on all files
Fixed ReferenceError issues in 8 failing tests caused by inconsistent variable naming in beforeEach/beforeAll hooks:

- tests/e2e/teams/team-invitation.spec.ts: Standardized page object variables to use underscore prefix (_loginPage, _dashboardPage, _teamsPage, _orgPage)
- tests/e2e/organizations/blocking-feature.spec.ts: Standardized to use underscore prefix for all variables (_loginPage, _dashboardPage)
- tests/e2e/timers/work-summary.spec.ts: Fixed database variable from db to _db

All 91 E2E tests now pass successfully.
@Dharp02 Dharp02 merged commit d6fb94b into main Jul 6, 2026
3 of 4 checks passed
@github-project-automation github-project-automation Bot moved this from Ready to Done in Scrum Team Jerry Jul 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants