Skip to content

feat(task): track task author and surface as sticker#1166

Open
RainyPixel wants to merge 3 commits into
usekaneo:mainfrom
ONREZA:feat/task-author-sticker
Open

feat(task): track task author and surface as sticker#1166
RainyPixel wants to merge 3 commits into
usekaneo:mainfrom
ONREZA:feat/task-author-sticker

Conversation

@RainyPixel
Copy link
Copy Markdown
Contributor

@RainyPixel RainyPixel commented Apr 6, 2026

Summary

In shared workspaces it is currently impossible to tell who created a task — the only user shown on a card is the assignee. This PR adds an explicit created_by field to the task table and surfaces it in the UI.

Backend

  • New created_by column on task (FK → user, ON DELETE SET NULL) with index, plus a Drizzle migration (0028_fantastic_thor_girl.sql).
  • taskTableRelations gets a new creator relation; userTableRelations gets createdTasks. Both assignee and creator use explicit relationNames so Drizzle can disambiguate the two foreign keys to user.
  • createTask accepts an optional createdBy and the POST /:projectId route fills it from c.get("userId"). Existing tasks remain null (the column is nullable, no backfill).
  • getTask and getTasks add a self-join on user aliased as creator_user and expose createdBy, creatorName, creatorImage in the response.
  • taskSchema (Valibot, used in OpenAPI) gets createdBy: nullable(string).
  • update-task, move-task, and bulk-update-tasks are intentionally left untouched — createdBy is immutable like createdAt.

Frontend

  • Task type gets optional createdBy, creatorName, creatorImage fields.
  • Kanban card (task-card.tsx): when the author differs from the assignee, a compact badge with the author's avatar and a localized "Author" label is rendered next to the priority badge. Hovering shows a tooltip with the author's name.
  • Task properties sidebar (task-properties-sidebar.tsx): a read-only author chip is added next to the assignee chip in all three layouts (compact, mobile, desktop).

i18n

  • New keys: tasks:creator.label, tasks:creator.unknown, tasks:creator.tooltip (with {{name}} interpolation).
  • Translations added for all nine supported locales (en-US, ru-RU, uk-UA, de-DE, es-ES, fr-FR, nl-NL, mk-MK, el-GR).
  • i18n/schema.json updated and creator added to the tasks namespace's required list.

Test plan

  • pnpm --filter @kaneo/api db:migrate runs the new migration cleanly on an existing database
  • Creating a new task via the UI sets created_by to the current user (verify in DB or via GET /:id)
  • Existing tasks created before this change still load (with createdBy: null) and the kanban card renders without the author badge
  • Assigning a task to someone else makes the author badge appear on the card and in the sidebar
  • Self-assigned tasks (author = assignee) hide the author badge on the card to avoid duplication
  • When the original creator has been removed from the workspace, the badge falls back to creatorName/creatorImage returned from the API rather than disappearing
  • Switching the UI language renders the badge label and tooltip in each of the nine supported locales

Summary by CodeRabbit

New Features

  • Tasks now display creator information prominently on kanban board cards and in the task properties sidebar
  • Creator details including name and avatar are displayed alongside other task metadata such as assignee and priority
  • Full multi-language support for task creator information across all supported locales and languages

Adds a `created_by` column to the task table that stores the user who
opens a task, separate from the assignee. The field is set automatically
from the request context on task creation, joined into both single-task
and project-board reads, and exposed through the API as `createdBy`,
`creatorName`, and `creatorImage`.

On the frontend, the kanban card now shows a small author sticker next
to the priority badge when the task author differs from the assignee, so
shared workspaces no longer make it ambiguous who created a task. The
task properties sidebar also displays the author next to the assignee in
all three layouts (compact, mobile, desktop).

New i18n keys: `tasks:creator.label`, `tasks:creator.unknown`,
`tasks:creator.tooltip`. Translations added for all nine supported
locales.
@qodo-free-for-open-source-projects
Copy link
Copy Markdown

Review Summary by Qodo

Track task author and surface as sticker in UI

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Add explicit created_by field to task table with foreign key to user and index for performance
• Implement bidirectional creator-task relationships in Drizzle ORM with explicit relation names to
  disambiguate multiple foreign keys to user table
• Extend task queries (getTask, getTasks) with self-join on user table to fetch creator details
  (createdBy, creatorName, creatorImage)
• Populate createdBy automatically from authenticated user when creating new tasks via API
• Extend Task type with optional creator information fields (createdBy, creatorName,
  creatorImage)
• Display task creator as compact badge on kanban cards when author differs from assignee, with
  avatar and localized "Author" label
• Add read-only creator chip in task properties sidebar next to assignee chip across all layout
  variants
• Add comprehensive i18n support with creator translations for all nine supported locales (en-US,
  ru-RU, uk-UA, de-DE, es-ES, fr-FR, nl-NL, mk-MK, el-GR)
• Update i18n schema to include creator namespace with required fields
• Create Drizzle migration 0028_fantastic_thor_girl.sql to add created_by column with proper
  constraints and indexing
Diagram
flowchart LR
  A["User creates task"] -->|"createdBy set from userId"| B["Task stored with created_by FK"]
  B -->|"self-join on user table"| C["Creator details fetched"]
  C -->|"creatorName, creatorImage"| D["Kanban card badge"]
  C -->|"creator chip"| E["Task sidebar"]
  D -->|"i18n translations"| F["Localized UI"]
  E -->|"i18n translations"| F
Loading

Grey Divider

File Changes

1. apps/api/src/task/controllers/get-tasks.ts ✨ Enhancement +7/-0

Add creator user details to task queries via self-join

• Added alias import from drizzle-orm/pg-core for table aliasing
• Created creatorUser alias for self-join on userTable to fetch creator details
• Extended taskSelection to include createdBy, creatorName, and creatorImage fields
• Added left join with aliased creatorUser table to match taskTable.createdBy with creator user
 ID

apps/api/src/task/controllers/get-tasks.ts


2. apps/api/src/task/controllers/get-task.ts ✨ Enhancement +7/-0

Include creator information in single task retrieval

• Added alias import from drizzle-orm/pg-core
• Created creatorUser alias for self-join on userTable
• Extended task selection to include createdBy, creatorName, and creatorImage
• Added left join with aliased creatorUser table to fetch creator information

apps/api/src/task/controllers/get-task.ts


3. apps/api/src/database/schema.ts Database schema +5/-0

Add created_by column and index to task table

• Added createdBy column to taskTable as foreign key to userTable with SET NULL on delete
• Added index task_createdBy_idx on the createdBy column for query performance
• Configured cascade update behavior for the foreign key relationship

apps/api/src/database/schema.ts


View more (20)
4. apps/api/src/database/relations.ts ✨ Enhancement +8/-1

Define bidirectional creator-task relationships

• Updated assignedTasks relation with explicit relationName: "taskAssignee" for disambiguation
• Added new createdTasks relation to userTableRelations with relationName: "taskCreator"
• Added creator relation to taskTableRelations linking to userTable via createdBy field
• Both relations use explicit relationNames to handle multiple foreign keys to the same table

apps/api/src/database/relations.ts


5. apps/api/src/task/controllers/create-task.ts ✨ Enhancement +3/-0

Support creator assignment during task creation

• Added createdBy parameter to createTask function signature
• Updated function to accept optional createdBy string parameter
• Pass createdBy value to database insert operation (defaults to null if not provided)

apps/api/src/task/controllers/create-task.ts


6. apps/web/src/types/task/index.ts ✨ Enhancement +3/-0

Extend Task type with creator information fields

• Added optional createdBy field to Task type (nullable string)
• Added optional creatorName field to Task type (nullable string)
• Added optional creatorImage field to Task type (nullable string)

apps/web/src/types/task/index.ts


7. apps/api/src/task/index.ts ✨ Enhancement +2/-0

Populate creator from authenticated user on task creation

• Extract createdBy from current user ID via c.get("userId")
• Pass createdBy parameter to createTask function when creating new tasks
• Automatically set task creator to the authenticated user making the request

apps/api/src/task/index.ts


8. apps/api/src/schemas.ts ✨ Enhancement +1/-0

Add createdBy field to task validation schema

• Added createdBy: v.nullable(v.string()) to taskSchema validation object
• Allows API responses to include nullable creator ID field

apps/api/src/schemas.ts


9. apps/api/drizzle/meta/0028_snapshot.json Database schema +3314/-0

Database migration snapshot for creator tracking

• Generated Drizzle migration snapshot for database schema version 0028
• Captures complete database schema state including new created_by column and index
• Documents all tables, foreign keys, and constraints after migration

apps/api/drizzle/meta/0028_snapshot.json


10. apps/web/src/components/task/task-properties-sidebar.tsx ✨ Enhancement +36/-0

Display task creator badge in properties sidebar

• Added UserRound icon import from lucide-react
• Implemented creator member lookup from workspace users
• Created creatorBadge component displaying creator avatar, name, and tooltip
• Rendered creator badge in all three layout variants (compact, mobile, desktop) next to assignee
• Badge shows creator avatar with fallback initial or icon, with hover tooltip showing full name

apps/web/src/components/task/task-properties-sidebar.tsx


11. i18n/schema.json ⚙️ Configuration changes +18/-1

Add creator translation schema to i18n configuration

• Added creator object to tasks namespace schema with three required properties
• Defined label, unknown, and tooltip string fields for creator translations
• Added creator to the tasks namespace required array

i18n/schema.json


12. i18n/el-GR.json 📝 Documentation +5/-0

Add Greek translations for task creator feature

• Added Greek translations for creator-related keys under tasks.creatorlabel: "Συγγραφέας" (Author)
• unknown: "Άγνωστος" (Unknown)
• tooltip: "Συγγραφέας: {{name}}" (Author: {{name}})

i18n/el-GR.json


13. apps/web/src/components/kanban-board/task-card.tsx ✨ Enhancement +37/-0

Display task creator as compact badge on kanban card

• Added creator memoized object that resolves creator details from workspace members or fallback
 API data
• Added showCreatorBadge boolean flag to conditionally display author badge when creator differs
 from assignee
• Rendered compact author badge with avatar, localized "Author" label, and tooltip showing creator
 name

apps/web/src/components/kanban-board/task-card.tsx


14. i18n/en-US.json I18n +5/-0

Add English translations for task creator feature

• Added creator namespace with three new translation keys: label, unknown, and tooltip
• Provides English localization for author badge display

i18n/en-US.json


15. i18n/ru-RU.json I18n +5/-0

Add Russian translations for task creator feature

• Added creator namespace with Russian translations for label, unknown, and tooltip

i18n/ru-RU.json


16. i18n/uk-UA.json I18n +5/-0

Add Ukrainian translations for task creator feature

• Added creator namespace with Ukrainian translations for label, unknown, and tooltip

i18n/uk-UA.json


17. i18n/de-DE.json I18n +5/-0

Add German translations for task creator feature

• Added creator namespace with German translations for label, unknown, and tooltip

i18n/de-DE.json


18. i18n/fr-FR.json I18n +5/-0

Add French translations for task creator feature

• Added creator namespace with French translations for label, unknown, and tooltip

i18n/fr-FR.json


19. i18n/es-ES.json I18n +5/-0

Add Spanish translations for task creator feature

• Added creator namespace with Spanish translations for label, unknown, and tooltip

i18n/es-ES.json


20. i18n/nl-NL.json I18n +5/-0

Add Dutch translations for task creator feature

• Added creator namespace with Dutch translations for label, unknown, and tooltip

i18n/nl-NL.json


21. i18n/mk-MK.json I18n +5/-0

Add Macedonian translations for task creator feature

• Added creator namespace with Macedonian translations for label, unknown, and tooltip

i18n/mk-MK.json


22. apps/api/drizzle/0028_fantastic_thor_girl.sql Database +3/-0

Database migration to add task creator tracking

• Added created_by text column to task table with foreign key constraint to user table
• Configured cascade on update and set null on delete for referential integrity
• Created index on created_by column for query performance

apps/api/drizzle/0028_fantastic_thor_girl.sql


23. apps/api/drizzle/meta/_journal.json ⚙️ Configuration changes +7/-0

Update migration journal with new creator column migration

• Registered new migration entry 0028_fantastic_thor_girl with timestamp and version metadata

apps/api/drizzle/meta/_journal.json


Grey Divider

Qodo Logo

@qodo-free-for-open-source-projects
Copy link
Copy Markdown

qodo-free-for-open-source-projects Bot commented Apr 6, 2026

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX Issues (0)

Grey Divider


Action required

1. Task.created actor wrong🐞
Description
createTask now persists createdBy, but the emitted task.created event still sets userId from
createdTask.userId (assignee), so downstream consumers record the assignee as the creator when
they differ. This breaks activity attribution (and any integrations consuming task.created.userId)
for tasks created on behalf of someone else.
Code

apps/api/src/task/controllers/create-task.ts[R65-69]

  .values({
    projectId,
    userId: userId || null,
+      createdBy: createdBy || null,
    title: title || "",
Evidence
The route passes the authenticated user as createdBy, and createTask writes it to the DB, but
the published task.created event still uses createdTask.userId (assignee) as the event userId.
The activity subscriber uses data.userId as the actor for the created activity entry, so the
activity log will show the assignee as the creator when createdBy != assignee_id.

apps/api/src/task/index.ts[190-213]
apps/api/src/task/controllers/create-task.ts[63-93]
apps/api/src/activity/index.ts[192-202]
apps/api/src/notification/index.ts[159-176]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`task.created` events currently set `userId` to the task assignee (`assignee_id`). After introducing `created_by`, this causes activity/integrations to attribute task creation to the wrong user whenever the creator differs from the assignee.
### Issue Context
- `createdBy` is sourced from `c.get("userId")` and saved as `task.created_by`.
- Event subscribers (e.g. activity) treat `data.userId` as the actor.
- Notifications for assignees can still be supported by including an explicit `assigneeId` field in the event payload.
### Fix Focus Areas
- apps/api/src/task/controllers/create-task.ts[63-93]
- apps/api/src/activity/index.ts[192-202]
- apps/api/src/notification/index.ts[159-176]
- apps/api/src/plugins/registry.ts[32-52]
### Suggested approach
1. Change the `task.created` publish payload to use `createdBy` as the event `userId` (actor).
2. Add `assigneeId: createdTask.userId` (and keep `userId` as actor) so notification/integration consumers can target assignees explicitly.
3. Update subscribers:
- Activity: keep using `data.userId` (now creator).
- Notifications: send `task_created` to `assigneeId` (optionally only when `assigneeId` is present and differs from actor).
- Any plugin broadcasts/webhooks that rely on `userId` meaning assignee should switch to `assigneeId`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. createdBy FK uses set null📘
Description
The new task.created_by foreign key is configured with onDelete: "set null" / `ON DELETE set
null` instead of the conventionally required cascade behavior. This diverges from the database
schema conventions and may fail compliance checks expecting cascade deletes/updates on foreign keys.
Code

apps/api/src/database/schema.ts[R299-302]

+    createdBy: text("created_by").references(() => userTable.id, {
+      onDelete: "set null",
+      onUpdate: "cascade",
+    }),
Evidence
PR Compliance ID 9 requires foreign keys to use explicit cascade behavior; the PR introduces a new
FK for created_by with onDelete set to set null (in both the Drizzle schema and SQL
migration).

CLAUDE.md
apps/api/src/database/schema.ts[299-302]
apps/api/drizzle/0028_fantastic_thor_girl.sql[2-2]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new `task.created_by` foreign key uses `ON DELETE set null` / `onDelete: "set null"`, which conflicts with the schema convention requiring cascade behavior on foreign keys.
## Issue Context
This was introduced with the `created_by` column addition for tracking task authors.
## Fix Focus Areas
- apps/api/src/database/schema.ts[299-302]
- apps/api/drizzle/0028_fantastic_thor_girl.sql[1-3]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Advisory comments

3. Migration missing newline🐞
Description
The new SQL migration file is missing a trailing newline at EOF, which can fail strict linters or
formatting checks. Add a final newline to keep repo formatting consistent.
Code

apps/api/drizzle/0028_fantastic_thor_girl.sql[R1-3]

+ALTER TABLE "task" ADD COLUMN "created_by" text;--> statement-breakpoint
+ALTER TABLE "task" ADD CONSTRAINT "task_created_by_user_id_fk" FOREIGN KEY ("created_by") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE cascade;--> statement-breakpoint
+CREATE INDEX "task_createdBy_idx" ON "task" USING btree ("created_by");
Evidence
The PR diff explicitly marks the migration with “No newline at end of file”, indicating the file
ends without a newline.

apps/api/drizzle/0028_fantastic_thor_girl.sql[1-3]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The migration SQL file lacks a trailing newline at EOF.
### Issue Context
Some repositories enforce trailing newlines via formatters/linters, and diffs will continue to show the EOF marker until fixed.
### Fix Focus Areas
- apps/api/drizzle/0028_fantastic_thor_girl.sql[1-3]
### Suggested approach
Add a newline character after the final semicolon.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment thread apps/api/src/task/controllers/create-task.ts
The task.created event was passing the assignee as the actor (`userId`),
which since the introduction of `created_by` makes activity attribution
and integration broadcasts record the wrong user when the creator and
assignee differ.

The event payload now distinguishes the two roles:
- `userId` = the actor (creator), falling back to the assignee for tasks
  created without an authenticated context (e.g. legacy paths)
- `assigneeId` = the notification target

The notification subscriber for `task.created` is updated to use
`assigneeId` so assignees still receive their "task assigned to you"
notification regardless of who created it. The activity and plugin
broadcast subscribers continue to read `userId`, which now correctly
identifies the actor.
@tiran133
Copy link
Copy Markdown
Contributor

tiran133 commented Apr 7, 2026

It shows in the activity of the task

image

But in the current version it's broken. The activity isn't recorded because the current userId is not passed correctly.

I have fix that in my pull request #1161

Not sure if this is enough or not. In my opinion this should be enough.

Copy link
Copy Markdown
Member

@andrejsshell andrejsshell left a comment

Choose a reason for hiding this comment

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

When I create a task it says: Andrej Task 4 seconds ago
Image

Also, maybe in the task details sidebar, to add some indicator that the person is the creator, it's confusing if I have the assignee and creator the same:

Image

- Change createdBy FK onDelete from "set null" to "cascade" (schema convention)
- Update migration SQL: cascade delete + add trailing newline
- Add "Author" label to creator badge in task sidebar for clarity
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 8, 2026

📝 Walkthrough

Walkthrough

This PR introduces task creator tracking, distinguishing between who created a task and who it's assigned to. Database schema adds a createdBy field with foreign key relation. API endpoints capture, persist, and retrieve creator data. Web components display creator information via badges. Internationalization strings support multiple languages.

Changes

Cohort / File(s) Summary
Database Schema & Relations
apps/api/src/database/schema.ts, apps/api/src/database/relations.ts
Added createdBy column to taskTable with cascade delete/update, indexed on the new field. Updated relations to distinguish assignedTasks and createdTasks on user side, and added creator relation alongside existing assignee on task side.
API Task Controllers
apps/api/src/task/controllers/create-task.ts, apps/api/src/task/controllers/get-task.ts, apps/api/src/task/controllers/get-tasks.ts, apps/api/src/task/index.ts
Updated task creation to accept and persist createdBy parameter. Extended task queries with LEFT JOIN to retrieve creator name and image. Updated task creation route handler to extract createdBy from request context.
Notification & Validation
apps/api/src/notification/index.ts, apps/api/src/schemas.ts
Modified "task.created" event payload to include assigneeId and target notification to assigneeId instead of creator. Added createdBy field to task schema validation.
Web UI Components
apps/web/src/components/kanban-board/task-card.tsx, apps/web/src/components/task/task-properties-sidebar.tsx
Added creator badge rendering in task card (shown when creator differs from assignee). Added creator display in task properties sidebar with avatar and fallback icon across compact, mobile, and desktop layouts.
Type Definitions
apps/web/src/types/task/index.ts
Extended Task type with optional creator fields: createdBy, creatorName, creatorImage (all `string
Internationalization
i18n/de-DE.json, i18n/el-GR.json, i18n/en-US.json, i18n/es-ES.json, i18n/fr-FR.json, i18n/mk-MK.json, i18n/nl-NL.json, i18n/ru-RU.json, i18n/uk-UA.json, i18n/schema.json
Added tasks.creator translation block across all language files with label, unknown, and tooltip keys. Updated JSON schema to enforce required creator properties.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • tinsever
  • andrejsshell
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(task): track task author and surface as sticker' directly describes the main objective of the PR: adding task creator tracking and displaying it as a visual badge (sticker).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands and usage tips.

@RainyPixel
Copy link
Copy Markdown
Contributor Author

RainyPixel commented Apr 8, 2026

@andrejsshell thanks for the review! I've addressed all the feedback:

  1. FK onDelete rule — changed onDelete: "set null" to onDelete: "cascade" in both schema.ts and SQL migration (follows project convention)

  2. Missing newline — added trailing newline at end of 0028_fantastic_thor_girl.sql

  3. Sidebar UX — added "Author:" label before the creator name in task properties sidebar so it's clear who the creator is even when creator = assignee

@RainyPixel RainyPixel requested a review from andrejsshell April 8, 2026 22:09
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/api/src/task/index.ts (1)

162-201: ⚠️ Potential issue | 🟠 Major

Add Valibot param validation for projectId on create-task route.

Line 191 reads projectId from c.req.param() without validator("param", ...) middleware on this endpoint, which violates the route input-validation convention used elsewhere in this file.

🔧 Proposed fix
 .post(
   "/:projectId",
   describeRoute({
@@
     }),
+    validator("param", v.object({ projectId: v.string() })),
     validator(
       "json",
       v.object({
@@
     workspaceAccess.fromProject("projectId"),
     async (c) => {
-      const { projectId } = c.req.param();
+      const { projectId } = c.req.valid("param");
       const {
         title,
         description,

As per coding guidelines: "All API inputs must be validated using Valibot schemas with validator middleware".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/task/index.ts` around lines 162 - 201, The POST createTask route
is missing Valibot validation for the URL param projectId; add a
validator("param", v.object({ projectId: v.string() })) middleware to the route
chain (before workspaceAccess.fromProject("projectId") and the async (c)
handler) so c.req.param().projectId is validated; keep the existing body
validator and other middleware order intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/api/src/database/schema.ts`:
- Around line 299-302: The task.creator foreign key currently uses onDelete:
"cascade" on the createdBy column (createdBy: text("created_by").references(()
=> userTable.id, ...)), which will delete tasks when a user is removed; change
the reference option to onDelete: "set null" and ensure the createdBy column is
defined as nullable (e.g., call nullable()/allowNull as required by the schema
API) so deleted users set created_by to NULL instead of deleting the task;
update any related type/interface if needed to accept null for createdBy.

In `@apps/api/src/notification/index.ts`:
- Around line 159-169: The subscriber for subscribeToEvent("task.created", ...)
currently ignores events that lack assigneeId; to preserve backward
compatibility, make the handler tolerant of missing assigneeId by resolving the
assignee before skipping: when data.assigneeId is absent, load the task by
data.taskId (e.g., via your Task model/service used elsewhere) to determine its
assignee and fall back to that; then call createNotification({ userId:
resolvedAssigneeId, type: "task_created", ... }) only if a resolvedAssigneeId
exists. Update the subscribeToEvent handler (and any related variable names) so
it attempts the DB lookup when data.assigneeId is null/undefined instead of
immediately returning.

In `@apps/api/src/task/controllers/create-task.ts`:
- Around line 90-91: The event payload currently sets userId to an empty string
(userId: createdTask.createdBy ?? createdTask.userId ?? ""), which can break
consumers; change the assignment in the create-task controller (look for
createdTask.createdBy / createdTask.userId) to omit or set userId to
null/undefined when no real ID exists (e.g., use createdTask.createdBy ??
createdTask.userId ?? undefined) so no empty string is emitted, and update any
event-builder call that uses this field to accept an optional userId.

---

Outside diff comments:
In `@apps/api/src/task/index.ts`:
- Around line 162-201: The POST createTask route is missing Valibot validation
for the URL param projectId; add a validator("param", v.object({ projectId:
v.string() })) middleware to the route chain (before
workspaceAccess.fromProject("projectId") and the async (c) handler) so
c.req.param().projectId is validated; keep the existing body validator and other
middleware order intact.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 03d4f598-1419-4baf-9e14-bd8da17d3c56

📥 Commits

Reviewing files that changed from the base of the PR and between e15a68b and 444adf6.

⛔ Files ignored due to path filters (3)
  • apps/api/drizzle/0028_fantastic_thor_girl.sql is excluded by !apps/api/drizzle/**
  • apps/api/drizzle/meta/0028_snapshot.json is excluded by !apps/api/drizzle/**
  • apps/api/drizzle/meta/_journal.json is excluded by !apps/api/drizzle/**
📒 Files selected for processing (21)
  • apps/api/src/database/relations.ts
  • apps/api/src/database/schema.ts
  • apps/api/src/notification/index.ts
  • apps/api/src/schemas.ts
  • apps/api/src/task/controllers/create-task.ts
  • apps/api/src/task/controllers/get-task.ts
  • apps/api/src/task/controllers/get-tasks.ts
  • apps/api/src/task/index.ts
  • apps/web/src/components/kanban-board/task-card.tsx
  • apps/web/src/components/task/task-properties-sidebar.tsx
  • apps/web/src/types/task/index.ts
  • i18n/de-DE.json
  • i18n/el-GR.json
  • i18n/en-US.json
  • i18n/es-ES.json
  • i18n/fr-FR.json
  • i18n/mk-MK.json
  • i18n/nl-NL.json
  • i18n/ru-RU.json
  • i18n/schema.json
  • i18n/uk-UA.json

Comment on lines +299 to +302
createdBy: text("created_by").references(() => userTable.id, {
onDelete: "cascade",
onUpdate: "cascade",
}),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Use ON DELETE SET NULL for task.created_by to prevent task data loss.

Line 300 currently cascades deletes from user → task creator, which can remove tasks when a creator account is deleted. For creator attribution, this should null the reference instead of deleting the task.

💡 Suggested fix
-    createdBy: text("created_by").references(() => userTable.id, {
-      onDelete: "cascade",
-      onUpdate: "cascade",
-    }),
+    createdBy: text("created_by").references(() => userTable.id, {
+      onDelete: "set null",
+      onUpdate: "cascade",
+    }),
📝 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
createdBy: text("created_by").references(() => userTable.id, {
onDelete: "cascade",
onUpdate: "cascade",
}),
createdBy: text("created_by").references(() => userTable.id, {
onDelete: "set null",
onUpdate: "cascade",
}),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/database/schema.ts` around lines 299 - 302, The task.creator
foreign key currently uses onDelete: "cascade" on the createdBy column
(createdBy: text("created_by").references(() => userTable.id, ...)), which will
delete tasks when a user is removed; change the reference option to onDelete:
"set null" and ensure the createdBy column is defined as nullable (e.g., call
nullable()/allowNull as required by the schema API) so deleted users set
created_by to NULL instead of deleting the task; update any related
type/interface if needed to accept null for createdBy.

Comment on lines 159 to 169
subscribeToEvent<{
taskId: string;
userId: string;
assigneeId: string | null;
title: string;
projectId: string;
}>("task.created", async (data) => {
if (data.userId) {
if (data.assigneeId) {
await createNotification({
userId: data.userId,
userId: data.assigneeId,
type: "task_created",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Preserve backward compatibility for task.created payloads.

Line 162 requires assigneeId, and Line 166 skips notification when it’s absent. At least one publisher (apps/api/src/task/controllers/import-tasks.ts) still emits task.created without assigneeId, so those assignee notifications are silently dropped.

💡 Suggested compatibility fix
 subscribeToEvent<{
   taskId: string;
   userId: string;
-  assigneeId: string | null;
+  assigneeId?: string | null;
   title: string;
   projectId: string;
 }>("task.created", async (data) => {
-  if (data.assigneeId) {
+  const recipientId =
+    data.assigneeId === undefined ? data.userId : data.assigneeId;
+
+  if (recipientId) {
     await createNotification({
-      userId: data.assigneeId,
+      userId: recipientId,
       type: "task_created",
       eventData: {
         taskTitle: data.title,
       },
       resourceId: data.taskId,
       resourceType: "task",
     });
   }
 });
📝 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
subscribeToEvent<{
taskId: string;
userId: string;
assigneeId: string | null;
title: string;
projectId: string;
}>("task.created", async (data) => {
if (data.userId) {
if (data.assigneeId) {
await createNotification({
userId: data.userId,
userId: data.assigneeId,
type: "task_created",
subscribeToEvent<{
taskId: string;
userId: string;
assigneeId?: string | null;
title: string;
projectId: string;
}>("task.created", async (data) => {
const recipientId =
data.assigneeId === undefined ? data.userId : data.assigneeId;
if (recipientId) {
await createNotification({
userId: recipientId,
type: "task_created",
eventData: {
taskTitle: data.title,
},
resourceId: data.taskId,
resourceType: "task",
});
}
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/notification/index.ts` around lines 159 - 169, The subscriber
for subscribeToEvent("task.created", ...) currently ignores events that lack
assigneeId; to preserve backward compatibility, make the handler tolerant of
missing assigneeId by resolving the assignee before skipping: when
data.assigneeId is absent, load the task by data.taskId (e.g., via your Task
model/service used elsewhere) to determine its assignee and fall back to that;
then call createNotification({ userId: resolvedAssigneeId, type: "task_created",
... }) only if a resolvedAssigneeId exists. Update the subscribeToEvent handler
(and any related variable names) so it attempts the DB lookup when
data.assigneeId is null/undefined instead of immediately returning.

Comment on lines +90 to +91
userId: createdTask.createdBy ?? createdTask.userId ?? "",
assigneeId: createdTask.userId ?? null,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid publishing userId: "" in task.created events.

Line 90 can emit an empty actor ID, which can break downstream consumers expecting a real user identifier.

💡 Suggested fix
-  await publishEvent("task.created", {
-    ...createdTask,
-    taskId: createdTask.id,
-    userId: createdTask.createdBy ?? createdTask.userId ?? "",
-    assigneeId: createdTask.userId ?? null,
-    type: "task",
-    content: null,
-  });
+  const actorId = createdTask.createdBy ?? createdTask.userId;
+  if (actorId) {
+    await publishEvent("task.created", {
+      ...createdTask,
+      taskId: createdTask.id,
+      userId: actorId,
+      assigneeId: createdTask.userId ?? null,
+      type: "task",
+      content: null,
+    });
+  }
📝 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
userId: createdTask.createdBy ?? createdTask.userId ?? "",
assigneeId: createdTask.userId ?? null,
const actorId = createdTask.createdBy ?? createdTask.userId;
if (actorId) {
await publishEvent("task.created", {
...createdTask,
taskId: createdTask.id,
userId: actorId,
assigneeId: createdTask.userId ?? null,
type: "task",
content: null,
});
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/api/src/task/controllers/create-task.ts` around lines 90 - 91, The event
payload currently sets userId to an empty string (userId: createdTask.createdBy
?? createdTask.userId ?? ""), which can break consumers; change the assignment
in the create-task controller (look for createdTask.createdBy /
createdTask.userId) to omit or set userId to null/undefined when no real ID
exists (e.g., use createdTask.createdBy ?? createdTask.userId ?? undefined) so
no empty string is emitted, and update any event-builder call that uses this
field to accept an optional userId.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants