Skip to content

Commit

Permalink
Allow users to delete their own messages
Browse files Browse the repository at this point in the history
  • Loading branch information
cephalization committed Oct 29, 2023
1 parent 4de4e34 commit fa40608
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 9 deletions.
4 changes: 4 additions & 0 deletions apps/messages/src/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,10 @@ export default class Agent implements Party.Server {
this.client.on('MessageEdited', (e) => {
this.messages = this.messages.map((m) => (m.id === e.message.id ? e.message : m));
});

this.client.on('MessageDeleted', (e) => {
this.messages = this.messages.filter((m) => m.id !== e.messageId);
});
}

async respond() {
Expand Down
22 changes: 21 additions & 1 deletion apps/web/src/lib/stores/messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import invariant from 'tiny-invariant';
import throttle from 'just-throttle';
import { object, optional, parse, string } from 'valibot';

type OptimisticMessage = {
export type OptimisticMessage = {
content: string;
authorId: string;
};
Expand Down Expand Up @@ -142,6 +142,22 @@ export function createMessagesStore({
updateMessages(e.message);
});

state.client.on('MessageDeleted', (e) => {
update((state) => {
const newState = {
...state,
messages: state.messages.filter((message) => {
if ('id' in message && message.id === e.messageId) {
return false;
}
return true;
})
};
return newState;
});
callbacks?.MessageDeleted?.();
});

state.client.on('UserJoined', (e) => {
update((state) => {
const newState = {
Expand Down Expand Up @@ -183,6 +199,10 @@ export function createMessagesStore({
addMessage: (message: OptimisticMessage) => {
invariant(state.client, 'client should be defined');
state.client.send({ type: 'addMessage', ...message });
},
deleteMessage: (messageId: number) => {
invariant(state.client, 'client should be defined');
state.client.send({ type: 'deleteMessage', messageId });
}
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import Avatar from '$lib/components/avatar.svelte';
import AvatarGroup from '$lib/components/avatarGroup.svelte';
import Container from '$lib/components/container.svelte';
import { Info, Settings, X } from 'lucide-svelte';
import { Info, MoreHorizontalIcon, Settings, X } from 'lucide-svelte';
import { tick } from 'svelte';
import type { ActionData, PageData } from './$types';
import clsx from 'clsx';
Expand All @@ -12,6 +12,7 @@
import throttle from 'just-throttle';
// @ts-expect-error
import autosize from 'svelte-autosize';
import { user } from 'database/schema';
export let data: PageData;
export let form: ActionData;
Expand Down Expand Up @@ -94,7 +95,7 @@
}
}
async function handleSubmit(event: { currentTarget: EventTarget & HTMLFormElement }) {
function handleSubmit(event: { currentTarget: EventTarget & HTMLFormElement }) {
const formData = new FormData(event.currentTarget);
const content = formData.get('content')?.toString() ?? '';
Expand All @@ -117,6 +118,14 @@
throttledScroll();
});
}
function handleDeleteMessage(messageId: number) {
try {
messagesStore.deleteMessage(messageId);
} catch {
console.error('Could not delete message with id', messageId);
}
}
</script>

<Container
Expand Down Expand Up @@ -163,7 +172,7 @@
style={chatStyles}
>
{#each messages as message, i}
<li class="w-full flex gap-2">
<li class="w-full flex gap-2 group">
{#if messages[i - 1]?.authorId !== message.authorId}
<Avatar
className="self-start"
Expand All @@ -189,7 +198,27 @@
>
</div>
{/if}
<Message content={message.content} />
<div class="flex justify-between items-start">
<Message content={message.content} />
<div class="dropdown">
<button
tabindex="0"
class={clsx(
'btn btn-sm btn-ghost hidden',
message.authorId === data.user.id && 'focus-visible:block group-hover:block'
)}><MoreHorizontalIcon /></button
>
<ul class="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-52">
{#if message.authorId === data.user.id && 'id' in message}
<li>
<button on:click={() => 'id' in message && handleDeleteMessage(message.id)}
>Delete</button
>
</li>
{/if}
</ul>
</div>
</div>
</div>
</li>
{/each}
Expand Down
50 changes: 50 additions & 0 deletions packages/database/queries/topics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -389,4 +389,54 @@ export class TopicQueries {

return result[0];
};

deleteMessageInTopic = async (
topicId: number,
userId: string,
messageId: number
) => {
// anyone can delete their own message
// only admins can delete other messages
const isAdminResult = await this.db
.select({ admin: usersInTopics.admin })
.from(usersInTopics)
.where(
and(
eq(usersInTopics.topicId, topicId),
eq(usersInTopics.userId, userId)
)
);

const isAdmin = isAdminResult.length && isAdminResult[0].admin;

if (!isAdmin) {
const result = await this.db
.delete(message)
.where(
and(
eq(message.id, messageId),
eq(message.topicId, topicId),
eq(message.authorId, userId)
)
)
.returning();

if (!result.length) {
return null;
}

return result[0];
} else {
const result = await this.db
.delete(message)
.where(and(eq(message.id, messageId), eq(message.topicId, topicId)))
.returning();

if (!result.length) {
return null;
}

return result[0];
}
};
}
38 changes: 34 additions & 4 deletions packages/mclient/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,26 +21,28 @@ type SetMessagesResponse = { type: "SetMessages"; messages: Schema.Message[] };
type UserJoinedResponse = { type: "UserJoined"; userId: string };
type UserLeftResponse = { type: "UserLeft"; userId: string };
type MessageEditedResponse = { type: "MessageEdited"; message: Schema.Message };
type MessageDeletedResponse = { type: "MessageDeleted"; messageId: number };

type PartyResponses =
| InitResponse
| SetMessagesResponse
| UserJoinedResponse
| UserLeftResponse
| MessageEditedResponse;
| MessageEditedResponse
| MessageDeletedResponse;

const party = createPartyRpc<PartyResponses, ClientContext>();

export const partyEvents = party.events({
addMessage: {
schema: v.object({
content: v.string(),
authorId: v.string(),
}),
async onMessage(message, _, room, ctx) {
async onMessage(message, conn, room, ctx) {
const authorId = conn.id;
const newMessage = await ctx.Queries.Topic.addMessageToTopic(
ctx.topicId,
message.authorId,
authorId,
message.content
);

Expand Down Expand Up @@ -141,6 +143,34 @@ export const partyEvents = party.events({
});
},
},
deleteMessage: {
schema: v.object({
messageId: v.number(),
}),
async onMessage(message, conn, room, ctx) {
const userId = conn.id;
// users can only delete their own messages
const deletedMessage = await ctx.Queries.Topic.deleteMessageInTopic(
ctx.topicId,
userId,
message.messageId
);

if (!deletedMessage) {
console.log("did not delete message");
return;
}

ctx.messages = ctx.messages?.filter(
(m) => m.id !== deletedMessage.id
) as Schema.Message[];

partyEvents.broadcast(room, {
type: "MessageDeleted",
messageId: deletedMessage.id,
});
},
},
});

export type SafePartyEvents = typeof partyEvents.events;
Expand Down

0 comments on commit fa40608

Please sign in to comment.