Skip to content

Modernize BookIT UI, runtime, and deployment - #117

Open
Portals wants to merge 56 commits into
mainfrom
shore/upgrade-dependencies
Open

Portals wants to merge 56 commits into
mainfrom
shore/upgrade-dependencies

Conversation

@Portals

@Portals Portals commented Sep 6, 2026 •

Copy link
Copy Markdown
Member

Important

Deploy steps, in order

  1. Upgrade Gamma to 2.5.3+.
  2. In Gamma, add a client authority booking on the BookIT client and assign the super groups that may book.
  3. Back up the database. Keep the PostgreSQL 12 and Redis volumes.
  4. Replace the frontend and backend containers with ghcr.io/cthit/bookit:<tag>, port 8080 serving / and /api. Keep the backend's env vars, add DB_HOST, DB_NAME, DB_USER, DB_PASS, and NODE_ENV=production.
  5. Baseline once: prisma migrate resolve --applied 20260920000000_initial with the app's env. Without it the image exits with P3005.
  6. Start the container. Log shows No pending migrations to apply.
  7. Remove the db-scripts cleanup job; the backend does it now.
  8. Everyone signs in again. A log warning about the booking authority means step 2 is missing.

Details: README, Production.

Modernizes BookIT across the UI, runtime, and deployment. Replaces the legacy frontend with TypeScript and shadcn/ui, generates GraphQL contracts, strengthens booking concurrency and contact-data privacy, and serves the frontend and API from one container.

Review guide

  • Backend: authentication/session handling, the Gamma booking authority deciding bookable groups, per-booking can_edit/can_move flags, transactional overlap checks, and automatic contact-data cleanup.
  • Deployment: the migration instructions above, Dockerfile, DB configuration, and the CI → tested image → release flow.
  • Frontend: calendar navigation, booking creation/editing/drag confirmation, rules management, and keyboard/mobile behavior.

Dependency changes

Dependency / tooling Change
Node / package manager Node 24.19.0; npm replaced with pnpm 12.3.4 workspaces
React / React DOM 17 → 19.3.1
TypeScript 4.9 → 7.0.2
Create React App / ESLint / Mocha / Chai Replaced with Vite+ 1.3.0: Vitest 5.0.5, Oxlint and Oxfmt
Digit Components / Material UI icons Replaced with shadcn/ui, Radix UI 1.8.1, Tailwind CSS 4.3.3 and Lucide 1.61.0
React Router 5 Replaced with TanStack Router 1.170.45; added TanStack Query 5.104.5
FullCalendar 5.9 → 7.1.0, using the Monarch shadcn integration
Moment Replaced with date-fns 4.4.0
Express 4 → 5.3.0
express-graphql Replaced with Apollo Server 5.5.1 and its Express 5 integration 1.1.2
GraphQL 15 → 16.14.2; added GraphQL Codegen
Prisma 5.9 prerelease → 7.10.1, with the PostgreSQL driver adapter
Redis client 3 → 6.3.0
express-openid-connect 2 → 3.4.0
Axios / dotenv 0.21 → 1.20.0 / 16 → 17.4.2
ts-node / ts-node-dev Replaced with tsx 4.23.15
Browser tooling Added Playwright 1.64.1 and Testcontainers 12.2.1

Removed unused legacy dependencies including express-session, connect-redis, js-base64, qs, prop-types and web-vitals. Moved personal-data cleanup from the separate Jobber container into the backend. Updated CI and container builds for the workspace layout.

Existing PostgreSQL 12 and Redis 5 deployments remain supported. The optional production Compose stack uses PostgreSQL 16; migrating existing PostgreSQL 12 data to it requires dump/restore, not reusing the old data directory.

Upgrade verified from main

Tested locally by running the current main release (its backend creates the schema with prisma db push) against a fresh PostgreSQL 12, Redis 5, and Gamma 2.5.3, creating 14 bookings through its UI, then switching the same database and Redis volumes to this branch's image:

  1. Without the baseline, the new image refused to start with Prisma's P3005 "database schema is not empty" and exited.
  2. prisma migrate diff from the live database to the new schema produced an empty migration, so the pushed schema matches.
  3. After the documented one-time prisma migrate resolve --applied 20260920000000_initial, the image started with "No pending migrations to apply".
  4. All 14 rows were identical before and after, the new API returned them with contact data intact, and they rendered in the new calendar. Old sessions did not carry over, so users sign in again.

Issues resolved

Screenshots

Desktop

Rules table rules
Calendar — list view download
Calendar — month view download
Calendar — week view download

Mobile

Mobile — booking form mobile-booking
Mobile — rules table mobile-rules
Mobile — list view download
Mobile — month view download

Migrate to pnpm 12 and Vite+, React 19 with shadcn and FullCalendar, TanStack Router and Query, Apollo Server, Prisma 7, and generated TypeScript contracts.

Restore compact room filters and original calendar, booking, and rules behavior. Add signed sessions, private phone fields, serializable booking writes, modern CI, and real Gamma Playwright coverage.

Keep PostgreSQL 12 and Redis 5 server upgrades as documented separate follow-ups. Verified 45 unit/API tests, four E2E flows, strict types and lint, production builds, clean dependency audit, and local Docker smoke tests.
Invalidate the moved booking's detail query alongside all calendar ranges. Cover opening details, dragging, immediately editing and preserving the moved timestamps in the real browser suite.
Build frontend and backend from the repository root, replace obsolete demo Gamma settings with required current OIDC configuration, and keep backing services private on the deferred database versions. Document explicit schema setup and existing database storage migration precautions.
Split four packed flows into ten independent tests while sharing the existing worker environment. Separate setup, actions and assertions with whitespace, format all E2E files with Vite+, and document role-first selectors with date/time label fallbacks.

Verified ten browser tests, the retained mobile day-view regression, 45 unit/API tests, strict TypeScript and formatting/lint checks.
Comment thread e2e/accessibility.spec.ts Outdated
Comment thread e2e/screenshots.ts Outdated
Comment thread .github/workflows/ci.yml Outdated
@cthit cthit deleted a comment from github-advanced-security AI Sep 6, 2026
@Goosetaf

Goosetaf commented Sep 6, 2026

Copy link
Copy Markdown
Member

The desktop week view has strange edges. Also, maybe the label background could be improved somewhat?

image

@Portals
Portals requested review from Goosetaf and molleer September 6, 2026 17:05
@Portals

Portals commented Sep 6, 2026

Copy link
Copy Markdown
Member Author

The desktop week view has strange edges. Also, maybe the label background could be improved somewhat?

image

I think I fixed it. Updated the screenshots. @GAsplund

@Goosetaf

Goosetaf commented Sep 6, 2026

Copy link
Copy Markdown
Member

The labels, yes I guess. For the edges, not really. Check the top edge for example, where is misaligns:
image

@Portals

Portals commented Sep 6, 2026

Copy link
Copy Markdown
Member Author

@GAsplund I see! Check it out, all fixed.

@Goosetaf

Copy link
Copy Markdown
Member

The desktop week view has strange edges. Also, maybe the label background could be improved somewhat?
image

Thanks for the change. However, from my own preference maybe the background dim should be just with a container around the text and not the entire container?

@Portals

Portals commented Sep 20, 2026

Copy link
Copy Markdown
Member Author

Thanks for the change. However, from my own preference maybe the background dim should be just with a container around the text and not the entire container?

Fixed @Goosetaf

@Goosetaf

Copy link
Copy Markdown
Member

Having tried to set it up locally, I found a few things:

  • Is it possible to use alpine instead of bookworm-slim in the Dockerfile? This would greatly reduce image size
  • Having tried to set up my own test compose, I could not get it to migrate. The Dockerfile should probably run database migrations before starting the server
  • It would be nice to have a ready-to-go compose file both for production (app + db) and for testing the full environment (that has an additional service that builds and runs the Dockerfile) rather than just a development compose. This would resolve headaches for finding Gamma's test API keys and client.
  • It's a bit confusing with the .env.example file being in the bookit directory rather than the root, but I understand if it needs to be there.

@Goosetaf

Goosetaf commented Sep 20, 2026 •

Copy link
Copy Markdown
Member

Nice! I have two more suggestions for the UI, and then I think I'm confident to approve. I have looked through the frontend and backend and I cannot find anything that I have any protest about.

The new color scheme looks a bit dark. Maybe this is a side-effect from attempting to darken the background before?
imageimage

What exactly is secure here? If it has no real meaning, maybe it should be removed to reduce potential visual clutter.
image

Edit: third thing. The background is inconsistent depending on if one or multiple rooms are booked. Could also remove the little dot as it does not seem to represent anything in multi-room context.
image

Portals and others added 2 commits October 22, 2026 18:25
`pnpm deploy --legacy` in the Dockerfile re-resolves dependencies instead of
using the lockfile, so the exact-version allowlist entries broke the image
build once @prisma/engines 7.10.1 was published.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- Restore the original brighter room colors; the theme refresh had muted them.
- Drop the admin shield icon from the header since it carried no meaning.
- Keep list and month list-item rows plain for multi-room bookings and show
  the booked rooms as diagonal bands in the dot, so single- and multi-room
  rows look consistent. Block events keep the striped background.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@Portals

Portals commented Oct 22, 2026 •

Copy link
Copy Markdown
Member Author

The new color scheme looks a bit dark. Maybe this is a side-effect from attempting to darken the background before?

It was from the theme refresh, not the dimming. Restored the original room colors (#0C6291, #E28413, #A63446, #3F784C) in 6cf3fce.

What exactly is secure here? If it has no real meaning, maybe it should be removed to reduce potential visual clutter.

It was an admin marker with no function. Removed.

The background is inconsistent depending on if one or multiple rooms are booked. Could also remove the little dot as it does not seem to represent anything in multi-room context.

The list view (and the month view's timed entries) now keeps a plain row for every booking, and the dot is gone. The striped background is kept only for block events in the week/day views and multi-day rows.

Also fixed in 62d2b43: the Docker image build started failing because pnpm deploy --legacy re-resolves @prisma/engines outside the lockfile, and 7.10.1 was published after the last CI run. The build-script allowlist is no longer pinned to an exact version.

And in a7ce9eb: the CI audit step failed on advisories published since the last run, so Vite+ is now 1.3.0 (Vitest 5) and the remaining dependencies were refreshed within their ranges. The PR description's dependency table is updated.

Portals and others added 10 commits October 22, 2026 19:14
The CI audit started failing on advisories published after the last run,
most of them in Vite+ 0.3 and its bundled Vitest, oxfmt and tinypool.
Move to Vite+ 1.3.0 (Vitest 5) in the workspace, overrides and CI, and
refresh the remaining dependencies within their ranges to pick up the
patched Express, axios, Prisma, testcontainers and GraphQL tooling
releases. TanStack Router now types route errors as unknown, so the root
error component coerces before rendering.

The one remaining advisory, braces stack exhaustion, has no patched
release and is only reached when globbing our own schema files, so it is
ignored explicitly in the audit configuration.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Review feedback: the dot carried no meaning for multi-room bookings, so
list-item entries now show only the time and title on a plain row.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The rule label had z-index 2 so it stayed clickable above FullCalendar's
event layer, which spans the whole lane. That also drew it over bookings
that overlapped the rule. The event layer is now click-transparent with
events and more-links opting back in, so the label can sit in the
background layer and still receive clicks where no booking covers it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Found with knip and a manual sweep of the configuration:

- bookit: drop the direct pg and @types/pg dependencies, which
  @prisma/adapter-pg already provides; move tsx to devDependencies since
  only the dev script uses it and the image runs the compiled build; drop
  the unreferenced start script, the missing index.js entry, the empty
  description and author fields, and correct the license to MIT to match
  the LICENSE file.
- root: drop the vite alias, which Vite+ provides transitively and nothing
  at the root peers on.
- frontend: remove the unused calendar expander icon, the unused shadcn
  card, dialog, table, and tabs sub-components, exports nothing imports,
  and the unused eyebrow style and token.
- e2e: keep the container image list internal to compose.ts.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The calendar and booking page derived edit permission from the user's
groups, but the server applies more checks when a booking is moved: an
anonymized contact or a private booking of a restricted room fails
validation even for an administrator, so a drag ended in an error toast.

Each booking now carries can_edit and can_move flags computed by the
same predicates the mutations use. The calendar only enables dragging
for movable bookings, and the booking page only shows Edit and Delete
when the server would accept them.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Review finding: can_move only mirrored two of the checks a move runs
against the unchanged booking, so a legacy booking longer than the
maximum range or with an invalid stored phone number still reported
can_move true and failed with a toast after the drag.

The checks that depend only on the booking and the acting user now live
in one function shared by validation and can_move: end after start,
maximum length, group membership, contact present, no private booking
of restricted rooms, at least one room, and a valid phone number.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Any non-alumni super group a user belonged to could book rooms, so the
set of booking groups could only be changed by editing Gamma group types.
Maintainers now assign the super groups that may book to a client
authority on the BookIT client in Gamma (BOOKING_AUTHORITY, default
"booking"), read through Gamma's new GET /api/client/v1/authorities/{name}.

Sessions get the user's non-alumni super groups that also hold the
authority. A missing authority is logged and leaves nobody able to book
as a group rather than falling back to every membership; other Gamma
errors still fail the sign-in. The admin authority is unchanged.

The e2e harness provisions the authority for digit and accepts a
GAMMA_IMAGE override, since the pinned Gamma 2.5.1 predates the endpoint.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The local Gamma seed now grants digIT the booking authority and adds a
hobbyIT super group, of which bookmember is also a member, without it,
so the local stack shows that membership alone no longer allows booking.

Gamma creates users before super groups and groups, so gamma-init now
also waits for the seeded groups before inserting rows that reference
the digit super group.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Gamma 2.5.3 ships GET /api/client/v1/authorities/{name}, so local
development and the e2e suite now run against it instead of 2.5.1,
and the documentation names the minimum version.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Review findings on the booking authority:

- Gamma's mock bootstrap appends the year to group names, so gamma-init
  never saw digit-local and hobbyit-local and the local stack could not
  start. It now waits for the fixture group IDs, which Gamma keeps.
- Sessions stored in Redis by an older build kept their group list and
  bypassed the authority check until they expired. The store now tags
  each session with a version and discards sessions from other versions,
  so an upgrade forces a fresh sign-in.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

@Goosetaf Goosetaf left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have been demoed the application and I can see clear improvements. Thank you for this.

LGTM

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants