diff --git a/packages/core/src/models/conversation.model.ts b/packages/core/src/models/conversation.model.ts index ef20be2..30b8a43 100644 --- a/packages/core/src/models/conversation.model.ts +++ b/packages/core/src/models/conversation.model.ts @@ -5,23 +5,36 @@ export default class Conversation { constructor( public createdByUserId: string, - public membersList: string[], - public name: string | null = null, - public lastUpdatedDate = Timestamp.now(), - public createdDate = Timestamp.now(), public membersInRoom: string[] = [], + + public lastActivityDate: Timestamp = Timestamp.now(), ) {} } -// export class Member { -// constructor( -// public id: string, -// public userId: string, -// public role: 'admin' | 'regular', -// public joinedDate = Timestamp.now(), -// ) {} -// } +export class ConversationMember { + constructor( + public id: string, // NOTE: serves as the user ID + public conversationId: string, + + public role: MemberRole, + + public memberState: MemberState, + + public lastActiveDate: Timestamp | null = null, + public joinedDate = Timestamp.now(), + ) {} +} + +export enum MemberRole { + admin = 'admin', + regular = 'regular', +} +export enum MemberState { + priority = 'priority', + inbox = 'inbox', + // deleted = "deleted" +} diff --git a/packages/desktop/src/components/Terminal.tsx b/packages/desktop/src/components/Terminal.tsx index be54619..71a77e1 100644 --- a/packages/desktop/src/components/Terminal.tsx +++ b/packages/desktop/src/components/Terminal.tsx @@ -48,25 +48,27 @@ import { useSnackbar } from 'notistack'; import NirvanaLogo from './NirvanaLogo'; -import Conversation from '@nirvana/core/src/models/conversation.model'; +import Conversation, { ConversationMember } from '@nirvana/core/src/models/conversation.model'; import useAuth from '../providers/AuthProvider'; import { createOneOnOneConversation, - getConversationsQueryLIVE, getUserById, + getUserConversationMembersQueryLIVE, searchUsers, } from '../firebase/firestore'; import { Link, AudioClip, Image } from '@nirvana/core/src/models/content.model'; import { useImmer } from 'use-immer'; import { User } from '@nirvana/core/src/models/user.model'; -import { onSnapshot } from 'firebase/firestore'; +import { onSnapshot, Unsubscribe } from 'firebase/firestore'; import NirvanaAvatar from './NirvanaAvatar'; import { useDebounce, useKey, useKeyPressEvent } from 'react-use'; import KeyboardShortcutLabel from './KeyboardShortcutLabel'; +import { getConversationQueryLIVE } from '../firebase/firestore'; + type ConversationMap = { - [conversationId: string]: Conversation; + [conversationId: string]: Partial; }; type UserMap = { @@ -88,6 +90,8 @@ interface ITerminalContext { getUser?: (userId: string) => Promise; } +type MasterConversation = Conversation & { members: ConversationMember[] }; + const TerminalContext = React.createContext({ conversationMap: {}, }); @@ -121,23 +125,52 @@ export function TerminalProvider({ children }: { children?: React.ReactNode }) { const [userMap, updateUserMap] = useImmer({}); const [contentMap, updatecontentMap] = useImmer({}); - // fetch conversations + // fetch conversations that I am in useEffect(() => { - const unsub = onSnapshot(getConversationsQueryLIVE(user.uid), (querySnapshot) => { - const conversations = querySnapshot.docs.map((doc) => doc.data()); + const unsubs: Unsubscribe[] = []; - console.log('got new or updated conversations', conversations); + const unsubMyConversationMembers = onSnapshot( + getUserConversationMembersQueryLIVE(user.uid), + (querySnapshot) => { + querySnapshot.docChanges().forEach((docSnapChange) => { + const conversationMember = docSnapChange.doc.data(); + const conversationId = conversationMember.conversationId; - updateConversationMap((draft) => { - conversations.forEach((convo) => { - draft[convo.id] = convo; + // start listening to conversations + // get all of the other members + if (docSnapChange.type === 'added') { + const conversationSub = onSnapshot(getConversationQueryLIVE, (conversationSnapshot) => { + const conversation = conversationSnapshot + }) + console.log('New city: ', docSnapChange.doc.data()); + } + if (docSnapChange.type === 'modified') { + console.log('Modified city: ', docSnapChange.doc.data()); + } + if (docSnapChange.type === 'removed') { + console.log('Removed city: ', docSnapChange.doc.data()); + } + + updateConversationMap((draft) => { + if (draft[conversationMember.conversationId]) { + if (draft[conversationMember.conversationId].members) { + draft[conversationMember.conversationId].members.push(conversationMember); + } else { + draft[conversationMember.conversationId].members = [conversationMember]; + } + } else { + draft[conversationMember.conversationId] = { members: [conversationMember] }; + } + }); }); - }); + }, + ); - enqueueSnackbar('got conversations...logging', { variant: 'success' }); - }); + unsubs.push(unsubMyConversationMembers) - return () => unsub(); + return () => { + unsubs.forEach((unsub) => unsub()); + }; }, [user, enqueueSnackbar, updateConversationMap]); // cache of selected conversation diff --git a/packages/desktop/src/firebase/firestore.ts b/packages/desktop/src/firebase/firestore.ts index e56da6d..7faa310 100644 --- a/packages/desktop/src/firebase/firestore.ts +++ b/packages/desktop/src/firebase/firestore.ts @@ -17,13 +17,18 @@ import { startAt, endAt, addDoc, + writeBatch, + collectionGroup, } from 'firebase/firestore'; import { User as FirebaseUser } from 'firebase/auth'; import { firestoreDb } from './connect'; import { User } from '@nirvana/core/src/models/user.model'; -import Conversation from '@nirvana/core/src/models/conversation.model'; -import useAuth from '../providers/AuthProvider'; +import Conversation, { + ConversationMember, + MemberRole, + MemberState, +} from '@nirvana/core/src/models/conversation.model'; interface Document { id: string; @@ -50,11 +55,17 @@ const docPoint = (collectionPath: string) => const collectionPoint = (collectionPath: string) => firestoreDb && collection(getFirestore(), collectionPath).withConverter(converter()); +// const collectionGroupPoint = (collectionPath: string) => +// firestoreDb && collectionGroup(getFirestore(), collectionPath).withConverter(converter()); + const db = { users: collectionPoint(`users`), user: (userId: string) => docPoint(`users/${userId}`), conversations: collectionPoint(`conversations`), - conversation: (conversationId: string) => docPoint(`conversations/${conversationId}`), + conversation: (conversationId: string) => + docPoint(`conversations/${conversationId}`), + conversationMembers: collectionPoint(`conversationMembers`), + conversationMember: () => docPoint(`conversationMembers`), }; // enum COLLECTION { @@ -127,9 +138,20 @@ export const getUserById = async (userId: string): Promise => return undefined; }; -// get the conversations for particular user -export const getConversationsQueryLIVE = (userId: string) => - query(db.conversations, where('membersList', 'array-contains', userId)); +// get conversation members for all conversations that I am in +export const getUserConversationMembersQueryLIVE = (userId: string) => { + return query(db.conversationMembers, where('userId', '==', userId)); +}; + +// get all conversation members for a conversation +export const getConversationMembersQueryLIVE = (conversationId: string) => { + return query(db.conversationMembers, where('conversationId', '==', conversationId)); +}; + +// get document reference for specific conversation +export const getConversationQueryLIVE = (conversationId: string) => { + return db.conversation(conversationId); +}; /** * @@ -141,11 +163,34 @@ export const createOneOnOneConversation = async ( otherUserId: string, myUserId: string, ): Promise => { - const newConversation = new Conversation(myUserId, [myUserId, otherUserId], null); - try { - const newDoc = await addDoc(db.conversations, newConversation); - return newDoc.id; + const newConversation = new Conversation(myUserId); + const newDocConversationInserted = await addDoc(db.conversations, newConversation); + + const batch = writeBatch(firestoreDb); + + // create all members + const adminMember = new ConversationMember( + myUserId, + newDocConversationInserted.id, + MemberRole.admin, + MemberState.priority, + null, + ); + const otherMember = new ConversationMember( + otherUserId, + newDocConversationInserted.id, + MemberRole.regular, + MemberState.inbox, + null, + ); + + batch.set(db.conversationMember(), adminMember); + batch.set(db.conversationMember(), otherMember); + + await batch.commit(); + + return newDocConversationInserted.id; } catch (e) { console.error('Error creating user: ', e); @@ -156,11 +201,38 @@ export const createOneOnOneConversation = async ( export const createGroupConversation = async ( otherUserIds: string[], myUserId: string, -): Promise => { - const newConversation = new Conversation(myUserId, [myUserId, ...otherUserIds]); - +): Promise => { try { - await addDoc(db.conversations, newConversation); + const newConversation = new Conversation(myUserId); + const newDocConversationInserted = await addDoc(db.conversations, newConversation); + + const batch = writeBatch(firestoreDb); + + const adminMember = new ConversationMember( + myUserId, + newDocConversationInserted.id, + MemberRole.admin, + MemberState.priority, + null, + ); + batch.set(db.conversationMember(), adminMember); + + // create all members + otherUserIds.forEach((otherUserId) => { + const otherMember = new ConversationMember( + otherUserId, + newDocConversationInserted.id, + MemberRole.regular, + MemberState.inbox, + null, + ); + + batch.set(db.conversationMember(), otherMember); + }); + + await batch.commit(); + + return newDocConversationInserted.id; } catch (e) { console.error('Error creating user: ', e);