Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 55 additions & 28 deletions functions/create-room/src/main.js
Original file line number Diff line number Diff line change
@@ -1,66 +1,93 @@
import AppwriteService from "./appwrite.js";
import LivekitService from "./livekit.js";
import { throwIfMissing } from "./utils.js";
import { throwIfMissing, parseBody } from "./utils.js";

export default async ({ req, res, log, error }) => {
throwIfMissing(process.env, [
"APPWRITE_API_KEY",
"MASTER_DATABASE_ID",
"ROOMS_COLLECTION_ID",
"LIVEKIT_HOST",
"LIVEKIT_API_KEY",
"LIVEKIT_API_SECRET",
"LIVEKIT_SOCKET_URL",
]);
// Validate environment variables
try {
throwIfMissing(process.env, [
"APPWRITE_API_KEY",
"MASTER_DATABASE_ID",
"ROOMS_COLLECTION_ID",
"LIVEKIT_HOST",
"LIVEKIT_API_KEY",
"LIVEKIT_API_SECRET",
"LIVEKIT_SOCKET_URL",
]);
} catch (err) {
error("[ENV_ERROR] " + err.message);
return res.json({
success: false,
message: err.message,
}, 500);
}

const appwrite = new AppwriteService();
const livekit = new LivekitService();

let data;

// βœ… Safe parsing + validation
try {
throwIfMissing(JSON.parse(req.body), ["name", "adminUid", "tags"]);
data = parseBody(req.body);
throwIfMissing(data, ["name", "adminUid", "tags"]);
} catch (err) {
error(err.message);
return res.json({ msg: err.message }, 400);
error("[VALIDATION_ERROR] " + err.message);
return res.json({
success: false,
message: err.message,
}, 400);
}

const { name, description = "", adminUid, tags } = data;

try {
log(req);
const { name, description, adminUid, tags } = JSON.parse(req.body);
log("[CREATE_ROOM_REQUEST]", { name, adminUid, tags });

// create a new room on appwrite
// Create room in Appwrite
const newRoomdata = {
name,
description,
adminUid,
tags,
totalParticipants: 1,
};

const appwriteRoomId = await appwrite.createRoom(newRoomdata);
log(appwriteRoomId);
log("[APPWRITE_ROOM_CREATED]", appwriteRoomId);

// create a new livekit room
// Create room in LiveKit
const livekitRoomOptions = {
name: appwriteRoomId, // using appwrite room doc id as livekit room name
emptyTimeout: 300, // timeout in seconds
name: appwriteRoomId,
emptyTimeout: 300,
};

const livekitRoom = await livekit.createRoom(livekitRoomOptions);
log(livekitRoom);
log("[LIVEKIT_ROOM_CREATED]", livekitRoom);

// Creating a token for the admin
// Generate token for admin
const accessToken = livekit.generateToken(
appwriteRoomId,
adminUid,
true
);

return res.json({
msg: "Room created Successfully",
livekit_room: livekitRoom,
livekit_socket_url: `${process.env.LIVEKIT_SOCKET_URL}`,
access_token: accessToken,
success: true,
message: "Room created successfully",
data: {
roomId: appwriteRoomId,
livekit_room: livekitRoom,
livekit_socket_url: process.env.LIVEKIT_SOCKET_URL,
access_token: accessToken,
},
});
} catch (e) {
error(String(e));
return res.json({ msg: "Room creation failed" }, 500);
error("[CREATE_ROOM_ERROR] " + String(e));

return res.json({
success: false,
message: "Room creation failed",
}, 500);
}
};
16 changes: 15 additions & 1 deletion functions/create-room/src/utils.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
export const throwIfMissing = (obj, keys) => {
const missing = [];

for (let key of keys) {
if (!(key in obj) || !obj[key]) {
if (!(key in obj) || obj[key] === undefined || obj[key] === null) {
missing.push(key);
}
}

if (missing.length > 0) {
throw new Error(`Missing required fields: ${missing.join(", ")}`);
}
};

export const parseBody = (body) => {
if (!body) {
throw new Error("Request body is empty");
}

try {
return JSON.parse(body);
} catch (err) {
throw new Error("Invalid JSON body");
}
}
48 changes: 34 additions & 14 deletions functions/database-cleaner/src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,55 @@ import AppwriteService from "./appwrite.js";
import { throwIfMissing } from "./utils.js";

export default async (context) => {
throwIfMissing(process.env, [
"APPWRITE_API_KEY",
"ROOMS_COLLECTION_ID",
"PARTICIPANTS_COLLECTION_ID",
"ACTIVE_PAIRS_COLLECTION_ID",
"RETENTION_PERIOD_DAYS",
"VERIFICATION_DATABASE_ID",
"OTP_COLLECTION_ID",
]);
const { res, log, error } = context;

// Environment validation
try {
throwIfMissing(process.env, [
"APPWRITE_API_KEY",
"ROOMS_COLLECTION_ID",
"PARTICIPANTS_COLLECTION_ID",
"ACTIVE_PAIRS_COLLECTION_ID",
"RETENTION_PERIOD_DAYS",
"VERIFICATION_DATABASE_ID",
"OTP_COLLECTION_ID",
]);
} catch (err) {
error("[ENV_ERROR] " + err.message);
return res.json({
success: false,
message: err.message,
}, 500);
}

const appwrite = new AppwriteService();

// Cleanup Participants
try {
await appwrite.cleanParticipantsCollection();
log("[CLEANUP] Participants collection cleaned");
} catch (e) {
context.error(String(e));
error("[CLEANUP_ERROR] Participants: " + String(e));
}
Comment on lines 29 to 34

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Try/catch ineffective for cleanParticipantsCollection errors.

Per the relevant code snippet at functions/database-cleaner/src/appwrite.js:31-46, cleanParticipantsCollection uses forEach with async callbacks:

participantDocs.documents.forEach(async (participantDoc) => {
    // errors here won't propagate to the caller
});

Errors from individual document deletions won't propagate to this try/catch, and the success log on line 31 will print even if deletions fail silently. Consider refactoring cleanParticipantsCollection to use Promise.all like the other cleanup methods.

πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@functions/database-cleaner/src/main.js` around lines 29 - 34,
cleanParticipantsCollection currently uses participantDocs.documents.forEach
with async callbacks so individual deletion errors don't propagate and the
try/catch around its caller won't catch failures; refactor the async forEach
into a Promise array (map participantDocs.documents to deletion promises) and
await Promise.all on that array inside cleanParticipantsCollection (similar to
the other cleanup methods), and only resolve/return after all deletions complete
so the caller's try/catch and the success log in the caller reflect actual
outcome.


// Cleanup Active Pairs
try {
await appwrite.cleanActivePairsCollection();
log("[CLEANUP] Active pairs collection cleaned");
} catch (e) {
context.error(String(e));
error("[CLEANUP_ERROR] ActivePairs: " + String(e));
}

// Cleanup OTPs
try {
await appwrite.clearOldOTPs();
log("[CLEANUP] Old OTPs cleared");
} catch (e) {
context.error(String(e));
error("[CLEANUP_ERROR] OTP: " + String(e));
}

return context.res.send("Database Cleanup completed. And unnecessary OTPs are also cleared.");
};
return res.json({
success: true,
message: "Database cleanup completed and expired OTPs cleared",
});
Comment on lines +52 to +55

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟑 Minor

Success response returned even when individual cleanup steps fail.

The function returns success: true unconditionally, even if one or more cleanup phases encountered errors (logged but swallowed). Consider tracking failures and returning a partial success indicator or including error details in the response.

πŸ’‘ Suggested approach
+    const errors = [];
+
     // Cleanup Participants
     try {
         await appwrite.cleanParticipantsCollection();
         log("[CLEANUP] Participants collection cleaned");
     } catch (e) {
         error("[CLEANUP_ERROR] Participants: " + String(e));
+        errors.push("Participants");
     }
     // ... similar for other phases
 
     return res.json({
-        success: true,
+        success: errors.length === 0,
         message: "Database cleanup completed and expired OTPs cleared",
+        ...(errors.length > 0 && { failedPhases: errors }),
     });
πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@functions/database-cleaner/src/main.js` around lines 52 - 55, Change the
unconditional success response to return an aggregated result: during each
cleanup phase push any caught error messages into an errors array (instead of
only logging), compute success = errors.length === 0, and then call res.json
with success, a message that reflects full/partial success (e.g., "Database
cleanup completed" vs "Completed with errors"), and include the errors array (or
error count) alongside the existing "expired OTPs cleared" info; update the code
path that currently calls res.json({ success: true, message: "Database cleanup
completed and expired OTPs cleared" }) to use these aggregated values.

};
18 changes: 16 additions & 2 deletions functions/database-cleaner/src/utils.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,32 @@
export const throwIfMissing = (obj, keys) => {
const missing = [];

for (let key of keys) {
if (!(key in obj) || (!obj[key] && obj[key] != 0)) {
if (!(key in obj) || obj[key] === undefined || obj[key] === null) {
missing.push(key);
}
}

if (missing.length > 0) {
throw new Error(`Missing required fields: ${missing.join(", ")}`);
}
};

export const parseBody = (body) => {
if (!body) {
throw new Error("Request body is empty");
}

try {
return JSON.parse(body);
} catch (err) {
throw new Error("Invalid JSON body");
}
};
Comment on lines +15 to +25

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟑 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if parseBody is imported/used in database-cleaner/main.js
rg -n "parseBody" functions/database-cleaner/src/

Repository: AOSSIE-Org/Resonate-Backend

Length of output: 150


🏁 Script executed:

# Check imports and usage of parseBody in database-cleaner main.js
rg -n "parseBody\|import.*parseBody" functions/database-cleaner/src/main.js

Repository: AOSSIE-Org/Resonate-Backend

Length of output: 53


🏁 Script executed:

# Check the main.js file to understand what the database-cleaner function does
cat -n functions/database-cleaner/src/main.js

Repository: AOSSIE-Org/Resonate-Backend

Length of output: 1991


🏁 Script executed:

# Verify if parseBody is called anywhere within utils.js itself
rg -n "parseBody(" functions/database-cleaner/src/utils.js

Repository: AOSSIE-Org/Resonate-Backend

Length of output: 141


🏁 Script executed:

# Check if parseBody is called within utils.js (fixing regex)
rg -n "parseBody\s*\(" functions/database-cleaner/src/utils.js

Repository: AOSSIE-Org/Resonate-Backend

Length of output: 53


🏁 Script executed:

# Also check the full utils.js to see what functions are defined and exported
cat -n functions/database-cleaner/src/utils.js

Repository: AOSSIE-Org/Resonate-Backend

Length of output: 1110


Remove unused parseBody function.

The parseBody export at lines 15-25 is never imported or used in this module. Since database-cleaner is a scheduled cleanup function that doesn't parse HTTP request bodies, this function is dead code and should be removed.

πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@functions/database-cleaner/src/utils.js` around lines 15 - 25, Remove the
unused parseBody export from utils.js: locate the parseBody function declaration
and export (function name parseBody) and delete the entire function block and
its export so it's no longer part of the module's public API; ensure no other
code references parseBody (remove any leftover imports/usages if present) and
run tests/lint to confirm no missing symbols remain.


export const getExpiryDate = () => {
const retentionPeriod = +(process.env.RETENTION_PERIOD_DAYS ?? 1);
const expiryDate = new Date();
expiryDate.setDate(expiryDate.getDate() - retentionPeriod);
return expiryDate.toISOString();
};
};
Loading