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
93 changes: 93 additions & 0 deletions src/services/chatService.js
Original file line number Diff line number Diff line change
@@ -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]);
};
127 changes: 127 additions & 0 deletions src/services/firestoreService.js
Original file line number Diff line number Diff line change
@@ -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 };
};
113 changes: 113 additions & 0 deletions src/services/matchService.js
Original file line number Diff line number Diff line change
@@ -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 };
};