feat(sase): user management Phase 7a — listing + detail (RO)

Adım 1 — User listesi sayfası
- Extend Sase RO Prisma schema: Plan, Brand, UserBrand, BankAccount, Payment, QueryLog
- listUsers() + listPlans() repo with filter/sort/page in apps/web/src/lib/sase/users.ts
- /projects/sase/users — TanStack-free server-rendered table with URL-param filters
  (plan multi-select, status, activity preset, search) + pager
- Sase landing "Users →" link

Adım 2 — User detay 360°
- /projects/sase/users/[id] — header (masked email + audit-logged reveal button)
  + 5 tabs: Özet · Subscription & Billing · Kullanım · Aktivite · Audit
- Usage stats: 30d/90d/lifetime queries, success rate, avg response, daily sparkline,
  brand/source distribution, top VINs
- Timeline: merged signup + subscriptions + payments + recent queries
- User-scoped audit trail (AuditLog endpoint LIKE filter)
- POST /api/sase/users/[id]/reveal-email — audit-logged full email reveal

teknikborc.md — Phase 7 PRD vs Sase.tr şeması gap'leri kaydedildi
(EFT yok, B2B yok, API keys/webhooks yok, vb.)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Semih
2026-05-18 00:42:59 +03:00
parent bc67c387cd
commit 0b31685e95
12 changed files with 1707 additions and 0 deletions

View File

@@ -0,0 +1,41 @@
import { NextResponse } from "next/server";
import { headers } from "next/headers";
import { auth } from "@/lib/auth";
import { saseDb } from "@/lib/db-sase";
import { writeAudit } from "@/lib/audit";
export const dynamic = "force-dynamic";
export async function POST(
_req: Request,
ctx: { params: Promise<{ id: string }> },
) {
const session = await auth.api.getSession({ headers: await headers() });
if (!session) {
return NextResponse.json({ ok: false, error: "unauthenticated" }, { status: 401 });
}
const { id } = await ctx.params;
const user = await saseDb.user.findUnique({
where: { id },
select: { email: true },
});
if (!user) {
await writeAudit({
projectKey: "sase",
endpoint: `/api/sase/users/${id}/reveal-email`,
method: "POST",
responseStatus: 404,
});
return NextResponse.json({ ok: false, error: "not_found" }, { status: 404 });
}
await writeAudit({
projectKey: "sase",
endpoint: `/api/sase/users/${id}/reveal-email`,
method: "POST",
responseStatus: 200,
});
return NextResponse.json({ ok: true, email: user.email });
}