Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
-- CreateTable
CREATE TABLE "Tutorial" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"repoUrl" TEXT NOT NULL,
"projectName" TEXT NOT NULL,
"language" TEXT NOT NULL DEFAULT 'english',
"indexContent" TEXT NOT NULL,
"mermaidDiagram" TEXT NOT NULL,
"chapters" JSONB NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,

CONSTRAINT "Tutorial_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE INDEX "Tutorial_userId_idx" ON "Tutorial"("userId");

-- CreateIndex
CREATE INDEX "Tutorial_repoUrl_idx" ON "Tutorial"("repoUrl");
Comment on lines +1 to +21
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Consider adding a foreign key constraint for userId.

The Tutorial table references userId but doesn't enforce referential integrity with a foreign key constraint. This could lead to orphaned tutorial records if users are deleted. Consider adding a foreign key constraint with an appropriate ON DELETE action (CASCADE or SET NULL).

🔎 Suggested fix

After line 15, add:

-- Add foreign key constraint
ALTER TABLE "Tutorial" ADD CONSTRAINT "Tutorial_userId_fkey" 
  FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE;

Also ensure the Prisma schema reflects this relationship with:

model Tutorial {
  // ... existing fields
  user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}

model User {
  // ... existing fields
  tutorials Tutorial[]
}
🤖 Prompt for AI Agents
In apps/api/prisma/migrations/20251215163900_add_tutorial_model/migration.sql
around lines 1 to 21, the "Tutorial" table defines userId but lacks a foreign
key constraint; add a constraint after the table definition to enforce
referential integrity, e.g. ALTER TABLE "Tutorial" ADD CONSTRAINT
"Tutorial_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE
CASCADE (or ON DELETE SET NULL if preferred), and update the Prisma schema so
Tutorial has user User @relation(fields: [userId], references: [id], onDelete:
Cascade) and User includes tutorials Tutorial[] to keep the DB and Prisma models
in sync.

16 changes: 16 additions & 0 deletions apps/api/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,19 @@ model Plan {
updatedAt DateTime @updatedAt
subscriptions Subscription[]
}

model Tutorial {
id String @id @default(cuid())
userId String
repoUrl String
projectName String
language String @default("english")
indexContent String @db.Text
mermaidDiagram String @db.Text
chapters Json // Array of {filename: string, content: string}
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

@@index([userId])
@@index([repoUrl])
}
Comment on lines +106 to +120
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Add Prisma relation between Tutorial and User models.

The Tutorial model references userId but doesn't define a Prisma relation to the User model. This prevents you from using Prisma's relation features and doesn't enforce referential integrity at the schema level.

🔎 Recommended changes

Update the Tutorial model:

 model Tutorial {
   id             String   @id @default(cuid())
   userId         String
   repoUrl        String
   projectName    String
   language       String   @default("english")
   indexContent   String   @db.Text
   mermaidDiagram String   @db.Text
   chapters       Json
   createdAt      DateTime @default(now())
   updatedAt      DateTime @updatedAt
+  user           User     @relation(fields: [userId], references: [id], onDelete: Cascade)

   @@index([userId])
   @@index([repoUrl])
 }

And add to the User model (around line 44):

 model User {
   id             String         @id @default(cuid())
   email          String         @unique
   firstName      String
   authMethod     String
   createdAt      DateTime       @default(now())
   lastLogin      DateTime       @updatedAt
   completedSteps Json?
   accounts       Account[]
   payments       Payment[]
   subscriptions  Subscription[]
+  tutorials      Tutorial[]
 }

After making these changes, run:

npx prisma migrate dev --name add_tutorial_user_relation
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
model Tutorial {
id String @id @default(cuid())
userId String
repoUrl String
projectName String
language String @default("english")
indexContent String @db.Text
mermaidDiagram String @db.Text
chapters Json // Array of {filename: string, content: string}
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([userId])
@@index([repoUrl])
}
model Tutorial {
id String @id @default(cuid())
userId String
repoUrl String
projectName String
language String @default("english")
indexContent String @db.Text
mermaidDiagram String @db.Text
chapters Json
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId])
@@index([repoUrl])
}
🤖 Prompt for AI Agents
In apps/api/prisma/schema.prisma around lines 106 to 120, the Tutorial model
declares userId but lacks a Prisma relation to the User model; add a relation by
keeping the userId scalar and adding a relation field (e.g., user User
@relation(fields: [userId], references: [id])) and ensure userId remains String,
then update the User model (around line ~44) to include a back-relation field
(e.g., tutorials Tutorial[]) so Prisma enforces referential integrity; after
editing the schema run npx prisma migrate dev --name add_tutorial_user_relation
to apply the migration.

8 changes: 6 additions & 2 deletions apps/api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,12 @@ const apiLimiter = rateLimit({

// Request size limits (except for webhook - needs raw body)
app.use("/webhook/razorpay", express.raw({ type: "application/json" }));
app.use(express.json({ limit: "10kb" }));
app.use(express.urlencoded({ limit: "10kb", extended: true }));
// Higher limit for tRPC (tutorial generation sends file content up to 3.0MB)
app.use("/trpc", express.json({ limit: "5mb" }));
app.use("/trpc", express.urlencoded({ limit: "5mb", extended: true }));
// Default limit for other endpoints
app.use(express.json({ limit: "100kb" }));
app.use(express.urlencoded({ limit: "100kb", extended: true }));

// CORS configuration
const corsOptions: CorsOptionsType = {
Expand Down
2 changes: 2 additions & 0 deletions apps/api/src/routers/_app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { userRouter } from "./user.js";
import { projectRouter } from "./projects.js";
import { authRouter } from "./auth.js";
import { paymentRouter } from "./payment.js";
import { tutorialRouter } from "./tutorial.js";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find and examine key files in the tutorial router area
find apps/api/src -name "*.ts" | grep -E "(trpc|router)" | head -20

Repository: apsinghdev/opensox

Length of output: 299


🏁 Script executed:

#!/bin/bash
# Look at the _app.ts file where the import is
cat apps/api/src/routers/_app.ts

Repository: apsinghdev/opensox

Length of output: 862


🏁 Script executed:

#!/bin/bash
# Look for tutorial router file
find apps/api -name "*tutorial*" -type f

Repository: apsinghdev/opensox

Length of output: 136


🏁 Script executed:

#!/bin/bash
# Search for protectedProcedure and publicProcedure definitions
rg -n "protectedProcedure|publicProcedure" apps/api/src --type ts -A 2

Repository: apsinghdev/opensox

Length of output: 6084


🏁 Script executed:

#!/bin/bash
# Examine the delete mutation and service implementation
sed -n '157,175p' apps/api/src/routers/tutorial.ts
cat apps/api/src/services/tutorial.service.ts | head -100

Repository: apsinghdev/opensox

Length of output: 2882


🏁 Script executed:

#!/bin/bash
# Check for rate limiting middleware/configuration
rg -n "rateLimit|middleware" apps/api/src --type ts | head -20

Repository: apsinghdev/opensox

Length of output: 624


🏁 Script executed:

#!/bin/bash
# Look for rate limiting or middleware configuration files
find apps/api -name "*rate*" -o -name "*middleware*" | head -20

Repository: apsinghdev/opensox

Length of output: 85


🏁 Script executed:

#!/bin/bash
# Check the rate limiting setup in index.ts
sed -n '40,100p' apps/api/src/index.ts

Repository: apsinghdev/opensox

Length of output: 1880


🏁 Script executed:

#!/bin/bash
# Check if there's endpoint-specific rate limiting for generate
rg -n "generate.*rate|rate.*generate" apps/api/src --type ts -i

Repository: apsinghdev/opensox

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Look at the full trpc middleware setup and how rate limiting applies
sed -n '260,280p' apps/api/src/index.ts

Repository: apsinghdev/opensox

Length of output: 560


🏁 Script executed:

#!/bin/bash
# Check the generate endpoint implementation and if it has any internal protection
sed -n '112,155p' apps/api/src/routers/tutorial.ts

Repository: apsinghdev/opensox

Length of output: 1349


Secure tutorial endpoints with authentication and per-endpoint rate limiting.

All tutorial router endpoints use publicProcedure and fall back to "anonymous" user when unauthenticated. This creates security and data consistency issues:

  1. generate: Allows anonymous users to trigger resource-intensive LLM-powered tutorial generation without authentication
  2. delete/getUserTutorials: Use a shared "anonymous" userId, causing multiple unauthenticated users to share the same data scope—allowing unauthorized access to or deletion of others' tutorials
  3. Rate limiting: Global /trpc rate limit (100 req/15min) exists but is insufficient for the expensive generate endpoint

Refactor to:

  • Use protectedProcedure for all three endpoints (generate, delete, getUserTutorials)
  • Remove the "anonymous" fallback and require authentication
  • Consider adding endpoint-specific rate limiting for generate via middleware

Per the learnings: "Use middleware for auth checks; never duplicate auth logic in route handlers" and "Implement rate limiting on all public endpoints."

🤖 Prompt for AI Agents
In apps/api/src/routers/_app.ts around line 7, the tutorial router currently
exposes endpoints as publicProcedure and relies on an "anonymous" fallback;
update the router so generate, delete and getUserTutorials use
protectedProcedure (remove any anonymous user fallback and require
authentication), move all auth checks into middleware rather than inside
handlers, and add/attach an endpoint-specific rate-limit middleware to the
generate endpoint to throttle expensive LLM requests (e.g., per-user or per-IP
limits) while keeping global rate limits intact.

import { z } from "zod";

const testRouter = router({
Expand All @@ -21,6 +22,7 @@ export const appRouter = router({
project: projectRouter,
auth: authRouter,
payment: paymentRouter,
tutorial: tutorialRouter,
});

export type AppRouter = typeof appRouter;
183 changes: 183 additions & 0 deletions apps/api/src/routers/tutorial.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import { router, publicProcedure } from "../trpc.js";
import { z } from "zod";
import { tutorialService, type GenerateTutorialOptions } from "../services/tutorial.service.js";
import { listRepoFiles } from "../services/github-crawler.service.js";
import prismaModule from "../prisma.js";

const { prisma } = prismaModule;

const generateTutorialInputSchema = z.object({
repoUrl: z.string().url().refine(
(url) => url.includes("github.com"),
{ message: "Must be a valid GitHub repository URL" }
),
language: z.string().optional().default("english"),
maxAbstractions: z.number().min(3).max(15).optional().default(8),
maxFiles: z.number().min(5).max(100).optional().default(30),
selectedFiles: z.array(z.string()).optional(), // Optional: specific file paths
});

export const tutorialRouter = router({
/**
* List files in a repository for the file browser
*/
listRepoFiles: publicProcedure
.input(z.object({
repoUrl: z.string().url().refine(
(url) => url.includes("github.com"),
{ message: "Must be a valid GitHub repository URL" }
),
}))
.query(async ({ input }) => {
const result = await listRepoFiles(input.repoUrl);
return result;
}),

/**
* Check if a tutorial already exists for a repo URL (shows all public tutorials)
*/
checkExisting: publicProcedure
.input(z.object({ repoUrl: z.string() }))
.query(async ({ input, ctx }) => {
// @ts-ignore
const currentUserId = ctx.user?.id || "anonymous";

const tutorials = await prisma.tutorial.findMany({
where: {
repoUrl: input.repoUrl,
},
orderBy: { createdAt: "desc" },
select: {
id: true,
projectName: true,
language: true,
createdAt: true,
userId: true,
},
});

return {
exists: tutorials.length > 0,
tutorials: tutorials.map(t => ({
...t,
isOwnTutorial: t.userId === currentUserId,
})),
};
}),
Comment on lines +39 to +66
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Remove @ts-ignore and properly type the context user.

The @ts-ignore directive on line 42 suppresses TypeScript errors instead of fixing the underlying type issue. This hides potential runtime errors and makes the code harder to maintain.

Additionally, the "anonymous" fallback enables unauthenticated access which may not be the intended behavior for comparing tutorial ownership.

🔎 Recommended fix

Define proper types for the context:

// In your context.ts or types file
interface AuthenticatedContext {
  user: {
    id: string;
    email: string;
    // ... other user properties
  };
}

Then update the procedure:

   checkExisting: publicProcedure
     .input(z.object({ repoUrl: z.string() }))
     .query(async ({ input, ctx }) => {
-      // @ts-ignore
-      const currentUserId = ctx.user?.id || "anonymous";
+      const currentUserId = ctx.user?.id;

Consider whether this endpoint should require authentication or if anonymous access is intentional.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/api/src/routers/tutorial.ts around lines 39 to 66, remove the //
@ts-ignore and properly type ctx.user instead of falling back to the string
"anonymous": update the tRPC context types (or import the existing
Context/Session types) so ctx.user is typed as optional or required user object,
then either make this procedure require authentication (switch to
protectedProcedure or guard at the router level) so currentUserId comes from
ctx.user!.id, or if anonymous access is intended, set currentUserId to
undefined/null and adjust the isOwnTutorial comparison to handle absence of a
user; ensure you update imports and the context/type definitions rather than
suppressing the TypeScript error.


/**
* Get a specific tutorial by ID
*/
getById: publicProcedure
.input(z.object({ id: z.string() }))
.query(async ({ input }) => {
const tutorial = await prisma.tutorial.findFirst({
where: {
id: input.id,
},
});

if (!tutorial) {
throw new Error("Tutorial not found");
}

return tutorial;
}),

/**
* Get all tutorials for the current user
*/
getUserTutorials: publicProcedure.query(async ({ ctx }) => {
// @ts-ignore
const userId = ctx.user?.id || "anonymous";

const tutorials = await prisma.tutorial.findMany({
where: { userId },
orderBy: { createdAt: "desc" },
select: {
id: true,
projectName: true,
repoUrl: true,
language: true,
createdAt: true,
},
});

return tutorials;
}),
Comment on lines +90 to +107
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

getUserTutorials should require authentication.

This endpoint retrieves user-specific tutorials but falls back to "anonymous" when no user is authenticated. This could lead to:

  1. Anonymous users seeing each other's tutorials
  2. Misleading UI showing tutorials for unauthenticated users
  3. Type safety issues with the @ts-ignore directive

This endpoint should use a protectedProcedure instead of publicProcedure to enforce authentication.

🔎 Recommended fix
-  getUserTutorials: publicProcedure.query(async ({ ctx }) => {
-    // @ts-ignore
-    const userId = ctx.user?.id || "anonymous";
+  getUserTutorials: protectedProcedure.query(async ({ ctx }) => {
+    const userId = ctx.user.id;

     const tutorials = await prisma.tutorial.findMany({

Ensure protectedProcedure is imported from your trpc.ts file.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
getUserTutorials: publicProcedure.query(async ({ ctx }) => {
// @ts-ignore
const userId = ctx.user?.id || "anonymous";
const tutorials = await prisma.tutorial.findMany({
where: { userId },
orderBy: { createdAt: "desc" },
select: {
id: true,
projectName: true,
repoUrl: true,
language: true,
createdAt: true,
},
});
return tutorials;
}),
getUserTutorials: protectedProcedure.query(async ({ ctx }) => {
const userId = ctx.user.id;
const tutorials = await prisma.tutorial.findMany({
where: { userId },
orderBy: { createdAt: "desc" },
select: {
id: true,
projectName: true,
repoUrl: true,
language: true,
createdAt: true,
},
});
return tutorials;
}),
🤖 Prompt for AI Agents
In apps/api/src/routers/tutorial.ts around lines 90 to 107, the getUserTutorials
handler uses publicProcedure and falls back to "anonymous" with a @ts-ignore,
which allows unauthenticated access and risks leaking tutorials; change it to
use protectedProcedure (import it from your trpc setup), remove the @ts-ignore,
read userId from ctx.user.id (no fallback), and rely on the protectedProcedure
guard so you can safely query prisma.tutorial.findMany({ where: { userId }, ...
}) and return the results.


/**
* Generate a tutorial from a GitHub repository and save to DB
*/
generate: publicProcedure
.input(generateTutorialInputSchema)
.mutation(async ({ input, ctx }) => {
// @ts-ignore
const userId = ctx.user?.id || "anonymous";

console.log(`Generating tutorial for: ${input.repoUrl}`);

const options: GenerateTutorialOptions = {
repoUrl: input.repoUrl,
language: input.language,
maxAbstractions: input.maxAbstractions,
maxFiles: input.maxFiles,
selectedFiles: input.selectedFiles,
};

const result = await tutorialService.generateTutorial(options);

// Save to database
const tutorial = await prisma.tutorial.create({
data: {
userId,
repoUrl: input.repoUrl,
projectName: result.projectName,
language: input.language || "english",
indexContent: result.indexContent,
mermaidDiagram: result.mermaidDiagram,
chapters: result.chapters,
},
});

return {
success: true,
id: tutorial.id,
projectName: result.projectName,
indexContent: result.indexContent,
chapters: result.chapters,
mermaidDiagram: result.mermaidDiagram,
createdAt: tutorial.createdAt,
};
}),
Comment on lines +112 to +152
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

CRITICAL: Tutorial generation must require authentication.

The generate endpoint performs expensive AI-powered analysis and LLM calls but is exposed as a public procedure with only an "anonymous" user fallback. This creates several critical issues:

  1. Resource abuse: Unauthenticated users can trigger unlimited expensive operations
  2. Cost exposure: AI/LLM API calls cost money and have quotas that could be exhausted
  3. No accountability: Anonymous usage prevents tracking abuse or implementing user-specific rate limits
  4. Type safety: The @ts-ignore directive hides the authentication issue
🔎 Required fix

Change to a protected procedure:

-  generate: publicProcedure
+  generate: protectedProcedure
     .input(generateTutorialInputSchema)
     .mutation(async ({ input, ctx }) => {
-      // @ts-ignore
-      const userId = ctx.user?.id || "anonymous";
+      const userId = ctx.user.id;

Additionally, consider implementing:

  1. User-specific rate limiting (e.g., 5 generations per hour per user)
  2. Usage quotas based on subscription tier
  3. Cost tracking per user
  4. Asynchronous job queue for generation to prevent timeout issues
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
generate: publicProcedure
.input(generateTutorialInputSchema)
.mutation(async ({ input, ctx }) => {
// @ts-ignore
const userId = ctx.user?.id || "anonymous";
console.log(`Generating tutorial for: ${input.repoUrl}`);
const options: GenerateTutorialOptions = {
repoUrl: input.repoUrl,
language: input.language,
maxAbstractions: input.maxAbstractions,
maxFiles: input.maxFiles,
selectedFiles: input.selectedFiles,
};
const result = await tutorialService.generateTutorial(options);
// Save to database
const tutorial = await prisma.tutorial.create({
data: {
userId,
repoUrl: input.repoUrl,
projectName: result.projectName,
language: input.language || "english",
indexContent: result.indexContent,
mermaidDiagram: result.mermaidDiagram,
chapters: result.chapters,
},
});
return {
success: true,
id: tutorial.id,
projectName: result.projectName,
indexContent: result.indexContent,
chapters: result.chapters,
mermaidDiagram: result.mermaidDiagram,
createdAt: tutorial.createdAt,
};
}),
generate: protectedProcedure
.input(generateTutorialInputSchema)
.mutation(async ({ input, ctx }) => {
const userId = ctx.user.id;
console.log(`Generating tutorial for: ${input.repoUrl}`);
const options: GenerateTutorialOptions = {
repoUrl: input.repoUrl,
language: input.language,
maxAbstractions: input.maxAbstractions,
maxFiles: input.maxFiles,
selectedFiles: input.selectedFiles,
};
const result = await tutorialService.generateTutorial(options);
// Save to database
const tutorial = await prisma.tutorial.create({
data: {
userId,
repoUrl: input.repoUrl,
projectName: result.projectName,
language: input.language || "english",
indexContent: result.indexContent,
mermaidDiagram: result.mermaidDiagram,
chapters: result.chapters,
},
});
return {
success: true,
id: tutorial.id,
projectName: result.projectName,
indexContent: result.indexContent,
chapters: result.chapters,
mermaidDiagram: result.mermaidDiagram,
createdAt: tutorial.createdAt,
};
}),
🤖 Prompt for AI Agents
In apps/api/src/routers/tutorial.ts around lines 112 to 152, the generate
endpoint is defined as a publicProcedure with a ts-ignore and anonymous fallback
which allows unauthenticated expensive AI calls; change the endpoint to a
protected procedure (e.g., protectedProcedure) so ctx.user is guaranteed, remove
the @ts-ignore and read userId from ctx.user.id (throw/return an unauthorized
error if absent), and wire any existing auth middleware; additionally hook in
(or add TODOs) for per-user rate limiting/quotas and consider enqueuing
generation jobs asynchronously to avoid timeouts and track cost per user.


/**
* Delete a tutorial
*/
delete: publicProcedure
.input(z.object({ id: z.string() }))
.mutation(async ({ input, ctx }) => {
// @ts-ignore
const userId = ctx.user?.id || "anonymous";

await prisma.tutorial.deleteMany({
where: {
id: input.id,
userId: userId,
},
});

return { success: true };
}),
Comment on lines +157 to +171
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Delete operation should require authentication.

The delete endpoint uses deleteMany with a userId filter but falls back to "anonymous". This creates a security vulnerability where:

  1. One anonymous user could delete tutorials created by other anonymous users
  2. No audit trail for deletion actions
  3. The @ts-ignore directive masks the authentication requirement
🔎 Required fix
-  delete: publicProcedure
+  delete: protectedProcedure
     .input(z.object({ id: z.string() }))
     .mutation(async ({ input, ctx }) => {
-      // @ts-ignore
-      const userId = ctx.user?.id || "anonymous";
+      const userId = ctx.user.id;

       await prisma.tutorial.deleteMany({

Also consider:

  • Using delete instead of deleteMany with a compound unique constraint
  • Adding soft delete functionality for data recovery
  • Logging deletion events for audit purposes
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
delete: publicProcedure
.input(z.object({ id: z.string() }))
.mutation(async ({ input, ctx }) => {
// @ts-ignore
const userId = ctx.user?.id || "anonymous";
await prisma.tutorial.deleteMany({
where: {
id: input.id,
userId: userId,
},
});
return { success: true };
}),
delete: protectedProcedure
.input(z.object({ id: z.string() }))
.mutation(async ({ input, ctx }) => {
const userId = ctx.user.id;
await prisma.tutorial.deleteMany({
where: {
id: input.id,
userId: userId,
},
});
return { success: true };
}),
🤖 Prompt for AI Agents
In apps/api/src/routers/tutorial.ts around lines 157 to 171, the delete handler
currently falls back to "anonymous" and uses @ts-ignore which allows
unauthenticated deletions; replace that by enforcing authentication: remove the
@ts-ignore, read ctx.user and if missing throw an unauthorized error (HTTP 401)
so unauthenticated requests cannot proceed, then use the authenticated user id
for the delete operation (prefer using prisma.tutorial.delete with a compound
unique constraint of id+userId to ensure only the owner can delete; if you
cannot add the DB constraint immediately, keep deleteMany but ensure userId is
always present and not "anonymous"), and additionally consider implementing
soft-delete (e.g., set deletedAt flag) and emit a deletion audit log with user
id, tutorial id and timestamp.


/**
* Health check for tutorial service
*/
healthCheck: publicProcedure.query(() => {
return {
status: "ok",
service: "tutorial",
timestamp: new Date().toISOString(),
};
}),
});
Loading