diff --git a/packages/api/index.ts b/packages/api/index.ts index cec449f..221214f 100644 --- a/packages/api/index.ts +++ b/packages/api/index.ts @@ -8,7 +8,7 @@ import SocketChannels from "@nirvana/core/sockets/channels"; import { UserService } from "./services/user.service"; import { UserStatus } from "@nirvana/core/models"; import cors from "cors"; -import getConversationRoutes from "./routes/conversation"; +import getLineRoutes from "./routes/line"; import getSearchRoutes from "./routes/search"; import getUserRoutes from "./routes/user"; @@ -28,7 +28,7 @@ app.get("/", (req: Request, res: Response) => { app.use("/api/user", getUserRoutes()); app.use("/api/search", getSearchRoutes()); -app.use("/api/conversations", getConversationRoutes()); +app.use("/api/conversations", getLineRoutes()); const PORT = 5000; var server = app.listen(PORT, () => console.log("express running")); diff --git a/packages/api/routes/conversation.ts b/packages/api/routes/conversation.ts deleted file mode 100644 index a76eeea..0000000 --- a/packages/api/routes/conversation.ts +++ /dev/null @@ -1,150 +0,0 @@ -import { - Conversation, - ConversationMember, - ConversationMemberState, -} from "../../core/models/conversation.model"; -import { JwtClaims, authCheck } from "../middleware/auth"; -import express, { Application, Request, Response } from "express"; - -import Content from "@nirvana/core/models/content.model"; -import { ConversationService } from "../services/conversation.service"; -import CreateLineRequest from "../../core/requests/createLine.request"; -import GetConversationDetailsResponse from "@nirvana/core/responses/getConversationDetails.response"; -import GetDmConversationByOtherUserIdResponse from "../../core/responses/getDmConversationByOtherUserId.response"; -import GetUserConversationsResponse from "../../core/responses/getUserConversations.response"; -import MasterConversation from "../../core/models/masterConversation.model"; -import { ObjectId } from "mongodb"; -import Relationship from "@nirvana/core/models/relationship.model"; -import { UserService } from "../services/user.service"; -import { collections } from "../services/database.service"; - -export default function getConversationRoutes() { - const router = express.Router(); - - router.use(express.json()); - - // get data for a one on one conversation - // router.get("/:otherUserGoogleUserId", authCheck, getConversationDetails); - - // create a convo - router.post("/", authCheck, createConversation); - - // get all of user's convos - router.get("/", authCheck, getUserConvos); - - // get conversation between user and other user - router.get("/dm/:otherUserId", authCheck, getDmByOtherUserId); - - return router; -} - -async function getDmByOtherUserId(req: Request, res: Response) { - try { - const { otherUserId } = req.params; - const userInfo = res.locals.userInfo as JwtClaims; - - console.log(otherUserId); - - // check db for convos between me and this other person - // if there is one, then return it with 200 status - // else return it with custom status that frontend will read - - res.status(200).json(); - return; - - res.status(205).json("no such conversation between you two"); - } catch (error) { - res.status(500).json(error); - } -} - -async function createConversation(req: Request, res: Response) { - try { - const reqObj: CreateLineRequest = req.body as CreateLineRequest; - console.log(req.body); - - if (!reqObj?.otherMemberIds.length) { - res.status(400).json("must provide member Ids"); - return; - } - - const userInfo = res.locals.userInfo as JwtClaims; - - const newConvo = new Conversation(new ObjectId()); - const convoMembers: ConversationMember[] = - reqObj.otherMemberIds.map((memId) => { - const newConvoMember = new ConversationMember( - newConvo._id!, - new ObjectId(memId), - ConversationMemberState.INVITED - ); - - return newConvoMember; - }) ?? []; - - convoMembers.push( - new ConversationMember( - newConvo._id!, - new ObjectId(userInfo.userId), - ConversationMemberState.INBOX - ) - ); - - const transactionResult = await ConversationService.createConversation( - newConvo, - convoMembers - ); - - transactionResult - ? res.status(200).json(newConvo) - : res.status(400).json("unable to create convo"); - } catch (error) { - console.log(error); - res.status(500).json(error); - } -} - -async function getUserConvos(req: Request, res: Response) { - try { - const userInfo = res.locals.userInfo as JwtClaims; - - // get all of user's convoMember entries - const convoMembers = - await ConversationService.getConversationsMembersByUserId( - userInfo.userId - ); - - if (!convoMembers?.length) { - res.status(400).json(); - - return; - } - - const convoIds = - convoMembers?.map((convoMem) => convoMem.conversationId) ?? []; - - // get all convos from the list of relevant convos - const convos = - (await ConversationService.getConversationsByIds(convoIds)) ?? []; - - const masterConvos = - convos.map((convo) => { - const assocConvoMember = convoMembers.find((convMem) => - convMem.conversationId.equals(convo._id!) - ); - - return new MasterConversation( - convo._id!, - convo.createdDate, - convo.lastUpdatedDate, - assocConvoMember - ); - }) ?? []; - - const resObj = new GetUserConversationsResponse(convos); - - res.json(masterConvos); - } catch (error) { - res.status(500).json(error); - } -} diff --git a/packages/api/routes/line.ts b/packages/api/routes/line.ts new file mode 100644 index 0000000..f658d22 --- /dev/null +++ b/packages/api/routes/line.ts @@ -0,0 +1,151 @@ +import { JwtClaims, authCheck } from "../middleware/auth"; +import { + Line, + LineMember, + LineMemberState, +} from "@nirvana/core/models/line.model"; +import express, { Application, Request, Response } from "express"; + +import Content from "@nirvana/core/models/content.model"; +import CreateLineRequest from "@nirvana/core/requests/createLine.request"; +import GetConversationDetailsResponse from "@nirvana/core/responses/getConversationDetails.response"; +import GetDmConversationByOtherUserIdResponse from "@nirvana/core/responses/getDmConversationByOtherUserId.response"; +import GetUserLinesResponse from "@nirvana/core/responses/getUserLines.response"; +import { LineService } from "../services/line.service"; +import MasterLineData from "@nirvana/core/models/masterLineData.model"; +import { ObjectId } from "mongodb"; +import Relationship from "@nirvana/core/models/relationship.model"; +import { UserService } from "../services/user.service"; +import { collections } from "../services/database.service"; + +export default function getLineRoutes() { + const router = express.Router(); + + router.use(express.json()); + + // get data for a one on one conversation + // router.get("/:otherUserGoogleUserId", authCheck, getConversationDetails); + + // create a line + router.post("/", authCheck, createLine); + + // get all of user's lines + router.get("/", authCheck, getUserLines); + + // get conversation between user and other user + router.get("/dm/:otherUserId", authCheck, getDmByOtherUserId); + + return router; +} + +async function getDmByOtherUserId(req: Request, res: Response) { + try { + const { otherUserId } = req.params; + const userInfo = res.locals.userInfo as JwtClaims; + + console.log(otherUserId); + + // check db for lines between me and this other person + // if there is one, then return it with 200 status + // else return it with custom status that frontend will read + + res.status(200).json(); + return; + + res.status(205).json("no such conversation between you two"); + } catch (error) { + res.status(500).json(error); + } +} + +async function createLine(req: Request, res: Response) { + try { + const reqObj: CreateLineRequest = req.body as CreateLineRequest; + console.log(req.body); + + if (!reqObj?.otherMemberIds.length) { + res.status(400).json("must provide member Ids"); + return; + } + + const userInfo = res.locals.userInfo as JwtClaims; + + const newLine = new Line(new ObjectId(), new Date()); + const lineMembers: LineMember[] = + reqObj.otherMemberIds.map((memId) => { + const newLineMember = new LineMember( + newLine._id!, + new ObjectId(memId), + LineMemberState.INBOX + ); + + return newLineMember; + }) ?? []; + + lineMembers.push( + new LineMember( + newLine._id!, + new ObjectId(userInfo.userId), + LineMemberState.INBOX + ) + ); + + const transactionResult = await LineService.createLine( + newLine, + lineMembers + ); + + transactionResult + ? res.status(200).json(newLine) + : res.status(400).json("unable to create line"); + } catch (error) { + console.log(error); + res.status(500).json(error); + } +} + +async function getUserLines(req: Request, res: Response) { + try { + const userInfo = res.locals.userInfo as JwtClaims; + + // get all of user's lineMember entries + const lineMembers = await LineService.getLineMembersByUserId( + userInfo.userId + ); + + if (!lineMembers?.length) { + res.status(400).json(); + + return; + } + + const lineIds = lineMembers?.map((lineMem) => lineMem.lineId) ?? []; + + // get all lines from the list of relevant lines + const lines = (await LineService.getLinesByIds(lineIds)) ?? []; + + const masterLines = + lines.map((currentLine) => { + const associatedLineMember = lineMembers.find((lineMember) => + lineMember.lineId.equals(currentLine._id!) + ); + + // TODO: get all of the other members on the line + + // TODO: get the latest audio blocks for this line...maybe like today and yesterday or by block count + + return new MasterLineData( + currentLine._id!, + currentLine.createdDate, + currentLine.lastUpdatedDate, + associatedLineMember + ); + }) ?? []; + + const resObj = new GetUserLinesResponse(lines); + + res.json(masterLines); + } catch (error) { + res.status(500).json(error); + } +} diff --git a/packages/api/services/database.service.ts b/packages/api/services/database.service.ts index b9b38fc..497f340 100644 --- a/packages/api/services/database.service.ts +++ b/packages/api/services/database.service.ts @@ -6,9 +6,8 @@ import { loadConfig } from "../config"; // Global Variables export const collections: { users?: mongoDB.Collection; - conversations?: mongoDB.Collection; - conversationMembers?: mongoDB.Collection; - audioClips?: mongoDB.Collection; + lines?: mongoDB.Collection; + lineMembers?: mongoDB.Collection; } = {}; // Initialize Connection @@ -23,16 +22,12 @@ client.connect(); const db: mongoDB.Db = client.db(process.env.DB_NAME); const usersCollection: mongoDB.Collection = db.collection("users"); -const convoCollection: mongoDB.Collection = db.collection("conversations"); -const convoMembersCollection: mongoDB.Collection = db.collection( - "conversationMembers" -); -const audioClipsCollection: mongoDB.Collection = db.collection("audioClips"); +const lineCollection: mongoDB.Collection = db.collection("lines"); +const lineMembersCollection: mongoDB.Collection = db.collection("lineMembers"); collections.users = usersCollection; -collections.conversations = convoCollection; -collections.conversationMembers = convoMembersCollection; -collections.audioClips = audioClipsCollection; +collections.lines = lineCollection; +collections.lineMembers = lineMembersCollection; console.log( `Successfully connected to database: ${db.databaseName} and collections` diff --git a/packages/api/services/conversation.service.ts b/packages/api/services/line.service.ts similarity index 63% rename from packages/api/services/conversation.service.ts rename to packages/api/services/line.service.ts index 99548d4..9a7eed1 100644 --- a/packages/api/services/conversation.service.ts +++ b/packages/api/services/line.service.ts @@ -1,13 +1,10 @@ -import { - Conversation, - ConversationMember, -} from "../../core/models/conversation.model"; +import { Line, LineMember } from "@nirvana/core/models/line.model"; import { client, collections } from "./database.service"; import { ObjectId } from "mongodb"; -export class ConversationService { - static async getConversationByOtherUserId(otherUserId: ObjectId) { +export class LineService { + static async getLineByOtherUserId(otherUserId: ObjectId) { // get all of the conversations for this user that have exactly two conversation members // get all of the conversationMembers for this user // get all of the conversations for this user @@ -21,58 +18,54 @@ export class ConversationService { // return null; } - static async getConversationsByIds(convoIds: ObjectId[]) { + static async getLinesByIds(convoIds: ObjectId[]) { const query = { _id: { $in: convoIds } }; - const convosRes = await collections.conversations?.find(query).toArray(); + const convosRes = await collections.lines?.find(query).toArray(); // exists if (convosRes?.length) { - return convosRes as Conversation[]; + return convosRes as Line[]; } return null; } - static async getConversationsMembersByUserId(userId: string) { + static async getLineMembersByUserId(userId: string) { const query = { userId: new ObjectId(userId) }; - const convoMembersRes = await collections.conversationMembers + const convoMembersRes = await collections.lineMembers ?.find(query) .toArray(); // exists if (convoMembersRes?.length) { - return convoMembersRes as ConversationMember[]; + return convoMembersRes as LineMember[]; } return null; } - static async createConversation( - convo: Conversation, - convoMembers: ConversationMember[] - ) { + static async createLine(line: Line, lineMembers: LineMember[]) { const session = client.startSession(); try { const transactionResults = await session.withTransaction(async () => { // todo: check if convoMembers userId's actually exist - const insertConvoRes = await collections.conversations?.insertOne( - convo - ); - if (!insertConvoRes?.insertedId) { + const insertLineRes = await collections.lines?.insertOne(line); + if (!insertLineRes?.insertedId) { await session.abortTransaction(); - console.error("failed to create convo"); + console.error("failed to create line"); return; } - const insertConvoMembersRes = - await collections.conversationMembers?.insertMany(convoMembers); + const insertConvoMembersRes = await collections.lineMembers?.insertMany( + lineMembers + ); if (!insertConvoMembersRes?.insertedCount) { await session.abortTransaction(); - console.error("failed to create conversation members"); + console.error("failed to create line members"); return; } diff --git a/packages/core/models/conversation.model.ts b/packages/core/models/conversation.model.ts deleted file mode 100644 index c272cf9..0000000 --- a/packages/core/models/conversation.model.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { ObjectId } from "mongodb"; - -export class Conversation { - name?: string; - - constructor( - public _id?: ObjectId, - public createdDate: Date = new Date(), - public lastUpdatedDate: Date = new Date() - ) {} -} - -export class ConversationMember { - constructor( - // unique constraint - public conversationId: ObjectId, - public userId: ObjectId, - - public state: ConversationMemberState = ConversationMemberState.INVITED, - - public createdDate: Date = new Date(), - public _id?: ObjectId - ) {} -} - -export enum ConversationMemberState { - INVITED = "INVITED", // user gets this in their request inbox - INBOX = "INBOX", // user decided to join this conversation after being invited - TUNED = "TUNED", // user upgraded priority of this and now is tuned in live to convo - ARCHIVED = "ARCHIVED", // user no longer wants anything to do with convo -} diff --git a/packages/core/models/line.model.ts b/packages/core/models/line.model.ts new file mode 100644 index 0000000..8609a97 --- /dev/null +++ b/packages/core/models/line.model.ts @@ -0,0 +1,37 @@ +import { ObjectId } from "mongodb"; + +export class Line { + constructor( + public _id?: ObjectId, + public createdDate: Date = new Date(), + public lastUpdatedDate: Date = new Date(), + public name?: string + ) {} +} + +export class LineMember { + // the last time that the user visited the line + // for new activity monitoring + lastVisitDate?: Date; + + constructor( + // unique constraint + public lineId: ObjectId, + public userId: ObjectId, + + public state: LineMemberState = LineMemberState.INBOX, + + public createdDate: Date = new Date(), + public _id?: ObjectId + ) {} +} + +export enum LineMemberState { + // these states will be for later when we want an inbox and such + // INVITED = "INVITED", // user gets this in their request inbox + + // ARCHIVED = "ARCHIVED", // user no longer wants anything to do with convo + + INBOX = "INBOX", // user decided to join this conversation after being invited...for now things just land in inbox anyway + TUNED = "TUNED", // user upgraded priority of this and now is toggle tuned in live to convo +} diff --git a/packages/core/models/masterConversation.model.ts b/packages/core/models/masterLineData.model.ts similarity index 64% rename from packages/core/models/masterConversation.model.ts rename to packages/core/models/masterLineData.model.ts index 0999dae..cb2091c 100644 --- a/packages/core/models/masterConversation.model.ts +++ b/packages/core/models/masterLineData.model.ts @@ -1,8 +1,8 @@ import AudioClip from "./audioClip.model"; -import { ConversationMember } from "./conversation.model"; +import { LineMember } from "./line.model"; import { ObjectId } from "mongodb"; -export default class MasterConversation { +export default class MasterLineData { constructor( // attributes of conversation public id: ObjectId, // id of the conversation @@ -11,9 +11,9 @@ export default class MasterConversation { public lastUpdatedDate: Date, // compiled data for easy client read - public currentUserMember?: ConversationMember, + public currentUserMember?: LineMember, - public otherMembers?: ConversationMember[], + public otherMembers?: LineMember[], public audioClips: AudioClip[] = [], diff --git a/packages/core/package.json b/packages/core/package.json index 48bb738..9c17a7c 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -4,6 +4,7 @@ "main": "index.js", "license": "MIT", "dependencies": { - "mongodb": "^4.4.1" + "mongodb": "^4.4.1", + "@nirvana/core": "*" } } diff --git a/packages/core/responses/getDmConversationByOtherUserId.response.ts b/packages/core/responses/getDmConversationByOtherUserId.response.ts index 3454257..fcb38a9 100644 --- a/packages/core/responses/getDmConversationByOtherUserId.response.ts +++ b/packages/core/responses/getDmConversationByOtherUserId.response.ts @@ -1,4 +1,4 @@ -import { Conversation } from "../models/conversation.model"; +import { Line } from "../models/line.model"; export default class GetDmConversationByOtherUserIdResponse { constructor(public conversation: Conversation) {} diff --git a/packages/core/responses/getUserConversations.response.ts b/packages/core/responses/getUserConversations.response.ts deleted file mode 100644 index 3c45d5b..0000000 --- a/packages/core/responses/getUserConversations.response.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Conversation } from "../models/conversation.model"; - -export default class GetUserConversationsResponse { - constructor(public convos: Conversation[]) {} -} diff --git a/packages/core/responses/getUserLines.response.ts b/packages/core/responses/getUserLines.response.ts new file mode 100644 index 0000000..b5ee047 --- /dev/null +++ b/packages/core/responses/getUserLines.response.ts @@ -0,0 +1,5 @@ +import { Line } from "../models/line.model"; + +export default class GetUserLinesResponse { + constructor(public lines: Line[]) {} +} diff --git a/packages/desktop/src/components/conversation/basicRow.tsx b/packages/desktop/src/components/conversation/basicRow.tsx index 2edab71..b08b3b5 100644 --- a/packages/desktop/src/components/conversation/basicRow.tsx +++ b/packages/desktop/src/components/conversation/basicRow.tsx @@ -1,4 +1,4 @@ -import MasterConversation from "../../../../core/models/masterConversation.model"; +import MasterConversation from "@nirvana/core/models/masterLineData.model"; import UserStatusText from "../User/userStatusText"; export default function BasicConversationRow({ masterConvo, diff --git a/packages/desktop/src/controller/nirvanaApi.ts b/packages/desktop/src/controller/nirvanaApi.ts index 36e7058..fb71b17 100644 --- a/packages/desktop/src/controller/nirvanaApi.ts +++ b/packages/desktop/src/controller/nirvanaApi.ts @@ -1,9 +1,9 @@ import axios, { AxiosRequestConfig, AxiosResponse, Method } from "axios"; -import { Conversation } from "../../../core/models/conversation.model"; import CreateLineRequest from "@nirvana/core/requests/createLine.request"; +import { Line } from "@nirvana/core/models/line.model"; import LoginResponse from "../../../core/responses/login.response"; -import MasterConversation from "../../../core/models/masterConversation.model"; +import MasterConversation from "@nirvana/core/models/masterLineData.model"; import { User } from "@nirvana/core/models"; import UserDetailsResponse from "../../../core/responses/userDetails.response"; import UserSearchResponse from "../../../core/responses/userSearch.response"; @@ -85,7 +85,7 @@ async function getUserConversations(): Promise { return await NirvanaApi.fetch(`/conversations`, "GET", true); } -async function getDmByUserId(otherUserId: string): Promise { +async function getDmByUserId(otherUserId: string): Promise { return await NirvanaApi.fetch( `/conversations/dm/${otherUserId}`, "GET", diff --git a/packages/desktop/src/pages/terminal/newLine/index.tsx b/packages/desktop/src/pages/terminal/newLine/index.tsx index a0ddf1b..2ba3971 100644 --- a/packages/desktop/src/pages/terminal/newLine/index.tsx +++ b/packages/desktop/src/pages/terminal/newLine/index.tsx @@ -96,6 +96,8 @@ export default function NewLineModal({ } catch (error) { toast.error(error); console.error(error); + } finally { + console.log("done"); } }, [lineName, selectedPeople]);