Skip to content

Audio sync with cloudfare - #188

Draft
Joel-Joseph-George wants to merge 5 commits into
mainfrom
ft/audio-record-sync
Draft

Audio sync with cloudfare#188
Joel-Joseph-George wants to merge 5 commits into
mainfrom
ft/audio-record-sync

Conversation

@Joel-Joseph-George

@Joel-Joseph-George Joel-Joseph-George commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

This pr is made in response with the recorded audio being synced with the server first. then it will be synced to the cloudfare and keeps a record of it in our database .

Summary by CodeRabbit

Release Notes

  • New Features

    • Added a new authenticated POST /recordings/sync endpoint to upload .m4a recordings (multipart form) and persist them to the backend.
    • Saves recording details with associated project/bible context and optional metadata (file size and recorded time), using an upsert approach.
  • Chores

    • Extended environment configuration with Cloudflare R2 credentials and bucket settings.
    • Added database migrations for a new recordings table, including a JSON metadata column.

@Joel-Joseph-George
Joel-Joseph-George marked this pull request as draft June 17, 2026 15:14
@Joel-Joseph-George
Joel-Joseph-George marked this pull request as ready for review June 18, 2026 05:39
@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a POST /recordings/sync endpoint for mobile audio upload. Introduces a hand-rolled AWS SigV4 signing library to upload .m4a files to Cloudflare R2, a new recordings PostgreSQL table (with two migrations), a full recordings domain (types, repository, service), and R2 credential environment variables wired through EnvSchema.

Changes

Audio Recording Sync with Cloudflare R2

Layer / File(s) Summary
R2 environment configuration
src/env.ts, .env.example, .env.test
EnvSchema gains four required R2 credential fields (R2_ACCOUNT_ID, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_BUCKET_NAME), with matching placeholders in .env.example and test values in .env.test.
SigV4-signed R2 upload library
src/lib/r2-upload.ts
New module with SHA256/HMAC helpers, a SigV4 Authorization header builder, and the exported uploadToR2(buffer, r2Key, contentType) that performs a signed PUT to the R2 S3-compatible endpoint with 15s timeout and detailed error reporting.
Database migrations and recordings schema
src/db/migrations/0012_add_recordings.sql, src/db/migrations/0013_add_metadata_to_recordings.sql, src/db/migrations/meta/_journal.json, src/db/migrations/meta/0012_snapshot.json, src/db/migrations/meta/0013_snapshot.json, src/db/schema.ts
Two SQL migrations create the recordings table (with FK columns, unique relative_path, cascade delete) and add a jsonb metadata column; migration journal entries track both versions; schema snapshots document the full database state; schema.ts adds the recordings pgTable and exports selectRecordingsSchema/insertRecordingsSchema with validation constraints.
Recordings domain types and repository
src/domains/recordings/recordings.types.ts, src/domains/recordings/recordings.repository.ts
Defines RecordingMetadata, UpsertRecordingData, syncSuccessSchema, and SyncSuccessResponse; implements upsertRecording with a conflict-update on relative_path that refreshes metadata and recordedByUserId on duplicate inserts.
Recordings sync service
src/domains/recordings/recordings.service.ts
Exports buildR2Key, buildMetadata, verifyProjectUnitExists, and syncRecording, which orchestrates project-unit verification, R2 key derivation, file upload, and DB upsert, throwing typed error codes (NOT_FOUND, INVALID_PATH, DB_ERROR).
POST /recordings/sync route and app wiring
src/domains/recordings/recordings.route.ts, src/app.ts
OpenAPI-documented POST /recordings/sync handler that parses multipart form data, validates fields, calls syncRecording, maps service error codes to HTTP 400/403/404/500, and is registered via a new import in app.ts.

Sequence Diagram(s)

sequenceDiagram
  participant Client as Mobile Client
  participant Route as POST /recordings/sync
  participant syncRecording as syncRecording (service)
  participant uploadToR2 as uploadToR2 (lib)
  participant R2 as Cloudflare R2
  participant upsertRecording as upsertRecording (repository)
  participant DB as PostgreSQL

  Client->>Route: multipart/form-data (file, project_unit_id, bible_text_id, relative_path)
  Route->>Route: parse & validate fields (400 on failure)
  Route->>syncRecording: SyncRecordingInput
  syncRecording->>DB: SELECT project_units WHERE id = projectUnitId
  DB-->>syncRecording: exists / not found (throws NOT_FOUND)
  syncRecording->>syncRecording: buildR2Key (throws INVALID_PATH)
  syncRecording->>uploadToR2: buffer, r2Key, contentType
  uploadToR2->>R2: PUT /{bucket}/{r2Key} with SigV4 Authorization
  R2-->>uploadToR2: 200 OK
  uploadToR2-->>syncRecording: void
  syncRecording->>upsertRecording: UpsertRecordingData
  upsertRecording->>DB: INSERT INTO recordings ON CONFLICT UPDATE
  DB-->>upsertRecording: { relativePath }
  upsertRecording-->>syncRecording: { relativePath }
  syncRecording-->>Route: { transformedRelativePath }
  Route-->>Client: 200 { success: true, data: { relative_path } }
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'Audio sync with cloudfare' is related to the changeset but contains a typo ('cloudfare' instead of 'Cloudflare') and is somewhat vague about the specific implementation details. Consider correcting the typo to 'Cloudflare' and making the title more specific, such as 'Add audio recording sync with Cloudflare R2 storage' to better clarify the main change.
✅ 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.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ft/audio-record-sync

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🤖 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 `@src/db/schema.ts`:
- Around line 563-568: The metadata field in insertRecordingsSchema lacks
runtime validation, allowing invalid metadata objects to be stored despite
compile-time typing. In the createInsertSchema call for the recordings schema,
add a validation rule for the metadata field that enforces its expected runtime
structure and shape, similar to how other fields like projectUnitId and
relativePath are validated, to ensure the jsonb column only accepts payloads
that match the defined contract.

In `@src/domains/recordings/recordings.repository.ts`:
- Around line 25-30: The onConflictDoUpdate block in the recordings repository
is incorrectly mutating the createdAt field by resetting it to now() on
conflict, which breaks creation audit semantics since createdAt should represent
the original creation time. Additionally, recordedByUserId is not being updated
in the conflict resolution, causing stale uploader attribution on re-syncs.
Remove the createdAt line from the set object in onConflictDoUpdate and add
recordedByUserId to the set to update it with the excluded.recordedByUserId
value, ensuring the uploader attribution stays current. If you need to track
when records were last modified, consider adding an updated_at column to
schema.recordings instead of mutating createdAt.

In `@src/domains/recordings/recordings.route.ts`:
- Around line 142-145: The error response in the return statement of the error
handler is exposing the internal error?.message to the client, which could leak
sensitive implementation details. Replace the dynamic error?.message with a
static generic message in the json response returned at the end of the error
handler block, keeping only the generic 'An unexpected error occurred.' message
since the full error details are already being logged server-side earlier in the
handler.
- Around line 128-133: In the recordings route error handling, change the HTTP
status code returned for the 'NOT_FOUND' error condition from
HttpStatusCodes.BAD_REQUEST to HttpStatusCodes.NOT_FOUND to better align with
REST conventions where 404 is semantically appropriate for missing resources.
Additionally, update the OpenAPI schema definition in
syncRecordingRoute.responses to include a 404 response specification to reflect
this change in the API documentation.

In `@src/domains/recordings/recordings.service.ts`:
- Around line 64-80: The `userId` parameter in the `verifyProjectUnitExists`
function is not being used in the query logic. Either remove this unused
parameter from the function signature or, if it's intended for future
authorization validation (as suggested by the commented-out code), add a TODO
comment above the function explaining its intended purpose for upcoming
assignment validation checks. Choose the approach that best aligns with the
team's immediate plans for this function.
- Around line 35-43: The buildR2Key function validates that at least one slash
exists in the path but doesn't ensure that the rest segment (the part after the
first slash) is non-empty, allowing paths like "projectName/" to create keys
with trailing slashes. Add validation to the buildR2Key function to return null
if the rest substring is empty, or add validation in the route handler to reject
such invalid paths. Additionally, create unit tests for buildR2Key covering edge
cases including empty rest segments, paths ending with slashes, and missing
slash separators to prevent future regressions.

In `@src/lib/r2-upload.ts`:
- Around line 169-186: The fetch call in the uploadToR2 function lacks an
explicit timeout mechanism, which can cause socket stalls to hang indefinitely.
Add an AbortController with a timeout to the fetch request in the try block
where the PUT request is made to the R2 URL. Set up the AbortController to abort
the request after a reasonable timeout duration (such as several seconds), and
pass the abort signal in the fetch options alongside the existing method,
headers, and body configuration. This ensures requests that exceed the timeout
are automatically cancelled rather than hanging indefinitely.
🪄 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: ASSERTIVE

Plan: Pro Plus

Run ID: 3cb259a7-2c02-4645-a193-07d3482e507a

📥 Commits

Reviewing files that changed from the base of the PR and between 3c471df and 20cd0e9.

📒 Files selected for processing (15)
  • .env.example
  • .env.test
  • src/app.ts
  • src/db/migrations/0012_add_recordings.sql
  • src/db/migrations/0013_add_metadata_to_recordings.sql
  • src/db/migrations/meta/0012_snapshot.json
  • src/db/migrations/meta/0013_snapshot.json
  • src/db/migrations/meta/_journal.json
  • src/db/schema.ts
  • src/domains/recordings/recordings.repository.ts
  • src/domains/recordings/recordings.route.ts
  • src/domains/recordings/recordings.service.ts
  • src/domains/recordings/recordings.types.ts
  • src/env.ts
  • src/lib/r2-upload.ts

Comment thread src/db/schema.ts
Comment thread src/domains/recordings/recordings.repository.ts
Comment thread src/domains/recordings/recordings.route.ts
Comment thread src/domains/recordings/recordings.route.ts
Comment thread src/domains/recordings/recordings.service.ts
Comment thread src/domains/recordings/recordings.service.ts
Comment thread src/lib/r2-upload.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
src/domains/recordings/recordings.route.ts (1)

113-127: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Return the canonical transformed path from the service, not the raw client path.

Line 113 drops the syncRecording result, and Line 127 responds with the untransformed relativePath. The service uploads/upserts using transformedRelativePath, so this response can point clients to the wrong object key.

Proposed fix
-    await service.syncRecording({
+    const { transformedRelativePath } = await service.syncRecording({
       projectUnitId,
       bibleTextId,
       relativePath,
       file,
       fileSizeRaw,
       recordedAtRaw,
       userId: user.id,
     });
@@
-        data: { relative_path: relativePath },
+        data: { relative_path: transformedRelativePath },
🤖 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 `@src/domains/recordings/recordings.route.ts` around lines 113 - 127, The
syncRecording service call result is not being captured and the response is
returning the raw client-provided relativePath instead of the canonical
transformed path used by the service. Modify the code to capture the return
value from the syncRecording function call and use the transformed path returned
from that service call in the JSON response data object where relative_path is
set, rather than using the untransformed relativePath variable from the request.
🤖 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.

Outside diff comments:
In `@src/domains/recordings/recordings.route.ts`:
- Around line 113-127: The syncRecording service call result is not being
captured and the response is returning the raw client-provided relativePath
instead of the canonical transformed path used by the service. Modify the code
to capture the return value from the syncRecording function call and use the
transformed path returned from that service call in the JSON response data
object where relative_path is set, rather than using the untransformed
relativePath variable from the request.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: f06d9d72-834a-4830-8a22-812f85b46076

📥 Commits

Reviewing files that changed from the base of the PR and between 3c9cf2e and 8b22e47.

📒 Files selected for processing (1)
  • src/domains/recordings/recordings.route.ts

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