From 29960a2d9a1811a766a5e5506f00db6ba1352d09 Mon Sep 17 00:00:00 2001 From: NicholasSerwatowski Date: Fri, 14 Nov 2025 17:30:44 -0500 Subject: [PATCH] Add files via upload --- src/services/chatService.js | 93 ++++++++++++++++++++++ src/services/firestoreService.js | 127 +++++++++++++++++++++++++++++++ src/services/matchService.js | 113 +++++++++++++++++++++++++++ 3 files changed, 333 insertions(+) create mode 100644 src/services/chatService.js create mode 100644 src/services/firestoreService.js create mode 100644 src/services/matchService.js diff --git a/src/services/chatService.js b/src/services/chatService.js new file mode 100644 index 0000000..c05288e --- /dev/null +++ b/src/services/chatService.js @@ -0,0 +1,93 @@ +// src/services/chatService.js +import { db } from "../config/firebase.js"; + +/** + * Create a new chat room for a matched study group + * users = array of user IDs + */ +export const createChatRoom = async (users) => { + const chatRef = db.collection("chats").doc(); + + const chatData = { + users, + createdAt: new Date(), + }; + + await chatRef.set(chatData); + + return { chatId: chatRef.id, ...chatData }; +}; + +/** + * Add a message to a chat room + */ +export const sendMessage = async ({ chatId, senderId, text }) => { + const message = { + senderId, + text, + timestamp: new Date(), + }; + + const messagesRef = db + .collection("chats") + .doc(chatId) + .collection("messages"); + + const newMessageRef = await messagesRef.add(message); + + return { messageId: newMessageRef.id, ...message }; +}; + +/** + * Get all messages in a chat (NOT real-time; controllers handle that) + */ +export const getMessages = async (chatId) => { + const messagesRef = db + .collection("chats") + .doc(chatId) + .collection("messages") + .orderBy("timestamp", "asc"); + + const snapshot = await messagesRef.get(); + + return snapshot.docs.map((doc) => ({ + messageId: doc.id, + ...doc.data(), + })); +}; + +/** + * Get chats that a specific user is part of + */ +export const getUserChats = async (uid) => { + const snapshot = await db + .collection("chats") + .where("users", "array-contains", uid) + .get(); + + return snapshot.docs.map((doc) => ({ + chatId: doc.id, + ...doc.data(), + })); +}; + +/** + * Ensure a chat exists between two matched users + */ +export const ensureChatRoom = async (uid1, uid2) => { + // Check if a chat already exists + const existingSnap = await db + .collection("chats") + .where("users", "array-contains", uid1) + .get(); + + for (const doc of existingSnap.docs) { + const data = doc.data(); + if (data.users.includes(uid2)) { + return { chatId: doc.id, ...data }; + } + } + + // If not found → create new chat + return await createChatRoom([uid1, uid2]); +}; \ No newline at end of file diff --git a/src/services/firestoreService.js b/src/services/firestoreService.js new file mode 100644 index 0000000..c39685b --- /dev/null +++ b/src/services/firestoreService.js @@ -0,0 +1,127 @@ +// src/services/firestoreService.js +import { db } from "../config/firebase.js"; + +/** + * Get a document by ID. + */ +export const getDoc = async (collection, docId) => { + const ref = db.collection(collection).doc(docId); + const doc = await ref.get(); + + if (!doc.exists) return null; + + return { id: doc.id, ...doc.data() }; +}; + +/** + * Set (overwrite) a document. + */ +export const setDoc = async (collection, docId, data, merge = false) => { + const ref = db.collection(collection).doc(docId); + await ref.set(data, { merge }); + return { id: docId, ...data }; +}; + +/** + * Update a document (partial update). + */ +export const updateDoc = async (collection, docId, data) => { + const ref = db.collection(collection).doc(docId); + await ref.update(data); + return true; +}; + +/** + * Delete a document. + */ +export const deleteDoc = async (collection, docId) => { + const ref = db.collection(collection).doc(docId); + await ref.delete(); + return true; +}; + +/** + * Get all documents in a collection. + */ +export const getCollection = async (collection) => { + const snapshot = await db.collection(collection).get(); + + return snapshot.docs.map((doc) => ({ + id: doc.id, + ...doc.data(), + })); +}; + +/** + * Query a collection using where conditions. + * Example: queryCollection("users", ["major", "==", "Computer Science"]) + */ +export const queryCollection = async (collection, whereClause) => { + const [field, operator, value] = whereClause; + + const snapshot = await db + .collection(collection) + .where(field, operator, value) + .get(); + + return snapshot.docs.map((doc) => ({ + id: doc.id, + ...doc.data(), + })); +}; + +/** + * Create a new document with auto-generated ID. + */ +export const addDoc = async (collection, data) => { + const ref = await db.collection(collection).add(data); + return { id: ref.id, ...data }; +}; + +/** + * Run a Firestore transaction safely. + */ +export const runTransaction = async (handler) => { + return await db.runTransaction(async (transaction) => { + try { + return await handler(transaction); + } catch (err) { + console.error("❌ Transaction failed:", err); + throw err; + } + }); +}; + +/** + * Get subcollection documents. + */ +export const getSubcollection = async (collection, docId, subcollection) => { + const snapshot = await db + .collection(collection) + .doc(docId) + .collection(subcollection) + .get(); + + return snapshot.docs.map((doc) => ({ + id: doc.id, + ...doc.data(), + })); +}; + +/** + * Add a doc to a subcollection. + */ +export const addSubcollectionDoc = async ( + collection, + docId, + subcollection, + data +) => { + const ref = await db + .collection(collection) + .doc(docId) + .collection(subcollection) + .add(data); + + return { id: ref.id, ...data }; +}; \ No newline at end of file diff --git a/src/services/matchService.js b/src/services/matchService.js new file mode 100644 index 0000000..29ad052 --- /dev/null +++ b/src/services/matchService.js @@ -0,0 +1,113 @@ +// src/services/matchService.js +import { db } from "../config/firebase.js"; + +/** + * Generates a match score between two users based on shared preferences. + * You can expand this later with better scoring (ML, weighting, etc.) + */ +export const calculateMatchScore = (userA, userB) => { + let score = 0; + + // Matching major + if (userA.major === userB.major) score += 30; + + // Matching study preferences + const sharedPrefs = userA.studyPreferences.filter((pref) => + userB.studyPreferences.includes(pref) + ); + score += sharedPrefs.length * 10; + + // Availability overlap (if implemented) + if (userA.availability && userB.availability) { + const overlap = userA.availability.some((slot) => + userB.availability.includes(slot) + ); + if (overlap) score += 20; + } + + return Math.min(score, 100); // keep score in 0–100 range +}; + +/** + * Fetch user from Firestore + */ +export const fetchUser = async (uid) => { + const userDoc = await db.collection("users").doc(uid).get(); + if (!userDoc.exists) return null; + return { uid, ...userDoc.data() }; +}; + +/** + * Create or update match entry in Firestore + */ +export const saveMatch = async (userA, userB, score) => { + const matchId = userA.uid < userB.uid ? `${userA.uid}_${userB.uid}` : `${userB.uid}_${userA.uid}`; + + const matchRef = db.collection("matches").doc(matchId); + + await matchRef.set( + { + users: [userA.uid, userB.uid], + score, + updatedAt: new Date(), + }, + { merge: true } + ); + + return { matchId, score }; +}; + +/** + * Generate match suggestions for a user + */ +export const generateRecommendations = async (uid) => { + const user = await fetchUser(uid); + if (!user) return []; + + // Get all other users + const usersSnap = await db.collection("users").get(); + + const otherUsers = usersSnap.docs + .map((doc) => ({ uid: doc.id, ...doc.data() })) + .filter((u) => u.uid !== uid); + + const results = []; + + for (const target of otherUsers) { + const score = calculateMatchScore(user, target); + if (score > 0) { + const match = await saveMatch(user, target, score); + results.push({ + ...match, + user: target, + }); + } + } + + // Sort descending by score + return results.sort((a, b) => b.score - a.score); +}; + +/** + * Confirm a mutual match (both users accept) + */ +export const confirmMatch = async (uid1, uid2) => { + const matchId = uid1 < uid2 ? `${uid1}_${uid2}` : `${uid2}_${uid1}`; + + const matchRef = db.collection("matches").doc(matchId); + const matchDoc = await matchRef.get(); + + if (!matchDoc.exists) { + return { error: "Match does not exist" }; + } + + const matchData = matchDoc.data(); + + // Update to a confirmed match + await matchRef.update({ + confirmed: true, + confirmedAt: new Date(), + }); + + return { matchId, ...matchData, confirmed: true }; +}; \ No newline at end of file