Skip to content

refactor: extract room creation logic into a dedicated service (#150)#151

Open
cannonerd007 wants to merge 7 commits intoAOSSIE-Org:mainfrom
cannonerd007:refactor/extract-room-creation-service
Open

refactor: extract room creation logic into a dedicated service (#150)#151
cannonerd007 wants to merge 7 commits intoAOSSIE-Org:mainfrom
cannonerd007:refactor/extract-room-creation-service

Conversation

@cannonerd007
Copy link

@cannonerd007 cannonerd007 commented Jan 25, 2026

Hi @M4dhav! I saw the proposal for refactoring the room creation logic and decided to tackle it since the issue had been open for a few days.

In this PR, I've decoupled the business logic from the create-room controller in functions/create-room/src/main.js. Following the suggested service/use-case pattern, I extracted the core responsibilities—like generating room metadata, handling LiveKit integration, and persisting data in Appwrite—into a standalone service layer. This makes the main entry point much cleaner by focusing it strictly on request and response handling.

I've ensured this is a non-breaking change and kept the existing input/output signatures intact. This should provide a much better foundation for testing and scaling the room management logic moving forward.

Summary by CodeRabbit

  • New Features

    • OTP requests now enforce 60-second rate limiting and send secure 6‑digit OTPs via SMTP.
  • Improvements

    • Room creation flow refactored for more reliable and consistent room provisioning.
  • Chores

    • Updated package dependencies and streamlined backend configuration, reducing unused services and simplifying manifests.
  • Documentation

    • OTP function README updated with feature-focused usage and simplified configuration.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 25, 2026

📝 Walkthrough

Walkthrough

The manifest was reduced from many resources to a minimal single-function project (projectId updated). A RoomCreationService was added and create-room refactored to use it. A new send-otp function was introduced with OTP generation, 60s rate limiting, SMTP delivery, and Appwrite user timestamp management.

Changes

Cohort / File(s) Summary
Configuration & Manifest
appwrite.json
Replaced large multi-resource manifest with a minimal manifest containing only the send-otp function; updated projectId to 6968ad380036f9106f75.
Room Creation Refactor
functions/create-room/src/main.js, functions/create-room/src/room-creation-service.js
Added RoomCreationService; main.js delegates room creation to the service, centralizes validation, and returns the service result (livekitRoom, socketUrl, accessToken, appwriteRoomId).
Send-OTP: repo & style
functions/send-otp/.gitignore, functions/send-otp/.prettierrc.json, functions/send-otp/README.md
Added standard .gitignore, Prettier config, and updated README describing OTP features, env vars, and runtime/entrypoint simplification.
Send-OTP: deps & package
functions/send-otp/package.json
Replaced package.json content (project name changed to starter-template); bumped node-appwrite to ^20.3.0, nodemailer to ^7.0.12; removed start script, kept format script.
Send-OTP: Appwrite integration
functions/send-otp/src/appwrite.js
Added Query import; added getUserByEmail(email) and updateUserLastOtpSent(userId,timestamp); createOtpDocument now includes recipient email.
Send-OTP: function logic
functions/send-otp/src/main.js
Added 60s rate-limiting check using getUserByEmail; generates 6-digit OTP, sends via SMTP, creates OTP document with email and date, updates last_otp_sent, and returns 429 when rate limited; expanded env var validation and error handling.

Sequence Diagrams

sequenceDiagram
    participant Client
    participant CreateRoom as create-room<br/>Function
    participant RoomService as RoomCreationService
    participant AppwriteService
    participant LiveKitService

    Client->>CreateRoom: POST /create-room with details
    CreateRoom->>CreateRoom: Parse & validate request
    CreateRoom->>RoomService: createRoom(details)
    RoomService->>AppwriteService: create room document (metadata)
    AppwriteService-->>RoomService: appwriteRoomId
    RoomService->>LiveKitService: create LiveKit room (name=appwriteRoomId, emptyTimeout=300)
    LiveKitService-->>RoomService: livekitRoom
    RoomService->>LiveKitService: generate access token (admin)
    LiveKitService-->>RoomService: accessToken
    RoomService-->>CreateRoom: { livekitRoom, livekitSocketUrl, accessToken, appwriteRoomId }
    CreateRoom-->>Client: 200 + room details
Loading
sequenceDiagram
    participant Client
    participant SendOTP as send-otp<br/>Function
    participant AppwriteService
    participant SMTP as EmailService

    Client->>SendOTP: POST /send-otp { email }
    SendOTP->>AppwriteService: getUserByEmail(email)
    AppwriteService-->>SendOTP: user doc or null
    SendOTP->>SendOTP: check last_otp_sent (rate limiter)
    alt last_otp_sent within 60s
        SendOTP-->>Client: 429 + wait message
    else allowed
        SendOTP->>SendOTP: generate 6-digit OTP
        SendOTP->>SMTP: send email with OTP
        SMTP-->>SendOTP: success/failure
        SendOTP->>AppwriteService: createOtpDocument(otpId, otp, recipientEmail, date)
        AppwriteService-->>SendOTP: created otp doc
        SendOTP->>AppwriteService: updateUserLastOtpSent(userId, timestamp)
        AppwriteService-->>SendOTP: update result
        SendOTP-->>Client: 200 + success
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

enhancement

Poem

🐰 Hopped through code with nimble feet,
I stitched a service, tidy and neat.
OTPs sent, rate limits guard the way,
Rooms stand up when users play,
A carrot-cheer for cleaner day! 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately summarizes the main objective: extracting room creation logic into a dedicated service, which aligns with the primary changes in the create-room function.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing touches
  • 📝 Generate docstrings

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 and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@appwrite.json`:
- Around line 2-27: The appwrite.json manifest only lists the "send-otp"
function but must include every function you intend to deploy (e.g.,
create-room, delete-room, join-room, livekit-webhook, match-maker, verify-otp,
verify-email, etc.) or explicitly omit them; update the "functions" array in
appwrite.json to restore entries for each function directory documented in the
README (matching their $id, name, runtime, entrypoint, path, scopes, timeout,
enabled, and other metadata) or remove unused function dirs and update README to
match—ensure the function names in the manifest exactly match the filesystem
directories and README to keep appwrite push functions in sync.

In `@functions/send-otp/src/appwrite.js`:
- Around line 27-40: The createOtpDocument currently only saves otp and date
while getUserByEmail queries for email, so update the createOtpDocument function
signature to accept an email parameter and include an email field in the
document payload when calling this.databases.createDocument (refer to
createOtpDocument), then update the caller in main.js that invokes
createOtpDocument (around the OTP creation call) to pass the user's email so
getUserByEmail and rate-limiting work correctly.

In `@functions/send-otp/src/main.js`:
- Around line 22-39: The rate-limit check in send-otp's main function
incorrectly skips when appwrite.getUserByEmail returns null; update the logic to
ensure rate limiting for all emails by attempting to read-or-create/upsert a
per-email rate record (use the same identifier used in getUserByEmail) before
checking last_otp_sent, i.e., call appwrite.getUserByEmail(email) and if null
create or upsert a minimal record with last_otp_sent = 0 (or set a last_otp_sent
field) then perform the existing timestamp comparison against that record's
last_otp_sent and persist the updated last_otp_sent after sending the OTP;
reference appwrite.getUserByEmail and the last_otp_sent field so the rate-limit
applies even for new users.
🧹 Nitpick comments (5)
functions/send-otp/src/appwrite.js (1)

42-56: Silent failure may hide persistent rate-limiting failures.

The updateUserLastOtpSent method logs errors but silently continues. While the comment says this is "non-critical," if updates consistently fail (e.g., due to schema mismatch or permissions), the rate-limiting feature will never work and you won't get alerted beyond console logs.

Consider tracking or alerting on repeated failures to detect misconfiguration early.

functions/send-otp/src/main.js (2)

51-59: Redundant try-catch block.

updateUserLastOtpSent already contains its own try-catch that logs errors and doesn't rethrow. This outer try-catch will never catch anything from that method call, making it unnecessary.

♻️ Suggested simplification
         // Update last_otp_sent timestamp in user document
         if (userDoc) {
-            try {
-                await appwrite.updateUserLastOtpSent(userDoc.$id, new Date().toISOString());
-            } catch (updateError) {
-                // Log error but don't fail the request if timestamp update fails
-                log(`Warning: Failed to update last_otp_sent timestamp: ${updateError}`);
-            }
+            await appwrite.updateUserLastOtpSent(userDoc.$id, new Date().toISOString());
         }

41-41: Consider using cryptographically secure random number generation.

Math.random() is not cryptographically secure. While it's often acceptable for 6-digit OTPs, using crypto.randomInt provides better security guarantees.

♻️ Suggested improvement
+import { randomInt } from 'crypto';
+
-        const otp = String(Math.floor(100000 + Math.random() * 900000));
+        const otp = String(randomInt(100000, 1000000));
functions/send-otp/package.json (2)

2-4: Package name and description should be updated.

The package name "starter-template" doesn't reflect the actual function. Consider updating to "send-otp" or a similar descriptive name, and adding a meaningful description.

♻️ Suggested fix
 {
-  "name": "starter-template",
+  "name": "send-otp",
   "version": "1.0.0",
-  "description": "",
+  "description": "Appwrite function for sending OTP via email with rate limiting",

10-13: Consider upgrading node-appwrite to the latest version.

nodemailer@^7.0.12 is current and has no known security issues. However, node-appwrite@^20.3.0 is outdated; the latest available version is 21.1.0. Both versions are currently not deprecated and show no security advisories. If upgrading node-appwrite to 21.1.0, verify compatibility with your code since it's a major version bump.

Comment on lines +2 to 27
"projectId": "6968ad380036f9106f75",
"functions": [
{
"$id": "65368a58ef47cf6861200",
"name": "Upcoming Rooms Time Checker",
"runtime": "node-16.0",
"path": "functions/upcomingRoom-isTime-checker",
"entrypoint": "src/main.js",
"ignore": [
"node_modules",
".npm"
],
"vars": [
{
"key": "UpcomingRoomsDataBaseID",
"value": "6522fcf27a1bbc4238df"
},
{
"key": "UpcomingRoomsCollectionID",
"value": "6522fd163103bd453183"
},
{
"key": "SubscriberCollectionID",
"value": "6522fd267db6fdad3392"
}
],
"execute": [
"any"
],
"events": [],
"schedule": "*/5 * * * *",
"timeout": 100,
"commands": "npm install",
"enabled": true,
"logging": true,
"scopes": []
},
{
"$id": "65368a58ef47cf6861206",
"name": "Upcoming Rooms Message Notification",
"runtime": "node-16.0",
"path": "functions/upcomingRoom-Message-Notification",
"entrypoint": "src/main.js",
"ignore": [
"node_modules",
".npm"
],
"vars": [
{
"key": "UpcomingRoomsDataBaseID",
"value": "6522fcf27a1bbc4238df"
},
{
"key": "UpcomingRoomsCollectionID",
"value": "6522fd163103bd453183"
},
{
"key": "SubscriberCollectionID",
"value": "6522fd267db6fdad3392"
}
],
"$id": "6968b4f000222d5d8d93",
"name": "send-otp",
"runtime": "node-18.0",
"specification": "s-0.5vcpu-512mb",
"execute": [
"any"
],
"events": [],
"schedule": "",
"timeout": 100,
"commands": "npm install",
"enabled": true,
"logging": true,
"scopes": []
},
{
"$id": "6513e9d40b57c6ec156f",
"name": "Send OTP",
"runtime": "node-16.0",
"path": "functions/send-otp",
"entrypoint": "src/main.js",
"ignore": [
"node_modules",
".npm"
],
"vars": [
{
"key": "SENDER_MAIL",
"value": "aossieorgforu@gmail.com"
},
{
"key": "VERIFICATION_DATABASE_ID",
"value": "64a7bfd6b09121548bfe"
},
{
"key": "SENDER_PASSWORD",
"value": "xizkyvzhduejxjel"
},
{
"key": "OTP_COLLECTION_ID",
"value": "64a7bfe3a3a7ee5bf7f8"
}
],
"execute": [
"any"
],
"events": [],
"schedule": "",
"timeout": 100,
"commands": "npm install",
"enabled": true,
"logging": true,
"scopes": []
},
{
"$id": "6513df34a0de595ccfb3",
"name": "Verify Email",
"runtime": "node-16.0",
"path": "functions/verify-email",
"entrypoint": "src/main.js",
"ignore": [
"node_modules",
".npm"
],
"vars": [],
"execute": [
"any"
],
"events": [],
"schedule": "",
"timeout": 100,
"commands": "npm install",
"enabled": true,
"logging": true,
"scopes": []
},
{
"$id": "651303df122abc151bf3",
"name": "Verify OTP",
"runtime": "node-16.0",
"path": "functions/verify-otp",
"entrypoint": "src/main.js",
"ignore": [
"node_modules",
".npm"
],
"vars": [
{
"key": "VERIFY_COLLECTION_ID",
"value": "64a7c0100eabfe8d3844"
},
{
"key": "OTP_COLLECTION_ID",
"value": "64a7bfe3a3a7ee5bf7f8"
},
{
"key": "VERIFICATION_DATABASE_ID",
"value": "64a7bfd6b09121548bfe"
}
],
"execute": [
"any"
],
"events": [],
"schedule": "",
"timeout": 100,
"commands": "npm install",
"enabled": true,
"logging": true,
"scopes": []
},
{
"$id": "651e2670b1e4a26e3cf1",
"name": "Create Room",
"runtime": "node-16.0",
"path": "functions/create-room",
"entrypoint": "src/main.js",
"ignore": [
"node_modules",
".npm"
],
"vars": [
{
"key": "MASTER_DATABASE_ID",
"value": "64a521785f5be62b796f"
},
{
"key": "ROOMS_COLLECTION_ID",
"value": "64a5217e695bf2c4ec9c"
}
],
"execute": [
"any"
],
"events": [],
"schedule": "",
"timeout": 100,
"commands": "npm install",
"enabled": true,
"logging": true,
"scopes": []
},
{
"$id": "6534b7949d12c9867bd2",
"name": "Database Cleaner",
"runtime": "node-16.0",
"path": "functions/database-cleaner",
"entrypoint": "src/main.js",
"ignore": [
"node_modules",
".npm"
],
"vars": [
{
"key": "OTP_COLLECTION_ID",
"value": "64a7bfe3a3a7ee5bf7f8"
},
{
"key": "VERIFICATION_DATABASE_ID",
"value": "64a7bfd6b09121548bfe"
},
{
"key": "RETENTION_PERIOD_DAYS",
"value": "0"
},
{
"key": "PARTICIPANTS_COLLECTION_ID",
"value": "64a63e508145d1084abf"
},
{
"key": "ROOMS_COLLECTION_ID",
"value": "64a5217e695bf2c4ec9c"
},
{
"key": "ACTIVE_PAIRS_COLLECTION_ID",
"value": "64d980cd65ff2e08ab97"
},
{
"key": "MASTER_DATABASE_ID",
"value": "64a521785f5be62b796f"
}
],
"execute": [
"any"
],
"events": [],
"schedule": "0 4 * * *",
"timeout": 100,
"commands": "npm install",
"enabled": true,
"logging": true,
"scopes": []
},
{
"$id": "651e348775f28d84e11e",
"name": "Delete Room",
"runtime": "node-16.0",
"path": "functions/delete-room",
"entrypoint": "src/main.js",
"ignore": [
"node_modules",
".npm"
],
"vars": [
{
"key": "PARTICIPANTS_COLLECTION_ID",
"value": "64a63e508145d1084abf"
},
{
"key": "ROOMS_COLLECTION_ID",
"value": "64a5217e695bf2c4ec9c"
},
{
"key": "MASTER_DATABASE_ID",
"value": "64a521785f5be62b796f"
}
],
"execute": [
"any"
],
"events": [],
"schedule": "",
"timeout": 100,
"commands": "npm install",
"enabled": true,
"logging": true,
"scopes": []
},
{
"$id": "651e3d8fa35c690ed957",
"name": "Join Room",
"runtime": "node-16.0",
"path": "functions/join-room",
"entrypoint": "src/main.js",
"ignore": [
"node_modules",
".npm"
],
"vars": [],
"execute": [
"any"
],
"events": [],
"schedule": "",
"timeout": 100,
"commands": "npm install",
"enabled": true,
"logging": true,
"scopes": []
},
{
"$id": "6513ed3a0ac8808c33e4",
"name": "Livekit Webhook",
"runtime": "node-16.0",
"path": "functions/livekit-webhook",
"entrypoint": "src/main.js",
"ignore": [
"node_modules",
".npm"
],
"vars": [
{
"key": "ROOMS_COLLECTION_ID",
"value": "64a5217e695bf2c4ec9c"
},
{
"key": "PARTICIPANTS_COLLECTION_ID",
"value": "64a63e508145d1084abf"
},
{
"key": "MASTER_DATABASE_ID",
"value": "64a521785f5be62b796f"
}
],
"execute": [
"any"
],
"events": [],
"schedule": "",
"timeout": 100,
"commands": "npm install",
"enabled": true,
"logging": true,
"scopes": []
},
{
"$id": "6513f1648a9654b77b7c",
"name": "Match Maker",
"runtime": "node-16.0",
"path": "functions/match-maker",
"entrypoint": "src/main.js",
"ignore": [
"node_modules",
".npm"
],
"vars": [
{
"key": "ACTIVE_PAIRS_COLLECTION_ID",
"value": "64d980cd65ff2e08ab97"
},
{
"key": "DATABASE_ID",
"value": "64a521785f5be62b796f"
},
{
"key": "REQUESTS_COLLECTION_ID",
"value": "64d980211f1395263ebe"
},
{
"key": "APPWRITE_FUNCTION_PROJECT_ID",
"value": "resonate"
}
],
"execute": [
"any"
],
"events": [
"databases.64a521785f5be62b796f.collections.64d980211f1395263ebe.documents.*.create"
],
"schedule": "",
"timeout": 100,
"commands": "npm install",
"enabled": true,
"logging": true,
"scopes": []
},
{
"$id": "68b241f500012870fca3",
"vars": [
{
"key": "UserDataDatabaseID",
"value": "64a1319104a149e16f5c"
},
{
"key": "UsersCollectionID",
"value": "64a52f0a6c41ded09def"
}
],
"execute": [
"any"
],
"name": "Send Story Notifications",
"enabled": true,
"logging": true,
"runtime": "node-16.0",
"scopes": [
"sessions.write",
"users.read",
"users.write",
"teams.read",
"teams.write",
"databases.read",
"databases.write",
"collections.read",
"collections.write",
"attributes.read",
"attributes.write",
"indexes.read",
"indexes.write",
"documents.read",
"documents.write"
],
"events": [],
"schedule": "",
"timeout": 15,
"entrypoint": "src/main.js",
"commands": "npm install",
"path": "functions/send-story-notification"
},
{
"$id": "68b76fe00027c243610e",
"execute": [
"any"
],
"name": "Start Friend Call",
"enabled": true,
"logging": true,
"runtime": "node-16.0",
"scopes": [
"users.read"
],
"events": [],
"schedule": "",
"timeout": 15,
"entrypoint": "src/main.js",
"commands": "npm install",
"path": "functions/start-friend-call"
},
{
"$id": "68c2c4e20013c58af2c9",
"execute": [
"any"
],
"name": "Sync Stories with Meilisearch",
"enabled": true,
"logging": true,
"runtime": "node-16.0",
"scopes": [
"databases.read",
"collections.read",
"documents.read"
],
"events": [
"databases.stories.collections.670259e900321c12a5a2.documents.*.create",
"databases.stories.collections.670259e900321c12a5a2.documents.*.delete",
"databases.stories.collections.670259e900321c12a5a2.documents.*.update"
],
"schedule": "",
"timeout": 15,
"entrypoint": "src/main.js",
"commands": "npm install",
"path": "functions/sync-stories-with-meilisearch"
},
{
"$id": "68c2ddc8001dded982ef",
"execute": [
"any"
],
"name": "Sync Users with Meilisearch",
"enabled": true,
"logging": true,
"runtime": "node-16.0",
"scopes": [
"databases.read",
"collections.read",
"documents.read"
],
"events": [
"databases.64a1319104a149e16f5c.collections.64a52f0a6c41ded09def.documents.*.create",
"databases.64a1319104a149e16f5c.collections.64a52f0a6c41ded09def.documents.*.delete",
"databases.64a1319104a149e16f5c.collections.64a52f0a6c41ded09def.documents.*.update"
],
"schedule": "",
"timeout": 15,
"entrypoint": "src/main.js",
"commands": "npm install",
"path": "functions/sync-users-with-meilisearch"
},
{
"$id": "68c3d37e003118410c75",
"execute": [
"any"
],
"name": "Sync All Documents with Meilisearch",
"enabled": true,
"logging": true,
"runtime": "node-16.0",
"scopes": [
"databases.read",
"collections.read",
"documents.read"
],
"events": [],
"schedule": "",
"timeout": 15,
"entrypoint": "src/main.js",
"commands": "npm install",
"path": "functions/sync-all-documents-with-meilisearch"
}
],
"buckets": [
{
"$id": "64a13095a4c87fd78bc6",
"$permissions": [
"create(\"any\")",
"read(\"any\")",
"update(\"any\")",
"delete(\"any\")"
],
"fileSecurity": false,
"name": "assets",
"enabled": true,
"maximumFileSize": 29000000,
"allowedFileExtensions": [],
"compression": "none",
"encryption": true,
"antivirus": true
},
{
"$id": "64a52ab306331f167ce6",
"$permissions": [
"create(\"any\")",
"read(\"any\")",
"update(\"any\")",
"delete(\"any\")"
],
"fileSecurity": false,
"name": "user-images",
"enabled": true,
"maximumFileSize": 29000000,
"allowedFileExtensions": [],
"compression": "none",
"encryption": true,
"antivirus": true
},
{
"$id": "6703f4c70037edfd8429",
"$permissions": [
"create(\"any\")",
"read(\"any\")",
"update(\"any\")",
"delete(\"any\")"
],
"fileSecurity": false,
"name": "stories",
"enabled": true,
"maximumFileSize": 30000000,
"allowedFileExtensions": [],
"compression": "none",
"encryption": true,
"antivirus": true
}
],
"databases": [
{
"$id": "64a1319104a149e16f5c",
"name": "user-data",
"enabled": true
},
{
"$id": "64a521785f5be62b796f",
"name": "master",
"enabled": true
},
{
"$id": "64a7bfd6b09121548bfe",
"name": "verification",
"enabled": true
},
{
"$id": "6522fcf27a1bbc4238df",
"name": "upcoming-rooms",
"enabled": true
},
{
"$id": "stories",
"name": "stories",
"enabled": true
}
],
"collections": [
{
"$id": "64a131980b5388c2a0af",
"$permissions": [
"create(\"any\")",
"read(\"any\")",
"update(\"any\")",
"delete(\"any\")"
],
"databaseId": "64a1319104a149e16f5c",
"name": "usernames",
"enabled": true,
"documentSecurity": false,
"attributes": [
{
"key": "email",
"type": "string",
"required": true,
"array": false,
"format": "email",
"default": null
}
],
"indexes": []
},
{
"$id": "64a52f0a6c41ded09def",
"$permissions": [
"create(\"any\")",
"read(\"any\")",
"update(\"any\")",
"delete(\"any\")"
],
"databaseId": "64a1319104a149e16f5c",
"name": "users",
"enabled": true,
"documentSecurity": false,
"attributes": [
{
"key": "name",
"type": "string",
"required": true,
"array": false,
"size": 100,
"default": null
},
{
"key": "dob",
"type": "string",
"required": false,
"array": false,
"size": 15,
"default": null
},
{
"key": "username",
"type": "string",
"required": true,
"array": false,
"size": 100,
"default": null
},
{
"key": "profileImageUrl",
"type": "string",
"required": true,
"array": false,
"size": 500,
"default": null
},
{
"key": "email",
"type": "string",
"required": true,
"array": false,
"format": "email",
"default": null
},
{
"key": "profileImageID",
"type": "string",
"required": false,
"array": false,
"size": 300,
"default": null
},
{
"key": "ratingCount",
"type": "integer",
"required": false,
"array": false,
"min": -9223372036854775808,
"max": 9223372036854775807,
"default": 1
},
{
"key": "ratingTotal",
"type": "double",
"required": false,
"array": false,
"min": -1.7976931348623157e+308,
"max": 1.7976931348623157e+308,
"default": 5
},
{
"key": "followers",
"type": "relationship",
"required": false,
"array": false,
"relatedCollection": "68b16bae0027e57ba2c6",
"relationType": "oneToMany",
"twoWay": true,
"twoWayKey": "followingUserId",
"onDelete": "cascade",
"side": "parent"
},
{
"key": "friends",
"type": "relationship",
"required": false,
"array": false,
"relatedCollection": "68b43e30002f89343479",
"relationType": "manyToMany",
"twoWay": true,
"twoWayKey": "users",
"onDelete": "cascade",
"side": "parent"
}
],
"indexes": [
{
"key": "name_index",
"type": "fulltext",
"status": "available",
"attributes": [
"name"
],
"orders": [
"ASC"
]
},
{
"key": "username_index",
"type": "fulltext",
"status": "available",
"attributes": [
"username"
],
"orders": [
"ASC"
]
}
]
},
{
"$id": "68b16bae0027e57ba2c6",
"$permissions": [
"create(\"any\")",
"read(\"any\")",
"update(\"any\")",
"delete(\"any\")"
],
"databaseId": "64a1319104a149e16f5c",
"name": "followers",
"enabled": true,
"documentSecurity": false,
"attributes": [
{
"key": "followingUserId",
"type": "relationship",
"required": false,
"array": false,
"relatedCollection": "64a52f0a6c41ded09def",
"relationType": "oneToMany",
"twoWay": true,
"twoWayKey": "followers",
"onDelete": "cascade",
"side": "child"
},
{
"key": "followerUserId",
"type": "string",
"required": true,
"array": false,
"size": 100,
"default": null
},
{
"key": "followerUsername",
"type": "string",
"required": true,
"array": false,
"size": 100,
"default": null
},
{
"key": "followerName",
"type": "string",
"required": true,
"array": false,
"size": 100,
"default": null
},
{
"key": "followerFCMToken",
"type": "string",
"required": true,
"array": false,
"size": 1000,
"default": null
},
{
"key": "followerProfileImageUrl",
"type": "string",
"required": true,
"array": false,
"format": "url",
"default": null
},
{
"key": "followerRating",
"type": "double",
"required": true,
"array": false,
"min": 0,
"max": 5,
"default": null
}
],
"indexes": []
},
{
"$id": "64a5217e695bf2c4ec9c",
"$permissions": [
"create(\"any\")",
"read(\"any\")",
"update(\"any\")",
"delete(\"any\")"
],
"databaseId": "64a521785f5be62b796f",
"name": "rooms",
"enabled": true,
"documentSecurity": false,
"attributes": [
{
"key": "name",
"type": "string",
"required": true,
"array": false,
"size": 200,
"default": null
},
{
"key": "tags",
"type": "string",
"required": false,
"array": true,
"size": 50,
"default": null
},
{
"key": "description",
"type": "string",
"required": false,
"array": false,
"size": 1024,
"default": null
},
{
"key": "totalParticipants",
"type": "integer",
"required": false,
"array": false,
"min": 0,
"max": 10000000,
"default": 0
},
{
"key": "adminUid",
"type": "string",
"required": true,
"array": false,
"size": 255,
"default": null
}
],
"indexes": []
},
{
"$id": "64a63e508145d1084abf",
"$permissions": [
"create(\"any\")",
"read(\"any\")",
"update(\"any\")",
"delete(\"any\")"
],
"databaseId": "64a521785f5be62b796f",
"name": "participants",
"enabled": true,
"documentSecurity": false,
"attributes": [
{
"key": "uid",
"type": "string",
"required": true,
"array": false,
"size": 100,
"default": null
},
{
"key": "isAdmin",
"type": "boolean",
"required": true,
"array": false,
"default": null
},
{
"key": "isModerator",
"type": "boolean",
"required": true,
"array": false,
"default": null
},
{
"key": "isSpeaker",
"type": "boolean",
"required": true,
"array": false,
"default": null
},
{
"key": "isMicOn",
"type": "boolean",
"required": true,
"array": false,
"default": null
},
{
"key": "roomId",
"type": "string",
"required": true,
"array": false,
"size": 100,
"default": null
},
{
"key": "hasRequestedToBeSpeaker",
"type": "boolean",
"required": false,
"array": false,
"default": null
}
],
"indexes": [
{
"key": "roomId",
"type": "key",
"status": "available",
"attributes": [
"roomId"
],
"orders": [
"ASC"
]
},
{
"key": "uid",
"type": "key",
"status": "available",
"attributes": [
"uid"
],
"orders": [
"ASC"
]
},
{
"key": "roomUser",
"type": "key",
"status": "available",
"attributes": [
"uid",
"roomId"
],
"orders": [
"ASC",
"ASC"
]
}
]
},
{
"$id": "64d980211f1395263ebe",
"$permissions": [
"create(\"any\")",
"read(\"any\")",
"update(\"any\")",
"delete(\"any\")"
],
"databaseId": "64a521785f5be62b796f",
"name": "pair-requests",
"enabled": true,
"documentSecurity": false,
"attributes": [
{
"key": "languageIso",
"type": "string",
"required": true,
"array": false,
"size": 20,
"default": null
},
{
"key": "isAnonymous",
"type": "boolean",
"required": true,
"array": false,
"default": null
},
{
"key": "uid",
"type": "string",
"required": true,
"array": false,
"size": 100,
"default": null
},
{
"key": "userName",
"type": "string",
"required": false,
"array": false,
"size": 100,
"default": null
},
{
"key": "name",
"type": "string",
"required": false,
"array": false,
"size": 100,
"default": null
},
{
"key": "isRandom",
"type": "boolean",
"required": false,
"array": false,
"default": true
},
{
"key": "profileImageUrl",
"type": "string",
"required": false,
"array": false,
"format": "url",
"default": null
},
{
"key": "userRating",
"type": "double",
"required": false,
"array": false,
"min": -1.7976931348623157e+308,
"max": 1.7976931348623157e+308,
"default": null
}
],
"indexes": [
{
"key": "languageIso",
"type": "key",
"status": "available",
"attributes": [
"languageIso"
],
"orders": [
"ASC"
]
},
{
"key": "docId",
"type": "key",
"status": "available",
"attributes": [
"$id"
],
"orders": [
"ASC"
]
},
{
"key": "createdAt",
"type": "key",
"status": "available",
"attributes": [
"$createdAt"
],
"orders": [
"ASC"
]
},
{
"key": "requestIndex",
"type": "key",
"status": "available",
"attributes": [
"$id",
"languageIso",
"$createdAt"
],
"orders": [
"ASC",
"ASC",
"ASC"
]
},
{
"key": "requestIndex1",
"type": "key",
"status": "available",
"attributes": [
"$id",
"languageIso"
],
"orders": [
"ASC",
"ASC"
]
}
]
},
{
"$id": "64d980cd65ff2e08ab97",
"$permissions": [
"create(\"any\")",
"read(\"any\")",
"update(\"any\")",
"delete(\"any\")"
],
"databaseId": "64a521785f5be62b796f",
"name": "active-pairs",
"enabled": true,
"documentSecurity": false,
"attributes": [
{
"key": "userDocId1",
"type": "string",
"required": true,
"array": false,
"size": 100,
"default": null
},
{
"key": "userDocId2",
"type": "string",
"required": true,
"array": false,
"size": 100,
"default": null
},
{
"key": "userName1",
"type": "string",
"required": false,
"array": false,
"size": 50,
"default": "Anonymous "
},
{
"key": "userName2",
"type": "string",
"required": false,
"array": false,
"size": 50,
"default": "Anonymous"
},
{
"key": "uid1",
"type": "string",
"required": true,
"array": false,
"size": 50,
"default": null
},
{
"key": "uid2",
"type": "string",
"required": true,
"array": false,
"size": 50,
"default": null
}
],
"indexes": [
{
"key": "userDocId1",
"type": "unique",
"status": "available",
"attributes": [
"userDocId1"
],
"orders": [
"ASC"
]
},
{
"key": "userDocId2",
"type": "unique",
"status": "available",
"attributes": [
"userDocId2"
],
"orders": [
"ASC"
]
}
]
},
{
"$id": "64a7bfe3a3a7ee5bf7f8",
"$permissions": [
"create(\"any\")",
"read(\"any\")",
"update(\"any\")",
"delete(\"any\")"
],
"databaseId": "64a7bfd6b09121548bfe",
"name": "OTP's",
"enabled": true,
"documentSecurity": false,
"attributes": [
{
"key": "otp",
"type": "string",
"required": false,
"array": false,
"size": 100000,
"default": null
},
{
"key": "date",
"type": "string",
"required": false,
"array": false,
"size": 50,
"default": "None"
}
],
"indexes": []
},
{
"$id": "64a7c0100eabfe8d3844",
"$permissions": [
"create(\"any\")",
"read(\"any\")",
"update(\"any\")",
"delete(\"any\")"
],
"databaseId": "64a7bfd6b09121548bfe",
"name": "Verifications",
"enabled": true,
"documentSecurity": false,
"attributes": [
{
"key": "status",
"type": "string",
"required": false,
"array": false,
"size": 1000000,
"default": null
}
],
"indexes": []
},
{
"$id": "6522fd163103bd453183",
"$permissions": [
"create(\"any\")",
"read(\"any\")",
"update(\"any\")",
"delete(\"any\")"
],
"databaseId": "6522fcf27a1bbc4238df",
"name": "Upcoming Rooms",
"enabled": true,
"documentSecurity": false,
"attributes": [
{
"key": "isTime",
"type": "boolean",
"required": false,
"array": false,
"default": false
},
{
"key": "name",
"type": "string",
"required": false,
"array": false,
"size": 1000,
"default": null
},
{
"key": "scheduledDateTime",
"type": "datetime",
"required": false,
"array": false,
"format": "",
"default": null
},
{
"key": "tags",
"type": "string",
"required": false,
"array": true,
"size": 10000,
"default": null
},
{
"key": "description",
"type": "string",
"required": false,
"array": false,
"size": 100000,
"default": null
},
{
"key": "creatorUid",
"type": "string",
"required": true,
"array": false,
"size": 1000,
"default": null
},
{
"key": "creator_fcm_tokens",
"type": "string",
"required": false,
"array": true,
"size": 1000,
"default": null
}
],
"indexes": []
},
{
"$id": "6522fd267db6fdad3392",
"$permissions": [
"create(\"any\")",
"read(\"any\")",
"update(\"any\")",
"delete(\"any\")"
],
"databaseId": "6522fcf27a1bbc4238df",
"name": "Subscribed Users",
"enabled": true,
"documentSecurity": false,
"attributes": [
{
"key": "userID",
"type": "string",
"required": false,
"array": false,
"size": 10000,
"default": null
},
{
"key": "userProfileUrl",
"type": "string",
"required": false,
"array": false,
"size": 100000,
"default": null
},
{
"key": "upcomingRoomId",
"type": "string",
"required": false,
"array": false,
"size": 1000,
"default": null
},
{
"key": "registrationTokens",
"type": "string",
"required": false,
"array": true,
"size": 1000,
"default": null
}
],
"indexes": []
},
{
"$id": "670259e20000ddda49a0",
"$permissions": [
"create(\"any\")",
"read(\"any\")",
"update(\"any\")",
"delete(\"any\")"
],
"databaseId": "stories",
"name": "Likes",
"enabled": true,
"documentSecurity": false,
"attributes": [
{
"key": "uId",
"type": "string",
"required": true,
"array": false,
"size": 50,
"default": null
},
{
"key": "storyId",
"type": "string",
"required": true,
"array": false,
"size": 50,
"default": null
}
],
"indexes": [
{
"key": "uId_index",
"type": "key",
"status": "available",
"attributes": [
"uId"
],
"orders": [
"ASC"
]
},
{
"key": "storyId_index",
"type": "key",
"status": "available",
"attributes": [
"storyId"
],
"orders": [
"ASC"
]
}
]
},
{
"$id": "670277ad002530531daf",
"$permissions": [
"create(\"any\")",
"read(\"any\")",
"update(\"any\")",
"delete(\"any\")"
],
"databaseId": "stories",
"name": "Chapters",
"enabled": true,
"documentSecurity": false,
"attributes": [
{
"key": "title",
"type": "string",
"required": true,
"array": false,
"size": 20,
"default": null
},
{
"key": "description",
"type": "string",
"required": false,
"array": false,
"size": 2000,
"default": null
},
{
"key": "coverImgUrl",
"type": "string",
"required": false,
"array": false,
"format": "url",
"default": null
},
{
"key": "lyrics",
"type": "string",
"required": false,
"array": false,
"size": 50000,
"default": null
},
{
"key": "storyId",
"type": "string",
"required": true,
"array": false,
"size": 20,
"default": null
},
{
"key": "audioFileUrl",
"type": "string",
"required": false,
"array": false,
"format": "url",
"default": null
},
{
"key": "tintColor",
"type": "string",
"required": false,
"array": false,
"size": 20,
"default": null
},
{
"key": "playDuration",
"type": "integer",
"required": true,
"array": false,
"min": 0,
"max": 3600000,
"default": null
}
],
"indexes": [
{
"key": "storyId_index",
"type": "key",
"status": "available",
"attributes": [
"storyId"
],
"orders": [
"ASC"
]
}
]
},
{
"$id": "670259e900321c12a5a2",
"$permissions": [
"create(\"any\")",
"read(\"any\")",
"update(\"any\")",
"delete(\"any\")"
],
"databaseId": "stories",
"name": "Stories",
"enabled": true,
"documentSecurity": false,
"attributes": [
{
"key": "title",
"type": "string",
"required": true,
"array": false,
"size": 100,
"default": null
},
{
"key": "description",
"type": "string",
"required": false,
"array": false,
"size": 2000,
"default": null
},
{
"key": "category",
"type": "string",
"required": true,
"array": false,
"elements": [
"horror",
"comedy",
"thriller",
"romance",
"spiritual",
"drama"
],
"format": "enum",
"default": null
},
{
"key": "coverImgUrl",
"type": "string",
"required": false,
"array": false,
"format": "url",
"default": null
},
{
"key": "creatorId",
"type": "string",
"required": true,
"array": false,
"size": 50,
"default": null
},
{
"key": "creatorName",
"type": "string",
"required": true,
"array": false,
"size": 100,
"default": null
},
{
"key": "creatorImgUrl",
"type": "string",
"required": true,
"array": false,
"format": "url",
"default": null
},
{
"key": "likes",
"type": "integer",
"required": false,
"array": false,
"min": 0,
"max": 100000000000,
"default": null
},
{
"key": "tintColor",
"type": "string",
"required": false,
"array": false,
"size": 20,
"default": null
},
{
"key": "playDuration",
"type": "integer",
"required": true,
"array": false,
"min": 0,
"max": 3600000,
"default": null
}
],
"indexes": [
{
"key": "title_index",
"type": "fulltext",
"status": "available",
"attributes": [
"title"
],
"orders": [
"ASC"
]
},
{
"key": "creatorname_index",
"type": "fulltext",
"status": "available",
"attributes": [
"creatorName"
],
"orders": [
"ASC"
]
},
{
"key": "description_index",
"type": "fulltext",
"status": "available",
"attributes": [
"description"
],
"orders": [
"ASC"
]
},
{
"key": "creatorId",
"type": "key",
"status": "available",
"attributes": [
"creatorId"
],
"orders": [
"ASC"
]
},
{
"key": "categoryIndex",
"type": "key",
"status": "available",
"attributes": [
"category"
],
"orders": [
"ASC"
]
}
]
},
{
"$id": "670d812c0002c33c09a8",
"$permissions": [
"create(\"any\")",
"read(\"any\")",
"update(\"any\")",
"delete(\"any\")"
],
"databaseId": "64a521785f5be62b796f",
"name": "messages",
"enabled": true,
"documentSecurity": false,
"attributes": [
{
"key": "roomId",
"type": "string",
"required": true,
"array": false,
"size": 50,
"default": null
},
{
"key": "creatorId",
"type": "string",
"required": true,
"array": false,
"size": 50,
"default": null
},
{
"key": "creatorUsername",
"type": "string",
"required": true,
"array": false,
"size": 20,
"default": null
},
{
"key": "hasValidTag",
"type": "boolean",
"required": false,
"array": false,
"default": false
},
{
"key": "creatorName",
"type": "string",
"required": false,
"array": false,
"size": 20,
"default": null
},
{
"key": "messageId",
"type": "string",
"required": true,
"array": false,
"size": 1000,
"default": null
},
{
"key": "index",
"type": "integer",
"required": true,
"array": false,
"min": 0,
"max": 1000,
"default": null
},
{
"key": "isEdited",
"type": "boolean",
"required": false,
"array": false,
"default": false
},
{
"key": "content",
"type": "string",
"required": true,
"array": false,
"size": 1000,
"default": null
},
{
"key": "creatorImgUrl",
"type": "string",
"required": false,
"array": false,
"format": "url",
"default": null
},
{
"key": "creationDateTime",
"type": "datetime",
"required": true,
"array": false,
"format": "",
"default": null
}
],
"indexes": []
},
{
"$id": "672759820027801f121f",
"$permissions": [
"create(\"any\")",
"read(\"any\")",
"update(\"any\")",
"delete(\"any\")"
],
"databaseId": "64a521785f5be62b796f",
"name": "reply_to",
"enabled": true,
"documentSecurity": false,
"attributes": [
{
"key": "creatorUsername",
"type": "string",
"required": true,
"array": false,
"size": 30,
"default": null
},
{
"key": "creatorImgUrl",
"type": "string",
"required": true,
"array": false,
"format": "url",
"default": null
},
{
"key": "index",
"type": "integer",
"required": true,
"array": false,
"min": 0,
"max": 1000,
"default": null
},
{
"key": "content",
"type": "string",
"required": true,
"array": false,
"size": 1000,
"default": null
},
{
"key": "messageId",
"type": "string",
"required": true,
"array": false,
"size": 50,
"default": null
}
],
"indexes": []
},
{
"$id": "68b43e30002f89343479",
"$permissions": [
"create(\"any\")",
"read(\"any\")",
"update(\"any\")",
"delete(\"any\")"
],
"databaseId": "64a1319104a149e16f5c",
"name": "friends",
"enabled": true,
"documentSecurity": false,
"attributes": [
{
"key": "users",
"type": "relationship",
"required": false,
"array": false,
"relatedCollection": "64a52f0a6c41ded09def",
"relationType": "manyToMany",
"twoWay": true,
"twoWayKey": "friends",
"onDelete": "cascade",
"side": "child"
},
{
"key": "senderId",
"type": "string",
"required": true,
"array": false,
"size": 50,
"default": null
},
{
"key": "recieverId",
"type": "string",
"required": true,
"array": false,
"size": 50,
"default": null
},
{
"key": "senderUsername",
"type": "string",
"required": true,
"array": false,
"size": 100,
"default": null
},
{
"key": "recieverUsername",
"type": "string",
"required": true,
"array": false,
"size": 100,
"default": null
},
{
"key": "senderName",
"type": "string",
"required": true,
"array": false,
"size": 100,
"default": null
},
{
"key": "recieverName",
"type": "string",
"required": true,
"array": false,
"size": 100,
"default": null
},
{
"key": "senderFCMToken",
"type": "string",
"required": false,
"array": false,
"size": 5000,
"default": null
},
{
"key": "recieverFCMToken",
"type": "string",
"required": false,
"array": false,
"size": 5000,
"default": null
},
{
"key": "requestStatus",
"type": "string",
"required": true,
"array": false,
"elements": [
"sent",
"accepted"
],
"format": "enum",
"default": null
},
{
"key": "requestSentByUserId",
"type": "string",
"required": true,
"array": false,
"size": 50,
"default": null
},
{
"key": "senderRating",
"type": "double",
"required": true,
"array": false,
"min": -1.7976931348623157e+308,
"max": 1.7976931348623157e+308,
"default": null
},
{
"key": "recieverRating",
"type": "double",
"required": true,
"array": false,
"min": -1.7976931348623157e+308,
"max": 1.7976931348623157e+308,
"default": null
},
{
"key": "senderProfileImgUrl",
"type": "string",
"required": true,
"array": false,
"format": "url",
"default": null
},
{
"key": "recieverProfileImgUrl",
"type": "string",
"required": true,
"array": false,
"format": "url",
"default": null
}
],
"indexes": []
},
{
"$id": "68b764ba002794fa2f61",
"$permissions": [
"create(\"any\")",
"read(\"any\")",
"update(\"any\")",
"delete(\"any\")"
],
"databaseId": "64a521785f5be62b796f",
"name": "friend-calls",
"enabled": true,
"documentSecurity": false,
"attributes": [
{
"key": "callerName",
"type": "string",
"required": true,
"array": false,
"size": 100,
"default": null
},
{
"key": "recieverName",
"type": "string",
"required": true,
"array": false,
"size": 100,
"default": null
},
{
"key": "callerUsername",
"type": "string",
"required": true,
"array": false,
"size": 100,
"default": null
},
{
"key": "recieverUsername",
"type": "string",
"required": true,
"array": false,
"size": 100,
"default": null
},
{
"key": "callerUid",
"type": "string",
"required": true,
"array": false,
"size": 100,
"default": null
},
{
"key": "recieverUid",
"type": "string",
"required": true,
"array": false,
"size": 100,
"default": null
},
{
"key": "livekitRoomId",
"type": "string",
"required": true,
"array": false,
"size": 100,
"default": null
},
{
"key": "callStatus",
"type": "string",
"required": true,
"array": false,
"elements": [
"waiting",
"connected",
"declined",
"ended"
],
"format": "enum",
"default": null
},
{
"key": "callerProfileImageUrl",
"type": "string",
"required": true,
"array": false,
"format": "url",
"default": null
},
{
"key": "recieverProfileImageUrl",
"type": "string",
"required": true,
"array": false,
"format": "url",
"default": null
}
],
"indexes": []
"path": "functions/send-otp"
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -name "appwrite.json" -type f

Repository: AOSSIE-Org/Resonate-Backend

Length of output: 86


🏁 Script executed:

# Check if appwrite.json exists and read its full content
if [ -f "appwrite.json" ]; then
  echo "=== Full appwrite.json ===" 
  cat -n appwrite.json
else
  echo "appwrite.json not found"
fi

Repository: AOSSIE-Org/Resonate-Backend

Length of output: 1077


🏁 Script executed:

# Check for git history of appwrite.json to see if functions were removed
git log --oneline appwrite.json 2>/dev/null | head -20

Repository: AOSSIE-Org/Resonate-Backend

Length of output: 142


🏁 Script executed:

# Search for any function definitions or references to create-room or other functions
rg "create-room|function" --type json --type js --type yaml -i 2>/dev/null | head -30

Repository: AOSSIE-Org/Resonate-Backend

Length of output: 2989


🏁 Script executed:

# Get git diff for appwrite.json to see what changed
git show HEAD:appwrite.json 2>/dev/null || echo "Failed to get HEAD version"

Repository: AOSSIE-Org/Resonate-Backend

Length of output: 847


🏁 Script executed:

# Check git diff for appwrite.json to see recent changes
git diff HEAD~1 appwrite.json 2>/dev/null | head -100

Repository: AOSSIE-Org/Resonate-Backend

Length of output: 53


🏁 Script executed:

# List all function directories that exist in the project
ls -la functions/ | grep "^d"

Repository: AOSSIE-Org/Resonate-Backend

Length of output: 1267


🏁 Script executed:

# Check if there's a create-room directory
find ./functions -maxdepth 1 -name "*create-room*" -o -name "*room*" 2>/dev/null

Repository: AOSSIE-Org/Resonate-Backend

Length of output: 140


🏁 Script executed:

# Check README and any documentation about appwrite.json
find . -maxdepth 2 -type f \( -name "README*" -o -name "*.md" \) | head -5

Repository: AOSSIE-Org/Resonate-Backend

Length of output: 82


🏁 Script executed:

# Check if there's recent history changes to appwrite.json
git log --oneline appwrite.json 2>/dev/null | head -10

Repository: AOSSIE-Org/Resonate-Backend

Length of output: 142


🏁 Script executed:

# Look for deployment or setup scripts that reference appwrite.json
rg "appwrite.json" --type sh --type js --type md -l 2>/dev/null

Repository: AOSSIE-Org/Resonate-Backend

Length of output: 78


🏁 Script executed:

# Check git history of appwrite.json - show more context
git log -p appwrite.json 2>/dev/null | head -200

Repository: AOSSIE-Org/Resonate-Backend

Length of output: 53


🏁 Script executed:

# Read README to understand appwrite.json usage
cat README.md | head -100

Repository: AOSSIE-Org/Resonate-Backend

Length of output: 2475


🏁 Script executed:

# Check init.sh to see how appwrite.json is used in deployment
cat init.sh

Repository: AOSSIE-Org/Resonate-Backend

Length of output: 8401


🏁 Script executed:

# Check if there's any indication of what appwrite.json is used for
rg "deploy|appwrite" README.md

Repository: AOSSIE-Org/Resonate-Backend

Length of output: 291


🏁 Script executed:

# Search for documentation or comments about how appwrite.json and functions are deployed
rg "appwrite.*push.*functions|push.*functions" --type sh --type md -B 2 -A 2

Repository: AOSSIE-Org/Resonate-Backend

Length of output: 462


🏁 Script executed:

# Check if there's any .appwriterc or similar config file
find . -maxdepth 2 -type f -name ".appwrite*" -o -name "appwrite*" | grep -v node_modules | grep -v ".git"

Repository: AOSSIE-Org/Resonate-Backend

Length of output: 86


🌐 Web query:

Appwrite CLI push functions behavior appwrite.json source of truth

💡 Result:

appwrite push functions treats your local CLI config file as the deployment “source of truth”—it reads function metadata (like path, entrypoint, commands, etc.) from the config file in your repo and deploys what’s on disk. Appwrite’s current docs consistently refer to this file as appwrite.config.json and instruct you to edit it to change deployment settings (project/endpoint, function path/entrypoint, etc.). [1][2]

Key behaviors:

  • Push is one-way (local → server): you change code/config locally, then run appwrite push functions to deploy those changes. [2][1]
  • Pull is the opposite (server → local): appwrite pull functions pulls existing function definitions into your local appwrite.config.json. If you want the server state to become your local baseline, you pull first, then commit the resulting config. [2]
  • If you’re thinking “appwrite.json”: that’s legacy. Older/archived CLI materials and the old CLI repo describe an appwrite.json manifest created by init and used for deployments. Newer CLI documentation has moved to appwrite.config.json. [3][2]
  • No VCS commits happen: push deploys to Appwrite; it doesn’t create Git commits or push anything to GitHub for you. [4]

So, for today’s CLI, treat appwrite.config.json as the authoritative local manifest for what gets deployed, and use pull when you want to update that manifest from what currently exists on the Appwrite server. [2][1]

Sources:
[1] Appwrite Docs – Deploy manually (Functions + CLI config via appwrite.config.json)
[2] Appwrite Docs – CLI Functions (pull/push expects appwrite.config.json)
[3] Appwrite GitHub – sdk-for-cli README (legacy appwrite.json)
[4] Appwrite Threads – “appwrite push functions doesn’t commit?”


Manifest is incomplete and out of sync with documented functions.

The appwrite.json file currently declares only send-otp, but the README documents at least 10 functions (create-room, delete-room, join-room, livekit-webhook, match-maker, send-otp, verify-otp, verify-email, and others), and the filesystem contains 16+ function directories. Since appwrite push functions uses this manifest as the deployment source of truth, functions not listed will not be deployed. Either restore the missing function entries or confirm these functions are intentionally not part of this deployment configuration.

🤖 Prompt for AI Agents
In `@appwrite.json` around lines 2 - 27, The appwrite.json manifest only lists the
"send-otp" function but must include every function you intend to deploy (e.g.,
create-room, delete-room, join-room, livekit-webhook, match-maker, verify-otp,
verify-email, etc.) or explicitly omit them; update the "functions" array in
appwrite.json to restore entries for each function directory documented in the
README (matching their $id, name, runtime, entrypoint, path, scopes, timeout,
enabled, and other metadata) or remove unused function dirs and update README to
match—ensure the function names in the manifest exactly match the filesystem
directories and README to keep appwrite push functions in sync.

Comment on lines +27 to +40
async getUserByEmail(email) {
try {
const response = await this.databases.listDocuments(
process.env.VERIFICATION_DATABASE_ID,
process.env.OTP_COLLECTION_ID,
[Query.equal('email', email), Query.limit(1)]
);
return response.documents.length > 0 ? response.documents[0] : null;
} catch (e) {
// Return null if user not found or any error occurs
console.error("Error getting user by email:", e);
return null;
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

rg -n "createOtpDocument|OTP_COLLECTION_ID|email.*otp|last_otp_sent" --type js

Repository: AOSSIE-Org/Resonate-Backend

Length of output: 1709


🏁 Script executed:

cat -n functions/send-otp/src/appwrite.js | sed -n '15,60p'

Repository: AOSSIE-Org/Resonate-Backend

Length of output: 1781


🏁 Script executed:

cat -n functions/send-otp/src/main.js | sed -n '20,60p'

Repository: AOSSIE-Org/Resonate-Backend

Length of output: 2053


🏁 Script executed:

cat -n functions/verify-otp/src/appwrite.js | head -40

Repository: AOSSIE-Org/Resonate-Backend

Length of output: 139


🏁 Script executed:

ls -la functions/verify-otp/src/ && echo "---" && grep -rn "email" functions/send-otp/src/appwrite.js

Repository: AOSSIE-Org/Resonate-Backend

Length of output: 467


🏁 Script executed:

# Check if there are any other OTP document creation patterns or if email should be added to createOtpDocument
rg -B3 -A3 "createOtpDocument" --type js

Repository: AOSSIE-Org/Resonate-Backend

Length of output: 1027


Email field missing from OTP documents breaks rate-limiting functionality.

The createOtpDocument method (lines 15-25) only stores otp and date fields, but getUserByEmail (line 32) queries for an email field that is never created. This causes getUserByEmail to always return null, making the rate-limiting check in main.js ineffective.

To fix this, add the email parameter to createOtpDocument and store it in the document:

async createOtpDocument(otpId, otp, email, date) {
    await this.databases.createDocument(
        process.env.VERIFICATION_DATABASE_ID,
        process.env.OTP_COLLECTION_ID,
        otpId,
        {
            email,      // Add this
            otp,
            date
        }
    );
}

Then update the call in main.js (line 49) to pass the email parameter.

🤖 Prompt for AI Agents
In `@functions/send-otp/src/appwrite.js` around lines 27 - 40, The
createOtpDocument currently only saves otp and date while getUserByEmail queries
for email, so update the createOtpDocument function signature to accept an email
parameter and include an email field in the document payload when calling
this.databases.createDocument (refer to createOtpDocument), then update the
caller in main.js that invokes createOtpDocument (around the OTP creation call)
to pass the user's email so getUserByEmail and rate-limiting work correctly.

Comment on lines +22 to +39
// Rate limit check: Check if user has requested OTP in the last 60 seconds
const userDoc = await appwrite.getUserByEmail(recipientEmail);

if (userDoc && userDoc.last_otp_sent) {
const lastOtpSentTime = new Date(userDoc.last_otp_sent).getTime();
const currentTime = Date.now();
const timeDifference = (currentTime - lastOtpSentTime) / 1000; // Convert to seconds

if (timeDifference < 60) {
const remainingSeconds = Math.ceil(60 - timeDifference);
return res.json(
{
message: `Too many requests. Please wait ${remainingSeconds} second(s) before requesting another OTP.`
},
429
);
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Rate limiting bypassed when user document doesn't exist.

If getUserByEmail returns null (user not found or error), the rate-limiting check is completely bypassed. Combined with the schema concern in appwrite.js, this could mean rate limiting never applies.

Additionally, new users without an existing document can request unlimited OTPs until their first document is created. Consider creating/upserting a rate-limit record for all email addresses, not just existing ones.

🤖 Prompt for AI Agents
In `@functions/send-otp/src/main.js` around lines 22 - 39, The rate-limit check in
send-otp's main function incorrectly skips when appwrite.getUserByEmail returns
null; update the logic to ensure rate limiting for all emails by attempting to
read-or-create/upsert a per-email rate record (use the same identifier used in
getUserByEmail) before checking last_otp_sent, i.e., call
appwrite.getUserByEmail(email) and if null create or upsert a minimal record
with last_otp_sent = 0 (or set a last_otp_sent field) then perform the existing
timestamp comparison against that record's last_otp_sent and persist the updated
last_otp_sent after sending the OTP; reference appwrite.getUserByEmail and the
last_otp_sent field so the rate-limit applies even for new users.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

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

⚠️ Outside diff range comments (1)
functions/send-otp/src/appwrite.js (1)

15-26: Critical: email parameter missing from function signature causes ReferenceError.

The function signature on line 15 is createOtpDocument(otpId, otp, date) but line 21 references an email variable that is not defined. This will throw a ReferenceError at runtime.

Additionally, main.js line 49 calls this function with 4 arguments: createOtpDocument(otpID, otp, recipientEmail, currentDate), but the signature only accepts 3 parameters.

🐛 Proposed fix
-    async createOtpDocument(otpId, otp, date) {
+    async createOtpDocument(otpId, otp, email, date) {
         await this.databases.createDocument(
             process.env.VERIFICATION_DATABASE_ID,
             process.env.OTP_COLLECTION_ID,
             otpId,
             {
                 email,
                 otp,
                 date
             }
         );
     }
♻️ Duplicate comments (1)
functions/send-otp/src/main.js (1)

22-39: Rate limiting bypassed for new email addresses.

When getUserByEmail returns null (first OTP request for an email), rate limiting is skipped entirely. Additionally, updateUserLastOtpSent on line 54 is only called when userDoc exists, so new emails never get a timestamp recorded. This allows unlimited OTP requests for any email until a document with that email already exists from a previous request.

Since createOtpDocument now stores the email (once the signature is fixed), subsequent requests should find it via getUserByEmail. However, a race condition exists where rapid requests before the first document is created can bypass rate limiting.

🔧 Suggested approach

Consider creating the OTP document first (with a placeholder or initial timestamp), then checking rate limits based on that document's existence and timestamp. Alternatively, use an atomic "create-if-not-exists" pattern or leverage Appwrite's document ID based on email hash for idempotent creation.

+        // For new users, the rate limit will apply on subsequent requests
+        // since createOtpDocument now stores the email
+        // Consider: update last_otp_sent on the newly created document as well
         await appwrite.createOtpDocument(otpID, otp, recipientEmail, currentDate);
 
         // Update last_otp_sent timestamp in user document
-        if (userDoc) {
-            try {
-                await appwrite.updateUserLastOtpSent(userDoc.$id, new Date().toISOString());
-            } catch (updateError) {
-                // Log error but don't fail the request if timestamp update fails
-                log(`Warning: Failed to update last_otp_sent timestamp: ${updateError}`);
-            }
-        }
+        // Update the document we just created/found with the timestamp
+        const docId = userDoc ? userDoc.$id : otpID;
+        await appwrite.updateUserLastOtpSent(docId, new Date().toISOString());

Also applies to: 51-59

🧹 Nitpick comments (3)
functions/send-otp/src/main.js (3)

41-41: Consider using cryptographically secure random for OTP generation.

Math.random() is not cryptographically secure. While acceptable for short-lived 6-digit OTPs, Node.js's crypto.randomInt() provides better randomness.

♻️ Optional improvement
+import { randomInt } from 'crypto';
+
-        const otp = String(Math.floor(100000 + Math.random() * 900000));
+        const otp = String(randomInt(100000, 1000000));

53-58: Redundant try-catch block.

updateUserLastOtpSent in appwrite.js already catches errors internally and logs them without throwing. This nested try-catch is unnecessary.

♻️ Simplify
         if (userDoc) {
-            try {
-                await appwrite.updateUserLastOtpSent(userDoc.$id, new Date().toISOString());
-            } catch (updateError) {
-                // Log error but don't fail the request if timestamp update fails
-                log(`Warning: Failed to update last_otp_sent timestamp: ${updateError}`);
-            }
+            await appwrite.updateUserLastOtpSent(userDoc.$id, new Date().toISOString());
         }

61-64: Avoid exposing internal error details to clients.

Returning String(e) in the response may leak implementation details (stack traces, internal paths, database errors) to clients.

♻️ Proposed fix
     } catch (e) {
         error(String(e));
-        return res.json({ message: String(e) },500);
+        return res.json({ message: "An error occurred while processing your request" }, 500);
     }

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.

2 participants