diff --git a/CLAUDE.md b/CLAUDE.md index 31cd0f5..164bffd 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -147,14 +147,266 @@ UPSTASH_SEARCH_TOKEN= - **Error Handling**: Standardized error handling across search operations - **Performance**: Optimized search indexing with automatic content updates -## Development Workflow Guidance -- Do not build the app after every change you make, only rebuild the app when I ask you to or after making large changes spanning many files - -## Code Style Guidelines -- **No obvious comments**: Never add comments for obvious code. Comments should only provide context or insights that aren't clear from reading the code itself -- **Self-documenting code**: Write code that is clear and self-explanatory. Variable and function names should describe what they do -- **Avoid unnecessary intermediate variables**: Don't create variables just to "improve readability" when the expression is already clear -- Examples of bad comments to avoid: - - `// Use current time as the start` before `buildQuery(now, end)` - - `// Increment counter` before `i++` - - `// Check if user exists` before `if (user)` \ No newline at end of file +## Development Workflow Guidance - CRITICAL BUILD AND TEST RESTRICTIONS + +### ⚠️ WHEN NOT TO BUILD OR TEST (99% of the time) + +**DO NOT run `bun run build`, `bun run test`, or any testing commands unless EXPLICITLY requested by the user.** + +This is absolutely critical for the following reasons: + +#### Performance and Resource Management +- **Builds are expensive**: Each build takes 20-40 seconds and consumes significant CPU/memory +- **Unnecessary resource waste**: Most code changes don't require validation through full builds +- **Development server already validates**: The `bun dev` server catches most issues in real-time +- **TypeScript catches errors**: The IDE and development server provide immediate type checking + +#### User Experience and Workflow +- **Interrupts development flow**: Unexpected builds break the user's mental model and workflow +- **Creates false urgency**: Running builds suggests something is broken when it isn't +- **User controls their workflow**: Developers know when they want to test their changes +- **Build timing is contextual**: Users build when ready to deploy, test, or validate large changes + +#### Technical Reasons +- **TypeScript already validates**: Most errors are caught by the TypeScript compiler in the editor +- **Hot reload exists**: Development server provides instant feedback on changes +- **Builds don't catch runtime issues**: Many bugs only appear during actual usage, not builds +- **False confidence**: A successful build doesn't guarantee the code works as intended + +#### Professional Development Practices +- **Respect user autonomy**: Developers decide when to run builds and tests +- **Avoid assumption-driven actions**: Don't assume code needs validation through builds +- **Focus on code quality**: Write good code rather than relying on builds to catch issues +- **Trust the development environment**: Modern tooling catches most issues without full builds + +### ✅ WHEN TO BUILD (Rare exceptions) + +Only run builds in these specific situations: + +1. **User explicitly requests it**: "Can you build this?" or "Run the build command" +2. **Major refactoring across many files**: When changing 10+ files that interact with each other +3. **Type system changes**: When modifying TypeScript configurations or complex type definitions +4. **Production deployment preparation**: When user specifically mentions deployment or production +5. **Debugging build-specific issues**: When the user reports build failures or deployment problems + +### Alternative Approaches Instead of Building + +#### For Code Validation +- **Read the files**: Examine the code structure and logic directly +- **Check TypeScript types**: Review type definitions and interfaces +- **Review imports/exports**: Verify module dependencies and structure +- **Analyze function signatures**: Ensure API contracts are correct + +#### For Issue Detection +- **Use static analysis**: Examine code patterns and potential issues +- **Check for common mistakes**: Look for typical bugs like unused variables, incorrect types +- **Verify logic flow**: Trace through code execution paths mentally +- **Review error handling**: Check if proper error cases are covered + +#### For Confidence Building +- **Code review approach**: Examine code like you're reviewing a pull request +- **Focus on clarity**: Ensure code is readable and maintainable +- **Check best practices**: Verify adherence to project conventions and patterns +- **Document assumptions**: Note any assumptions or potential edge cases + +### Impact of Unnecessary Builds + +#### On Development Velocity +- **Context switching**: Builds interrupt the natural development flow +- **Waiting time**: 30+ seconds per build adds up quickly over multiple changes +- **Mental overhead**: Constantly waiting for builds creates cognitive load +- **Reduced experimentation**: Developers avoid trying things if builds are frequent + +#### On User Trust +- **Perceived inefficiency**: Unnecessary builds make the assistant seem inefficient +- **Loss of control**: Users feel like the assistant is doing things they didn't ask for +- **Workflow disruption**: Breaks the user's established development rhythm +- **Professional credibility**: Shows lack of understanding of development workflows + +Remember: The goal is to write good code and make requested changes efficiently. Builds should be a deliberate choice, not a reflexive action after every change. + +## Code Style Guidelines - CRITICAL COMMENT RESTRICTIONS + +### ⚠️ NEVER ADD USELESS COMMENTS (99% of comments are useless) + +**DO NOT add comments that merely describe what the code is doing. Comments should ONLY provide context or insights that aren't obvious from reading the code.** + +This is absolutely critical for the following reasons: + +#### Why Most Comments Are Harmful +- **Code duplication**: Comments that describe what code does create maintenance burden when code changes +- **Noise pollution**: Obvious comments make it harder to find actually useful information +- **False documentation**: Comments become outdated when code changes, creating confusion +- **Cognitive overhead**: Reading redundant information slows down code comprehension +- **Professional credibility**: Obvious comments make the author appear inexperienced + +#### What Makes a Comment Useless +- **Restating the obvious**: If the code clearly shows what it does, don't comment it +- **Function name repetition**: If calling `canViewProject()`, don't comment "Check if user can view project" +- **Operation description**: Don't describe basic operations like loops, conditionals, or assignments +- **API call description**: Don't comment what an API call does if the function name is clear +- **Variable explanation**: Don't explain what a variable contains if the name is descriptive + +#### Examples of Absolutely Useless Comments to NEVER Write + +```typescript +// BAD: These comments add zero value +// Check if user has permission to view this project +const hasAccess = await canViewProject(ctx, projectId); + +// Check if user has edit permission for this project +const canEdit = await canEditProject(ctx, input.id); + +// Get the user's role for this project +const userRole = await checkProjectPermission(ctx.db, input.id, ctx.userId); + +// Create a new project +const newProject = await ctx.db.insert(project).values({...}); + +// Increment the counter +counter++; + +// Set loading to true +setLoading(true); + +// Check if data exists +if (data) { + // Return the data + return data; +} + +// Loop through all items +for (const item of items) { + // Process each item + processItem(item); +} +``` + +#### What Comments Should Actually Do +- **Explain WHY, not WHAT**: Business logic reasoning, architectural decisions, edge case handling +- **Provide context**: External constraints, API limitations, performance considerations +- **Document complexity**: Algorithms, mathematical formulas, regex patterns +- **Warn about gotchas**: Subtle bugs, timing issues, browser quirks +- **Reference external sources**: Ticket numbers, documentation links, standards + +#### Examples of Actually Useful Comments + +```typescript +// GOOD: These comments add real value +// Using exponential backoff to prevent API rate limiting (max 3 retries) +const result = await retryWithBackoff(apiCall, 3); + +// Edge case: Safari requires explicit width for flex containers in modals +const modalStyle = { width: '100%', ...baseStyle }; + +// Performance: Debounced to prevent excessive API calls during typing +const debouncedSearch = useMemo(() => debounce(searchFn, 300), []); + +// TODO: Remove after 2024-02-01 when legacy API is deprecated +const fallbackData = await legacyApiCall(); + +// Regex explanation: Matches email with optional subdomain +const emailRegex = /^[^\s@]+@([^\s@]+\.)?[^\s@]+\.[^\s@]+$/; +``` + +### Impact of Useless Comments + +#### On Code Quality +- **Maintenance debt**: Every comment needs to be updated when code changes +- **Review overhead**: Code reviewers waste time reading obvious information +- **Merge conflicts**: Comments create unnecessary conflicts during refactoring +- **Code bloat**: Files become longer and harder to navigate + +#### On Team Productivity +- **Slower reading**: Developers scan past obvious comments, reducing comprehension speed +- **Trust erosion**: Teams lose faith in comment quality when most are useless +- **Bad habits**: Junior developers learn to write obvious comments instead of clear code +- **Review fatigue**: Code reviews become tedious when filled with comment noise + +#### On Professional Standards +- **Industry perception**: Obvious comments are considered anti-patterns in professional development +- **Code smell indicator**: Heavy commenting often indicates unclear or overly complex code +- **Maintenance signal**: Well-commented code is often harder to maintain than self-documenting code +- **Experience indicator**: Senior developers write fewer, higher-quality comments + +### Alternative Approaches Instead of Comments + +#### For Code Clarity +- **Better naming**: Use descriptive function and variable names that explain intent +- **Extract functions**: Break complex operations into well-named smaller functions +- **Use constants**: Replace magic numbers/strings with named constants +- **Type annotations**: Use TypeScript types to document expected data structures + +#### For Documentation +- **README files**: Document high-level architecture and setup instructions +- **API documentation**: Use tools like JSDoc for public API documentation +- **Code examples**: Provide usage examples in documentation, not inline comments +- **Architecture diagrams**: Visual documentation for complex system interactions + +#### For Context Preservation +- **Commit messages**: Explain WHY changes were made in version control +- **Pull request descriptions**: Document reasoning and trade-offs in PR descriptions +- **Design documents**: Document architectural decisions in separate documents +- **Issue tracking**: Reference tickets for business context and requirements + +### Comment Quality Standards + +#### Before Adding Any Comment, Ask: +1. **Would a developer understand this code without the comment?** If yes, don't add it +2. **Does the comment explain WHY rather than WHAT?** If no, rewrite or remove it +3. **Will this comment become outdated when code changes?** If yes, reconsider +4. **Does this comment provide information not available in the code?** If no, remove it +5. **Would better naming eliminate the need for this comment?** If yes, rename instead + +#### Comment Maintenance Rules +- **Update comments when code changes** or remove them entirely +- **Remove outdated comments** immediately when found +- **Prefer self-documenting code** over commented code in all cases +- **Question existing comments** during code reviews - many can be removed + +Remember: The best comment is the one you don't need to write because the code is clear. Every comment is a failure to make the code self-explanatory. Focus on writing code so clear that comments become unnecessary. + +# Communication Guidelines + +## Avoid Sycophantic Language +- **NEVER** use phrases like "You're absolutely right!", "You're absolutely correct!", "Excellent point!", or similar flattery +- **NEVER** validate statements as "right" when the user didn't make a factual claim that could be evaluated +- **NEVER** use general praise or validation as conversational filler + +## Appropriate Acknowledgments +Use brief, factual acknowledgments only to confirm understanding of instructions: +- "Got it." +- "Ok, that makes sense." +- "I understand." +- "I see the issue." + +These should only be used when: +1. You genuinely understand the instruction and its reasoning +2. The acknowledgment adds clarity about what you'll do next +3. You're confirming understanding of a technical requirement or constraint + +## Examples + +### ❌ Inappropriate (Sycophantic) +User: "Yes please." +Assistant: "You're absolutely right! That's a great decision." + +User: "Let's remove this unused code." +Assistant: "Excellent point! You're absolutely correct that we should clean this up." + +### ✅ Appropriate (Brief Acknowledgment) +User: "Yes please." +Assistant: "Got it." [proceeds with the requested action] + +User: "Let's remove this unused code." +Assistant: "I'll remove the unused code path." [proceeds with removal] + +### ✅ Also Appropriate (No Acknowledgment) +User: "Yes please." +Assistant: [proceeds directly with the requested action] + +## Rationale +- Maintains professional, technical communication +- Avoids artificial validation of non-factual statements +- Focuses on understanding and execution rather than praise +- Prevents misrepresenting user statements as claims that could be "right" or "wrong" diff --git a/components/form/task.tsx b/components/form/task.tsx index 2152de3..6096bb8 100644 --- a/components/form/task.tsx +++ b/components/form/task.tsx @@ -2,8 +2,9 @@ import * as Dialog from "@radix-ui/react-dialog"; import { X } from "lucide-react"; -import { useCallback, useRef, useState } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import { Input } from "@/components/ui/input"; +import { Kbd } from "@/components/ui/kbd"; import { cn } from "@/lib/utils"; import { Button } from "../ui/button"; @@ -16,6 +17,30 @@ export default function InlineTaskForm({ const [value, setValue] = useState(""); const inputRef = useRef(null); + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if ((e.key === "n" || e.key === "N") && !e.metaKey && !e.ctrlKey && !e.altKey && !e.repeat) { + const target = e.target as HTMLElement; + + // Check if we're in an input field, textarea, or any contentEditable element + if ( + target.tagName === "INPUT" || + target.tagName === "TEXTAREA" || + target.isContentEditable || + target.closest('[contenteditable="true"]') + ) { + return; + } + + e.preventDefault(); + setIsCreating(true); + } + }; + + document.addEventListener("keydown", handleKeyDown); + return () => document.removeEventListener("keydown", handleKeyDown); + }, []); + const handleSubmit = useCallback(async () => { await action(value); setValue(""); @@ -32,8 +57,10 @@ export default function InlineTaskForm({ e.preventDefault(); setIsCreating(true); }} + className="flex items-center gap-2" > Add task + N diff --git a/components/ui/kbd.tsx b/components/ui/kbd.tsx new file mode 100644 index 0000000..cd665eb --- /dev/null +++ b/components/ui/kbd.tsx @@ -0,0 +1,19 @@ +import { cn } from "@/lib/utils"; +import * as React from "react"; + +const Kbd = React.forwardRef< + HTMLElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)); +Kbd.displayName = "Kbd"; + +export { Kbd }; \ No newline at end of file diff --git a/lib/permissions/index.ts b/lib/permissions/index.ts index 4951fd5..68cba87 100644 --- a/lib/permissions/index.ts +++ b/lib/permissions/index.ts @@ -2,6 +2,12 @@ import { and, eq } from "drizzle-orm"; import { project, projectPermission } from "@/drizzle/schema"; import type { Database } from "@/drizzle/types"; +export type PermissionContext = { + db: Database; + userId: string; + isOrgAdmin: boolean; +}; + export type ProjectRole = "editor" | "reader"; export interface ProjectPermission { @@ -40,37 +46,41 @@ export async function getUserProjectIds( } export async function canEditProject( - db: Database, + ctx: PermissionContext, projectId: number, - userId: string, ): Promise { + // Organization admins have edit access to all projects + if (ctx.isOrgAdmin) return true; + // Check explicit permission first - const role = await checkProjectPermission(db, projectId, userId); + const role = await checkProjectPermission(ctx.db, projectId, ctx.userId); if (role === "editor") return true; // For backward compatibility, check if user is the creator - const proj = await db.query.project.findFirst({ + const proj = await ctx.db.query.project.findFirst({ where: eq(project.id, projectId), columns: { createdByUser: true }, }); - return proj?.createdByUser === userId; + return proj?.createdByUser === ctx.userId; } export async function canViewProject( - db: Database, + ctx: PermissionContext, projectId: number, - userId: string, ): Promise { + // Organization admins have view access to all projects + if (ctx.isOrgAdmin) return true; + // Check explicit permission first - const role = await checkProjectPermission(db, projectId, userId); + const role = await checkProjectPermission(ctx.db, projectId, ctx.userId); if (role !== null) return true; // For backward compatibility, check if user is the creator - const proj = await db.query.project.findFirst({ + const proj = await ctx.db.query.project.findFirst({ where: eq(project.id, projectId), columns: { createdByUser: true }, }); - return proj?.createdByUser === userId; + return proj?.createdByUser === ctx.userId; } diff --git a/trpc/routers/events.ts b/trpc/routers/events.ts index 0b3dbe6..324fadc 100644 --- a/trpc/routers/events.ts +++ b/trpc/routers/events.ts @@ -1,3 +1,4 @@ +import { TRPCError } from "@trpc/server"; import { calendarEvent } from "@/drizzle/schema"; import { logActivity } from "@/lib/activity"; import { canEditProject, canViewProject } from "@/lib/permissions"; @@ -69,10 +70,12 @@ export const eventsRouter = createTRPCRouter({ .query(async ({ ctx, input }) => { const { date, projectId } = input; - // Check if user has permission to view this project - const hasAccess = await canViewProject(ctx.db, projectId, ctx.userId); + const hasAccess = await canViewProject(ctx, projectId); if (!hasAccess) { - throw new Error("Project access denied"); + throw new TRPCError({ + code: "FORBIDDEN", + message: "Project access denied", + }); } const { startOfDay, endOfDay } = getStartEndDateRangeInUtc( @@ -95,17 +98,16 @@ export const eventsRouter = createTRPCRouter({ .query(async ({ ctx, input }) => { const { projectId } = input; - // Check if user has permission to view this project - const hasAccess = await canViewProject(ctx.db, projectId, ctx.userId); + const hasAccess = await canViewProject(ctx, projectId); if (!hasAccess) { - throw new Error("Project access denied"); + throw new TRPCError({ + code: "FORBIDDEN", + message: "Project access denied", + }); } const now = new Date(); - const { end } = getStartEndWeekRangeInUtc( - ctx.timezone, - now, - ); + const { end } = getStartEndWeekRangeInUtc(ctx.timezone, now); const events = await ctx.db.query.calendarEvent .findMany(buildEventsQuery(projectId, now, end)) @@ -152,14 +154,12 @@ export const eventsRouter = createTRPCRouter({ throw new Error("Event not found"); } - // Check if user has edit permission for this project - const canEdit = await canEditProject( - ctx.db, - existingEvent.projectId, - ctx.userId, - ); + const canEdit = await canEditProject(ctx, existingEvent.projectId); if (!canEdit) { - throw new Error("Project edit access denied"); + throw new TRPCError({ + code: "FORBIDDEN", + message: "Project edit access denied", + }); } const event = await ctx.db @@ -213,10 +213,37 @@ export const eventsRouter = createTRPCRouter({ repeatUntil, } = input; - // Check if user has edit permission for this project - const canEdit = await canEditProject(ctx.db, projectId, ctx.userId); - if (!canEdit) { - throw new Error("Project edit access denied"); + // - Create: require edit on target project + // - Update: require edit on the existing event's project; if moving, also require edit on the target project + if (id) { + const existing = await ctx.db.query.calendarEvent.findFirst({ + where: eq(calendarEvent.id, id), + }); + if (!existing) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Event not found", + }); + } + const canEditSource = await canEditProject(ctx, existing.projectId); + const canEditTarget = + existing.projectId !== projectId + ? await canEditProject(ctx, projectId) + : true; + if (!canEditSource || !canEditTarget) { + throw new TRPCError({ + code: "FORBIDDEN", + message: "Project edit access denied", + }); + } + } else { + const canEditTarget = await canEditProject(ctx, projectId); + if (!canEditTarget) { + throw new TRPCError({ + code: "FORBIDDEN", + message: "Project edit access denied", + }); + } } if (allDay) { diff --git a/trpc/routers/projects.ts b/trpc/routers/projects.ts index 2e1b945..6e796dc 100644 --- a/trpc/routers/projects.ts +++ b/trpc/routers/projects.ts @@ -1,4 +1,5 @@ import { and, desc, eq } from "drizzle-orm"; +import { TRPCError } from "@trpc/server"; import { z } from "zod"; import { activity, @@ -32,7 +33,10 @@ export const projectsRouter = createTRPCRouter({ .mutation(async ({ ctx, input }) => { // Check if user is in an organization and if they're an admin if (ctx.orgId && !ctx.isOrgAdmin) { - throw new Error("Only organization admins can create projects"); + throw new TRPCError({ + code: "FORBIDDEN", + message: "Only organization admins can create projects", + }); } const newProject = await ctx.db @@ -108,10 +112,12 @@ export const projectsRouter = createTRPCRouter({ ), ) .mutation(async ({ ctx, input }) => { - // Check if user has edit permission for this project - const canEdit = await canEditProject(ctx.db, +input.id, ctx.userId); + const canEdit = await canEditProject(ctx, +input.id); if (!canEdit) { - throw new Error("Project edit access denied"); + throw new TRPCError({ + code: "FORBIDDEN", + message: "Project edit access denied", + }); } const currentProject = await ctx.db.query.project @@ -166,10 +172,12 @@ export const projectsRouter = createTRPCRouter({ }), ) .mutation(async ({ ctx, input }) => { - // Check if user has edit permission for this project (needed to delete) - const canEdit = await canEditProject(ctx.db, input.id, ctx.userId); + const canEdit = await canEditProject(ctx, input.id); if (!canEdit) { - throw new Error("Project delete access denied"); + throw new TRPCError({ + code: "FORBIDDEN", + message: "Project delete access denied", + }); } const deletedProject = await ctx.db @@ -199,10 +207,12 @@ export const projectsRouter = createTRPCRouter({ }), ) .query(async ({ ctx, input }) => { - // Check if user has permission to view this project - const hasAccess = await canViewProject(ctx.db, input.id, ctx.userId); + const hasAccess = await canViewProject(ctx, input.id); if (!hasAccess) { - throw new Error("Project access denied"); + throw new TRPCError({ + code: "FORBIDDEN", + message: "Project access denied", + }); } const data = await ctx.db.query.project @@ -221,7 +231,12 @@ export const projectsRouter = createTRPCRouter({ input.id, ctx.userId, ); - const canEdit = await canEditProject(ctx.db, input.id, ctx.userId); + + // Compute canEdit in-memory to avoid redundant DB query + const canEdit = + ctx.isOrgAdmin || + userRole === "editor" || + data.createdByUser === ctx.userId; return { ...data, @@ -256,7 +271,7 @@ export const projectsRouter = createTRPCRouter({ }), ) .mutation(async ({ ctx, input }) => { - const canEdit = await canEditProject(ctx.db, input.projectId, ctx.userId); + const canEdit = await canEditProject(ctx, input.projectId); if (!canEdit) { throw new Error( "You don't have permission to add comments to this project", diff --git a/trpc/routers/tasks.ts b/trpc/routers/tasks.ts index 7e0e536..817029c 100644 --- a/trpc/routers/tasks.ts +++ b/trpc/routers/tasks.ts @@ -1,4 +1,5 @@ import { and, asc, desc, eq, or } from "drizzle-orm"; +import { TRPCError } from "@trpc/server"; import { z } from "zod"; import { task, taskList } from "@/drizzle/schema"; import { TaskListStatus, TaskStatus } from "@/drizzle/types"; @@ -27,14 +28,12 @@ export const tasksRouter = createTRPCRouter({ }), ) .query(async ({ ctx, input }) => { - // Check if user has permission to view this project - const hasAccess = await canViewProject( - ctx.db, - input.projectId, - ctx.userId, - ); + const hasAccess = await canViewProject(ctx, input.projectId); if (!hasAccess) { - throw new Error("Project access denied"); + throw new TRPCError({ + code: "FORBIDDEN", + message: "Project access denied", + }); } const data = await ctx.db.query.taskList @@ -72,10 +71,12 @@ export const tasksRouter = createTRPCRouter({ }), ) .mutation(async ({ ctx, input }) => { - // Check if user has edit permission for this project - const canEdit = await canEditProject(ctx.db, input.projectId, ctx.userId); + const canEdit = await canEditProject(ctx, input.projectId); if (!canEdit) { - throw new Error("Project edit access denied"); + throw new TRPCError({ + code: "FORBIDDEN", + message: "Project edit access denied", + }); } const createdTaskList = await ctx.db @@ -170,12 +171,7 @@ export const tasksRouter = createTRPCRouter({ throw new Error("Task list not found"); } - // Check if user has edit permission for this project - const canEdit = await canEditProject( - ctx.db, - oldTaskList.projectId, - ctx.userId, - ); + const canEdit = await canEditProject(ctx, oldTaskList.projectId); if (!canEdit) { throw new Error( "You don't have permission to update task lists in this project", @@ -228,12 +224,7 @@ export const tasksRouter = createTRPCRouter({ throw new Error("Task list not found"); } - // Check if user has edit permission for this project - const canEdit = await canEditProject( - ctx.db, - taskListToDelete.projectId, - ctx.userId, - ); + const canEdit = await canEditProject(ctx, taskListToDelete.projectId); if (!canEdit) { throw new Error( "You don't have permission to delete task lists in this project", @@ -322,12 +313,7 @@ export const tasksRouter = createTRPCRouter({ throw new Error("Task list not found"); } - // Check if user has edit permission for this project - const canEdit = await canEditProject( - ctx.db, - taskListDetails.projectId, - ctx.userId, - ); + const canEdit = await canEditProject(ctx, taskListDetails.projectId); if (!canEdit) { throw new Error( "You don't have permission to add tasks to this project", @@ -448,12 +434,7 @@ export const tasksRouter = createTRPCRouter({ throw new Error("Task not found"); } - // Check if user has edit permission for this project - const canEdit = await canEditProject( - ctx.db, - oldTask.taskList.projectId, - ctx.userId, - ); + const canEdit = await canEditProject(ctx, oldTask.taskList.projectId); if (!canEdit) { throw new Error( "You don't have permission to update tasks in this project", @@ -524,12 +505,7 @@ export const tasksRouter = createTRPCRouter({ throw new Error("Task not found"); } - // Check if user has edit permission for this project - const canEdit = await canEditProject( - ctx.db, - currentTask.taskList.projectId, - ctx.userId, - ); + const canEdit = await canEditProject(ctx, currentTask.taskList.projectId); if (!canEdit) { throw new Error( "You don't have permission to delete tasks in this project", @@ -566,12 +542,7 @@ export const tasksRouter = createTRPCRouter({ throw new Error("Task list not found"); } - // Check if user has edit permission for this project - const canEdit = await canEditProject( - ctx.db, - taskListDetails.projectId, - ctx.userId, - ); + const canEdit = await canEditProject(ctx, taskListDetails.projectId); if (!canEdit) { throw new Error( "You don't have permission to modify tasks in this project",