diff --git a/appwrite.json b/appwrite.json index 7569a7d..9dbdbed 100644 --- a/appwrite.json +++ b/appwrite.json @@ -1,2113 +1,29 @@ { - "projectId": "resonate", - "projectName": "Resonate", + "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" } ] } \ No newline at end of file diff --git a/functions/create-room/src/main.js b/functions/create-room/src/main.js index 7a1e63b..6fc128d 100644 --- a/functions/create-room/src/main.js +++ b/functions/create-room/src/main.js @@ -1,8 +1,10 @@ import AppwriteService from "./appwrite.js"; import LivekitService from "./livekit.js"; +import RoomCreationService from "./room-creation-service.js"; import { throwIfMissing } from "./utils.js"; export default async ({ req, res, log, error }) => { + // Validate environment variables throwIfMissing(process.env, [ "APPWRITE_API_KEY", "MASTER_DATABASE_ID", @@ -13,51 +15,46 @@ export default async ({ req, res, log, error }) => { "LIVEKIT_SOCKET_URL", ]); - const appwrite = new AppwriteService(); - const livekit = new LivekitService(); - + // Initialize services + const appwriteService = new AppwriteService(); + const livekitService = new LivekitService(); + const roomCreationService = new RoomCreationService( + appwriteService, + livekitService, + process.env.LIVEKIT_SOCKET_URL + ); + + // Parse and validate request body + let requestBody; try { - throwIfMissing(JSON.parse(req.body), ["name", "adminUid", "tags"]); + requestBody = JSON.parse(req.body); + throwIfMissing(requestBody, ["name", "adminUid", "tags"]); } catch (err) { error(err.message); return res.json({ msg: err.message }, 400); } + // Handle room creation try { log(req); - const { name, description, adminUid, tags } = JSON.parse(req.body); + const { name, description, adminUid, tags } = requestBody; - // create a new room on appwrite - const newRoomdata = { + // Delegate business logic to the service + const result = await roomCreationService.createRoom({ name, description, adminUid, tags, - totalParticipants: 1, - }; - const appwriteRoomId = await appwrite.createRoom(newRoomdata); - log(appwriteRoomId); - - // create a new livekit room - const livekitRoomOptions = { - name: appwriteRoomId, // using appwrite room doc id as livekit room name - emptyTimeout: 300, // timeout in seconds - }; - const livekitRoom = await livekit.createRoom(livekitRoomOptions); - log(livekitRoom); + }); - // Creating a token for the admin - const accessToken = livekit.generateToken( - appwriteRoomId, - adminUid, - true - ); + log(`Room created: ${result.appwriteRoomId}`); + // Format and return response return res.json({ msg: "Room created Successfully", - livekit_room: livekitRoom, - livekit_socket_url: `${process.env.LIVEKIT_SOCKET_URL}`, - access_token: accessToken, + livekit_room: result.livekitRoom, + livekit_socket_url: result.livekitSocketUrl, + access_token: result.accessToken, }); } catch (e) { error(String(e)); diff --git a/functions/create-room/src/room-creation-service.js b/functions/create-room/src/room-creation-service.js new file mode 100644 index 0000000..2a12a01 --- /dev/null +++ b/functions/create-room/src/room-creation-service.js @@ -0,0 +1,62 @@ +import AppwriteService from "./appwrite.js"; +import LivekitService from "./livekit.js"; + +/** + * Service responsible for orchestrating room creation business logic. + * Handles the creation of room metadata in Appwrite and LiveKit room setup. + */ +class RoomCreationService { + constructor(appwriteService, livekitService, livekitSocketUrl) { + this.appwrite = appwriteService; + this.livekit = livekitService; + this.livekitSocketUrl = livekitSocketUrl; + } + + /** + * Creates a new room with the provided details. + * + * @param {Object} roomData - Room creation parameters + * @param {string} roomData.name - Room name + * @param {string} [roomData.description] - Room description (optional) + * @param {string} roomData.adminUid - Admin user ID + * @param {string[]} roomData.tags - Room tags + * @returns {Promise} Room creation result with room details and access token + * @throws {Error} If room creation fails + */ + async createRoom({ name, description, adminUid, tags }) { + // Create room metadata in Appwrite + const roomMetadata = { + name, + description, + adminUid, + tags, + totalParticipants: 1, + }; + + const appwriteRoomId = await this.appwrite.createRoom(roomMetadata); + + // Create LiveKit room using Appwrite room ID as the room name + const livekitRoomOptions = { + name: appwriteRoomId, + emptyTimeout: 300, // timeout in seconds + }; + + const livekitRoom = await this.livekit.createRoom(livekitRoomOptions); + + // Generate access token for the admin + const accessToken = this.livekit.generateToken( + appwriteRoomId, + adminUid, + true // isRoomAdmin + ); + + return { + livekitRoom, + livekitSocketUrl: this.livekitSocketUrl, + accessToken, + appwriteRoomId, + }; + } +} + +export default RoomCreationService; diff --git a/functions/send-otp/.gitignore b/functions/send-otp/.gitignore new file mode 100644 index 0000000..46afb6b --- /dev/null +++ b/functions/send-otp/.gitignore @@ -0,0 +1,133 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# Directory used by Appwrite CLI for local development +.appwrite \ No newline at end of file diff --git a/functions/send-otp/.prettierrc.json b/functions/send-otp/.prettierrc.json new file mode 100644 index 0000000..0a72520 --- /dev/null +++ b/functions/send-otp/.prettierrc.json @@ -0,0 +1,6 @@ +{ + "trailingComma": "es5", + "tabWidth": 2, + "semi": true, + "singleQuote": true +} diff --git a/functions/send-otp/README.md b/functions/send-otp/README.md index be757b9..a4e7e4b 100644 --- a/functions/send-otp/README.md +++ b/functions/send-otp/README.md @@ -1,86 +1,24 @@ -# Send OTP function +# Send OTP Function -Function to send OTPs. +This function handles the generation and delivery of a 6-digit OTP via email. It includes a **60-second backend rate-limiting** mechanism to prevent abuse. -## 🧰 Usage - -### POST / - -**Parameters** - -| Name | Description | Location | Type | Sample Value | -| -------------- | ------------------------- | -------- | ------ | ------------------ | -| otpId | Document ID of the otp | Body | String | `jcbd...kdsn` | -| recipientEmail | Email ID of the recipient | Body | String | `jcbd...@mail.com` | - -**Response** - -Sample `200` Response: - -```json -{ - "msg": "mail sent" -} -``` +## 🚀 Features +- **Secure OTP Generation**: Creates a random 6-digit code for authentication. +- **Rate Limiting**: Checks the `last_otp_sent` timestamp in the database; returns a **429 Too Many Requests** if a request is made within 60 seconds of the last one. +- **Email Integration**: Delivers the OTP using SMTP with secure credentials. ## ⚙️ Configuration - -| Setting | Value | -| ----------------- | ------------------------------ | -| Runtime | Node (18.0) | -| Entrypoint | `src/main.js` | -| Build Commands | `npm install && npm run start` | -| Permissions | `any` | -| Timeout (Seconds) | 15 | - -## 🔒 Environment Variables - -### APPWRITE_API_KEY - -API Key to use Appwrite Sever SDK. - -| Question | Answer | -| ------------- | ------------------------------------------------------------------------ | -| Required | Yes | -| Sample Value | `62...97` | -| Documentation | [Appwrite API Keys](https://appwrite.io/docs/advanced/platform/api-keys) | - -### VERIFICATION_DATABASE_ID - -Database ID of verification database in appwrite. - -| Question | Answer | -| ------------- | --------------------------------------------------------------------------------------- | -| Required | Yes | -| Sample Value | `Zjc...5PH` | -| Documentation | [Resonate](https://github.com/AOSSIE-Org/Resonate/blob/master/lib/utils/constants.dart) | - -### OTP_COLLECTION_ID - -Collection ID of otp collection. - -| Question | Answer | -| ------------- | --------------------------------------------------------------------------------------- | -| Required | Yes | -| Sample Value | `NXOi3...IBHDa` | -| Documentation | [Resonate](https://github.com/AOSSIE-Org/Resonate/blob/master/lib/utils/constants.dart) | - -### SENDER_MAIL - -Email of the sender. - -| Question | Answer | -| ------------- | ------------------------------------------------ | -| Required | Yes | -| Sample Value | `jsch...@mail.com` | -| Documentation | [Discord](https://discord.com/invite/6mFZ2S846n) | - -### SENDER_PASSWORD - -Password of the sender's account. - -| Question | Answer | -| ------------- | ------------------------------------------------ | -| Required | Yes | -| Sample Value | `HC1Itf...........dAAKF5o` | -| Documentation | [Discord](https://discord.com/invite/6mFZ2S846n) | +- **Runtime**: Node.js 18.0 (Updated to address security patches) +- **Entrypoint**: `src/main.js` + +## 🔐 Environment Variables +The following variables must be configured in your Appwrite Function settings: + +| Variable | Description | +|----------|-------------| +| `APPWRITE_API_KEY` | API key with database and document scopes. | +| `APPWRITE_FUNCTION_PROJECT_ID` | Your active project ID. | +| `VERIFICATION_DATABASE_ID` | ID for the database storing OTP metadata. | +| `OTP_COLLECTION_ID` | Collection ID for storing user-specific OTP records. | +| `SENDER_MAIL` | The SMTP email address used to send the OTP. | +| `SENDER_PASSWORD` | The SMTP password or App Password for the sender. | \ No newline at end of file diff --git a/functions/send-otp/package-lock.json b/functions/send-otp/package-lock.json index 08548f1..c938e6c 100644 --- a/functions/send-otp/package-lock.json +++ b/functions/send-otp/package-lock.json @@ -8,119 +8,41 @@ "name": "starter-template", "version": "1.0.0", "dependencies": { - "node-appwrite": "^17.0.0", - "nodemailer": "^6.9.5" + "node-appwrite": "^20.3.0", + "nodemailer": "^7.0.12" }, "devDependencies": { - "prettier": "^3.0.0" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - }, - "node_modules/axios": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", - "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", - "dependencies": { - "follow-redirects": "^1.15.0", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" + "prettier": "^3.2.5" } }, "node_modules/node-appwrite": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/node-appwrite/-/node-appwrite-9.0.0.tgz", - "integrity": "sha512-iTcHbuaJfr6bP/HFkRVV+FcaumKkbINqZyypQdl+tYxv6Dx0bkB/YKUXGYfTkgP18TLPWQQB++OGQhi98dlo2w==", + "version": "20.3.0", + "resolved": "https://registry.npmjs.org/node-appwrite/-/node-appwrite-20.3.0.tgz", + "integrity": "sha512-vaxlpkNTtjkccyngwc9HRSwQQasZ/CJri6KMZ62mlDAM1YdvOgqIM8EYJwvhx/fiHdyjfxithFcmnvzUR9TGTQ==", + "license": "BSD-3-Clause", "dependencies": { - "axios": "^1.3.6", - "form-data": "^4.0.0" + "node-fetch-native-with-agent": "1.7.2" } }, + "node_modules/node-fetch-native-with-agent": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/node-fetch-native-with-agent/-/node-fetch-native-with-agent-1.7.2.tgz", + "integrity": "sha512-5MaOOCuJEvcckoz7/tjdx1M6OusOY6Xc5f459IaruGStWnKzlI1qpNgaAwmn4LmFYcsSlj+jBMk84wmmRxfk5g==", + "license": "MIT" + }, "node_modules/nodemailer": { - "version": "6.9.5", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.5.tgz", - "integrity": "sha512-/dmdWo62XjumuLc5+AYQZeiRj+PRR8y8qKtFCOyuOl1k/hckZd8durUUHs/ucKx6/8kN+wFxqKJlQ/LK/qR5FA==", + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.12.tgz", + "integrity": "sha512-H+rnK5bX2Pi/6ms3sN4/jRQvYSMltV6vqup/0SFOrxYYY/qoNvhXPlYq3e+Pm9RFJRwrMGbMIwi81M4dxpomhA==", + "license": "MIT-0", "engines": { "node": ">=6.0.0" } }, "node_modules/prettier": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.0.0.tgz", - "integrity": "sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g==", + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" @@ -131,11 +53,6 @@ "funding": { "url": "https://github.com/prettier/prettier?sponsor=1" } - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" } } -} \ No newline at end of file +} diff --git a/functions/send-otp/package.json b/functions/send-otp/package.json index 73b4a81..d6ade0d 100644 --- a/functions/send-otp/package.json +++ b/functions/send-otp/package.json @@ -1,18 +1,17 @@ { - "name": "send-otp", - "version": "1.0.0", - "description": "", - "main": "src/main.js", - "type": "module", - "scripts": { - "start": "node src/main.js", - "format": "prettier --write ." - }, - "dependencies": { - "node-appwrite": "^17.0.0", - "nodemailer": "^6.9.5" - }, - "devDependencies": { - "prettier": "^3.0.0" - } -} \ No newline at end of file + "name": "starter-template", + "version": "1.0.0", + "description": "", + "main": "src/main.js", + "type": "module", + "scripts": { + "format": "prettier --write ." + }, + "dependencies": { + "node-appwrite": "^20.3.0", + "nodemailer": "^7.0.12" + }, + "devDependencies": { + "prettier": "^3.2.5" + } +} diff --git a/functions/send-otp/src/appwrite.js b/functions/send-otp/src/appwrite.js index b65dcc8..2f2c148 100644 --- a/functions/send-otp/src/appwrite.js +++ b/functions/send-otp/src/appwrite.js @@ -1,4 +1,4 @@ -import { Client, Databases } from 'node-appwrite'; +import { Client, Databases, Query } from 'node-appwrite'; class AppwriteService { constructor() { @@ -18,11 +18,43 @@ class AppwriteService { process.env.OTP_COLLECTION_ID, otpId, { + email, otp, date } ); } + + 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; + } + } + + async updateUserLastOtpSent(userId, timestamp) { + try { + await this.databases.updateDocument( + process.env.VERIFICATION_DATABASE_ID, + process.env.OTP_COLLECTION_ID, + userId, + { + last_otp_sent: timestamp + } + ); + } catch (e) { + // If update fails, log but don't throw (non-critical) + console.error("Error updating last_otp_sent:", e); + } + } } export default AppwriteService; diff --git a/functions/send-otp/src/main.js b/functions/send-otp/src/main.js index f4a95c0..4d8049b 100644 --- a/functions/send-otp/src/main.js +++ b/functions/send-otp/src/main.js @@ -5,10 +5,11 @@ import MailService from "./mail.js"; export default async ({ req, res, log, error }) => { throwIfMissing(process.env, [ "APPWRITE_API_KEY", - "VERIFICATION_DATABASE_ID", - "OTP_COLLECTION_ID", + "APPWRITE_FUNCTION_PROJECT_ID", "SENDER_MAIL", "SENDER_PASSWORD", + "VERIFICATION_DATABASE_ID", + "OTP_COLLECTION_ID", ]); const appwrite = new AppwriteService(); @@ -18,6 +19,25 @@ export default async ({ req, res, log, error }) => { log(req.body); const { otpID, email: recipientEmail } = JSON.parse(req.body); + // 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 + ); + } + } + const otp = String(Math.floor(100000 + Math.random() * 900000)); await mailService.sendMail(recipientEmail, otp); @@ -26,11 +46,22 @@ export default async ({ req, res, log, error }) => { const currentDate = new Date().toDateString(); log(`Current Date: ${currentDate}`); - await appwrite.createOtpDocument(otpID, otp, currentDate); + 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}`); + } + } + return res.json({ message: "mail sent" }); } catch (e) { error(String(e)); - return res.json({ message: String(e) }); + return res.json({ message: String(e) },500); } - return res.json({ message: "mail sent" }); + };