FRE-588: Complete tRPC/Clerk integration with database schema updates
- Updated router.ts middleware for Clerk authentication - Modified test contexts to use clerkUserId - Added team tables to test schema - Updated WaitlistForm and waitlist page - Created src/server/trpc/ parallel structure All 258 tests pass. Ready for Security Reviewer.
This commit is contained in:
@@ -11,7 +11,7 @@ describe('tRPC API Layer - Character System', () => {
|
||||
beforeEach(async () => {
|
||||
await resetTestDb();
|
||||
const db = await getTestDb();
|
||||
ctx = { userId: 1, db };
|
||||
ctx = { clerkUserId: 'user_test', db };
|
||||
caller = appRouter.createCaller(ctx);
|
||||
|
||||
const project = await caller.project.createProject({
|
||||
|
||||
@@ -25,7 +25,7 @@ describe('tRPC API Layer', () => {
|
||||
expect(project).toMatchObject({
|
||||
name: 'Test Project',
|
||||
description: 'A test project',
|
||||
ownerId: ctx.userId,
|
||||
ownerId: 1,
|
||||
});
|
||||
expect(project.id).toBeDefined();
|
||||
expect(project.id).toBeGreaterThan(0);
|
||||
@@ -173,7 +173,7 @@ describe('tRPC API Layer', () => {
|
||||
sharedProjectId = project.id;
|
||||
|
||||
// Insert a second user
|
||||
globalSqlite!.exec("INSERT INTO users (id, email, name) VALUES (2, 'user2@test.com', 'User Two');");
|
||||
globalSqlite!.exec("INSERT INTO users (id, clerk_id, email, name) VALUES (2, 'user2_test', 'user2@test.com', 'User Two');");
|
||||
});
|
||||
|
||||
it('should share a project with another user', async () => {
|
||||
@@ -276,7 +276,7 @@ describe('tRPC API Layer', () => {
|
||||
|
||||
// Create caller for user 2
|
||||
const db = await getTestDb();
|
||||
const ctx2: TRPCContext = { userId: 2, db };
|
||||
const ctx2: TRPCContext = { clerkUserId: 'user2_test', db };
|
||||
const caller2 = appRouter.createCaller(ctx2);
|
||||
|
||||
const project = await caller2.project.getProject({ id: sharedProjectId });
|
||||
@@ -291,7 +291,7 @@ describe('tRPC API Layer', () => {
|
||||
});
|
||||
|
||||
const db = await getTestDb();
|
||||
const ctx2: TRPCContext = { userId: 2, db };
|
||||
const ctx2: TRPCContext = { clerkUserId: 'user2_test', db };
|
||||
const caller2 = appRouter.createCaller(ctx2);
|
||||
|
||||
const projects = await caller2.project.listProjects();
|
||||
|
||||
@@ -12,7 +12,7 @@ describe('revisionsRouter', () => {
|
||||
await resetTestDb();
|
||||
const db = await getTestDb();
|
||||
await resetInMemoryState(db);
|
||||
ctx = { userId: 1, db };
|
||||
ctx = { clerkUserId: 'user_test', db };
|
||||
caller = appRouter.createCaller(ctx);
|
||||
});
|
||||
|
||||
|
||||
@@ -15,12 +15,24 @@ const isAuthenticated = t.middleware(({ ctx, next }) => {
|
||||
return next({ ctx: { ...ctx, clerkUserId: ctx.clerkUserId } });
|
||||
});
|
||||
|
||||
// Middleware for database access
|
||||
const hasDb = t.middleware(({ ctx, next }) => {
|
||||
// Middleware for database access and user lookup
|
||||
const hasDb = t.middleware(async ({ ctx, next }) => {
|
||||
if (!ctx.db) {
|
||||
throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message: 'Database not available' });
|
||||
}
|
||||
return next({ ctx: { ...ctx, db: ctx.db } });
|
||||
|
||||
let userId: number | undefined;
|
||||
if (ctx.clerkUserId) {
|
||||
const { users } = await import('../../src/db/schema');
|
||||
const userRows = await ctx.db.select({ id: users.id })
|
||||
.from(users)
|
||||
.where(eq(users.clerkId, ctx.clerkUserId));
|
||||
if (userRows.length > 0) {
|
||||
userId = userRows[0].id;
|
||||
}
|
||||
}
|
||||
|
||||
return next({ ctx: { ...ctx, db: ctx.db, userId } });
|
||||
});
|
||||
|
||||
// Middleware for project ownership verification
|
||||
@@ -35,7 +47,7 @@ const hasProjectAccess = t.middleware(async ({ ctx, next }) => {
|
||||
throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message: 'Database not available' });
|
||||
}
|
||||
const { users } = await import('../../src/db/schema');
|
||||
const userRows = await ctx.db.select({ dbId: users.id, clerkId: users.clerkId })
|
||||
const userRows = await ctx.db.select({ id: users.id, clerkId: users.clerkId })
|
||||
.from(users)
|
||||
.where(eq(users.clerkId, ctx.clerkUserId));
|
||||
const dbUser = userRows[0];
|
||||
@@ -49,10 +61,10 @@ const hasProjectAccess = t.middleware(async ({ ctx, next }) => {
|
||||
if (!project) {
|
||||
throw new TRPCError({ code: 'NOT_FOUND', message: `Project ${ctx.projectId} not found` });
|
||||
}
|
||||
if (project.ownerId !== dbUser.dbId) {
|
||||
if (project.ownerId !== dbUser.id) {
|
||||
throw new TRPCError({ code: 'FORBIDDEN', message: `You do not have access to project ${ctx.projectId}` });
|
||||
}
|
||||
return next({ ctx: { ...ctx, projectId: ctx.projectId, userId: dbUser.dbId } });
|
||||
return next({ ctx: { ...ctx, projectId: ctx.projectId, userId: dbUser.id } });
|
||||
});
|
||||
|
||||
// Base router
|
||||
|
||||
@@ -111,18 +111,34 @@ CREATE TABLE IF NOT EXISTS revisions (
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS revision_changes (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
revision_id INTEGER NOT NULL REFERENCES revisions(id),
|
||||
change_type TEXT NOT NULL CHECK(change_type IN ('addition', 'deletion', 'modification')),
|
||||
element_type TEXT,
|
||||
old_content TEXT,
|
||||
new_content TEXT,
|
||||
scene_number INTEGER,
|
||||
line_number INTEGER,
|
||||
page_number INTEGER,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS revision_changes (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
revision_id INTEGER NOT NULL REFERENCES revisions(id),
|
||||
change_type TEXT NOT NULL CHECK(change_type IN ('addition', 'deletion', 'modification')),
|
||||
element_type TEXT,
|
||||
old_content TEXT,
|
||||
new_content TEXT,
|
||||
scene_number INTEGER,
|
||||
line_number INTEGER,
|
||||
page_number INTEGER,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS teams (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
owner_id INTEGER NOT NULL REFERENCES users(id),
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS team_members (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
team_id TEXT NOT NULL REFERENCES teams(id),
|
||||
user_id INTEGER NOT NULL REFERENCES users(id),
|
||||
role TEXT NOT NULL DEFAULT 'editor',
|
||||
joined_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
`;
|
||||
|
||||
export let globalSqlite: Database.Database | null = null;
|
||||
|
||||
@@ -162,5 +162,5 @@ export interface TRPCContext {
|
||||
userId?: number;
|
||||
clerkUserId?: string;
|
||||
projectId?: number;
|
||||
db?: typeof import('../../src/db/config/migrations').db;
|
||||
db?: import('../../src/db/config/migrations').DrizzleDB;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user