Completes the billing surface; pairs with sase.tr#29.
Admin SDK
- changePlan(input) + cancelSubscriptionById(input) + resumeSubscription(input).
- PlanChangeResult + SubscriptionStateChange types exported.
- SASE_ADMIN_ENDPOINTS lists the three new spoke routes.
Route
- /api/sase/subscriptions/[subId] now accepts change-plan/cancel/resume
in addition to trial-extend/activate. newPlanId required when
action=change-plan. Reason ≥ 5 chars enforced. Audit captures the
action + days + newPlanId + reasonLen on both paths.
Repo
- getUser() also returns currentPlanId for the plan picker.
UI
- BillingActions extended:
active/trial → [Plan değiştir][İptal et]
cancelled → [Devam ettir]
- Change-plan modal lists active plans (current excluded) with brand
count hint. The spoke flags brand-reassignment via response field;
v1 surfaces only the confirmation.
- Cancel uses destructive variant + clear messaging.
Phase D ships the user-management mutation surface — A/B/C/D all live.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Billing tab on user detail page now has writable actions wired to spoke
endpoints landed in sase.tr#28.
Admin SDK
- extendTrial({ subscriptionId, days, reason, founderId }) + result type
- activateSubscription({ subscriptionId, reason, founderId }) + result type
- SASE_ADMIN_ENDPOINTS list updated
Repo
- getUser() now returns subscriptionId so the billing UI can act on it.
Route
- POST /api/sase/subscriptions/[subId] — single multiplexed endpoint:
body { action: 'trial-extend' | 'activate', reason, days? }.
Auth + spoke-wired + reason ≥ 5 chars + days 1..90 (trial-extend).
Audit on success and failure paths.
UI
- BillingActions client component on the Subscription & Billing tab.
- Trial state: [+7g][+14g][+30g] quick buttons + custom days input +
reason modal.
- Trial or pending state: [Subscription'ı aktive et] button.
- Other states show "no billing action available" hint.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Suspend / Reactivate / Ban buttons on the user detail page; wired to
spoke endpoints (sase.tr#27).
Admin SDK
- suspendUser/reactivateUser/banUser on SaseAdmin + LifecycleResult.
- SASE_ADMIN_ENDPOINTS updated.
Route
- POST /api/sase/users/[id]/lifecycle (action + reason). Auth + spoke
wired + min reason length checks. Audit on both success and failure.
RO model
- Sase Prisma schema adds status/statusReason/statusChangedAt/
statusChangedBy. getUser() returns lifecycleStatus + statusReason.
UI
- LifecycleStatusBadge in header next to email reveal.
- LifecycleButtons renders the right actions for the current state.
- Modals with reason textarea; ban requires a double-confirm checkbox.
- Impersonate hidden when user is suspended/banned (AuthGuard rejects).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Panel-side only — first writable feature on the Sase user detail page.
Notes are stored in the panel database (sase_user_notes); the spoke is
never touched (KVKK minimize).
Model
- SaseUserNote { id, saseUserId, authorUserId, body, pinned, createdAt }
- Index on (saseUserId, pinned, createdAt) for the default render order
API
- GET /api/sase/users/[id]/notes — list (pinned first, then newest)
- POST /api/sase/users/[id]/notes — { body, pinned } create (audit'li)
- PATCH/DELETE /api/sase/notes/[noteId] — toggle pin / hard delete
UI
- New "Notlar" tab on user detail. Textarea + pinned checkbox + submit;
list shows TR-localized timestamps, per-row pin/unpin and delete.
Schema sync
- package.json `start` now runs `prisma db push --skip-generate` before
`next start`. Panel uses db-push style (no migrations dir); this lets
the new table land on next deploy without a separate manual step.
Future destructive changes will require a smarter migration approach.
teknikborc.md updated: super_panel_reader role still needs to be created
on the new sase-postgres (current panel uses sase superuser).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adım 3 — Süper Panel side of the readonly impersonation flow.
- admin-sdk/sase.ts: impersonateReadonly(userId, founderId, ttlMinutes,
reason) — POSTs to /internal/admin/users/:id/impersonate-readonly.
notWiredSdk stub returns same shape so type contract holds when env vars
are absent.
- POST /api/sase/users/[id]/impersonate-readonly — auth-checked panel
endpoint. Validates ttl ∈ {15,30,60} + reason ≥ 5 chars. Calls spoke SDK,
writes audit (double-audit: AdminClient also logs at the wire), returns
{ redirectUrl, expiresAt, sessionIdPrefix }.
- _impersonate-button.tsx (client): shadcn Dialog. TTL pills (15/30/60) +
reason textarea + submit. On success opens spoke redirectUrl in new
window/_blank/noopener.
- Detail header gains [Impersonate (read-only)] button — gated on
saseAdminWired() so it stays hidden until SASE_ADMIN_API_BASE +
INTERNAL_API_TOKEN_SASE land in Coolify env.
Spoke side (sase.tr@79a2616 → … → next release): InternalAdminModule +
ImpersonationReadonlyGuard already merged.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Previously 'all' missed status='analyzed', so re-running v2 prompts on existing
analyzed sessions returned 0 reset.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
apps/web/src/lib/telegram.ts: server-side Telegram client + alertIssueCreated.
createGithubIssueForInsight action fires the alert after issue is created (after
audit log, before revalidate). Severity emoji + insight link + issue link.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Bug: previous logic advanced watermark to the latest session start_time including
ongoing sessions. PostHog session_recordings filters by start_time, so once a
session was 'seen' as ongoing the watermark moved past its start time and the
session was never re-fetched after it ended. Today 4 auth sessions on
/dashboard/vehicles/* and /dashboard/search (07:17-07:40 UTC) were lost this way.
Fix:
1. ROLLING_LOOKBACK_MINUTES (default 60): every cycle queries date_from =
min(watermark, now - 60min). Sessions that just finished get re-fetched
regardless of watermark drift. Upsert dedupes.
2. Track earliestOngoingStart; cap watermark to (earliestOngoingStart - 1s)
so subsequent cycles re-read that range.
Also added GET/DELETE /api/insights/watermark for manual reset (used to
trigger 24h backfill after this deploy).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- prisma/sase/schema.prisma with User + UserSubscription (partial view)
- lib/db-sase.ts exports typed RO client
- /projects/sase shows live metrics (users count, new users 7d, active subs)
- seed marks sase as active
DATABASE_URL_SASE_RO wired to super_panel_reader (SELECT-only)