Skip to content

Feat/phase2 tests ci#2

Merged
amirmalekian merged 16 commits into
mainfrom
feat/phase2-tests-ci
Jul 18, 2026
Merged

Feat/phase2 tests ci#2
amirmalekian merged 16 commits into
mainfrom
feat/phase2-tests-ci

Conversation

@amirmalekian

@amirmalekian amirmalekian commented Jul 18, 2026

Copy link
Copy Markdown
Owner

Summary

This PR adds the testing and continuous integration foundation for StreamForge.

What's included

  • Add unit tests for core packages:

    • Authentication
    • Configuration
    • Jobs
    • Queue
    • Worker pool
  • Add GitHub Actions CI workflow

  • Enable automated linting, testing, and build validation

  • Add Makefile for common development tasks

  • Configure golangci-lint

  • Update Go module dependencies for testing

Validation

The following commands complete successfully:

go test ./...
go test -race ./...
go vet ./...
golangci-lint run
go build ./...

Notes

This PR focuses only on improving code quality and developer tooling. No production features or API behavior have been changed.

Closes Phase 2 of the StreamForge roadmap.

Summary by CodeRabbit

  • New Features
    • Added user registration and JWT-based login.
    • Added job creation, listing, retrieval, cancellation, media item tracking, and job progress streaming (SSE).
    • Added background processing with queue support and a worker pool, including graceful shutdown.
    • Added rate limiting, request IDs, and consistent error handling.
    • Added Docker Compose setup for running the API, worker, and required services locally.
  • Documentation
    • Updated README with quick start, endpoints, environment configuration, and architecture overview.
    • Added a full OpenAPI specification for the API.
  • Chores
    • Added CI workflow (lint, race tests w/ coverage, builds) and a project Makefile.

- Modular monolith architecture with clean domain boundaries
- Go 1.23 with Gin, pgx/v5, redis/v9, RabbitMQ, JWT auth
- PostgreSQL schema: users, jobs, media_items with proper indexes
- Worker pool pattern with bounded channels, context cancellation
- Redis for progress tracking, rate limiting, SSE pub/sub
- RabbitMQ for async job processing
- SSE endpoint for real-time progress updates
- OpenAPI/Swagger specification
- Docker Compose for local development
- Database migrations with golang-migrate
@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@amirmalekian, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 9 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 86684f96-17c6-481c-86a1-634176bf22bc

📥 Commits

Reviewing files that changed from the base of the PR and between b9abf41 and e2c6baf.

📒 Files selected for processing (2)
  • .github/workflows/ci.yml
  • go.mod
📝 Walkthrough

Walkthrough

The PR adds a Go-based Streamforge API and worker system with PostgreSQL persistence, Redis progress tracking, RabbitMQ messaging, JWT authentication, job and media-item endpoints, Docker deployment, CI automation, database migrations, and operational documentation.

Changes

Streamforge platform

Layer / File(s) Summary
Configuration, persistence, and schema foundation
go.mod, internal/config/*, internal/database/*, migrations/*
Adds module dependencies, environment configuration, PostgreSQL repositories, and schemas for users, jobs, and media items.
Authentication and HTTP API
internal/auth/*, internal/api/*, internal/middleware/*, docs/openapi.yaml
Adds JWT registration and login, request middleware, authenticated job routes, SSE events, media-item queries, and OpenAPI definitions.
Job state, progress, and events
internal/jobs/*, internal/redis/*
Adds job lifecycle operations, authorization checks, Redis-backed progress and status storage, pagination, media-item handling, and event publication.
Queue consumption and worker processing
internal/queue/*, internal/worker/*
Adds RabbitMQ message handling, buffered worker execution, simulated media-item processing, progress updates, completion events, and tests.
Runtime orchestration and delivery
cmd/*, docker-compose.yml, Dockerfile*, .github/workflows/ci.yml, .golangci.yml, Makefile, README.md, .gitattributes
Adds API and worker startup/shutdown flows, local infrastructure, container builds, CI jobs, linting, automation targets, documentation, and line-ending rules.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant API
  participant JobsService
  participant PostgreSQL
  participant RabbitMQ
  participant WorkerPool
  participant Redis

  Client->>API: Create job request
  API->>JobsService: CreateJob(user_id, source_url)
  JobsService->>PostgreSQL: Insert job
  JobsService->>Redis: Initialize progress
  API->>RabbitMQ: Publish PROCESS message
  RabbitMQ->>WorkerPool: Consume job message
  WorkerPool->>JobsService: Update status and media progress
  JobsService->>PostgreSQL: Persist job and item updates
  JobsService->>Redis: Increment progress
  JobsService-->>API: Publish progress event
  API-->>Client: Stream SSE event
Loading
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

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.
Title check ❓ Inconclusive The title is related to the PR but too vague to clearly summarize the main change. Use a concise, specific title that mentions the added tests, CI workflow, and dev tooling foundation.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/phase2-tests-ci

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

🟠 Major comments (26)
migrations/000001_init.up.sql-1-73 (1)

1-73: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Resolve conflicting and duplicate migration files. The 000001_init.up.sql migration contains the consolidated schema for all tables, but the standalone migration files for users, jobs, and media items were not removed. Keeping both will cause migration tools to fail due to conflicting version prefixes (e.g., two 000001_ files) and duplicate table definitions.

  • migrations/000001_init.up.sql#L1-L73: Keep this consolidated initialization migration. Ensure its schema matches the struct models (e.g., size_bytes vs size, and ENUM values) before finalizing.
  • migrations/000001_create_users_table.up.sql#L1-L15: Delete this redundant file.
  • migrations/000002_create_jobs_table.up.sql#L1-L22: Delete this redundant file.
  • migrations/000003_create_media_items_table.up.sql#L1-L22: Delete this redundant file.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@migrations/000001_init.up.sql` around lines 1 - 73, Keep the consolidated
schema in migrations/000001_init.up.sql (lines 1-73), first aligning its columns
and ENUM values with the struct models, including size_bytes versus size. Delete
the redundant migrations/000001_create_users_table.up.sql (lines 1-15),
migrations/000002_create_jobs_table.up.sql (lines 1-22), and
migrations/000003_create_media_items_table.up.sql (lines 1-22) so only the
consolidated initialization migration defines these tables.
internal/database/postgres.go-20-22 (1)

20-22: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Prevent connection failures by URL-encoding credentials.

Similar to the configuration layer, constructing the connection URL via simple string concatenation will cause connection failures if the database username or password contains special characters. Use the net/url package to safely build the DSN.

🐛 Proposed fix

First, import net/url at the top of the file, then update the DSN method:

 func (c Config) DSN() string {
-	return "postgres://" + c.User + ":" + c.Password + "@" + c.Host + ":" + c.Port + "/" + c.Name
+	u := url.URL{
+		Scheme: "postgres",
+		User:   url.UserPassword(c.User, c.Password),
+		Host:   c.Host + ":" + c.Port,
+		Path:   "/" + c.Name,
+	}
+	return u.String()
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/database/postgres.go` around lines 20 - 22, Update Config.DSN to
construct the PostgreSQL connection string using net/url so User and Password
are URL-encoded before insertion, while preserving the existing host, port, and
database name values.
internal/config/config.go-61-63 (1)

61-63: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Prevent connection failures by URL-encoding credentials.

Constructing the RabbitMQ URI via simple string concatenation will cause connection failures if the username or password contains special characters (like @ or /). Use the net/url package to safely construct the URI.

🐛 Proposed fix

Ensure net/url is imported, then update the URI method:

 func (r RabbitMQConfig) URI() string {
-	return "amqp://" + r.User + ":" + r.Password + "@" + r.Host + ":" + r.Port + "/"
+	u := url.URL{
+		Scheme: "amqp",
+		User:   url.UserPassword(r.User, r.Password),
+		Host:   r.Host + ":" + r.Port,
+		Path:   "/",
+	}
+	return u.String()
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/config/config.go` around lines 61 - 63, Update RabbitMQConfig.URI to
construct the AMQP URI with net/url rather than concatenating credential
strings. URL-encode User and Password while preserving the existing Host, Port,
and path components so credentials containing reserved characters produce a
valid connection URI.
internal/config/config.go-38-40 (1)

38-40: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Prevent connection failures by URL-encoding credentials.

Constructing the connection URL via simple string concatenation will cause connection failures if the database username or password contains special characters like @, :, or /. Use the net/url package to safely build the DSN.

🐛 Proposed fix

First, import the net/url package at the top of the file:

 import (
+	"net/url"
 	"os"

Then update the DSN method:

 func (d DatabaseConfig) DSN() string {
-	return "postgres://" + d.User + ":" + d.Password + "@" + d.Host + ":" + d.Port + "/" + d.Name
+	u := url.URL{
+		Scheme: "postgres",
+		User:   url.UserPassword(d.User, d.Password),
+		Host:   d.Host + ":" + d.Port,
+		Path:   "/" + d.Name,
+	}
+	return u.String()
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/config/config.go` around lines 38 - 40, Update DatabaseConfig.DSN to
construct the PostgreSQL connection string with net/url, ensuring User and
Password are URL-encoded so special characters remain valid credentials.
Preserve the existing host, port, and database name components.
internal/api/middleware/middleware.go-76-85 (1)

76-85: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Recovery middleware exposes panic details in both packages. Return a generic response and log the recovered value server-side.

  • internal/api/middleware/middleware.go#L76-L85: remove the client-facing panic message and return after writing the response.
  • internal/middleware/middleware.go#L75-L84: apply the same generic recovery behavior.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/api/middleware/middleware.go` around lines 76 - 85, The Recovery
function in internal/api/middleware/middleware.go must stop exposing recovered
panic details: log the recovered value server-side, return only the generic
internal-server-error response, and return immediately after writing it. Apply
the same behavior in internal/middleware/middleware.go, updating its
corresponding recovery middleware as well.
internal/jobs/service.go-230-250 (1)

230-250: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Create media items and update the total atomically.

SetJobTotalItems errors are ignored and run before the item inserts. Any later insert failure leaves a partial item set with a total describing the full input. Move these writes into one repository transaction and propagate every error.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/jobs/service.go` around lines 230 - 250, Update CreateMediaItems to
execute SetJobTotalItems and all CreateMediaItem calls within one repository
transaction, propagating errors from each operation. Move the total update into
the transaction alongside the inserts so any failure rolls back the entire
operation and avoids partial item sets.
internal/jobs/service.go-159-167 (1)

159-167: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Do not report successful cancellation after dependent updates fail.

Redis status deletion and media-item cancellation errors are discarded. The job can be CANCELLED while its items remain active and the cache reports another state. Check each result and define transactional/compensating behavior.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/jobs/service.go` around lines 159 - 167, Update the cancellation
flow around UpdateJobStatus to check and propagate errors from
redis.SetJobStatus, redis.DeleteProgress, and repo.UpdateMediaItemsStatus
instead of discarding them. Define and apply the appropriate transactional or
compensating behavior so the method does not return success when any dependent
update fails and cannot leave inconsistent job, item, or cache state.
internal/jobs/service.go-267-310 (1)

267-310: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

The in-process SSE channel lifecycle can race and panic. Channel registration, removal, closure, and response writes lack a single owner.

  • internal/jobs/service.go#L267-L310: snapshot subscribers under lock and formalize channel ownership.
  • internal/api/routes.go#L274-L296: do not close the registered channel or write to Gin's response from a second goroutine.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/jobs/service.go` around lines 267 - 310, The SSE subscriber
lifecycle has competing owners, allowing channel sends, removal, closure, and
response writes to race or panic. In internal/jobs/service.go lines 267-310,
update Subscribe and PublishEvent so subscriber registration, removal,
snapshotting, and channel ownership follow one synchronized lifecycle without
sending to a closed channel; in internal/api/routes.go lines 274-296, stop
closing the registered channel and stop writing to Gin’s response from a
separate goroutine, leaving response writes to the request-handling goroutine
that owns them.
internal/api/routes.go-127-138 (1)

127-138: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Avoid persisting an orphan job when queue submission fails.

CreateJob commits the job before Submit. If submission fails, the API returns 500 while the job remains CREATED; client retries can create duplicates that will never run. Use a transactional outbox, or compensate by marking the persisted job failed/cancelled before returning.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/api/routes.go` around lines 127 - 138, Update the job-creation flow
around CreateJob and pool.Submit so a failed queue submission cannot leave the
persisted job in CREATED state. Use the existing transactional outbox mechanism
if available; otherwise, compensate by marking resp.ID failed or cancelled
before returning the 500 response, while preserving the current success path.
internal/jobs/service.go-253-262 (1)

253-262: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Job events are not routed across the API/worker process boundary. Item events lose their job ID, Redis publication is skipped without local subscribers, and the SSE route never consumes Redis.

  • internal/jobs/service.go#L253-L262: provide the media item's actual job ID.
  • internal/jobs/service.go#L287-L315: publish to Redis regardless of local subscriber count.
  • internal/api/routes.go#L245-L289: subscribe the SSE connection to the job's Redis event channel.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/jobs/service.go` around lines 253 - 262, Route item events across
processes by resolving and passing the media item’s actual job ID in
UpdateMediaItemStatus at internal/jobs/service.go:253-262; update PublishEvent
at internal/jobs/service.go:287-315 to publish to Redis regardless of local
subscriber count; update the SSE handler at internal/api/routes.go:245-289 to
subscribe each connection to the job’s Redis event channel.
docs/openapi.yaml-196-201 (1)

196-201: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Match the cancellation response to the implemented payload.

The route returns {"message":"job cancelled"}, not JobResponse. Document that object or change the handler to return the updated job.

Possible contract fix
         '200':
           description: Job cancelled
           content:
             application/json:
               schema:
-                $ref: '`#/components/schemas/JobResponse`'
+                type: object
+                required: [message]
+                properties:
+                  message:
+                    type: string
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/openapi.yaml` around lines 196 - 201, Update the OpenAPI 200 response
schema for the job-cancellation route to match the implemented payload: document
an object containing the cancellation message instead of referencing
JobResponse. Keep the existing cancellation response description and content
type unchanged.
internal/redis/redis.go-130-141 (1)

130-141: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Set the rate-limit expiry atomically. INCR followed by EXPIRE can leave a permanent counter if the second call fails or is skipped; use a Lua script or a Redis transaction so the TTL is always applied with the increment.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/redis/redis.go` around lines 130 - 141, Update Client.RateLimitCheck
to perform the counter increment and expiry assignment atomically, using a Lua
script or Redis transaction so every successful increment receives the requested
window TTL even if the counter is newly created or the expiry operation would
otherwise fail; preserve the existing limit comparison and error propagation.
internal/redis/redis.go-70-109 (1)

70-109: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Make the progress update atomic. GET happens outside the transaction and nothing is watched, so concurrent callers can overwrite each other and lose increments. Use WATCH with retries or move the read-modify-write into a Lua script.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/redis/redis.go` around lines 70 - 109, Update
Client.IncrementProgress so the read-modify-write operation is atomic: watch the
progress key and perform the GET, increment, and SET within a retryable
transaction, or replace the flow with an equivalent Lua script. Preserve the
existing missing-key, serialization, expiration, and TxFailedErr handling while
ensuring concurrent callers cannot lose increments.
internal/queue/queue.go-156-164 (1)

156-164: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Keep deliveries unacked until worker processing succeeds.

Line 158 ACKs after only buffering the task, so a worker crash loses the persistent RabbitMQ message. The default branch also immediately requeues when the buffer is full, potentially creating a redelivery loop. Pass a delivery/envelope to the worker and ACK/NACK upon processing completion; block for backpressure instead of using default.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/queue/queue.go` around lines 156 - 164, Update the queue dispatch
flow around the delivery select and worker processing so workers receive the
full delivery/envelope rather than only the message, and acknowledge it only
after processing succeeds; NACK with requeue on processing failure or context
cancellation. Remove the default branch and block on sending to the worker
channel to provide backpressure without premature requeue loops.
internal/queue/queue.go-140-169 (1)

140-169: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Expose the consumer lifetime to its caller.

Consume returns nil immediately and hides delivery-channel closure inside a detached goroutine. The worker process therefore cannot detect or recover from a broker/channel failure. Run this loop synchronously—the caller already invokes Consume in a goroutine—and return when msgs closes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/queue/queue.go` around lines 140 - 169, The Consume method currently
detaches its delivery loop and returns immediately, preventing callers from
observing broker or channel termination. Remove the inner goroutine around the
select loop in Consume, run the loop synchronously, and return when msgs closes
or ctx.Done() fires; preserve the existing acknowledgment and rejection
behavior.
.github/workflows/ci.yml-18-19 (1)

18-19: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Disable checkout credential persistence in every job.

Subsequent test, build, and third-party action steps do not require the checkout token to remain in Git configuration.

Proposed configuration
       - name: Checkout code
         uses: actions/checkout@v4
+        with:
+          persist-credentials: false

Also applies to: 75-76, 113-114, 145-146

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 18 - 19, Update every
actions/checkout@v4 step in the workflow, including the jobs at the referenced
locations, to disable persisted checkout credentials by configuring
persist-credentials as false. Apply the same setting consistently to all
checkout actions without changing unrelated job steps.

Source: Linters/SAST tools

cmd/worker/main.go-53-59 (1)

53-59: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Do not continue running after the queue consumer fails.

If Consume cannot start, the error is only logged and Line 59 still reports a started worker that will never process jobs. Supervise the consumer error alongside the signal channel, then cancel and enter shutdown or retry with backoff.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/worker/main.go` around lines 53 - 59, Update the queue consumer
supervision around Consume and the worker startup flow so a Consume failure is
propagated instead of only logged; coordinate the consumer error with the signal
channel, cancel the worker context, and enter the existing shutdown or
retry-with-backoff path. Do not report “Worker started” after startup has
failed, and preserve normal signal-driven shutdown behavior.
internal/worker/pool.go-148-151 (1)

148-151: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Do not close the externally writable task channel.

JobChan exposes taskQueue to the queue consumer, so closing it can race with a send and panic. It also makes the second Stop call from cmd/worker/main.go deterministically panic. Cancellation already stops the workers; removing close makes shutdown idempotent.

Proposed fix
 func (p *Pool) Stop() {
 	p.cancel()
-	close(p.taskQueue)
 	p.wg.Wait()
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/worker/pool.go` around lines 148 - 151, Update Pool.Stop to remove
the close(p.taskQueue) call; JobChan exposes taskQueue for external writes, so
cancellation via p.cancel followed by p.wg.Wait should be the only shutdown
behavior and must remain safe for repeated Stop calls.
internal/worker/pool.go-87-95 (1)

87-95: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Fail the job when media-item initialization fails.

Errors from SetJobTotalItems, CreateMediaItems, and the second GetMediaItems call are discarded. If creation fails, the loop is skipped and Lines 117-119 still mark the empty job completed.

Proposed handling
-		p.jobService.SetJobTotalItems(ctx, jobID, 10)
+		if err := p.jobService.SetJobTotalItems(ctx, jobID, 10); err != nil {
+			p.jobService.UpdateJobStatus(ctx, jobID, "FAILED", err.Error())
+			return
+		}
...
-			p.jobService.CreateMediaItems(ctx, jobID, []jobs.MediaItemInput{
+			if err := p.jobService.CreateMediaItems(ctx, jobID, []jobs.MediaItemInput{
 				{Title: fmt.Sprintf("Media Item %d", i), SourceURL: fmt.Sprintf("https://example.com/media/%d.mp4", i)},
-			})
+			}); err != nil {
+				p.jobService.UpdateJobStatus(ctx, jobID, "FAILED", err.Error())
+				return
+			}
 		}
-		items, _, _ = p.jobService.GetMediaItems(ctx, "", jobID, "", 100, 0)
+		items, _, err = p.jobService.GetMediaItems(ctx, "", jobID, "", 100, 0)
+		if err != nil {
+			p.jobService.UpdateJobStatus(ctx, jobID, "FAILED", err.Error())
+			return
+		}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/worker/pool.go` around lines 87 - 95, Handle errors from
SetJobTotalItems, each CreateMediaItems call, and the second GetMediaItems
invocation within the len(items) == 0 initialization path. On any failure, fail
or return the job through the existing worker error-handling flow so an empty or
partially initialized job cannot be marked completed; only continue to
completion after successful initialization and reload.
.github/workflows/ci.yml-9-12 (1)

9-12: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Restrict the workflow token to read-only repository access.

No permissions block is defined, so repository defaults apply across every job. These jobs only require source checkout.

Proposed fix
 env:
   GO_VERSION: '1.23'
   DATABASE_URL: postgres://streamforge:streamforge@localhost:5432/streamforge?sslmode=disable
 
+permissions:
+  contents: read
+
 jobs:
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 9 - 12, Add a top-level permissions
block in the CI workflow granting repository contents read-only access, so all
jobs—including source checkout—use the least-privileged token scope. Keep the
existing environment variables and job behavior unchanged.

Source: Linters/SAST tools

internal/worker/pool.go-97-114 (1)

97-114: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Remove the duplicate Redis progress increment and event.

Lines 110-112 increment and publish directly, then UpdateJobProgress increments Redis and publishes another progress event internally. Each item therefore advances Redis twice and emits duplicate events. Let jobs.Service.UpdateJobProgress own this operation.

Proposed fix
-	for _, item := range items {
+	for completed, item := range items {
...
-		updated, _ := p.redis.IncrementProgress(ctx, jobID)
-		if updated != nil {
-			p.redis.PublishJobEvent(ctx, jobID, "progress", updated)
-			p.jobService.UpdateJobProgress(ctx, jobID, updated.Completed)
+		if err := p.jobService.UpdateJobProgress(ctx, jobID, completed+1); err != nil {
+			p.jobService.UpdateJobStatus(ctx, jobID, "FAILED", err.Error())
+			return
 		}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/worker/pool.go` around lines 97 - 114, Remove the direct
IncrementProgress and PublishJobEvent calls from the item-processing loop,
leaving jobs.Service.UpdateJobProgress as the sole owner of Redis progress
updates and progress event publication. Preserve the existing completed-count
value passed to UpdateJobProgress in the worker flow.
.github/workflows/ci.yml-87-92 (1)

87-92: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Remove the host-side Redis and RabbitMQ checks. ubuntu-latest doesn’t include redis-cli or rabbitmq-diagnostics, and the service health checks already gate the job until those containers are ready.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 87 - 92, Update the “Wait for
services” step in the CI workflow to remove the host-side redis-cli and
rabbitmq-diagnostics commands, leaving only the necessary PostgreSQL check and
existing wait behavior because service health checks already verify Redis and
RabbitMQ readiness.
.golangci.yml-1-1 (1)

1-1: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Migrate .golangci.yml to golangci-lint v2 and pin the workflow version
.golangci.yml is still v1-style, and .github/workflows/ci.yml uses version: latest; that can break lint runs as soon as the action picks up v2. Migrate the config and pin CI to the same v2 release.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.golangci.yml at line 1, Migrate the .golangci.yml configuration from v1
syntax to the v2 schema, preserving the existing lint behavior and settings.
Update the version value in the CI workflow to pin the exact golangci-lint v2
release used by the migrated configuration, replacing latest.
cmd/api/main.go-53-65 (1)

53-65: 🚀 Performance & Scalability | 🟠 Major | 🏗️ Heavy lift

Remove worker pool execution and queue consumption from the API entrypoint.

The cmd/api/main.go file initializes a workerPool and starts consuming jobs from RabbitMQ. Because there is a dedicated cmd/worker service designed for this exact purpose, having the API server also act as a consumer defeats the separation of concerns. The API instances will compete for messages on QUEUE_NAME: media.processing.queue. If these jobs involve heavy media processing, it will starve the API nodes of CPU resources and cause severe latency spikes for HTTP requests.

Please remove the worker pool initialization and queue consumption routines from the API process. If the workerPool parameter in setupRouter (Line 67) and api.RegisterRoutes is not genuinely needed for HTTP handlers, it should be removed from their signatures as well.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmd/api/main.go` around lines 53 - 65, Remove worker pool creation, lifecycle
management, and queue consumption from the API entrypoint around worker.NewPool,
workerPool.Start, workerPool.Stop, and queueSvc. Then remove workerPool from
setupRouter and api.RegisterRoutes signatures and call sites if no HTTP handler
uses it, preserving only dependencies required by the API server.
Dockerfile-14-25 (1)

14-25: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Run containers as a non-root user. Both Dockerfiles run their final Alpine stages as the default root user, violating container security best practices (flagged by Trivy DS-0002).

  • Dockerfile#L14-L25: Create a non-root user/group, switch to it using USER, and update the COPY instructions to assign ownership. For example:
    RUN apk add --no-cache ca-certificates tzdata && \
        addgroup -S appgroup && adduser -S appuser -G appgroup
    
    WORKDIR /app
    
    COPY --chown=appuser:appgroup --from=builder /streamforge .
    COPY --chown=appuser:appgroup --from=builder /app/docs ./docs
    
    USER appuser
  • Dockerfile.worker#L14-L22: Apply the same user creation and switch. Update the COPY instruction to:
    COPY --chown=appuser:appgroup --from=builder /streamforge-worker .
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Dockerfile` around lines 14 - 25, Run both final Alpine stages as a non-root
user. In Dockerfile lines 14-25, create appuser/appgroup, add
--chown=appuser:appgroup to both COPY instructions, and switch with USER
appuser; apply the same user creation and USER appuser switch in
Dockerfile.worker lines 14-22, adding ownership to its streamforge-worker COPY.

Source: Linters/SAST tools

docker-compose.yml-60-64 (1)

60-64: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Use REDIS_HOST / REDIS_PORT and RABBITMQ_HOST / RABBITMQ_PORT here.
internal/config/config.go reads those names, so REDIS_ADDR / RABBITMQ_URL are ignored and the containers fall back to localhost, breaking Redis/RabbitMQ connectivity in compose.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docker-compose.yml` around lines 60 - 64, Update the compose environment
configuration to use the REDIS_HOST and REDIS_PORT variables consumed by the
config loader instead of REDIS_ADDR, and replace RABBITMQ_URL with RABBITMQ_HOST
and RABBITMQ_PORT. Set the values to the service hostnames and ports so the
containers connect to Redis and RabbitMQ rather than localhost.
🟡 Minor comments (2)
internal/auth/service.go-131-134 (1)

131-134: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Validate only HS256 tokens

ValidateToken should reject tokens signed with anything other than HS256, since generateToken always issues that algorithm. Pinning the parser keeps the auth contract explicit and avoids accepting unexpected HMAC variants.

Proposed fix
-token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) {
-	return s.jwtKey, nil
-})
+token, err := jwt.ParseWithClaims(
+	tokenString,
+	&Claims{},
+	func(token *jwt.Token) (interface{}, error) {
+		return s.jwtKey, nil
+	},
+	jwt.WithValidMethods([]string{jwt.SigningMethodHS256.Alg()}),
+)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/auth/service.go` around lines 131 - 134, Update ValidateToken’s
jwt.ParseWithClaims key-function to verify that token.Method is the HS256
signing method before returning s.jwtKey; return an error for any other
algorithm, preserving the existing validation flow for HS256 tokens.
README.md-75-77 (1)

75-77: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Correct the database password in the migration command.

The local development instructions instruct the user to run migrations with the streamforge password, but the docker-compose.yml file configures the local Postgres instance with streamforge_dev. This mismatch will cause the migration command to fail with an authentication error.

🩹 Proposed fix
 # Run migrations
-go run github.com/golang-migrate/migrate/v4/cmd/migrate@latest -path migrations -database "postgres://streamforge:streamforge@localhost:5432/streamforge?sslmode=disable" up
+go run github.com/golang-migrate/migrate/v4/cmd/migrate@latest -path migrations -database "postgres://streamforge:streamforge_dev@localhost:5432/streamforge?sslmode=disable" up
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 75 - 77, Update the migration command in the README
to use the local Postgres password configured by docker-compose.yml, replacing
the `streamforge` password with `streamforge_dev` while preserving the rest of
the connection string.
🧹 Nitpick comments (10)
migrations/000001_init.down.sql (3)

21-22: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove redundant DROP INDEX statements.

Indices are automatically dropped when the table they belong to is dropped. You can safely remove the DROP INDEX statements.

♻️ Proposed refactor
 -- Migration: 000001_drop_users_table.down.sql
-DROP INDEX IF EXISTS idx_users_email;
 DROP TABLE IF EXISTS users;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@migrations/000001_init.down.sql` around lines 21 - 22, Remove the redundant
DROP INDEX statement for idx_users_email from the migration rollback, leaving
the DROP TABLE IF EXISTS users operation unchanged.

8-10: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove redundant DROP INDEX statements.

Indices are automatically dropped when the table they belong to is dropped. You can safely remove the DROP INDEX statements.

♻️ Proposed refactor
 -- Migration: 000003_drop_media_items_table.down.sql
-DROP INDEX IF EXISTS idx_media_items_status;
-DROP INDEX IF EXISTS idx_media_items_job_id;
 DROP TABLE IF EXISTS media_items;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@migrations/000001_init.down.sql` around lines 8 - 10, Remove the redundant
DROP INDEX statements from the migration rollback, keeping the DROP TABLE IF
EXISTS media_items statement in the down migration.

14-17: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove redundant DROP INDEX statements.

Indices are automatically dropped when the table they belong to is dropped. You can safely remove the DROP INDEX statements.

♻️ Proposed refactor
 -- Migration: 000002_drop_jobs_table.down.sql
-DROP INDEX IF EXISTS idx_jobs_created_at;
-DROP INDEX IF EXISTS idx_jobs_status;
-DROP INDEX IF EXISTS idx_jobs_user_id;
 DROP TABLE IF EXISTS jobs;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@migrations/000001_init.down.sql` around lines 14 - 17, Remove the redundant
DROP INDEX statements for idx_jobs_created_at, idx_jobs_status, and
idx_jobs_user_id from the migration rollback, leaving DROP TABLE IF EXISTS jobs
as the sole cleanup operation.
internal/api/middleware/middleware.go (1)

70-74: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Do not silently discard every access log.

This middleware performs Gin's logging work but always emits an empty record, removing request-level observability. Use the standard logger or output a structured record.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/api/middleware/middleware.go` around lines 70 - 74, Update the
Logger function’s Gin formatter so it returns a meaningful request log record
instead of an empty string. Reuse Gin’s standard logger output or format the
available LogFormatterParams into a structured record, preserving request-level
observability.
internal/auth/service_test.go (1)

27-81: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Add a regression test for unsupported signing algorithms.

After restricting validation to HS256, generate an HS384/HS512 token with the same secret and assert ErrInvalidToken; otherwise the security contract can regress unnoticed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/auth/service_test.go` around lines 27 - 81, Extend
TestService_ValidateToken_Invalid or add a focused regression test using the
same secret to create an HS384 or HS512 token, then validate it through the
service and assert ErrInvalidToken with nil claims. Keep the existing HS256
generation and validation coverage unchanged, and exercise the algorithm
restriction in ValidateToken.
internal/middleware/middleware.go (1)

69-73: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Implement logging or remove this misleading middleware.

Logger() currently provides no request logging. Callers can believe logging is enabled while receiving no access records.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/middleware/middleware.go` around lines 69 - 73, Update the Logger
middleware function to emit request access logs after c.Next(), including the
relevant request and response details, or remove Logger and its callers if
logging is not intended. Ensure the resulting middleware does not advertise
enabled logging without producing records.
internal/jobs/service_test.go (1)

14-101: 🗄️ Data Integrity & Integration | 🔵 Trivial | 🏗️ Heavy lift

Cover the service's state-transition and event paths.

Add tests for partial media-item failures, cancellation dependency failures, empty/local subscriber sets, correct job-channel routing, and concurrent subscribe/unsubscribe/publish. The current mapping tests cannot detect the production issues in these paths.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/jobs/service_test.go` around lines 14 - 101, Extend the service
tests beyond NewService and the mapping helpers to cover state transitions and
event handling: partial media-item failures, cancellation dependency errors,
empty and local subscriber sets, correct per-job channel routing, and concurrent
subscribe/unsubscribe/publish operations. Exercise the service’s existing
failure, cancellation, subscription, and publishing methods using appropriate
mocks or test doubles, and assert both returned errors and resulting
state/events.
README.md (2)

131-131: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Specify a language for the fenced code block.

Add the text language identifier to resolve markdown lint warnings.

🎨 Proposed fix
-```
+```text
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` at line 131, Update the fenced code block in README.md by adding
the text language identifier to its opening fence, preserving the block’s
existing contents and closing fence.

Source: Linters/SAST tools


7-7: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Specify a language for the fenced code block.

Add a language identifier (e.g., text or mermaid) to the fenced code block to improve syntax highlighting and resolve markdown lint warnings.

🎨 Proposed fix
-```
+```text
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` at line 7, Specify a language identifier on the fenced code block
in the README, using the appropriate value such as text or mermaid, while
preserving the block’s existing content.

Source: Linters/SAST tools

Makefile (1)

1-2: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Declare all targets in .PHONY and add a default all target.

The clean, generate, and tidy targets are missing from .PHONY, which could cause failures if files with those names are created. Additionally, it is a standard convention to provide an all target.

🛠️ Proposed fix
-.PHONY: help build test lint docker-up docker-down migrate-up migrate-down fmt vet
+.PHONY: all help build test lint docker-up docker-down migrate-up migrate-down fmt vet generate tidy clean
+
+all: build
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Makefile` around lines 1 - 2, Update the Makefile’s .PHONY declaration to
include clean, generate, and tidy, and add all as the default target. Ensure all
depends on the appropriate primary build targets while preserving the existing
target behavior.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@go.mod`:
- Line 9: Update the github.com/jackc/pgx/v5 dependency in go.mod from v5.5.5 to
v5.9.2 or a later patched release, and refresh the corresponding go.sum entries
if required.
- Line 13: Update the golang.org/x/crypto dependency declaration in go.mod from
v0.29.0 to v0.52.0 or a newer secure release, and refresh the module checksums
as needed while preserving dependency consistency.

In `@internal/database/repository.go`:
- Line 112: Cast every listed string parameter to UUID in the SQL queries within
internal/database/repository.go: update the predicates at lines 112, 130, 164,
181, 189, 197, 242, 260, 294, 311, 319, and 327 by appending the PostgreSQL
::uuid cast to their respective placeholders, while preserving the existing
repository methods and parameter ordering.
- Around line 79-89: Update the Job.ErrorMessage field from string to *string so
nullable error_message database values can be scanned safely, preserving the
existing field and struct behavior otherwise.
- Around line 202-213: Update the MediaItem struct so the nullable database
columns Title, SizeBytes, and ErrorMessage use pointer types instead of
primitive string or int64 types, allowing NULL values to be scanned safely while
leaving non-nullable fields unchanged.

---

Major comments:
In @.github/workflows/ci.yml:
- Around line 18-19: Update every actions/checkout@v4 step in the workflow,
including the jobs at the referenced locations, to disable persisted checkout
credentials by configuring persist-credentials as false. Apply the same setting
consistently to all checkout actions without changing unrelated job steps.
- Around line 9-12: Add a top-level permissions block in the CI workflow
granting repository contents read-only access, so all jobs—including source
checkout—use the least-privileged token scope. Keep the existing environment
variables and job behavior unchanged.
- Around line 87-92: Update the “Wait for services” step in the CI workflow to
remove the host-side redis-cli and rabbitmq-diagnostics commands, leaving only
the necessary PostgreSQL check and existing wait behavior because service health
checks already verify Redis and RabbitMQ readiness.

In @.golangci.yml:
- Line 1: Migrate the .golangci.yml configuration from v1 syntax to the v2
schema, preserving the existing lint behavior and settings. Update the version
value in the CI workflow to pin the exact golangci-lint v2 release used by the
migrated configuration, replacing latest.

In `@cmd/api/main.go`:
- Around line 53-65: Remove worker pool creation, lifecycle management, and
queue consumption from the API entrypoint around worker.NewPool,
workerPool.Start, workerPool.Stop, and queueSvc. Then remove workerPool from
setupRouter and api.RegisterRoutes signatures and call sites if no HTTP handler
uses it, preserving only dependencies required by the API server.

In `@cmd/worker/main.go`:
- Around line 53-59: Update the queue consumer supervision around Consume and
the worker startup flow so a Consume failure is propagated instead of only
logged; coordinate the consumer error with the signal channel, cancel the worker
context, and enter the existing shutdown or retry-with-backoff path. Do not
report “Worker started” after startup has failed, and preserve normal
signal-driven shutdown behavior.

In `@docker-compose.yml`:
- Around line 60-64: Update the compose environment configuration to use the
REDIS_HOST and REDIS_PORT variables consumed by the config loader instead of
REDIS_ADDR, and replace RABBITMQ_URL with RABBITMQ_HOST and RABBITMQ_PORT. Set
the values to the service hostnames and ports so the containers connect to Redis
and RabbitMQ rather than localhost.

In `@Dockerfile`:
- Around line 14-25: Run both final Alpine stages as a non-root user. In
Dockerfile lines 14-25, create appuser/appgroup, add --chown=appuser:appgroup to
both COPY instructions, and switch with USER appuser; apply the same user
creation and USER appuser switch in Dockerfile.worker lines 14-22, adding
ownership to its streamforge-worker COPY.

In `@docs/openapi.yaml`:
- Around line 196-201: Update the OpenAPI 200 response schema for the
job-cancellation route to match the implemented payload: document an object
containing the cancellation message instead of referencing JobResponse. Keep the
existing cancellation response description and content type unchanged.

In `@internal/api/middleware/middleware.go`:
- Around line 76-85: The Recovery function in
internal/api/middleware/middleware.go must stop exposing recovered panic
details: log the recovered value server-side, return only the generic
internal-server-error response, and return immediately after writing it. Apply
the same behavior in internal/middleware/middleware.go, updating its
corresponding recovery middleware as well.

In `@internal/api/routes.go`:
- Around line 127-138: Update the job-creation flow around CreateJob and
pool.Submit so a failed queue submission cannot leave the persisted job in
CREATED state. Use the existing transactional outbox mechanism if available;
otherwise, compensate by marking resp.ID failed or cancelled before returning
the 500 response, while preserving the current success path.

In `@internal/config/config.go`:
- Around line 61-63: Update RabbitMQConfig.URI to construct the AMQP URI with
net/url rather than concatenating credential strings. URL-encode User and
Password while preserving the existing Host, Port, and path components so
credentials containing reserved characters produce a valid connection URI.
- Around line 38-40: Update DatabaseConfig.DSN to construct the PostgreSQL
connection string with net/url, ensuring User and Password are URL-encoded so
special characters remain valid credentials. Preserve the existing host, port,
and database name components.

In `@internal/database/postgres.go`:
- Around line 20-22: Update Config.DSN to construct the PostgreSQL connection
string using net/url so User and Password are URL-encoded before insertion,
while preserving the existing host, port, and database name values.

In `@internal/jobs/service.go`:
- Around line 230-250: Update CreateMediaItems to execute SetJobTotalItems and
all CreateMediaItem calls within one repository transaction, propagating errors
from each operation. Move the total update into the transaction alongside the
inserts so any failure rolls back the entire operation and avoids partial item
sets.
- Around line 159-167: Update the cancellation flow around UpdateJobStatus to
check and propagate errors from redis.SetJobStatus, redis.DeleteProgress, and
repo.UpdateMediaItemsStatus instead of discarding them. Define and apply the
appropriate transactional or compensating behavior so the method does not return
success when any dependent update fails and cannot leave inconsistent job, item,
or cache state.
- Around line 267-310: The SSE subscriber lifecycle has competing owners,
allowing channel sends, removal, closure, and response writes to race or panic.
In internal/jobs/service.go lines 267-310, update Subscribe and PublishEvent so
subscriber registration, removal, snapshotting, and channel ownership follow one
synchronized lifecycle without sending to a closed channel; in
internal/api/routes.go lines 274-296, stop closing the registered channel and
stop writing to Gin’s response from a separate goroutine, leaving response
writes to the request-handling goroutine that owns them.
- Around line 253-262: Route item events across processes by resolving and
passing the media item’s actual job ID in UpdateMediaItemStatus at
internal/jobs/service.go:253-262; update PublishEvent at
internal/jobs/service.go:287-315 to publish to Redis regardless of local
subscriber count; update the SSE handler at internal/api/routes.go:245-289 to
subscribe each connection to the job’s Redis event channel.

In `@internal/queue/queue.go`:
- Around line 156-164: Update the queue dispatch flow around the delivery select
and worker processing so workers receive the full delivery/envelope rather than
only the message, and acknowledge it only after processing succeeds; NACK with
requeue on processing failure or context cancellation. Remove the default branch
and block on sending to the worker channel to provide backpressure without
premature requeue loops.
- Around line 140-169: The Consume method currently detaches its delivery loop
and returns immediately, preventing callers from observing broker or channel
termination. Remove the inner goroutine around the select loop in Consume, run
the loop synchronously, and return when msgs closes or ctx.Done() fires;
preserve the existing acknowledgment and rejection behavior.

In `@internal/redis/redis.go`:
- Around line 130-141: Update Client.RateLimitCheck to perform the counter
increment and expiry assignment atomically, using a Lua script or Redis
transaction so every successful increment receives the requested window TTL even
if the counter is newly created or the expiry operation would otherwise fail;
preserve the existing limit comparison and error propagation.
- Around line 70-109: Update Client.IncrementProgress so the read-modify-write
operation is atomic: watch the progress key and perform the GET, increment, and
SET within a retryable transaction, or replace the flow with an equivalent Lua
script. Preserve the existing missing-key, serialization, expiration, and
TxFailedErr handling while ensuring concurrent callers cannot lose increments.

In `@internal/worker/pool.go`:
- Around line 148-151: Update Pool.Stop to remove the close(p.taskQueue) call;
JobChan exposes taskQueue for external writes, so cancellation via p.cancel
followed by p.wg.Wait should be the only shutdown behavior and must remain safe
for repeated Stop calls.
- Around line 87-95: Handle errors from SetJobTotalItems, each CreateMediaItems
call, and the second GetMediaItems invocation within the len(items) == 0
initialization path. On any failure, fail or return the job through the existing
worker error-handling flow so an empty or partially initialized job cannot be
marked completed; only continue to completion after successful initialization
and reload.
- Around line 97-114: Remove the direct IncrementProgress and PublishJobEvent
calls from the item-processing loop, leaving jobs.Service.UpdateJobProgress as
the sole owner of Redis progress updates and progress event publication.
Preserve the existing completed-count value passed to UpdateJobProgress in the
worker flow.

In `@migrations/000001_init.up.sql`:
- Around line 1-73: Keep the consolidated schema in
migrations/000001_init.up.sql (lines 1-73), first aligning its columns and ENUM
values with the struct models, including size_bytes versus size. Delete the
redundant migrations/000001_create_users_table.up.sql (lines 1-15),
migrations/000002_create_jobs_table.up.sql (lines 1-22), and
migrations/000003_create_media_items_table.up.sql (lines 1-22) so only the
consolidated initialization migration defines these tables.

---

Minor comments:
In `@internal/auth/service.go`:
- Around line 131-134: Update ValidateToken’s jwt.ParseWithClaims key-function
to verify that token.Method is the HS256 signing method before returning
s.jwtKey; return an error for any other algorithm, preserving the existing
validation flow for HS256 tokens.

In `@README.md`:
- Around line 75-77: Update the migration command in the README to use the local
Postgres password configured by docker-compose.yml, replacing the `streamforge`
password with `streamforge_dev` while preserving the rest of the connection
string.

---

Nitpick comments:
In `@internal/api/middleware/middleware.go`:
- Around line 70-74: Update the Logger function’s Gin formatter so it returns a
meaningful request log record instead of an empty string. Reuse Gin’s standard
logger output or format the available LogFormatterParams into a structured
record, preserving request-level observability.

In `@internal/auth/service_test.go`:
- Around line 27-81: Extend TestService_ValidateToken_Invalid or add a focused
regression test using the same secret to create an HS384 or HS512 token, then
validate it through the service and assert ErrInvalidToken with nil claims. Keep
the existing HS256 generation and validation coverage unchanged, and exercise
the algorithm restriction in ValidateToken.

In `@internal/jobs/service_test.go`:
- Around line 14-101: Extend the service tests beyond NewService and the mapping
helpers to cover state transitions and event handling: partial media-item
failures, cancellation dependency errors, empty and local subscriber sets,
correct per-job channel routing, and concurrent subscribe/unsubscribe/publish
operations. Exercise the service’s existing failure, cancellation, subscription,
and publishing methods using appropriate mocks or test doubles, and assert both
returned errors and resulting state/events.

In `@internal/middleware/middleware.go`:
- Around line 69-73: Update the Logger middleware function to emit request
access logs after c.Next(), including the relevant request and response details,
or remove Logger and its callers if logging is not intended. Ensure the
resulting middleware does not advertise enabled logging without producing
records.

In `@Makefile`:
- Around line 1-2: Update the Makefile’s .PHONY declaration to include clean,
generate, and tidy, and add all as the default target. Ensure all depends on the
appropriate primary build targets while preserving the existing target behavior.

In `@migrations/000001_init.down.sql`:
- Around line 21-22: Remove the redundant DROP INDEX statement for
idx_users_email from the migration rollback, leaving the DROP TABLE IF EXISTS
users operation unchanged.
- Around line 8-10: Remove the redundant DROP INDEX statements from the
migration rollback, keeping the DROP TABLE IF EXISTS media_items statement in
the down migration.
- Around line 14-17: Remove the redundant DROP INDEX statements for
idx_jobs_created_at, idx_jobs_status, and idx_jobs_user_id from the migration
rollback, leaving DROP TABLE IF EXISTS jobs as the sole cleanup operation.

In `@README.md`:
- Line 131: Update the fenced code block in README.md by adding the text
language identifier to its opening fence, preserving the block’s existing
contents and closing fence.
- Line 7: Specify a language identifier on the fenced code block in the README,
using the appropriate value such as text or mermaid, while preserving the
block’s existing content.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 45ae74b0-c4b1-4540-b0ea-1c1fa3f92de6

📥 Commits

Reviewing files that changed from the base of the PR and between 15004dd and 9d4116f.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (33)
  • .gitattributes
  • .github/workflows/ci.yml
  • .golangci.yml
  • Dockerfile
  • Dockerfile.worker
  • Makefile
  • README.md
  • cmd/api/main.go
  • cmd/worker/main.go
  • docker-compose.yml
  • docs/openapi.yaml
  • go.mod
  • internal/api/middleware/middleware.go
  • internal/api/routes.go
  • internal/auth/service.go
  • internal/auth/service_test.go
  • internal/config/config.go
  • internal/config/config_test.go
  • internal/database/postgres.go
  • internal/database/repository.go
  • internal/jobs/service.go
  • internal/jobs/service_test.go
  • internal/middleware/middleware.go
  • internal/queue/queue.go
  • internal/queue/queue_test.go
  • internal/redis/redis.go
  • internal/worker/pool.go
  • internal/worker/pool_test.go
  • migrations/000001_create_users_table.up.sql
  • migrations/000001_init.down.sql
  • migrations/000001_init.up.sql
  • migrations/000002_create_jobs_table.up.sql
  • migrations/000003_create_media_items_table.up.sql

Comment thread go.mod
github.com/gin-gonic/gin v1.10.0
github.com/golang-jwt/jwt/v5 v5.3.1
github.com/google/uuid v1.6.0
github.com/jackc/pgx/v5 v5.5.5

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find the latest secure version of pgx/v5
gh api graphql -f query='
{
  securityVulnerabilities(first: 5, ecosystem: GO, package: "github.com/jackc/pgx/v5") {
    nodes {
      advisory {
        summary
      }
      vulnerableVersionRange
      firstPatchedVersion {
        identifier
      }
    }
  }
}'

Repository: amirmalekian/streamforge

Length of output: 1057


Upgrade github.com/jackc/pgx/v5 to 5.9.2 or later. Version 5.5.5 is still covered by known SQL-injection and memory-safety advisories, so it needs a patched release.

🧰 Tools
🪛 OSV Scanner (2.4.0)

[CRITICAL] 9-9: github.com/jackc/pgx/v5 5.5.5: CVE-2026-33815 in github.com/jackc/pgx

(GO-2026-4771)


[CRITICAL] 9-9: github.com/jackc/pgx/v5 5.5.5: CVE-2026-33816 in github.com/jackc/pgx

(GO-2026-4772)


[CRITICAL] 9-9: github.com/jackc/pgx/v5 5.5.5: SQL Injection via placeholder confusion with dollar quoted string literals in github.com/jackc/pgx

(GO-2026-5004)


[CRITICAL] 9-9: github.com/jackc/pgx/v5 5.5.5: Memory-safety vulnerability in github.com/jackc/pgx/v5.

(GHSA-9jj7-4m8r-rfcm)


[CRITICAL] 9-9: github.com/jackc/pgx/v5 5.5.5: pgx: SQL Injection via placeholder confusion with dollar quoted string literals

(GHSA-j88v-2chj-qfwx)


[CRITICAL] 9-9: github.com/jackc/pgx/v5 5.5.5: pgx contains memory-safety vulnerability

(GHSA-xgrm-4fwx-7qm8)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@go.mod` at line 9, Update the github.com/jackc/pgx/v5 dependency in go.mod
from v5.5.5 to v5.9.2 or a later patched release, and refresh the corresponding
go.sum entries if required.

Source: Linters/SAST tools

Comment thread go.mod
github.com/rabbitmq/amqp091-go v1.10.0
github.com/redis/go-redis/v9 v9.5.1
github.com/stretchr/testify v1.9.0
golang.org/x/crypto v0.29.0

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find the latest secure version of golang.org/x/crypto
gh api graphql -f query='
{
  securityVulnerabilities(first: 5, ecosystem: GO, package: "golang.org/x/crypto") {
    nodes {
      advisory {
        summary
      }
      vulnerableVersionRange
      firstPatchedVersion {
        identifier
      }
    }
  }
}'

Repository: amirmalekian/streamforge

Length of output: 1134


Upgrade golang.org/x/crypto to v0.52.0 or newer. v0.29.0 falls within the vulnerable range for multiple advisories, including auth bypass and panic/DoS issues.

🧰 Tools
🪛 OSV Scanner (2.4.0)

[CRITICAL] 13-13: golang.org/x/crypto 0.29.0: Misuse of connection.serverAuthenticate may cause authorization bypass in golang.org/x/crypto

(GO-2024-3321)


[CRITICAL] 13-13: golang.org/x/crypto 0.29.0: Potential denial of service in golang.org/x/crypto

(GO-2025-3487)


[CRITICAL] 13-13: golang.org/x/crypto 0.29.0: Potential denial of service in golang.org/x/crypto/ssh/agent

(GO-2025-4116)


[CRITICAL] 13-13: golang.org/x/crypto 0.29.0: Unbounded memory consumption in golang.org/x/crypto/ssh

(GO-2025-4134)


[CRITICAL] 13-13: golang.org/x/crypto 0.29.0: Malformed constraint may cause denial of service in golang.org/x/crypto/ssh/agent

(GO-2025-4135)


[CRITICAL] 13-13: golang.org/x/crypto 0.29.0: Invoking key constraints not enforced in golang.org/x/crypto/ssh/agent

(GO-2026-5005)


[CRITICAL] 13-13: golang.org/x/crypto 0.29.0: Invoking agent constraints dropped when forwarding keys in golang.org/x/crypto/ssh/agent

(GO-2026-5006)


[CRITICAL] 13-13: golang.org/x/crypto 0.29.0: Invoking byte arithmetic causes underflow and panic in golang.org/x/crypto/ssh

(GO-2026-5013)


[CRITICAL] 13-13: golang.org/x/crypto 0.29.0: Invoking bypass of certificate restrictions in golang.org/x/crypto/ssh

(GO-2026-5014)


[CRITICAL] 13-13: golang.org/x/crypto 0.29.0: Invoking server panic during CheckHostKey/Authenticate in golang.org/x/crypto/ssh

(GO-2026-5015)


[CRITICAL] 13-13: golang.org/x/crypto 0.29.0: Invoking memory leak when rejecting channels can lead to DoS in golang.org/x/crypto/ssh

(GO-2026-5016)


[CRITICAL] 13-13: golang.org/x/crypto 0.29.0: Invoking client can cause server deadlock on unexpected responses in golang.org/x/crypto/ssh

(GO-2026-5017)


[CRITICAL] 13-13: golang.org/x/crypto 0.29.0: Invoking pathological RSA/DSA parameters may cause DoS in golang.org/x/crypto/ssh

(GO-2026-5018)


[CRITICAL] 13-13: golang.org/x/crypto 0.29.0: Invoking bypass of FIDO/U2F security keys physical interaction in golang.org/x/crypto/ssh

(GO-2026-5019)


[CRITICAL] 13-13: golang.org/x/crypto 0.29.0: Invoking infinite loop on large channel writes in golang.org/x/crypto/ssh

(GO-2026-5020)


[CRITICAL] 13-13: golang.org/x/crypto 0.29.0: Invoking auth bypass via unenforced @revoked status in golang.org/x/crypto/ssh/knownhosts

(GO-2026-5021)


[CRITICAL] 13-13: golang.org/x/crypto 0.29.0: Invoking VerifiedPublicKeyCallback permissions skip enforcement in golang.org/x/crypto/ssh

(GO-2026-5023)


[CRITICAL] 13-13: golang.org/x/crypto 0.29.0: Invoking pathological inputs can lead to client panic in golang.org/x/crypto/ssh/agent

(GO-2026-5033)


[CRITICAL] 13-13: golang.org/x/crypto 0.29.0: The golang.org/x/crypto/openpgp package is unmaintained, unsafe by design, and has known security issues

(GO-2026-5932)


[CRITICAL] 13-13: golang.org/x/crypto 0.29.0: golang.org/x/crypto vulnerable to invoking bypass of certificate restrictions

(GHSA-45gg-vh54-h5m9)


[CRITICAL] 13-13: golang.org/x/crypto 0.29.0: golang.org/x/crypto vulnerable to auth bypass via unenforced @revoked status

(GHSA-5cgq-3rg8-m6cv)


[CRITICAL] 13-13: golang.org/x/crypto 0.29.0: golang.org/x/crypto is vulnerable to invoking server panic during CheckHostKey/Authenticate flow

(GHSA-78mq-xcr3-xm33)


[CRITICAL] 13-13: golang.org/x/crypto 0.29.0: golang.org/x/crypto: FIDO/U2F security key physical presence check can be bypassed

(GHSA-89gr-r52h-f8rx)


[CRITICAL] 13-13: golang.org/x/crypto 0.29.0: golang.org/x/crypto: Invoking pathological inputs can lead to client panic

(GHSA-9m57-25v3-79x9)


[CRITICAL] 13-13: golang.org/x/crypto 0.29.0: golang.org/x/crypto doesn't drop invoking agent constraints when forwarding keys

(GHSA-f5wc-c3c7-36mc)


[CRITICAL] 13-13: golang.org/x/crypto 0.29.0: golang.org/x/crypto/ssh/agent vulnerable to panic if message is malformed due to out of bounds read

(GHSA-f6x5-jh6r-wrfv)


[CRITICAL] 13-13: golang.org/x/crypto 0.29.0: golang.org/x/crypto Vulnerable to Denial of Service (DoS) via Slow or Incomplete Key Exchange

(GHSA-hcg3-q754-cr77)


[CRITICAL] 13-13: golang.org/x/crypto 0.29.0: golang.org/x/crypto/ssh allows an attacker to cause unbounded memory consumption

(GHSA-j5w8-q4qc-rx2x)


[CRITICAL] 13-13: golang.org/x/crypto 0.29.0: golang.org/x/crypto doesn't enforce invoking key constraints

(GHSA-jppx-rxg9-jmrx)


[CRITICAL] 13-13: golang.org/x/crypto 0.29.0: golang.org/x/crypto: Invoking byte arithmetic causes underflow and panic

(GHSA-q4h4-gmj2-qvw2)


[CRITICAL] 13-13: golang.org/x/crypto 0.29.0: golang.org/x/crypto: Invoking memory leak when rejecting channels can lead to DoS

(GHSA-qpw4-5x99-6vjp)


[CRITICAL] 13-13: golang.org/x/crypto 0.29.0: golang.org/x/crypto vulnerable to infinite loop on large channel writes

(GHSA-rm3j-f69w-wqmq)


[CRITICAL] 13-13: golang.org/x/crypto 0.29.0: Misuse of ServerConfig.PublicKeyCallback may cause authorization bypass in golang.org/x/crypto

(GHSA-v778-237x-gjrc)


[CRITICAL] 13-13: golang.org/x/crypto 0.29.0: golang.org/x/crypto: Invoking client can cause server deadlock on unexpected responses

(GHSA-vgwf-h737-ff37)


[CRITICAL] 13-13: golang.org/x/crypto 0.29.0: golang.org/x/crypto: Invoking pathological RSA/DSA parameters may cause DoS

(GHSA-w879-237q-wc7r)


[CRITICAL] 13-13: golang.org/x/crypto 0.29.0: golang.org/x/crypto: Invoking VerifiedPublicKeyCallback permissions skip enforcement

(GHSA-x527-x647-q7gg)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@go.mod` at line 13, Update the golang.org/x/crypto dependency declaration in
go.mod from v0.29.0 to v0.52.0 or a newer secure release, and refresh the module
checksums as needed while preserving dependency consistency.

Source: Linters/SAST tools

Comment on lines +79 to +89
type Job struct {
ID uuid.UUID
UserID uuid.UUID
SourceURL string
Status string
TotalItems int
CompletedItems int
ErrorMessage string
CreatedAt string
UpdatedAt string
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🔴 Critical | ⚡ Quick win

Use a pointer for nullable database columns.

The error_message column in the jobs table is nullable. When pgx attempts to scan a NULL value into a primitive string variable, it will return an error (e.g., can't scan NULL into *string), causing job retrieval and creation to fail instantly for healthy jobs. Change ErrorMessage to *string to safely handle NULL values.

🐛 Proposed fix
 type Job struct {
 	ID              uuid.UUID
 	UserID          uuid.UUID
 	SourceURL       string
 	Status          string
 	TotalItems      int
 	CompletedItems  int
-	ErrorMessage    string
+	ErrorMessage    *string
 	CreatedAt       string
 	UpdatedAt       string
 }
📝 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
type Job struct {
ID uuid.UUID
UserID uuid.UUID
SourceURL string
Status string
TotalItems int
CompletedItems int
ErrorMessage string
CreatedAt string
UpdatedAt string
}
type Job struct {
ID uuid.UUID
UserID uuid.UUID
SourceURL string
Status string
TotalItems int
CompletedItems int
ErrorMessage *string
CreatedAt string
UpdatedAt string
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/database/repository.go` around lines 79 - 89, Update the
Job.ErrorMessage field from string to *string so nullable error_message database
values can be scanned safely, preserving the existing field and struct behavior
otherwise.

job := &Job{}
err := r.pool.QueryRow(ctx, `
SELECT id, user_id, source_url, status, total_items, completed_items, error_message, created_at, updated_at
FROM jobs WHERE id = $1

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🔴 Critical | ⚡ Quick win

Cast string parameters to UUID in queries. By default, pgx sends Go string arguments as text type. Since PostgreSQL strictly enforces types and does not implicitly cast text to uuid in extended query protocol comparisons, these queries will fail with operator does not exist: uuid = text. Add the ::uuid cast to all parameters where a string is checked against a UUID column.

  • internal/database/repository.go#L112-L112: Update WHERE id = $1 to WHERE id = $1::uuid.
  • internal/database/repository.go#L130-L130: Update WHERE user_id = $1 to WHERE user_id = $1::uuid.
  • internal/database/repository.go#L164-L164: Update WHERE user_id = $1 to WHERE user_id = $1::uuid.
  • internal/database/repository.go#L181-L181: Update WHERE id = $3 to WHERE id = $3::uuid.
  • internal/database/repository.go#L189-L189: Update WHERE id = $2 to WHERE id = $2::uuid.
  • internal/database/repository.go#L197-L197: Update WHERE id = $2 to WHERE id = $2::uuid.
  • internal/database/repository.go#L242-L242: Update WHERE id = $1 to WHERE id = $1::uuid.
  • internal/database/repository.go#L260-L260: Update WHERE job_id = $1 to WHERE job_id = $1::uuid.
  • internal/database/repository.go#L294-L294: Update WHERE job_id = $1 to WHERE job_id = $1::uuid.
  • internal/database/repository.go#L311-L311: Update WHERE id = $4 to WHERE id = $4::uuid.
  • internal/database/repository.go#L319-L319: Update WHERE id = $2 to WHERE id = $2::uuid.
  • internal/database/repository.go#L327-L327: Update WHERE job_id = $2 to WHERE job_id = $2::uuid.
📍 Affects 1 file
  • internal/database/repository.go#L112-L112 (this comment)
  • internal/database/repository.go#L130-L130
  • internal/database/repository.go#L164-L164
  • internal/database/repository.go#L181-L181
  • internal/database/repository.go#L189-L189
  • internal/database/repository.go#L197-L197
  • internal/database/repository.go#L242-L242
  • internal/database/repository.go#L260-L260
  • internal/database/repository.go#L294-L294
  • internal/database/repository.go#L311-L311
  • internal/database/repository.go#L319-L319
  • internal/database/repository.go#L327-L327
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/database/repository.go` at line 112, Cast every listed string
parameter to UUID in the SQL queries within internal/database/repository.go:
update the predicates at lines 112, 130, 164, 181, 189, 197, 242, 260, 294, 311,
319, and 327 by appending the PostgreSQL ::uuid cast to their respective
placeholders, while preserving the existing repository methods and parameter
ordering.

Comment on lines +202 to +213
type MediaItem struct {
ID uuid.UUID
JobID uuid.UUID
Title string
SourceURL string
Status string
Progress int
SizeBytes int64
ErrorMessage string
CreatedAt string
UpdatedAt string
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🔴 Critical | ⚡ Quick win

Use pointers for nullable database columns.

The title, size_bytes, and error_message columns in the media_items table are nullable. Scanning NULL into primitive string or int64 variables will cause pgx to return an error and fail the retrieval operation. Change these fields to pointers to safely handle NULL values.

🐛 Proposed fix
 type MediaItem struct {
 	ID            uuid.UUID
 	JobID         uuid.UUID
-	Title         string
+	Title         *string
 	SourceURL     string
 	Status        string
 	Progress      int
-	SizeBytes     int64
-	ErrorMessage  string
+	SizeBytes     *int64
+	ErrorMessage  *string
 	CreatedAt     string
 	UpdatedAt     string
 }
📝 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
type MediaItem struct {
ID uuid.UUID
JobID uuid.UUID
Title string
SourceURL string
Status string
Progress int
SizeBytes int64
ErrorMessage string
CreatedAt string
UpdatedAt string
}
type MediaItem struct {
ID uuid.UUID
JobID uuid.UUID
Title *string
SourceURL string
Status string
Progress int
SizeBytes *int64
ErrorMessage *string
CreatedAt string
UpdatedAt string
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/database/repository.go` around lines 202 - 213, Update the MediaItem
struct so the nullable database columns Title, SizeBytes, and ErrorMessage use
pointer types instead of primitive string or int64 types, allowing NULL values
to be scanned safely while leaving non-nullable fields unchanged.

- lint: golangci-lint with Go 1.23
- test: unit tests with postgres, redis, rabbitmq services
- build: compile API and worker binaries
- docker: multi-arch builds on main branch push
- Use go-version-file: go.mod for automatic Go version detection
- Pin golangci-lint to v1.63.4 (compatible with Go 1.23)
- Remove deprecated Node.js version warnings
- Fix redis service health check options
- Simplify to single workflow with lint, test, build jobs
- Remove apt-get install redis-tools step
- Rely on service container health checks for redis readiness
- Use go-version-file: go.mod for automatic Go version detection
- Keep lint/test/build stages only

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
.github/workflows/ci.yml (1)

24-27: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Pin the golangci-lint version.

The PR summary mentions "pinned golangci-lint compatibility," but the action uses version: latest. Using latest is highly discouraged because a new release will eventually introduce new linters and break the build unexpectedly. Pin this to a specific version (e.g., v1.59.1).

🛠️ Proposed fix
       - name: Run golangci-lint
         uses: golangci/golangci-lint-action@v6
         with:
-          version: latest
+          version: v1.59.1
           args: --timeout=5m
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 24 - 27, Update the
golangci/golangci-lint-action configuration to replace version: latest with a
specific pinned golangci-lint release, such as v1.59.1, while preserving the
existing action and timeout settings.
🧹 Nitpick comments (2)
.github/workflows/ci.yml (2)

5-7: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Restrict default workflow permissions.

By default, GitHub Actions workflows run with broad permissions. It is a security best practice to adhere to the principle of least privilege by explicitly setting top-level permissions.

Please consider adding a permissions block at the top of the file (before the on: or jobs: keys):

permissions:
  contents: read
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 5 - 7, Add a top-level permissions
block in the workflow configuration before the on or jobs keys, granting only
contents: read by default. Preserve the existing pull_request branch
configuration and other workflow behavior unchanged.

Source: Linters/SAST tools


83-88: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Remove the redundant wait step.

GitHub Actions natively pauses job execution until all defined service container health checks pass. Since the postgres service already has a comprehensive pg_isready health check configured (lines 41-45), this manual sleep and validation step is redundant and only slows down the pipeline.

♻️ Proposed fix to remove redundant step
-      - name: Wait for services
-        run: |
-          sleep 10
-          pg_isready -h localhost -p 5432 -U streamforge -d streamforge
-          # Redis and RabbitMQ readiness verified by their health checks
-
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 83 - 88, Remove the entire “Wait for
services” step from the workflow, including the sleep and pg_isready commands;
rely on the existing postgres service health check and the Redis/RabbitMQ health
checks to gate job execution.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/ci.yml:
- Around line 94-95: Update the “Run tests” CI step to pass the integration
build tag to the Go test command, ensuring integration tests are included while
preserving the existing race detection and coverage options.

---

Outside diff comments:
In @.github/workflows/ci.yml:
- Around line 24-27: Update the golangci/golangci-lint-action configuration to
replace version: latest with a specific pinned golangci-lint release, such as
v1.59.1, while preserving the existing action and timeout settings.

---

Nitpick comments:
In @.github/workflows/ci.yml:
- Around line 5-7: Add a top-level permissions block in the workflow
configuration before the on or jobs keys, granting only contents: read by
default. Preserve the existing pull_request branch configuration and other
workflow behavior unchanged.
- Around line 83-88: Remove the entire “Wait for services” step from the
workflow, including the sleep and pg_isready commands; rely on the existing
postgres service health check and the Redis/RabbitMQ health checks to gate job
execution.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: d8636945-d59a-45d0-8985-066bbe8d6f94

📥 Commits

Reviewing files that changed from the base of the PR and between 9d4116f and b9abf41.

📒 Files selected for processing (1)
  • .github/workflows/ci.yml

Comment thread .github/workflows/ci.yml Outdated
Comment on lines +94 to +95
- name: Run tests
run: go test -v -race -coverprofile=coverage.out -covermode=atomic ./...

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Run integration tests.

The CI test step does not run integration tests, despite .golangci.yml actively linting the integration build tag. Without -tags integration, any integration tests will be silently skipped during execution.

🛠️ Proposed fix to include integration tests
       - name: Run tests
-        run: go test -v -race -coverprofile=coverage.out -covermode=atomic ./...
+        run: go test -tags integration -v -race -coverprofile=coverage.out -covermode=atomic ./...
📝 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
- name: Run tests
run: go test -v -race -coverprofile=coverage.out -covermode=atomic ./...
- name: Run tests
run: go test -tags integration -v -race -coverprofile=coverage.out -covermode=atomic ./...
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 94 - 95, Update the “Run tests” CI
step to pass the integration build tag to the Go test command, ensuring
integration tests are included while preserving the existing race detection and
coverage options.

@amirmalekian
amirmalekian merged commit eb56492 into main Jul 18, 2026
3 checks passed
@amirmalekian
amirmalekian deleted the feat/phase2-tests-ci branch July 18, 2026 16:27
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.

1 participant