Skip to content

Commit

Permalink
[CIT-28] Allow users to message one another (#51)
Browse files Browse the repository at this point in the history
* [CIT-28] Make messaging API related changes

* [CIT-28] Get Ably demo working in prod

* [CIT-28] Begin adding code to persist messages

* [CIT-28] Group creation, message persistence

* [CIT-28] API rewrite into library functions

* [CIT-28] Intermediate commit

* [CIT-28] Cleanup, make groups secure

* [CIT-28] Cleanup

* [CIT-28] Cleanup, let users follow each other

* CIT-28: add link for chats

---------

Co-authored-by: Daniel <[email protected]>
  • Loading branch information
ishaan-upadhyay and TheDannyG authored Aug 10, 2023
1 parent c5dd676 commit cca3075
Show file tree
Hide file tree
Showing 25 changed files with 1,855 additions and 57 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,7 @@ citrus/next-env.d.ts

#data
cockroach-data/
node_modules/
node_modules/

#VS Code files
*.code-workspace
99 changes: 99 additions & 0 deletions citrus/app/(users)/groups/[group]/JSXStyleComponent.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
"use client"
import AblyChatComponent from "@/components/AblyChatComponent"

export default function ChatContainer(params) {
return (
<div className="container">

<main>
<h1 className="title">{params.groupName}</h1>
<AblyChatComponent channelID={params.groupID} />
</main>

<style jsx>{`
.container {
display: grid;
grid-template-rows: 1fr 100px;
min-height: 100vh;
background-color: #eee;
max-width: 100vw;
}
main {
display: grid;
grid-template-rows: auto 1fr;
width: calc(100% - 40px);
max-width: 900px;
margin: 20px auto;
border-radius: 10px;
overflow: hidden;
box-shadow: 0px 3px 10px 1px rgba(0,0,0,0.2);
background-color: white;
}
.title {
display: flex;
justify-content: center;
align-items: center;
height: 100px;
margin: 0;
color: white;
background: #005C97;
background: -webkit-linear-gradient(to right, #363795, #005C97);
background: linear-gradient(to right, #363795, #005C97);
}
.logo {
display: block;
height: 20px;
margin: 0.5em;
}
.svg {
fill:#005C97;
color:#fff;
position: absolute;
top: 0;
border: 0;
right: 0;
}
@media (min-width: 600px) {
.logo {
height: 40px;
margin: 1em;
}
.ably {
height: 60px;
}
}
`}</style>

<style jsx global>{`
html,
body {
padding: 0;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto,
Oxygen, Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue,
sans-serif;
}
* {
box-sizing: border-box;
}
[data-author="me"] {
background: linear-gradient(to right, #363795, #005C97); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
color: white;
align-self: flex-end;
border-bottom-right-radius: 0!important;
border-bottom-left-radius: 10px!important;
}
`}</style>
</div>
)
}
3 changes: 3 additions & 0 deletions citrus/app/(users)/groups/[group]/messagehistory/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function Page() {

}
25 changes: 25 additions & 0 deletions citrus/app/(users)/groups/[group]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import ChatContainer from './JSXStyleComponent';
import { getServerSession } from 'next-auth';
import { authOptions } from '@/app/api/auth/[...nextauth]/route';
import { isUserInGroup, getGroup } from '@/lib/messages';
export const dynamic = "force-dynamic"

export default async function Home({ params }: { params: { group: string } }) {
const groupID = params.group;
const session = await getServerSession(authOptions);

if (!session || !session.user) {
return <div>loading...</div>
}

const inGroup = await isUserInGroup(session.user.name as string, groupID)
if (!inGroup) {
return <div>loading...</div>
}

const group = await getGroup(groupID);

return (
<ChatContainer groupID={groupID} groupName={group.name}/>
)
}
29 changes: 29 additions & 0 deletions citrus/app/(users)/groups/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import GroupChatHolder from "@/components/GroupChatHolder"
import { use } from "react";
import { getGroupsForUser } from "@/lib/messages"
export const dynamic = "force-dynamic"

function GroupCard({ group }: { group: any }) {
return (
<li className="bg-blue-600 p-2 rounded-lg my-2 border-white border-2">
<a href={`/groups/${group.id}`}>{group.name}</a>
</li>
)
}

export default function Page() {
const groups = use(getGroupsForUser())
if (groups === "Unauthorized") {
return <div>loading...</div>
}
return (
<div className="text-center">
<h1 className="text-3xl mx-auto">Groups</h1>
<div className='flex flex-row flex-wrap'>
<ul className="mx-auto">
{groups.map((group: any) => <GroupCard group={group} key={group.id}/>)}
</ul>
</div>
</div>
)
}
4 changes: 3 additions & 1 deletion citrus/app/(users)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ export default async function RootLayout({
children: React.ReactNode
}) {
const session = await getServerSession(authOptions);

const correctUserType = (!session) || (session.user && session.user.userType != 'organizer');

return (
Expand All @@ -46,6 +45,9 @@ export default async function RootLayout({
<li className='flex-1'>
<a href="/about">About</a>
</li>
<li className='flex-1'>
<a href="/groups">Chats</a>
</li>
<NavBarLogin />
<li>
<a href="/organizer">Organizer portal</a>
Expand Down
44 changes: 44 additions & 0 deletions citrus/app/api/followers/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import Ably from "ably/promises";
import { NextResponse } from "next/server";
import * as db from "@/lib/db"
import "@/lib/patch"
import { getServerSession } from "next-auth";
import { authOptions } from "@/app/api/auth/[...nextauth]/route";

const prisma = db.getClient();

export async function POST(request: Request) {
const session = await getServerSession(authOptions);
const body = await request.json();

if (!session || !session.user) {
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}

try {
const follows = await prisma.Follows.create({
data: {
follower_username: session.user.name,
following_username: body.username
}
})

const group = await prisma.Group.create({
data: {
name: `${session.user.name} & ${body.username}`,
users: {
connect: [
{ username: session.user.name },
{ username: body.username }
]
}
}
})
return NextResponse.json(
{ follows, group, success: true },
{ status: 200 }
)
} catch (e) {
return db.handleError(e);
}
}
12 changes: 12 additions & 0 deletions citrus/app/api/messages/ably/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import Ably from "ably/promises";
import { NextResponse } from "next/server";

export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const clientId = searchParams.get("clientId");
if (!clientId) return NextResponse.json({ error: "clientId is required" }, { status: 400 })

const client = new Ably.Realtime(process.env.ABLY_API_KEY as string);
const tokenRequestData = await client.auth.createTokenRequest({ clientId: clientId});
return NextResponse.json(tokenRequestData);
};
88 changes: 88 additions & 0 deletions citrus/app/api/messages/groups/[id]/messages/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import * as db from "@/lib/db"
import { getServerSession } from "next-auth";
import { NextResponse } from "next/server";
import { authOptions } from "@/app/api/auth/[...nextauth]/route";

const prisma = db.getClient();

// Return messages in a group 200 at a time, starting from the most recent
export async function GET(request:Request, { params }: { params: { id: string } }) {
const session = await getServerSession(authOptions);
const { searchParams } = new URL(request.url)
const prevCursor = searchParams.get("cursor")
const nextCursor = searchParams.get("cursor")
var take = 200
var cursor = undefined
var skip = 1

if (prevCursor) {
cursor = {
id: prevCursor
}
take = -200
} else if (nextCursor) {
cursor = {
id: nextCursor
}
} else {
skip = 0
}

if (!session || !session.user) {
return NextResponse.json("Unauthorized", { status: 401 })
}

try {
const messages = await prisma.Message.findMany({
where: {
group_id: params.id
},
orderBy: {
created_at: "desc"
},
take: take,
skip: skip,
cursor: cursor
})

const nextCursor = messages[messages.length - 1]?.id
const prevCursor = messages[0]?.id

return NextResponse.json(
{ messages: messages,
nextCursor: nextCursor,
prevCursor: prevCursor },
{ status: 200 }
)
} catch(e) {
return db.handleError(e)
}
}

export async function POST(request: Request,
{ params }: { params: { id: string } }) {
const body = await request.json();
const { text } = body;
const session = await getServerSession(authOptions);

if (!session || !session.user) {
return NextResponse.json("Unauthorized", { status: 401 })
}

try {
await prisma.Message.create({
data: {
text: text,
user_id: session.user?.name,
group_id: params.id
}
})
} catch (e) {
return db.handleError(e)
}

return NextResponse.json(
{ message: "Message sent" },
{ status: 200 }
)
}
Loading

1 comment on commit cca3075

@vercel
Copy link

@vercel vercel bot commented on cca3075 Aug 10, 2023

Choose a reason for hiding this comment

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

Please sign in to comment.