Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
162 changes: 162 additions & 0 deletions lib/controllers/rooms_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import 'package:flutter/material.dart';
import 'package:resonate/l10n/app_localizations.dart';
import 'package:get/get.dart';
import 'package:loading_animation_widget/loading_animation_widget.dart';
import 'package:meilisearch/meilisearch.dart';
import 'package:resonate/controllers/tabview_controller.dart';
import 'package:resonate/models/appwrite_room.dart';
import 'package:resonate/models/appwrite_upcomming_room.dart';
import 'package:resonate/services/appwrite_service.dart';
import 'package:resonate/services/room_service.dart';
import 'package:resonate/themes/theme_controller.dart';
Expand All @@ -23,11 +25,25 @@ class RoomsController extends GetxController {
final Databases databases = AppwriteService.getDatabases();
RxList<AppwriteRoom> rooms = <AppwriteRoom>[].obs;
final ThemeController themeController = Get.find<ThemeController>();
RxBool isSearching = false.obs;
RxBool searchBarIsEmpty = true.obs;
RxList<AppwriteRoom> filteredRooms = <AppwriteRoom>[].obs;
RxList<AppwriteUpcommingRoom> filteredUpcomingRooms =
<AppwriteUpcommingRoom>[].obs;
Copy link
Contributor

Choose a reason for hiding this comment

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

Search Logic for Upcoming rooms should be in the Upcoming Rooms Controller

final MeiliSearchClient meilisearchClient = MeiliSearchClient(
meilisearchEndpoint,
meilisearchApiKey,
);
late final MeiliSearchIndex roomsIndex;
late final MeiliSearchIndex upcomingRoomsIndex;

Comment on lines 26 to 29
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify MeiliSearch API key scope and exposure.

Ensure meilisearchApiKey is a search‑only key (not master/admin) and isn’t hard‑coded in source control.


🏁 Script executed:

#!/bin/bash
# Inspect constants and usages to ensure a scoped search key is used and not inlined.
rg -n --no-ignore -C2 -e 'meilisearch(Api)?Key|Meili(Search)?(Key|API)' -e 'meilisearchEndpoint' \
  -g '!*build*' -g '!*ios*' -g '!*android*' -g '!*web/*'
# Show constant definition
fd -a constants.dart | xargs -I{} sh -c 'echo "---- {} ----"; sed -n "1,200p" "{}"'

Length of output: 6174


Use a search-only API key for MeiliSearch, not a master key; remove hardcoded default.

The meilisearchApiKey default value 'myMasterKey' is a master/admin key, not a search-only key, posing a security risk. Remove the hardcoded default or use an empty string/throw on missing environment variable to enforce explicit configuration in production.

🤖 Prompt for AI Agents
In lib/controllers/rooms_controller.dart around lines 28 to 39, the code
instantiates MeiliSearchClient with a hardcoded master API key
(meilisearchApiKey defaulting to 'myMasterKey'); replace this by removing the
hardcoded default and require an explicit search-only key at runtime: read the
key from configuration/environment without a fallback master value, validate it
at startup (throw or log and exit if missing or empty), and ensure
documentation/config uses a MeiliSearch search-only API key rather than a
master/admin key.

@override
void onInit() async {
super.onInit();
roomsIndex = meilisearchClient.index('live_rooms');
upcomingRoomsIndex = meilisearchClient.index('upcoming_rooms');
await getRooms();
filteredRooms.value = rooms;
}

Future<AppwriteRoom> createRoomObject(Document room, String userUid) async {
Expand Down Expand Up @@ -82,6 +98,7 @@ class RoomsController extends GetxController {
}
}
update();
updateFilteredRooms();
} catch (e) {
log(e.toString());
} finally {
Expand Down Expand Up @@ -142,4 +159,149 @@ class RoomsController extends GetxController {
Get.back();
}
}

Future<void> searchRooms(
Copy link
Contributor

Choose a reason for hiding this comment

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

refactor to put upcoming rooms search login in upcoming rooms controller

String query, {
bool isLiveRooms = true,
List<AppwriteUpcommingRoom>? upcomingRooms,
}) async {
if (query.isEmpty) {
if (isLiveRooms) {
filteredRooms.value = rooms;
searchBarIsEmpty.value = true;
} else {
filteredUpcomingRooms.value = upcomingRooms ?? [];
}
return;
}
if (isLiveRooms) {
isSearching.value = true;
searchBarIsEmpty.value = false;
}
try {
if (isUsingMeilisearch) {
try {
final indexToUse = isLiveRooms ? roomsIndex : upcomingRoomsIndex;
final meilisearchResult = await indexToUse.search(query);

if (isLiveRooms) {
filteredRooms.value = await convertMeilisearchResults(
meilisearchResult.hits,
isLiveRooms: true,
);
} else {
filteredUpcomingRooms.value = await convertMeilisearchResults(
meilisearchResult.hits,
isLiveRooms: false,
originalUpcomingRooms: upcomingRooms ?? [],
);
}
return;
} catch (meilisearchError) {
log(
'Meilisearch failed, falling back to local search: $meilisearchError',
);
}
}
// Local search
final lowerQuery = query.toLowerCase();
if (isLiveRooms) {
filteredRooms.value = rooms.where((room) {
return room.name.toLowerCase().contains(lowerQuery) ||
room.description.toLowerCase().contains(lowerQuery) ||
room.tags.any((tag) => tag.toLowerCase().contains(lowerQuery));
}).toList();
} else {
filteredUpcomingRooms.value = (upcomingRooms ?? []).where((room) {
return room.name.toLowerCase().contains(lowerQuery) ||
room.description.toLowerCase().contains(lowerQuery) ||
room.tags.any((tag) => tag.toLowerCase().contains(lowerQuery));
}).toList();
}
} catch (e) {
log('Error searching ${isLiveRooms ? 'rooms' : 'upcoming rooms'}: $e');
if (isLiveRooms) {
filteredRooms.value = [];
} else {
filteredUpcomingRooms.value = [];
}
} finally {
if (isLiveRooms) {
isSearching.value = false;
}
}
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Filter out reported rooms in search results (consistency with getRooms).

Currently, search can surface rooms that getRooms filtered out (reportedUsers contains current user).

-          if (isLiveRooms) {
-            filteredRooms.value = await convertMeilisearchResults(
+          if (isLiveRooms) {
+            final meiliHits = await convertMeilisearchResults(
               meilisearchResult.hits,
               isLiveRooms: true,
             );
+            final uid = Get.find<AuthStateController>().uid!;
+            filteredRooms.assignAll(
+              meiliHits.where((r) => !r.reportedUsers.contains(uid)),
+            );
             ...

Also apply the same filter after the local search branch if you ever remove the getRooms pre-filter:

-        filteredRooms.value = rooms.where((room) {
+        final uid = Get.find<AuthStateController>().uid!;
+        filteredRooms.assignAll(rooms.where((room) {
           return room.name.toLowerCase().contains(lowerQuery) ||
                  room.description.toLowerCase().contains(lowerQuery) ||
                  room.tags.any((tag) => tag.toLowerCase().contains(lowerQuery));
-        }).toList();
+        }).where((r) => !r.reportedUsers.contains(uid)).toList());

Committable suggestion skipped: line range outside the PR's diff.


Future<dynamic> convertMeilisearchResults(
List<Map<String, dynamic>> meilisearchHits, {
required bool isLiveRooms,
List<AppwriteUpcommingRoom>? originalUpcomingRooms,
}) async {
if (isLiveRooms) {
//live rooms
return await Future.wait(
meilisearchHits.map((hit) async {
String userUid = Get.find<AuthStateController>().uid!;
var participantCollectionRef = await databases.listDocuments(
databaseId: masterDatabaseId,
collectionId: participantsCollectionId,
queries: [Query.equal('roomId', hit['\$id']), Query.limit(3)],
);
List<String> memberAvatarUrls = [];
for (var p in participantCollectionRef.documents) {
Document participantDoc = await databases.getDocument(
databaseId: userDatabaseID,
collectionId: usersCollectionID,
documentId: p.data['uid'],
);
memberAvatarUrls.add(participantDoc.data['profileImageUrl']);
}
return AppwriteRoom(
id: hit['\$id'],
name: hit['name'],
description: hit['description'],
totalParticipants: hit['totalParticipants'],
tags: List<String>.from(hit['tags'] ?? []),
memberAvatarUrls: memberAvatarUrls,
state: RoomState.live,
isUserAdmin: hit['adminUid'] == userUid,
reportedUsers: List<String>.from(hit['reportedUsers'] ?? []),
);
}).toList(),
);
} else {
//upcoming rooms
return meilisearchHits.map((hit) {
final originalRoom = (originalUpcomingRooms ?? []).firstWhere(
(room) => room.id == hit['\$id'],
orElse: () => AppwriteUpcommingRoom(
id: hit['\$id'] ?? '',
name: hit['name'] ?? 'Unknown',
isTime: hit['isTime'] ?? false,
scheduledDateTime:
DateTime.tryParse(hit['scheduledDateTime'] ?? '') ??
DateTime.now(),
totalSubscriberCount: 0,
tags: List<String>.from(hit['tags'] ?? []),
subscribersAvatarUrls: [],
userIsCreator: false,
description: hit['description'] ?? '',
hasUserSubscribed: false,
),
);
return originalRoom;
}).toList();
}
}

void clearSearch() {
filteredRooms.value = rooms;
searchBarIsEmpty.value = true;
}

void updateFilteredRooms() {
if (searchBarIsEmpty.value) {
filteredRooms.value = rooms;
}
}
}
33 changes: 33 additions & 0 deletions lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"@@locale": "en",
"title": "Resonate",
"@title": {
"description": "The title of the application."
Expand Down Expand Up @@ -859,6 +860,38 @@
"@noSearchResults": {
"description": "Message displayed when a search returns no results."
},
"searchRooms": "Search rooms...",
"@searchRooms": {
"description": "Placeholder text for room search input field."
},
"searchingRooms": "Searching rooms...",
"@searchingRooms": {
"description": "Loading message shown while searching for rooms."
},
"clearSearch": "Clear search",
"@clearSearch": {
"description": "Text for button to clear search results."
},
"searchError": "Search Error",
"@searchError": {
"description": "Title for search error messages."
},
"searchRoomsError": "Failed to search rooms. Please try again.",
"@searchRoomsError": {
"description": "Error message when room search fails."
},
"searchUpcomingRoomsError": "Failed to search upcoming rooms. Please try again.",
"@searchUpcomingRoomsError": {
"description": "Error message when upcoming room search fails."
},
"search": "Search",
"@search": {
"description": "Tooltip text for search button."
},
"clear": "Clear",
"@clear": {
"description": "Tooltip text for clear button."
},
"shareRoomMessage": "🚀 Check out this amazing room: {roomName}!\n\n📖 Description: {description}\n👥 Join {participants} participants now!",
"@shareRoomMessage": {
"description": "The default message template used when sharing a room.",
Expand Down
32 changes: 32 additions & 0 deletions lib/l10n/app_hi.arb
Original file line number Diff line number Diff line change
Expand Up @@ -860,6 +860,38 @@
"@noSearchResults": {
"description": "Message displayed when a search returns no results."
},
"searchRooms": "रूम खोजें...",
"@searchRooms": {
"description": "Placeholder text for room search input field."
},
"searchingRooms": "रूम खोजे जा रहे हैं...",
"@searchingRooms": {
"description": "Loading message shown while searching for rooms."
},
"clearSearch": "खोज साफ़ करें",
"@clearSearch": {
"description": "Text for button to clear search results."
},
"searchError": "खोज त्रुटि",
"@searchError": {
"description": "Title for search error messages."
},
"searchRoomsError": "कमरों की खोज असफल हुई। कृपया दोबारा कोशिश करें।",
"@searchRoomsError": {
"description": "Error message when room search fails."
},
"searchUpcomingRoomsError": "आगामी कमरों की खोज असफल हुई। कृपया दोबारा कोशिश करें।",
"@searchUpcomingRoomsError": {
"description": "Error message when upcoming room search fails."
},
"search": "खोजें",
"@search": {
"description": "Tooltip text for search button."
},
"clear": "साफ़ करें",
"@clear": {
"description": "Tooltip text for clear button."
},
"shareRoomMessage": "🚀 इस शानदार रूम को देखें: {roomName}!\n\n📖 विवरण: {description}\n👥 अभी {participants} लोग जुड़ चुके हैं!",
"@shareRoomMessage": {
"description": "The default message template used when sharing a room.",
Expand Down
48 changes: 48 additions & 0 deletions lib/l10n/app_localizations.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1354,6 +1354,54 @@ abstract class AppLocalizations {
/// **'No Search Results'**
String get noSearchResults;

/// Placeholder text for room search input field.
///
/// In en, this message translates to:
/// **'Search rooms...'**
String get searchRooms;

/// Loading message shown while searching for rooms.
///
/// In en, this message translates to:
/// **'Searching rooms...'**
String get searchingRooms;

/// Text for button to clear search results.
///
/// In en, this message translates to:
/// **'Clear search'**
String get clearSearch;

/// Title for search error messages.
///
/// In en, this message translates to:
/// **'Search Error'**
String get searchError;

/// Error message when room search fails.
///
/// In en, this message translates to:
/// **'Failed to search rooms. Please try again.'**
String get searchRoomsError;

/// Error message when upcoming room search fails.
///
/// In en, this message translates to:
/// **'Failed to search upcoming rooms. Please try again.'**
String get searchUpcomingRoomsError;

/// Tooltip text for search button.
///
/// In en, this message translates to:
/// **'Search'**
String get search;

/// Tooltip text for clear button.
///
/// In en, this message translates to:
/// **'Clear'**
String get clear;

/// The default message template used when sharing a room.
///
/// In en, this message translates to:
Expand Down
25 changes: 25 additions & 0 deletions lib/l10n/app_localizations_en.dart
Original file line number Diff line number Diff line change
Expand Up @@ -663,6 +663,31 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get noSearchResults => 'No Search Results';

@override
String get searchRooms => 'Search rooms...';

@override
String get searchingRooms => 'Searching rooms...';

@override
String get clearSearch => 'Clear search';

@override
String get searchError => 'Search Error';

@override
String get searchRoomsError => 'Failed to search rooms. Please try again.';

@override
String get searchUpcomingRoomsError =>
'Failed to search upcoming rooms. Please try again.';

@override
String get search => 'Search';

@override
String get clear => 'Clear';

@override
String shareRoomMessage(
String roomName,
Expand Down
25 changes: 25 additions & 0 deletions lib/l10n/app_localizations_gu.dart
Original file line number Diff line number Diff line change
Expand Up @@ -663,6 +663,31 @@ class AppLocalizationsGu extends AppLocalizations {
@override
String get noSearchResults => 'કોઈ શોધ પરિણામો નથી';

@override
String get searchRooms => 'Search rooms...';

@override
String get searchingRooms => 'Searching rooms...';

@override
String get clearSearch => 'Clear search';

@override
String get searchError => 'Search Error';

@override
String get searchRoomsError => 'Failed to search rooms. Please try again.';

@override
String get searchUpcomingRoomsError =>
'Failed to search upcoming rooms. Please try again.';

@override
String get search => 'Search';

@override
String get clear => 'Clear';

Comment on lines +666 to +690
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Localize Gujarati strings (currently English).

Translate to match locale for a consistent experience.

-  String get searchRooms => 'Search rooms...';
+  String get searchRooms => 'રૂમ્સ શોધો...';
-  String get searchingRooms => 'Searching rooms...';
+  String get searchingRooms => 'રૂમ્સ શોધી રહ્યા છીએ...';
-  String get clearSearch => 'Clear search';
+  String get clearSearch => 'શોધ સાફ કરો';
-  String get searchError => 'Search Error';
+  String get searchError => 'શોધ ભૂલ';
-  String get searchRoomsError => 'Failed to search rooms. Please try again.';
+  String get searchRoomsError => 'રૂમ્સ શોધવામાં નિષ્ફળ. કૃપા કરીને ફરી પ્રયાસ કરો.';
-  String get searchUpcomingRoomsError =>
-      'Failed to search upcoming rooms. Please try again.';
+  String get searchUpcomingRoomsError =>
+      'આગામી રૂમ્સ શોધવામાં નિષ્ફળ. કૃપા કરીને ફરી પ્રયાસ કરો.';
-  String get search => 'Search';
+  String get search => 'શોધો';
-  String get clear => 'Clear';
+  String get clear => 'સાફ કરો';
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@override
String get searchRooms => 'Search rooms...';
@override
String get searchingRooms => 'Searching rooms...';
@override
String get clearSearch => 'Clear search';
@override
String get searchError => 'Search Error';
@override
String get searchRoomsError => 'Failed to search rooms. Please try again.';
@override
String get searchUpcomingRoomsError =>
'Failed to search upcoming rooms. Please try again.';
@override
String get search => 'Search';
@override
String get clear => 'Clear';
@override
String get searchRooms => 'રૂમ્સ શોધો...';
@override
String get searchingRooms => 'રૂમ્સ શોધી રહ્યા છીએ...';
@override
String get clearSearch => 'શોધ સાફ કરો';
@override
String get searchError => 'શોધ ભૂલ';
@override
String get searchRoomsError => 'રૂમ્સ શોધવામાં નિષ્ફળ. કૃપા કરીને ફરી પ્રયાસ કરો.';
@override
String get searchUpcomingRoomsError =>
'આગામી રૂમ્સ શોધવામાં નિષ્ફળ. કૃપા કરીને ફરી પ્રયાસ કરો.';
@override
String get search => 'શોધો';
@override
String get clear => 'સાફ કરો';
🤖 Prompt for AI Agents
In lib/l10n/app_localizations_gu.dart around lines 666 to 690 the getters return
English strings instead of Gujarati; replace each English string with its
correct Gujarati translation while preserving exact getter names, punctuation
and any ellipses; ensure translations are accurate and natural Gujarati (e.g.,
"Search rooms..." -> appropriate Gujarati equivalent including the trailing
ellipsis), "Search" -> Gujarati, "Clear" -> Gujarati, and error messages
likewise, keeping single-quoted string literals and escaping any characters as
per file conventions.

@override
String shareRoomMessage(
String roomName,
Expand Down
Loading