Skip to content

[comments] introduce module and API#7

Merged
capcom6 merged 6 commits into
masterfrom
comments/add-module
Apr 22, 2026
Merged

[comments] introduce module and API#7
capcom6 merged 6 commits into
masterfrom
comments/add-module

Conversation

@capcom6
Copy link
Copy Markdown
Contributor

@capcom6 capcom6 commented Apr 17, 2026

Summary by CodeRabbit

  • New Features

    • Task comments: create, edit, soft-delete, and list via new task-scoped comment endpoints; DB migration added.
  • Improvements

    • Unified pagination for projects, tasks, and users; list endpoints now return totals directly (int).
    • Task responses now include comments and use a shared brief user DTO.
  • Documentation

    • OpenAPI docs and HTTP request examples updated; package-level docs for the comments API added.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 17, 2026

📝 Walkthrough

Walkthrough

Adds a comments feature (domain, repo, service, handlers, docs, migration), introduces a generic DB pagination utility, updates projects/tasks/users to use shared pagination and return totals from List, updates server DTOs/docs/handlers to include comments and adjusted totals, registers comments.Module() in app wiring, and tweaks go.mod. (45 words)

Changes

Cohort / File(s) Summary
Comments module
internal/comments/doc.go, internal/comments/domain.go, internal/comments/errors.go, internal/comments/models.go, internal/comments/module.go, internal/comments/repository.go, internal/comments/service.go, internal/db/migrations/20260417000000_create_comments.sql
New comments feature: domain types and validation (MaxContentLength, Comment/CommentInput/CommentUpdate), sentinel errors, Bun model mapping, repo CRUD (Create/Get/List/Update/Delete with soft-delete), service orchestration (author/admin checks), FX module provider, and DB migration (comments table, indexes, FKs).
Generic DB pagination
internal/db/pagination.go
New generic pagination utility: DefaultPagination interface and generic Pagination[T] with NewPagination, Limit, Offset, and Apply to normalize and apply limits/offsets to Bun queries.
Projects pagination refactor
internal/projects/domain.go, internal/projects/repository.go, internal/projects/service.go
Projects now use db.Pagination alias; repo List accepts *Pagination, applies it and returns (items, total, error) via ScanAndCount; service List signature updated and Count removed.
Tasks pagination refactor & comments wiring
internal/tasks/domain.go, internal/tasks/repository.go, internal/tasks/service.go, internal/server/tasks/*, internal/server/tasks/dto_comments.go, internal/server/tasks/dto.go, internal/server/tasks/handler_comments.go
Replaced local pagination with db.Pagination; repo/service List return totals via ScanAndCount; removed separate Count. Server: added comment DTOs and handlers, task details now include comments, handlers accept comments.Service and map comment errors to HTTP statuses.
Users pagination & admin check
internal/users/domain.go, internal/users/repository.go, internal/users/service.go
Added db.Pagination alias/constructor; repo List accepts *Pagination and returns (items, total, error) via ScanAndCount; removed repo Count; added Repository.IsAdminByID and Service.IsAdmin for admin checks; service List signature updated.
Server DTOs, docs & handlers
internal/server/projects/dto.go, internal/server/projects/handler.go, internal/server/users/handler.go, internal/server/tasks/dto.go, internal/server/docs/docs.go, internal/server/dto/response.go
DTO totals changed from int64int; task DTOs now reuse shared dto.UserBrief; added TaskDetailsResponse (includes comments); handlers construct NewPagination and consume total returned from List (removed separate Count calls); OpenAPI updated with comment endpoints and schemas.
App wiring & go.mod
internal/app.go, go.mod
Registered comments.Module() in Fx wiring; moved golang.org/x/sync v0.20.0 to an indirect requirement in go.mod.
Requests/testing helpers
requests.http
Added HTTP request definitions exercising comments endpoints (create/update/delete) and fetching tasks with comments.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Handler as "HTTP Handler"
    participant CommentsSvc as "Comments Service"
    participant UsersSvc as "Users Service"
    participant Repo as "Comments Repository"
    participant DB as "Database"

    Client->>Handler: POST /tasks/{task_id}/comments
    Handler->>CommentsSvc: Create(ctx, CommentInput)
    CommentsSvc->>CommentsSvc: Validate input
    CommentsSvc->>Repo: Create(ctx, input)
    Repo->>DB: INSERT comment
    DB-->>Repo: row
    Repo-->>CommentsSvc: *Comment
    CommentsSvc-->>Handler: *Comment
    Handler-->>Client: 201 Created (Comment)

    Client->>Handler: PUT /tasks/{task_id}/comments/{id}
    Handler->>CommentsSvc: Update(ctx, userID, taskID, id, update)
    CommentsSvc->>Repo: GetByID(ctx, id)
    Repo->>DB: SELECT comment
    DB-->>Repo: row
    Repo-->>CommentsSvc: *Comment
    alt requester is author
        CommentsSvc->>Repo: Update(ctx, id, content)
    else
        CommentsSvc->>UsersSvc: IsAdmin(ctx, userID)
        UsersSvc->>DB: SELECT role WHERE id=?
        DB-->>UsersSvc: role
        UsersSvc-->>CommentsSvc: isAdmin
        alt isAdmin
            CommentsSvc->>Repo: Update(ctx, id, content)
        else
            CommentsSvc-->>Handler: ErrUnauthorized
            Handler-->>Client: 403
        end
    end

    Client->>Handler: DELETE /tasks/{task_id}/comments/{id}
    Handler->>CommentsSvc: Delete(ctx, userID, taskID, id)
    CommentsSvc->>Repo: GetByID(ctx, id)
    Repo->>DB: SELECT comment
    DB-->>Repo: row
    Repo-->>CommentsSvc: *Comment
    CommentsSvc->>UsersSvc: IsAdmin(ctx, userID) (if needed)
    UsersSvc->>DB: SELECT role
    DB-->>UsersSvc: role
    UsersSvc-->>CommentsSvc: isAdmin
    alt authorized
        CommentsSvc->>Repo: Delete(ctx, id)
        Repo->>DB: UPDATE comments SET deleted_at=NOW()
        DB-->>Repo: result
        Repo-->>CommentsSvc: nil
        CommentsSvc-->>Handler: nil
        Handler-->>Client: 204 No Content
    else
        CommentsSvc-->>Handler: ErrUnauthorized
        Handler-->>Client: 403
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.89% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title '[comments] introduce module and API' clearly and concisely summarizes the main change: introducing a new comments module and its API.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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


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

@coderabbitai coderabbitai Bot added the codex label Apr 17, 2026
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 17, 2026

🤖 Pull request artifacts

Platform File
🐳 Docker GitHub Container Registry
🍎 Darwin arm64 backend_Darwin_arm64.tar.gz
🍎 Darwin x86_64 backend_Darwin_x86_64.tar.gz
🐧 Linux arm64 backend_Linux_arm64.tar.gz
🐧 Linux i386 backend_Linux_i386.tar.gz
🐧 Linux x86_64 backend_Linux_x86_64.tar.gz
🪟 Windows arm64 backend_Windows_arm64.zip
🪟 Windows i386 backend_Windows_i386.zip
🪟 Windows x86_64 backend_Windows_x86_64.zip

Copy link
Copy Markdown

@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: 4

🧹 Nitpick comments (1)
internal/db/pagination.go (1)

17-38: Avoid making defaults depend on typed-nil receivers.

NewPagination leaves def zero-valued, so Limit() works only because all current DefaultPagination implementations use pointer receivers—allowing methods to be called on nil. This creates an implicit contract that future implementations must follow or the code breaks silently. Initialize a real default provider in the constructor, or store resolved default/max limit values directly in Pagination to make the contract explicit and enforce it at the type level.

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

In `@internal/db/pagination.go` around lines 17 - 38, NewPagination currently
leaves the def field zero-valued so Limit() relies on nil-receiver behavior of
DefaultPagination implementations; change NewPagination to initialize a concrete
default provider or resolve and store the default and max limits directly on the
Pagination[T] struct so Limit() no longer calls methods on a potentially
typed-nil def. Specifically, update NewPagination to populate Pagination.def
with a real DefaultPagination instance (or set pagination.defaultLimit and
pagination.maxLimit fields) and adjust Limit() to use those stored values
instead of calling p.def.DefaultLimit()/p.def.MaxLimit(), ensuring Pagination,
NewPagination, and Limit no longer depend on nil receiver method semantics.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/comments/doc.go`:
- Around line 13-33: Update the doc examples to match the current
comments.Service API: when calling ListByTask include the pagination argument
(use svc.ListByTask(ctx, taskID, pagination)); for Update swap the ID order so
userID comes before commentID (use svc.Update(ctx, userID, commentID, update));
for Delete likewise pass userID before commentID (use svc.Delete(ctx, userID,
commentID)); ensure the example uses the existing types CommentInput and
CommentUpdate and the service variable name svc to match comments.NewService.

In `@internal/comments/domain.go`:
- Around line 57-59: The validation currently uses len(content) (byte count) but
the error refers to characters; change the checks in domain.go that compare
content to MaxContentLength (and the similar check at the block around lines
72-74) to count Unicode code points instead (use utf8.RuneCountInString or
equivalent) so non-ASCII input is measured in characters, and keep the error
return formatting using ErrValidationFailed and MaxContentLength unchanged.

In `@internal/db/migrations/20260417000000_create_comments.sql`:
- Line 19: Remove the extraneous separator line containing only '---' between
the migration sections: locate the migration file's markers `-- +goose Up` and
`-- +goose Down` and delete the standalone `---` line so only the `-- +goose
Down` directive separates the Up and Down sections.

In `@internal/users/service.go`:
- Around line 74-80: IsAdmin currently calls s.repo.GetByID which returns
UserWithPasswordHash and unnecessarily loads password_hash; add a narrower repo
method (e.g. GetRoleByID(ctx, id int64) (Role, error) or IsAdminByID(ctx, id
int64) (bool, error)) in the repository interface and implement it to only
SELECT role or use an EXISTS admin query, then change Service.IsAdmin to call
that new method instead of repo.GetByID to avoid bringing credential material
into memory.

---

Nitpick comments:
In `@internal/db/pagination.go`:
- Around line 17-38: NewPagination currently leaves the def field zero-valued so
Limit() relies on nil-receiver behavior of DefaultPagination implementations;
change NewPagination to initialize a concrete default provider or resolve and
store the default and max limits directly on the Pagination[T] struct so Limit()
no longer calls methods on a potentially typed-nil def. Specifically, update
NewPagination to populate Pagination.def with a real DefaultPagination instance
(or set pagination.defaultLimit and pagination.maxLimit fields) and adjust
Limit() to use those stored values instead of calling
p.def.DefaultLimit()/p.def.MaxLimit(), ensuring Pagination, NewPagination, and
Limit no longer depend on nil receiver method semantics.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 56fc5f18-628d-4132-829e-1d9c03915907

📥 Commits

Reviewing files that changed from the base of the PR and between 8ada24c and b46f512.

📒 Files selected for processing (24)
  • go.mod
  • internal/app.go
  • internal/comments/doc.go
  • internal/comments/domain.go
  • internal/comments/errors.go
  • internal/comments/models.go
  • internal/comments/module.go
  • internal/comments/repository.go
  • internal/comments/service.go
  • internal/db/migrations/20260417000000_create_comments.sql
  • internal/db/pagination.go
  • internal/projects/domain.go
  • internal/projects/repository.go
  • internal/projects/service.go
  • internal/server/projects/dto.go
  • internal/server/projects/handler.go
  • internal/server/tasks/dto.go
  • internal/server/users/handler.go
  • internal/tasks/domain.go
  • internal/tasks/repository.go
  • internal/tasks/service.go
  • internal/users/domain.go
  • internal/users/repository.go
  • internal/users/service.go

Comment thread internal/comments/doc.go
Comment thread internal/comments/domain.go Outdated
Comment thread internal/db/migrations/20260417000000_create_comments.sql
Comment thread internal/users/service.go Outdated
Copy link
Copy Markdown

@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.

🧹 Nitpick comments (1)
internal/comments/domain.go (1)

52-60: Consider consolidating duplicated content validation logic.

Line 53-60 and Line 68-75 implement the same trim/empty/length checks. Extracting a shared helper will prevent rule drift between create/update validation paths.

♻️ Proposed refactor
+func validateContent(content string) error {
+	content = strings.TrimSpace(content)
+	if content == "" {
+		return fmt.Errorf("%w: content is required", ErrValidationFailed)
+	}
+	if utf8.RuneCountInString(content) > MaxContentLength {
+		return fmt.Errorf("%w: content too long (max %d characters)", ErrValidationFailed, MaxContentLength)
+	}
+	return nil
+}
+
 // Validate checks that the input data is valid for comment creation.
 func (i CommentInput) Validate() error {
 	// Validate task ID
 	if i.TaskID <= 0 {
 		return fmt.Errorf("%w: task_id must be positive", ErrValidationFailed)
 	}
@@
-	content := strings.TrimSpace(i.Content)
-	if content == "" {
-		return fmt.Errorf("%w: content is required", ErrValidationFailed)
-	}
-
-	if utf8.RuneCountInString(content) > MaxContentLength {
-		return fmt.Errorf("%w: content too long (max %d characters)", ErrValidationFailed, MaxContentLength)
-	}
+	if err := validateContent(i.Content); err != nil {
+		return err
+	}
 
 	return nil
 }
@@
 func (u CommentUpdate) Validate() error {
-	// Validate content
-	content := strings.TrimSpace(u.Content)
-	if content == "" {
-		return fmt.Errorf("%w: content is required", ErrValidationFailed)
-	}
-
-	if utf8.RuneCountInString(content) > MaxContentLength {
-		return fmt.Errorf("%w: content too long (max %d characters)", ErrValidationFailed, MaxContentLength)
+	if err := validateContent(u.Content); err != nil {
+		return err
 	}
 
 	return nil
 }

Also applies to: 67-75

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

In `@internal/comments/domain.go` around lines 52 - 60, The content
trim/empty/length checks (using strings.TrimSpace(i.Content),
ErrValidationFailed and MaxContentLength with utf8.RuneCountInString) are
duplicated; extract a single helper like validateContent(content string) error
(or validateCommentContent) that performs TrimSpace, empty check and max-length
check and returns the same fmt.Errorf(...) errors, then replace both inline
blocks with calls to that helper from the create/update validation paths so they
share one implementation and avoid rule drift.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@internal/comments/domain.go`:
- Around line 52-60: The content trim/empty/length checks (using
strings.TrimSpace(i.Content), ErrValidationFailed and MaxContentLength with
utf8.RuneCountInString) are duplicated; extract a single helper like
validateContent(content string) error (or validateCommentContent) that performs
TrimSpace, empty check and max-length check and returns the same fmt.Errorf(...)
errors, then replace both inline blocks with calls to that helper from the
create/update validation paths so they share one implementation and avoid rule
drift.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d8ee38f3-0132-49cc-b6bb-7cdb49f53aab

📥 Commits

Reviewing files that changed from the base of the PR and between b46f512 and 1577ad2.

📒 Files selected for processing (4)
  • internal/comments/doc.go
  • internal/comments/domain.go
  • internal/users/repository.go
  • internal/users/service.go
✅ Files skipped from review due to trivial changes (1)
  • internal/comments/doc.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/users/repository.go

@capcom6 capcom6 force-pushed the comments/add-module branch from 1577ad2 to de5cb64 Compare April 19, 2026 00:56
Copy link
Copy Markdown

@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: 2

Caution

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

⚠️ Outside diff range comments (2)
internal/server/tasks/handler.go (1)

150-152: ⚠️ Potential issue | 🟡 Minor

Swagger annotation is out of sync with the created-task payload.

Line 174 returns TaskDetailsResponse, but Lines 150-152 still document TaskResponse. The generated spec therefore omits the comments field for POST /tasks.

Suggested fix
-//	`@Success`		201		{object}	TaskResponse
+//	`@Success`		201		{object}	TaskDetailsResponse

Also applies to: 174-174

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

In `@internal/server/tasks/handler.go` around lines 150 - 152, The Swagger
annotations for the POST /tasks handler are stale: update the `@Success` 201
response type from TaskResponse to TaskDetailsResponse (the handler returns
TaskDetailsResponse which includes comments) and ensure any other occurrences of
TaskResponse for this endpoint (e.g., the earlier `@Param/`@Success block and the
later single-line annotation referenced at line 174) are changed to
TaskDetailsResponse so the generated spec includes the comments field; locate
annotations near the handler for TaskCreateRequest/TaskResponse and replace the
response type symbol with TaskDetailsResponse.
internal/server/tasks/dto.go (1)

118-135: ⚠️ Potential issue | 🟠 Major

Preserve due_date in single-task responses.

Line 133 hardcodes DueDate: nil, so GET /tasks/{id}, POST /tasks, and PATCH /tasks/{id} now drop an existing due date while the list mapper still includes it on Line 191.

Suggested fix
 	return TaskResponse{
 		ID:          task.ID,
 		ProjectSlug: task.ProjectSlug,
 		Number:      task.Number,
 		Title:       task.Title,
 		Description: task.Description,
 		Priority:    string(task.Priority),
 		Status:      string(task.Status),
 		Author: dto.UserBrief{
 			ID:        task.AuthorID,
 			Email:     "",
 			Role:      "",
 			CreatedAt: "",
 		},
 		Assignee:  assignee,
-		DueDate:   nil,
+		DueDate:   task.DueDate,
 		CreatedAt: task.CreatedAt.Format(time.RFC3339),
 		UpdatedAt: task.UpdatedAt.Format(time.RFC3339),
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/server/tasks/dto.go` around lines 118 - 135, The single-task mapper
is dropping the due date by hardcoding DueDate: nil in the TaskResponse
construction; update the mapping in the function that builds TaskResponse (the
block returning TaskResponse with ID, ProjectSlug, Number, etc.) to mirror the
list mapper's behavior and set DueDate based on task.DueDate (i.e., if
task.DueDate is non-nil, format it as RFC3339 and assign that pointer/string,
otherwise leave nil) so GET /tasks/{id}, POST /tasks and PATCH /tasks/{id}
preserve existing due dates.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/server/tasks/dto_comments.go`:
- Around line 36-48: toCommentResponse currently fills dto.UserBrief fields
(Email, Role, CreatedAt) with empty strings causing clients to receive a
misleading author object; either resolve and populate the actual author data or
shrink the contract to only expose AuthorID. Fix by updating
toCommentResponse(comment *comments.Comment) to fetch the author (e.g., via the
user service or repository available to handlers) and set
dto.UserBrief.{ID,Email,Role,CreatedAt} from the real user before returning
CommentResponse, or change CommentResponse to remove the full dto.UserBrief and
only include AuthorID and adjust callers (create/update handlers and task detail
responses) to expect the slimmer shape. Ensure you update any DTO type
definitions and usages of CommentResponse accordingly (referencing
CommentResponse, dto.UserBrief, comments.Comment).

In `@requests.http`:
- Around line 307-309: The explanatory note is pointing to a non-existent
request name createComment2; update the comment above the DELETE request (DELETE
{{baseURL}}/tasks/{{taskId}}/comments/{{commentId}}) to reference the actual
request that sets `@commentId` (createComment) or remove the bogus reference
entirely so it correctly instructs the reader to use the `@commentId` returned by
createComment.

---

Outside diff comments:
In `@internal/server/tasks/dto.go`:
- Around line 118-135: The single-task mapper is dropping the due date by
hardcoding DueDate: nil in the TaskResponse construction; update the mapping in
the function that builds TaskResponse (the block returning TaskResponse with ID,
ProjectSlug, Number, etc.) to mirror the list mapper's behavior and set DueDate
based on task.DueDate (i.e., if task.DueDate is non-nil, format it as RFC3339
and assign that pointer/string, otherwise leave nil) so GET /tasks/{id}, POST
/tasks and PATCH /tasks/{id} preserve existing due dates.

In `@internal/server/tasks/handler.go`:
- Around line 150-152: The Swagger annotations for the POST /tasks handler are
stale: update the `@Success` 201 response type from TaskResponse to
TaskDetailsResponse (the handler returns TaskDetailsResponse which includes
comments) and ensure any other occurrences of TaskResponse for this endpoint
(e.g., the earlier `@Param/`@Success block and the later single-line annotation
referenced at line 174) are changed to TaskDetailsResponse so the generated spec
includes the comments field; locate annotations near the handler for
TaskCreateRequest/TaskResponse and replace the response type symbol with
TaskDetailsResponse.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 896c02ca-6147-43b2-bcd2-51f7ed45e405

📥 Commits

Reviewing files that changed from the base of the PR and between de5cb64 and 77129f8.

📒 Files selected for processing (9)
  • internal/comments/domain.go
  • internal/comments/repository.go
  • internal/comments/service.go
  • internal/server/docs/docs.go
  • internal/server/dto/response.go
  • internal/server/tasks/dto.go
  • internal/server/tasks/dto_comments.go
  • internal/server/tasks/handler.go
  • requests.http
✅ Files skipped from review due to trivial changes (1)
  • internal/server/dto/response.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • internal/comments/service.go
  • internal/comments/repository.go

Comment thread internal/server/tasks/dto_comments.go Outdated
Comment thread requests.http
Copy link
Copy Markdown

@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: 1

🧹 Nitpick comments (1)
internal/server/tasks/dto.go (1)

155-199: Consider reusing newTaskResponse to reduce duplication.

The inline mapping logic (lines 166-194) duplicates newTaskResponse. Refactoring to reuse the existing function improves maintainability.

♻️ Proposed refactor
 func toTaskListResponse(items []tasks.Task, total int) TaskListResponse {
 	return TaskListResponse{
 		Items: lo.Map(
 			items,
 			func(t tasks.Task, _ int) TaskResponse {
-				var assignee *dto.UserBrief
-				if t.AssigneeID != nil {
-					assignee = &dto.UserBrief{
-						ID:        *t.AssigneeID,
-						Email:     "", // Will be populated by service
-						Role:      "",
-						CreatedAt: "",
-					}
-				}
-
-				return TaskResponse{
-					ID:          t.ID,
-					ProjectSlug: t.ProjectSlug,
-					Number:      t.Number,
-					Title:       t.Title,
-					Description: t.Description,
-					Priority:    string(t.Priority),
-					Status:      string(t.Status),
-					Author: dto.UserBrief{
-						ID:        t.AuthorID,
-						Email:     "", // Will be populated by service
-						Role:      "",
-						CreatedAt: "",
-					},
-					Assignee:  assignee,
-					DueDate:   t.DueDate,
-					CreatedAt: t.CreatedAt.Format(time.RFC3339),
-					UpdatedAt: t.UpdatedAt.Format(time.RFC3339),
-				}
+				return newTaskResponse(&t)
 			},
 		),
 		Total: total,
 	}
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/server/tasks/dto.go` around lines 155 - 199, The mapping in
toTaskListResponse duplicates the construction logic already implemented in
newTaskResponse; replace the inline lambda in lo.Map with a call to
newTaskResponse (or a small adapter that calls newTaskResponse) to build each
TaskResponse from tasks.Task, preserving special handling for nil AssigneeID and
ensuring CreatedAt/UpdatedAt are formatted (or that newTaskResponse handles
formatting). Update toTaskListResponse to pass the domain task into
newTaskResponse (or convert any missing fields before calling it) and remove the
duplicated field-by-field construction so all task->TaskResponse logic lives in
newTaskResponse.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/server/tasks/handler_comments.go`:
- Around line 100-138: The Swagger docs for deleteComment are missing a 400
response for invalid ID parsing; update the comment block above the
deleteComment function to include a `@Failure` 400 {object} fiberfx.ErrorResponse
(or similar) entry describing "invalid task ID or comment ID" so the OpenAPI
spec documents the bad-request cases returned when strconv.ParseInt fails for
task_id or id.

---

Nitpick comments:
In `@internal/server/tasks/dto.go`:
- Around line 155-199: The mapping in toTaskListResponse duplicates the
construction logic already implemented in newTaskResponse; replace the inline
lambda in lo.Map with a call to newTaskResponse (or a small adapter that calls
newTaskResponse) to build each TaskResponse from tasks.Task, preserving special
handling for nil AssigneeID and ensuring CreatedAt/UpdatedAt are formatted (or
that newTaskResponse handles formatting). Update toTaskListResponse to pass the
domain task into newTaskResponse (or convert any missing fields before calling
it) and remove the duplicated field-by-field construction so all
task->TaskResponse logic lives in newTaskResponse.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3923ee5a-70d1-411e-9bd9-8217fce32657

📥 Commits

Reviewing files that changed from the base of the PR and between 77129f8 and b975089.

📒 Files selected for processing (4)
  • internal/server/tasks/dto.go
  • internal/server/tasks/handler.go
  • internal/server/tasks/handler_comments.go
  • requests.http
🚧 Files skipped from review as they are similar to previous changes (1)
  • requests.http

Comment thread internal/server/tasks/handler_comments.go
Copy link
Copy Markdown

@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.

🧹 Nitpick comments (2)
internal/server/tasks/dto.go (2)

155-158: Minor type inconsistency for Total field.

TaskListResponse.Total is int while CommentListResponse.Total (in dto_comments.go) is int64. For consistency and to avoid potential truncation on 32-bit platforms, consider using int64 for all pagination totals.

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

In `@internal/server/tasks/dto.go` around lines 155 - 158, Change the Total field
on TaskListResponse from int to int64 to match CommentListResponse and avoid
platform truncation; update the struct TaskListResponse (type TaskListResponse {
Items []TaskResponse `json:"items"`; Total int -> int64 }) and any code that
constructs or reads TaskListResponse.Total to use int64 to keep types consistent
with CommentListResponse in dto_comments.go.

145-150: Consider avoiding intermediate allocation.

toCommentListResponse(comments, 0).Items allocates a CommentListResponse struct only to immediately discard everything except Items. Consider extracting the slice-mapping logic into a shared helper or use lo.Map directly.

♻️ Suggested refactor
 func newTaskDetailsResponse(task *tasks.Task, comments []comments.Comment) *TaskDetailsResponse {
+	commentResponses := lo.Map(comments, func(c comments.Comment, _ int) CommentResponse {
+		return *toCommentResponse(&c)
+	})
 	return &TaskDetailsResponse{
 		TaskResponse: newTaskResponse(task),
-		Comments:     toCommentListResponse(comments, 0).Items,
+		Comments:     commentResponses,
 	}
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/server/tasks/dto.go` around lines 145 - 150, newTaskDetailsResponse
currently calls toCommentListResponse(comments, 0).Items which creates a
CommentListResponse just to take its Items; change this to map the comments
directly to []CommentResponse to avoid the intermediate allocation—either add a
small helper like mapCommentsToResponses(comments []comments.Comment)
[]CommentResponse (or toCommentResponses) that performs the slice mapping using
the same logic as toCommentListResponse, or use lo.Map inline to produce the
[]CommentResponse; then replace the Comments field assignment in
newTaskDetailsResponse to call that helper (or lo.Map) instead of
toCommentListResponse(...).Items.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@internal/server/tasks/dto.go`:
- Around line 155-158: Change the Total field on TaskListResponse from int to
int64 to match CommentListResponse and avoid platform truncation; update the
struct TaskListResponse (type TaskListResponse { Items []TaskResponse
`json:"items"`; Total int -> int64 }) and any code that constructs or reads
TaskListResponse.Total to use int64 to keep types consistent with
CommentListResponse in dto_comments.go.
- Around line 145-150: newTaskDetailsResponse currently calls
toCommentListResponse(comments, 0).Items which creates a CommentListResponse
just to take its Items; change this to map the comments directly to
[]CommentResponse to avoid the intermediate allocation—either add a small helper
like mapCommentsToResponses(comments []comments.Comment) []CommentResponse (or
toCommentResponses) that performs the slice mapping using the same logic as
toCommentListResponse, or use lo.Map inline to produce the []CommentResponse;
then replace the Comments field assignment in newTaskDetailsResponse to call
that helper (or lo.Map) instead of toCommentListResponse(...).Items.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4dcbbd07-1a60-44b9-a629-ad73e67bdfd3

📥 Commits

Reviewing files that changed from the base of the PR and between b975089 and 90c57d0.

📒 Files selected for processing (3)
  • internal/server/docs/docs.go
  • internal/server/tasks/dto.go
  • internal/server/tasks/handler_comments.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/server/tasks/handler_comments.go

Copy link
Copy Markdown

@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.

♻️ Duplicate comments (1)
internal/server/tasks/dto_comments.go (1)

29-44: ⚠️ Potential issue | 🟠 Major

Don’t emit a full author object with placeholder empty fields.

Line 40–44 constructs author with empty email, role, and created_at, which makes the response shape misleading. Either hydrate author details before mapping, or expose author_id instead of dto.UserBrief.

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

In `@internal/server/tasks/dto_comments.go` around lines 29 - 44, The
toCommentResponse function is emitting a dto.UserBrief with empty fields which
is misleading; update the mapping in toCommentResponse to either (a) populate
dto.UserBrief by fetching/hydrating the author details (e.g., call your user
service/Repo to get user by comment.AuthorID and set ID/Email/Role/CreatedAt
accordingly) or (b) change the CommentResponse struct and this mapper to expose
AuthorID: comment.AuthorID instead of Author: dto.UserBrief so you don't return
placeholder empty fields (modify CommentResponse and any callers that expect
Author to use AuthorID if you choose option b).
🧹 Nitpick comments (1)
internal/server/tasks/dto_comments.go (1)

36-56: Return comment DTOs by value instead of pointer.

toCommentResponse returns *CommentResponse, but callers immediately dereference it. Returning by value removes unnecessary pointer churn.

♻️ Proposed simplification
-func toCommentResponse(comment *comments.Comment) *CommentResponse {
-	return &CommentResponse{
+func toCommentResponse(comment *comments.Comment) CommentResponse {
+	return CommentResponse{
 		ID: comment.ID,
 		Author: dto.UserBrief{
 			ID:        comment.AuthorID,
 			Email:     "",
 			Role:      "",
 			CreatedAt: "",
 		},
 		Content:   comment.Content,
 		CreatedAt: comment.CreatedAt.UTC().Format(time.RFC3339),
 		UpdatedAt: comment.UpdatedAt.UTC().Format(time.RFC3339),
 	}
 }
@@
 	comments := make([]CommentResponse, 0, len(items))
 	for _, item := range items {
-		comments = append(comments, *toCommentResponse(&item))
+		comments = append(comments, toCommentResponse(&item))
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/server/tasks/dto_comments.go` around lines 36 - 56, Change
toCommentResponse to return a CommentResponse by value instead of
*CommentResponse to avoid unnecessary pointers; update its signature
(toCommentResponse) and body to construct and return a value, then update
callers such as toCommentsList to append the returned value directly (remove the
dereference of *toCommentResponse(&item)), and scan for and fix any other call
sites that currently expect a pointer to use the new value return type.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@internal/server/tasks/dto_comments.go`:
- Around line 29-44: The toCommentResponse function is emitting a dto.UserBrief
with empty fields which is misleading; update the mapping in toCommentResponse
to either (a) populate dto.UserBrief by fetching/hydrating the author details
(e.g., call your user service/Repo to get user by comment.AuthorID and set
ID/Email/Role/CreatedAt accordingly) or (b) change the CommentResponse struct
and this mapper to expose AuthorID: comment.AuthorID instead of Author:
dto.UserBrief so you don't return placeholder empty fields (modify
CommentResponse and any callers that expect Author to use AuthorID if you choose
option b).

---

Nitpick comments:
In `@internal/server/tasks/dto_comments.go`:
- Around line 36-56: Change toCommentResponse to return a CommentResponse by
value instead of *CommentResponse to avoid unnecessary pointers; update its
signature (toCommentResponse) and body to construct and return a value, then
update callers such as toCommentsList to append the returned value directly
(remove the dereference of *toCommentResponse(&item)), and scan for and fix any
other call sites that currently expect a pointer to use the new value return
type.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c6da556b-b162-4690-b26e-ea89822c6078

📥 Commits

Reviewing files that changed from the base of the PR and between 90c57d0 and 374f63d.

📒 Files selected for processing (2)
  • internal/server/tasks/dto.go
  • internal/server/tasks/dto_comments.go

@capcom6 capcom6 force-pushed the comments/add-module branch from be55b0a to 5617b1a Compare April 22, 2026 02:13
Copy link
Copy Markdown

@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.

🧹 Nitpick comments (3)
internal/comments/domain.go (1)

50-58: Validation trims but doesn't persist the trimmed value.

The validation creates a local content := strings.TrimSpace(i.Content) for checking, but the original i.Content (potentially with whitespace) is what gets stored. Consider normalizing the content before validation or documenting this behavior.

♻️ Optional fix to normalize content
 func (i CommentInput) Validate() error {
+	i.Content = strings.TrimSpace(i.Content)
+
 	// Validate task ID
 	if i.TaskID <= 0 {
 		return fmt.Errorf("%w: task_id must be positive", ErrValidationFailed)
 	}
 
 	// Validate author ID
 	if i.AuthorID <= 0 {
 		return fmt.Errorf("%w: author_id must be positive", ErrValidationFailed)
 	}
 
 	// Validate content
-	content := strings.TrimSpace(i.Content)
-	if content == "" {
+	if i.Content == "" {
 		return fmt.Errorf("%w: content is required", ErrValidationFailed)
 	}
 
-	if utf8.RuneCountInString(content) > MaxContentLength {
+	if utf8.RuneCountInString(i.Content) > MaxContentLength {
 		return fmt.Errorf("%w: content too long (max %d characters)", ErrValidationFailed, MaxContentLength)
 	}
 
 	return nil
 }

Note: This requires changing the receiver to a pointer (i *CommentInput) to mutate the struct.

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

In `@internal/comments/domain.go` around lines 50 - 58, The validation trims into
a local variable but doesn't persist the trimmed string, so update the validator
to normalize input by assigning the trimmed value back to the struct (set
i.Content = strings.TrimSpace(i.Content)) before running the length checks; to
allow mutation change the receiver to a pointer (e.g., func (i *CommentInput)
Validate(...)) and then use utf8.RuneCountInString(i.Content) with
MaxContentLength and ErrValidationFailed as currently done to return the same
errors when validation fails.
internal/comments/repository.go (1)

45-61: Consider adding pagination for task comments.

The List method returns all comments for a task without pagination. For tasks with many comments, this could lead to performance issues and large response payloads. Consider adding optional pagination parameters consistent with the generic DB pagination utility mentioned in the PR summary.

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

In `@internal/comments/repository.go` around lines 45 - 61, Update Repository.List
to support pagination by adding optional pagination parameters (e.g., limit and
offset or a pagination options struct) to the method signature and applying them
to the query before calling OrderExpr/Scan; specifically, modify List(ctx
context.Context, taskID int64) to accept pagination inputs, apply those to
r.db.NewSelect() (using the project’s generic DB pagination utility if
available), and ensure the returned slice of commentModel -> Comment conversion
via model.toDomain() remains unchanged so callers get a paginated []Comment
result.
internal/server/tasks/dto.go (1)

107-137: Author/Assignee UserBrief fields are only partially populated.

The newTaskResponse function initializes dto.UserBrief with only ID populated, leaving Email, Role, and CreatedAt as empty strings. This is consistent with the pattern in dto_comments.go (which has a similar existing review comment). Clients will receive a UserBrief object with missing data.

If resolving user details is deferred intentionally (e.g., for performance or to allow client-side enrichment), consider documenting this contract or slimming the DTO to only include author_id and assignee_id.

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@internal/comments/domain.go`:
- Around line 50-58: The validation trims into a local variable but doesn't
persist the trimmed string, so update the validator to normalize input by
assigning the trimmed value back to the struct (set i.Content =
strings.TrimSpace(i.Content)) before running the length checks; to allow
mutation change the receiver to a pointer (e.g., func (i *CommentInput)
Validate(...)) and then use utf8.RuneCountInString(i.Content) with
MaxContentLength and ErrValidationFailed as currently done to return the same
errors when validation fails.

In `@internal/comments/repository.go`:
- Around line 45-61: Update Repository.List to support pagination by adding
optional pagination parameters (e.g., limit and offset or a pagination options
struct) to the method signature and applying them to the query before calling
OrderExpr/Scan; specifically, modify List(ctx context.Context, taskID int64) to
accept pagination inputs, apply those to r.db.NewSelect() (using the project’s
generic DB pagination utility if available), and ensure the returned slice of
commentModel -> Comment conversion via model.toDomain() remains unchanged so
callers get a paginated []Comment result.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b09c9c37-08da-4681-8af1-be9f5fb8dcf6

📥 Commits

Reviewing files that changed from the base of the PR and between be55b0a and 5617b1a.

📒 Files selected for processing (17)
  • go.mod
  • internal/app.go
  • internal/comments/doc.go
  • internal/comments/domain.go
  • internal/comments/errors.go
  • internal/comments/models.go
  • internal/comments/module.go
  • internal/comments/repository.go
  • internal/comments/service.go
  • internal/db/migrations/20260417000000_create_comments.sql
  • internal/server/docs/docs.go
  • internal/server/dto/response.go
  • internal/server/tasks/dto.go
  • internal/server/tasks/dto_comments.go
  • internal/server/tasks/handler.go
  • internal/server/tasks/handler_comments.go
  • requests.http
✅ Files skipped from review due to trivial changes (9)
  • internal/app.go
  • go.mod
  • internal/comments/module.go
  • internal/comments/errors.go
  • internal/server/dto/response.go
  • requests.http
  • internal/db/migrations/20260417000000_create_comments.sql
  • internal/comments/doc.go
  • internal/comments/models.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • internal/server/tasks/handler.go
  • internal/comments/service.go

@capcom6 capcom6 marked this pull request as ready for review April 22, 2026 02:50
@capcom6 capcom6 merged commit 7f79126 into master Apr 22, 2026
7 checks passed
@capcom6 capcom6 deleted the comments/add-module branch April 22, 2026 02:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant