Role hierarchy, file sharing, activity tracking, payments, and the guest demo experience. Privacy-first analytics throughout.
| Component | Status | Environment | Commit |
|---|---|---|---|
| Guest Activity Tracking | LIVE | dev + prod | f5087e0 (BE) · e91decf (FE) |
| Member & Guest Roles (Phases 1–3) | DEV | dev only | 2102f08 |
| Guest Registration (Phase 4) | DEV | dev only | ab73bb8 (BE) · 24d300f (FE) |
| Permission-Aware UI (Phase 5) | DEV | dev only | b72fc12 (FE) |
| Email Integration (Phase 6) | DEV | dev only | 5aaf1f1 |
| Paddle Payments (Phase 7) | DEV | dev only | 1f98ae8 (BE) · c7a4ddd (FE) |
| Reference Library (Phase 8) | DEV | dev only | 6c58c8f (BE) · 722b991 (FE) |
| Audit Logging (Phase 9) | DEV | dev only | 43f1a82 |
| Discovery Pipeline Dashboard | PUSHED | dev | FE only — dummy data, 3 view modes, 10 component files |
Demo recipients enter as Trial Members. Existing users have been backfilled as user_type='member'. The users table now includes user_type, trial_expires_at, invited_by, paddle_subscription_id, and paddle_customer_id columns.
Located at backend/app/middleware/permissions.py. Three decorators applied across 28 write endpoints.
| Decorator | Who Can Access | Behaviour |
|---|---|---|
require_member |
member, trial_member, admin, super_admin | Blocks guests. Auto-downgrades expired trials. 403 includes upgrade_url. |
require_admin |
admin, super_admin | Org-level management operations. |
require_super_admin |
super_admin only | Ops, analytics, system-wide admin. |
Applied to: upload, search, export, create project, share files, push to reference library, and all admin endpoints. 403 responses include an upgrade_url field so the frontend can show contextual upgrade prompts.
| Column | Type | Description |
|---|---|---|
id | BIGSERIAL PK | Auto-incrementing |
tenant_id | INTEGER FK | → tenants(id) |
user_id | INTEGER FK | → users(id) |
action_type | VARCHAR(50) | Specific action (structure_search, page_view, etc.) |
action_category | VARCHAR(30) | search / view / interact / export |
page_area | VARCHAR(50) | search / patents / reactions / targets / pathways / bioactivity |
metadata | JSONB | Safe metadata only — counts, types, modes |
created_at | TIMESTAMPTZ | Event timestamp |
session_id | UUID | Browser session grouping |
Indexes: user_id+created_at DESC, tenant_id+created_at DESC, session_id, action_category
| Column | Type | Description |
|---|---|---|
id | SERIAL PK | Auto-incrementing |
file_id | INTEGER FK | → files(id) |
shared_by | INTEGER FK | → users(id) who shared |
shared_with_email | VARCHAR | Recipient email |
token | UUID UNIQUE | Share link token |
permission | VARCHAR(20) | view / edit / comment |
expires_at | TIMESTAMPTZ | Optional expiry |
| Column | Type | Description |
|---|---|---|
id | SERIAL PK | Auto-incrementing |
tenant_id | INTEGER FK | → tenants(id) |
file_id | INTEGER FK | → files(id) |
pushed_by | INTEGER FK | Admin who pushed the file |
created_at | TIMESTAMPTZ | When pushed |
Reference library files are read-only. Guests can duplicate to their own project to work with the data.
| Table | New Columns |
|---|---|
users | user_type (VARCHAR 20), trial_expires_at, invited_by, paddle_subscription_id, paddle_customer_id |
roles | New entries: member, trial_member, guest, super_admin |
audit_log | user_agent column + 13 new action types |
Members can share files with anyone via token-based links. Recipients who don't have an account are prompted to register as a Guest (3-field inline form). Guests see only files shared with them.
| Endpoint | Method | Description |
|---|---|---|
/api/v1/files/{id}/share | POST | Create share with token, permission level, optional expiry |
/api/v1/files/shared-with-me | GET | List files shared with current user |
/api/v1/files/{id}/share/{share_id} | DELETE | Revoke a share |
/api/v1/shared/{token} | GET | Resolve share link (auth + unauth) |
/api/v1/files/{id}/duplicate | POST | Copy file + molecules to another project |
The GuestGate component greys out member-only features with upgrade tooltips. Locked reference files show a "Duplicate to My Project" button. The ShareDialog modal handles email entry, permission selection, and link copying.
POST /api/v1/auth/register-guest — creates a guest account and links the share token. The share link landing page at /shared/{token} shows an inline 3-field registration form (name, email, password) and auto-redirects to the shared file after registration.
Paddle.js loaded in index.html (sandbox mode). The UpgradePrompt component shows contextual upgrade overlays when guests or expired trial members hit member-only features.
POST /api/v1/webhooks/paddle — handles four event types:
| Paddle Event | Platform Action |
|---|---|
transaction.completed | Activate subscription, upgrade user_type |
subscription.activated | Confirm active subscription |
subscription.canceled | Downgrade to guest at period end |
subscription.updated | Update subscription metadata |
Webhook signature verification is enabled when PADDLE_WEBHOOK_SECRET is set. All payment features gracefully degrade if env vars aren't configured — no crashes, just "Contact us" fallbacks.
Via Resend API. Two email templates:
| Trigger | |
|---|---|
| Share invitation | When a member shares a file |
| Trial expiry reminder | 5 days before trial_expires_at |
Gracefully skips if RESEND_API_KEY is not configured — no errors, no crashes.
Admin-curated locked files pushed to tenants. Guests and members can view but not edit. "Duplicate to My Project" copies the file and its molecules for the user to work with.
| Endpoint | Method | Auth | Description |
|---|---|---|---|
/api/v1/admin/reference-library | POST | Admin | Push file to library |
/api/v1/reference-library | GET | Any | List reference files for tenant |
/api/v1/admin/reference-library/{id} | DELETE | Admin | Remove from library |
| Method | Route | action_type | category | page_area |
|---|---|---|---|---|
| POST | /api/v2/search/structure | structure_search | search | search |
| POST | /api/v2/search/text | text_search | search | search |
| POST | /api/v2/patents/collision | patent_collision_search | search | patents |
| GET | /api/v2/reactions | page_view | view | reactions |
| GET | /api/v2/reactions/{id} | reaction_detail_view | view | reactions |
| GET | /api/v2/compounds/{id} | compound_detail_view | view | search |
| GET | /api/v2/targets/{id} | target_detail_view | view | targets |
| GET | /api/v2/pathways/{id} | page_view | view | pathways |
| GET | /api/v2/bioactivity | page_view | view | bioactivity |
| GET | /api/v2/reactions/{id}/confidence | confidence_toggle | interact | reactions |
| GET | /api/v2/reactions/{id}/routes | route_drawer_opened | interact | reactions |
| GET | /api/v2/export/csv | data_exported | export | search |
| GET | /api/v2/export/rxn | data_exported | export | reactions |
| GET | /api/v2/export/sdf | data_exported | export | search |
| Action | Metadata | Example |
|---|---|---|
structure_search | Search type + count | {"search_type": "substructure", "result_count": 847} |
patent_collision_search | Result count | {"result_count": 47} |
data_exported | Format | {"format": "csv"} |
confidence_toggle | Mode | {"mode": "strict"} |
| Endpoint | Auth | Returns |
|---|---|---|
GET /api/admin/guest-analytics | Super Admin | Per-guest engagement summaries, feature usage, session counts |
GET /api/admin/guest-analytics/{user_id} | Super Admin | Detailed activity timeline for one guest |
GET /api/admin/guest-analytics/aggregate | Super Admin | Totals, feature popularity, path distribution |
| Level | Suggested Action |
|---|---|
| HIGH | Ready for a conversation. Ask what they found most useful. |
| MEDIUM | Moderate interest. Gentle follow-up about specific features. |
| LOW | Brief visit. Ask if they had time or if something was confusing. |
| NONE | Haven't opened. Send a nudge — link expires soon. |
13 distinct audit event types now logged, with the new user_agent column on audit_log:
| # | Tab | Status | Description |
|---|---|---|---|
| 1 | Users | EXISTING | Manage users, roles, status |
| 2 | Tenants | EXISTING | Manage organisations |
| 3 | Analytics | NEW | Guest/member engagement dashboard |
| 4 | Audit Log | EXTENDED | Now with 13 event types + user_agent |
All features gracefully degrade if these aren't set. No crashes — just "Contact us" fallbacks in the UI.
Top-level page at /pipeline for tracking drug discovery programs across stages. Added to the header nav between Quick Search and other items. Currently uses dummy data (7 programs) — backend API can be wired in later.
| View | Description |
|---|---|
| Pipeline (default) | Horizontal swim-lane chart. 10 stage columns (IDE → Ph3). Coloured dot at current stage with progress %. Completed stages show trail line. Cancelled programs show grey X. |
| Table | Sortable table with Program, Target, Indication, Stage, Status, Lead, Compounds, Budget (burn bar), Progress (bar), Updated. |
| Stage Board (Kanban) | Cards grouped by stage columns. Each card shows name, target, status badge, progress bar, compound count, budget spent. Horizontally scrollable. |
| ID | Target | Indication | Stage | Status | Compounds | Budget |
|---|---|---|---|---|---|---|
| EGFR-4801 | EGFR T790M/C797S | NSCLC | LO | ACTIVE | 847 | £2.4M (70%) |
| KRAS-7220 | KRAS G12D | Pancreatic | H2L | ACTIVE | 312 | £1.8M (40%) |
| BRD4-1155 | BRD4 BD1 | AML | CS | ACTIVE | 1,240 | £3.2M (90%) |
| CDK9-0342 | CDK9 | Triple-neg breast | HIT | AT RISK | 156 | £800K (80%) |
| PDE10A-6690 | PDE10A | Schizophrenia | PC | ON HOLD | 2,100 | £4.1M (90%) |
| SHP2-4401 | SHP2 | Solid tumours | IDE | ACTIVE | 0 | £400K (15%) |
| HDAC6-2201 | HDAC6 | Myeloma | H2L | CANCELLED | 450 | £1.2M (80%) |
Click any program row/card to open: program name, target, indication, stage pill + status badge, overall progress bar, compound/hit/lead counts, budget spend bar, milestones list with checkmark circles, cancellation reason (red background for cancelled programs), latest notes, lead & team details, start date, last update. Buttons: "Edit Program" (placeholder) and "View Compounds" (links to files page). Smooth slide animation.
src/pages/pipeline/PipelineDashboard.tsx — Main page, header, summary cards, toolbar src/types/pipeline.ts — Types + PIPELINE_STAGES constant src/data/dummyPipelineData.ts — 7 dummy programs with milestones src/components/pipeline/PipelineView.tsx — Swim-lane view (default) src/components/pipeline/StageBoard.tsx — Kanban view src/components/pipeline/PipelineTable.tsx — Table view src/components/pipeline/ProjectDetailPanel.tsx — Right slide-out detail src/components/pipeline/StatusBadge.tsx — Reusable status badge src/components/pipeline/StagePill.tsx — Reusable stage pill src/components/pipeline/ProgressBar.tsx — Reusable progress bar
Risk identified by Don: A guest could forward their share link to a competitor or someone who shouldn't see the data. This is a known vulnerability in any link-based sharing system. The pharma data room industry (ShareVault, Tresorit, Intralinks) has well-established patterns for this. Discoverant implements a layered defence model — each layer adds protection, and layers 1–2 together make forwarded links essentially unusable.
file_shares.shared_with_email, access is denied with: "This link was shared with dr.chen@vertex.com. Please ask them to share it with you directly."
file_shares table already has shared_with_email — this layer enforces the match at the GET /api/v1/shared/{token} endpoint. Based on DocuSign's "Recipient Locking" pattern.
share_otps table (token, code, email, expires_at, used_at). Codes expire after 10 minutes. 3 failed attempts locks the share for 1 hour. All OTP events logged to audit_log.
@vrtx.com addresses can be entered in the share dialog. Prevents sharing to personal Gmail, competitor domains, or disposable email services.
allowed_domains column on tenants table (JSONB array). Validated at share creation AND at share access. Share dialog shows domain restriction hint.
user_type is guest or trial_member. Frontend overlay watermark on data views.
guest_activity tracking — add IP/UA columns and anomaly scoring logic.
An attacker who receives a forwarded share link would need to defeat all of these in sequence:
| Layer | Effort | Impact | Dependencies |
|---|---|---|---|
| 1 Email-Pinned Access | Half day | Critical — blocks the entire forwarding problem | None — shared_with_email already exists in schema |
| 2 Email OTP | Half day | Critical — proves inbox ownership | Resend API key configured |
| 3 Domain Restriction | 2–3 hours | High — prevents sharing to personal/competitor emails | Layer 1 |
| 4 Dynamic Watermark | 1 day | Medium — deterrent, traceable leaks | None |
| 5 Anomaly Detection | 1 day | Medium — catch edge cases | Activity tracking (already live) |
| 6 Confidentiality Gate | Done | Legal backstop | Already in demo flow |
This layered approach is standard in pharma data rooms and enterprise document sharing:
| Platform | Approach |
|---|---|
| DocuSign | Recipient locking + SMS/phone OTP + access codes + knowledge-based authentication |
| ShareVault | Dynamic watermarking + domain restriction + granular permissions + full audit trail |
| Tresorit | Zero-knowledge E2E encryption + time-limited access + domain pinning |
| Intralinks | Email verification + NDA gate + watermarking + DRM controls |
| Discoverant | Email pin + OTP + domain lock + watermark + anomaly detection + confidentiality gate |