Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,6 @@ npx tsx --env-file=.env.local scripts/benchmark-sample.ts # live pipeline + to
|----------|--------|-----|
| **Share link** (+ password / expiry) | Shipped | Read-only stakeholder access without org membership or custom email domains |
| **Export PDF / CSV** | Shipped | Offline handoff to execs and clients |
| **Team workspaces** | Schema + API only | Multi-tenant path exists in Prisma; UI deferred to keep the portfolio demo focused |

---

Expand Down Expand Up @@ -309,7 +308,7 @@ npx tsx scripts/benchmark-sample.ts --estimate-only # Case study metrics (offl

1. **Problem** — Unstructured review text doesn't scale; manual theming breaks past dozens of rows.
2. **Approach** — Embeddings + k-means + LLM summarization with atomic job claiming and share-gated read-only reports.
3. **Tradeoff** — Built org/tenant models but shipped **share-link collaboration** instead of email invites (no custom domain on Vercel free tier).
3. **Tradeoff** — Chose **share-link collaboration** over team workspaces and email invites (no custom domain on Vercel free tier; removed half-built org schema in RL-013).
4. **Reliability** — Inngest + `waitUntil` fallback, Upstash rate limits, `/api/health`, **93 unit tests**, Playwright e2e, GitHub Actions CI.
5. **Outcome** — CSV → themed report in **< 60s**, **~$0.0004** per 12-review sample run, PDF/CSV export, password-protected links for stakeholders.

Expand Down
6 changes: 2 additions & 4 deletions app/api/analysis/[id]/export/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
shareAccessCookieName,
verifyShareAccessToken,
} from "@/lib/share-access";
import { canViewSession, isSessionCreator } from "@/lib/org/access";
import { isSessionCreator } from "@/lib/session-access";

interface RouteContext {
params: { id: string };
Expand All @@ -33,7 +33,6 @@ export async function GET(_req: Request, { params }: RouteContext) {
select: {
id: true,
userId: true,
organizationId: true,
fileName: true,
sharePasswordHash: true,
shareExpiresAt: true,
Expand Down Expand Up @@ -61,9 +60,8 @@ export async function GET(_req: Request, { params }: RouteContext) {
const authUser = await auth();
const userId = authUser?.user?.id;
const isCreator = isSessionCreator(userId, session);
const hasTeamAccess = await canViewSession(userId, session);

if (!isCreator && !hasTeamAccess) {
if (!isCreator) {
if (isShareExpired(session.shareExpiresAt)) {
return NextResponse.json(
{ success: false as const, error: "This link has expired." },
Expand Down
9 changes: 0 additions & 9 deletions app/api/analysis/[id]/status/route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
const mockFindUnique = vi.fn();
const mockAuth = vi.fn();
const mockCookiesGet = vi.fn();
const mockCanViewSession = vi.fn();

vi.mock("@/lib/prisma", () => ({
prisma: {
Expand All @@ -28,12 +27,6 @@ vi.mock("next/headers", () => ({
}),
}));

vi.mock("@/lib/org/access", () => ({
isSessionCreator: (userId: string | undefined, session: { userId: string | null }) =>
Boolean(userId && session.userId === userId),
canViewSession: (...args: unknown[]) => mockCanViewSession(...args),
}));

import { GET } from "./route";

const SLUG = "test-slug-abc";
Expand All @@ -52,7 +45,6 @@ function makeSession(overrides: Record<string, unknown> = {}) {
return {
id: SESSION_ID,
userId: OWNER_ID,
organizationId: null,
status: "COMPLETED",
totalReviews: 42,
updatedAt: new Date(),
Expand All @@ -72,7 +64,6 @@ function callGet(slug = SLUG) {
beforeEach(() => {
vi.clearAllMocks();
mockAuth.mockResolvedValue(null);
mockCanViewSession.mockResolvedValue(false);
mockCookiesGet.mockReturnValue(undefined);
});

Expand Down
6 changes: 2 additions & 4 deletions app/api/analysis/[id]/status/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
checkShareAccess,
shareAccessCookieName,
} from "@/lib/share-access";
import { canViewSession, isSessionCreator } from "@/lib/org/access";
import { isSessionCreator } from "@/lib/session-access";
import type { ApiResponse, SentimentBreakdown } from "@/types";
import type { ThemeAnalysis, StoredAnalysisResult } from "@/features/analysis/types";

Expand All @@ -34,7 +34,6 @@ export async function GET(
select: {
id: true,
userId: true,
organizationId: true,
status: true,
totalReviews: true,
updatedAt: true,
Expand Down Expand Up @@ -62,9 +61,8 @@ export async function GET(
const authUser = await auth();
const userId = authUser?.user?.id;
const isCreator = isSessionCreator(userId, session);
const hasTeamAccess = await canViewSession(userId, session);

if (!isCreator && !hasTeamAccess) {
if (!isCreator) {
const cookieToken = cookies().get(
shareAccessCookieName(session.id)
)?.value;
Expand Down
19 changes: 1 addition & 18 deletions app/api/analysis/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import {
unauthorizedResponse,
} from "@/lib/auth-helpers";
import { mapCreateAnalysisError } from "@/lib/create-analysis-errors";
import { getMembership } from "@/lib/org/access";
import { createLogger } from "@/lib/logger";
import type { ApiResponse } from "@/types";

Expand Down Expand Up @@ -77,22 +76,7 @@ export async function POST(
);
}

const { reviews, sourceType, sourceUrl, fileName, organizationId } =
parsed.data;

if (organizationId) {
const membership = await getMembership(authUser.userId, organizationId);
if (!membership) {
return NextResponse.json(
{
success: false as const,
error: "You are not a member of that workspace",
code: "FORBIDDEN",
},
{ status: 403 }
);
}
}
const { reviews, sourceType, sourceUrl, fileName } = parsed.data;

const prismaSourceType =
sourceType === "csv"
Expand All @@ -111,7 +95,6 @@ export async function POST(
data: {
shareableSlug: generateShareableSlug(),
userId: authUser.userId,
organizationId: organizationId ?? null,
sourceType: prismaSourceType,
sourceUrl: sourceUrl ?? null,
fileName: fileName ?? null,
Expand Down
238 changes: 0 additions & 238 deletions app/api/orgs/[slug]/route.ts

This file was deleted.

Loading
Loading