Skip to content
This repository was archived by the owner on Sep 18, 2024. It is now read-only.

Send message #20

Merged
merged 3 commits into from
Feb 1, 2024
Merged
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
48 changes: 47 additions & 1 deletion src/core/application/controllers/thread/threadController.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Elysia } from "elysia";
import { Elysia, t } from "elysia";
import { ulid } from "ulid";

import { getTokenPermissions, parseToken } from "../../services/tokenService";
Expand All @@ -17,6 +17,7 @@ import {
UNAUTHORIZED_USER_NOT_PARTICIPANT,
} from "./returnValues";
import { UNAUTHORIZED_MISSING_TOKEN } from "@/core/application/ports/returnValues";
import { createMessage } from "../../services/messageService";

type ThreadDecorator = {
request: {
Expand Down Expand Up @@ -127,3 +128,48 @@ threads.get("/thread/:id", async ({ params, bearer, set }) => {
set.status = 401;
return UNAUTHORIZED_MISSING_TOKEN;
});

threads.post(
"/thread/:id/message",
async ({ params, bearer, set, body }) => {
const permissions = await getTokenPermissions(bearer!);
const decodedToken = await parseToken(bearer!);

if (decodedToken) {
const { userId } = decodedToken;
const threadId = params.id;

// Check if the user has the permission to add a message
if (
permissions?.some(
(p) => p.key === "create_message_in_own_thread" || p.key === "*"
)
) {
// if the user has * they can send a message anywhere, if not they need to be in conversation
if (
permissions.some((p) => p.key === "*") ||
(await userOwnsOrParticipatesInThread(threadId, userId))
) {
const { message } = body;
const res = await createMessage(userId, threadId, message);
set.status = 200;
return res;
} else {
set.status = 403;
return UNAUTHORIZED_USER_NOT_PARTICIPANT;
}
} else {
set.status = 403;
return UNAUTHORIZED_NO_PERMISSION_READ;
}
}

set.status = 401;
return UNAUTHORIZED_MISSING_TOKEN;
},
{
body: t.Object({
message: t.String(),
}),
}
);
35 changes: 35 additions & 0 deletions src/core/application/controllers/thread/threadControllet.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,39 @@ describe("threadController", async () => {

expect(response.message).toBe(UNAUTHORIZED_MISSING_TOKEN.message);
});

test("should add a message to a thread", async () => {
const createThreadRequest = new Request("http://localhost:8080/thread", {
headers: {
authorization: `Bearer ${token}`,
},
method: "POST",
});

const createThreadResponse = await app.handle(createThreadRequest);
const createThreadResponseJson: any = await createThreadResponse.json();

expect(createThreadResponseJson).toHaveProperty("id");

const id = createThreadResponseJson.id;

const message = "why are cats cute?";

const request = new Request(`http://localhost:8080/thread/${id}/message`, {
headers: {
authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
method: "POST",
body: JSON.stringify({
message,
}),
});

const response: any = await app
.handle(request)
.then((response) => response.json());

expect(response.content).toBe(message);
});
});
71 changes: 71 additions & 0 deletions src/core/application/services/messageService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import type { Message } from "@/core/domain/messages";
import { getNeo4jSession } from "@/infrastructure/adaptaters/neo4jAdapter";

export async function createMessage(
userId: string,
threadId: string,
messageContent: string
): Promise<Message> {
const session = getNeo4jSession();

try {
const result = await session.run(
`
MATCH (u:User {id: $userId})
MATCH (t:Thread {id: $threadId})
CREATE (m:Message {id: randomUUID(), content: $messageContent, timestamp: timestamp()})
CREATE (u)-[:POSTS]->(m)
CREATE (m)-[:BELONGS_TO]->(t)
RETURN m
`,
{
userId,
threadId,
messageContent,
}
);

if (result.records.length === 0) {
throw new Error("Failed to create message");
}

const record = result.records[0];
const message = record.get("m").properties;

return {
id: message.id,
content: message.content,
senderId: userId,
timestamp: new Date(Number(message.timestamp)),
};
} catch (err) {
console.log(err);
throw err;
} finally {
session.close();
}
}

export async function getLastMessage(threadId: string) {
const session = getNeo4jSession();
const query = `
MATCH (u:User)-[:POSTS]->(m:Message)-[:BELONGS_TO]->(t:Thread {threadId: $threadId})
RETURN u, m
ORDER BY m.timestamp DESC
LIMIT 1
`;

try {
const result = await session.run(query, { threadId });
const singleRecord = result.records[0];
const user = singleRecord.get("u");
const message = singleRecord.get("m");

return {
user: user.properties,
message: message.properties,
};
} finally {
await session.close();
}
}
9 changes: 9 additions & 0 deletions src/core/domain/messages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* Represents a message in the system.
*/
export type Message = {
id: string;
content: string;
senderId: string;
timestamp: Date;
};
Loading