refactor: extract room creation logic into a dedicated service (#150)#151
refactor: extract room creation logic into a dedicated service (#150)#151cannonerd007 wants to merge 7 commits intoAOSSIE-Org:mainfrom
Conversation
📝 WalkthroughWalkthroughThe 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
Sequence DiagramssequenceDiagram
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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
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. Comment |
There was a problem hiding this comment.
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
updateUserLastOtpSentmethod 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.
updateUserLastOtpSentalready 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, usingcrypto.randomIntprovides 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 upgradingnode-appwriteto the latest version.
nodemailer@^7.0.12is current and has no known security issues. However,node-appwrite@^20.3.0is outdated; the latest available version is21.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.
| "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" | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -name "appwrite.json" -type fRepository: 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"
fiRepository: 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 -20Repository: 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 -30Repository: 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 -100Repository: 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/nullRepository: 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 -5Repository: 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 -10Repository: 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/nullRepository: 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 -200Repository: AOSSIE-Org/Resonate-Backend
Length of output: 53
🏁 Script executed:
# Read README to understand appwrite.json usage
cat README.md | head -100Repository: AOSSIE-Org/Resonate-Backend
Length of output: 2475
🏁 Script executed:
# Check init.sh to see how appwrite.json is used in deployment
cat init.shRepository: 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.mdRepository: 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 2Repository: 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 functionsto deploy those changes. [2][1] - Pull is the opposite (server → local):
appwrite pull functionspulls existing function definitions into your localappwrite.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.jsonmanifest created byinitand used for deployments. Newer CLI documentation has moved toappwrite.config.json. [3][2] - No VCS commits happen:
pushdeploys 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.
| 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; | ||
| } | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
rg -n "createOtpDocument|OTP_COLLECTION_ID|email.*otp|last_otp_sent" --type jsRepository: 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 -40Repository: 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.jsRepository: 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 jsRepository: 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.
| // 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 | ||
| ); | ||
| } | ||
| } |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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:The function signature on line 15 is
createOtpDocument(otpId, otp, date)but line 21 references anReferenceErrorat runtime.Additionally,
main.jsline 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
getUserByEmailreturnsnull(first OTP request for an email), rate limiting is skipped entirely. Additionally,updateUserLastOtpSenton line 54 is only called whenuserDocexists, 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
createOtpDocumentnow stores the email (once the signature is fixed), subsequent requests should find it viagetUserByEmail. 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'scrypto.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.
updateUserLastOtpSentinappwrite.jsalready 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); }
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
Improvements
Chores
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.