Skip to content

Store user preferences server-side (tour flags, last_login) #91

Description

@vanatteveldt

Goal

Tour "seen" flags are currently stored in localStorage (per-device), so users get the onboarding tour again on every new device.

The fix is to store these flags server-side in a system index, keyed by email. While we're at it, we probably want to have at least these files:

  • `last_login` — useful for spotting inactive accounts / future account cleanup
  • `terms_accepted_at` — stub for future T&C acceptance support (no UI needed yet)

`last_login` — record on every authenticated request

`last_login` should be updated in the `authenticated_user` dependency so it is recorded for both browser users and direct API users (Python/R). To avoid an ES write on every request, only update when `last_login` is older than a threshold (e.g. 1 day) — this effectively records "was active on this day" without excessive writes.

The alternative would be to only record e.g. get /users/me, which we probably call for every login anyway, but it's a bit less certain.

Frontend changes

  • `GET /users/me/profile` — returns the profile (tour flags etc.)
  • `PATCH /users/me/profile` — updates tour flags (`onboarding_tour_seen`, `project_tour_seen`)
  • New `useUserProfile` / `usePatchUserProfile` hooks replace `useLocalStorage` in `OnboardingTour`, `ProjectTour`, and `AccountMenu`
  • Guests (unauthenticated) fall back to localStorage

Backend: open design decision

Two options for where to store the profile data:

Option A: New `user_profiles` system index (requires v3)

  • Clean separation of concerns — one document per user, email as doc ID
  • Requires a v3 migration (the version system treats any index in `SYSTEM_INDICES` that is missing on disk as "broken" and refuses to start — so a new index can't be added to v2 without a version bump)
  • Migration is trivial: copy all v2 data as-is, new index starts empty

Option B: Add fields to the existing roles index (no migration)

  • Add `last_login`, `onboarding_tour_seen`, `project_tour_seen`, `terms_accepted_at` to `roles_mapping` in v2.py
  • `update_systemdata_mappings` already calls `put_mapping` on startup, so new fields are applied automatically — no version bump needed
  • Profile data is stored on the `_server:{email}` document with `role="NONE"`, creating it if it doesn't exist yet
  • All existing queries for privileged users already filter for `WRITER` or `ADMIN`, so `NONE` documents are naturally ignored — but verify that no query does a broad scan of all `_server` documents without filtering by role, as those would start returning profile-only users unexpectedly

Fields needed

```
email: keyword (doc ID)
last_login: date
onboarding_tour_seen: boolean
project_tour_seen: boolean
terms_accepted_at: date (nullable, for future use)
```

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions