feat(shared): pinned-messages list/pin/unpin helpers
This commit is contained in:
@@ -7,6 +7,7 @@ export * from './groups';
|
|||||||
export * from './messages';
|
export * from './messages';
|
||||||
export * from './types';
|
export * from './types';
|
||||||
export * from './userKeyMigration';
|
export * from './userKeyMigration';
|
||||||
|
export * from './pinnedMessages';
|
||||||
|
|
||||||
// ----- RPC wrappers ---------------------------------------------------------
|
// ----- RPC wrappers ---------------------------------------------------------
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,61 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
|
||||||
|
import { listPinnedMessages, pinMessage, unpinMessage } from './pinnedMessages';
|
||||||
|
|
||||||
|
function fakeClient(rows: Array<Record<string, unknown>>) {
|
||||||
|
const calls: Array<{ name: string; payload: unknown }> = [];
|
||||||
|
const client = {
|
||||||
|
from(_table: string) {
|
||||||
|
return {
|
||||||
|
select: () => ({
|
||||||
|
eq: () => ({
|
||||||
|
order: () => Promise.resolve({ data: rows, error: null }),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
insert: (payload: unknown) => {
|
||||||
|
calls.push({ name: 'insert', payload });
|
||||||
|
return Promise.resolve({ error: null });
|
||||||
|
},
|
||||||
|
delete: () => ({
|
||||||
|
eq: () => ({
|
||||||
|
eq: () => {
|
||||||
|
calls.push({ name: 'delete', payload: null });
|
||||||
|
return Promise.resolve({ error: null });
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return { client, calls };
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('pinnedMessages', () => {
|
||||||
|
it('listPinnedMessages maps DB rows to camelCase', async () => {
|
||||||
|
const rows = [
|
||||||
|
{ conversation_id: 'c1', message_id: 'm1', pinned_by: 'u1', pinned_at: '2026-05-16T10:00:00Z' },
|
||||||
|
];
|
||||||
|
const { client } = fakeClient(rows);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const result = await listPinnedMessages(client as any, 'c1');
|
||||||
|
expect(result).toEqual([
|
||||||
|
{ conversationId: 'c1', messageId: 'm1', pinnedBy: 'u1', pinnedAt: '2026-05-16T10:00:00Z' },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('pinMessage inserts with the right shape', async () => {
|
||||||
|
const { client, calls } = fakeClient([]);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
await pinMessage(client as any, 'c1', 'm1', 'u1');
|
||||||
|
expect(calls).toEqual([
|
||||||
|
{ name: 'insert', payload: { conversation_id: 'c1', message_id: 'm1', pinned_by: 'u1' } },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('unpinMessage deletes by conversation_id + message_id', async () => {
|
||||||
|
const { client, calls } = fakeClient([]);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
await unpinMessage(client as any, 'c1', 'm1');
|
||||||
|
expect(calls).toEqual([{ name: 'delete', payload: null }]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import type { AppSupabaseClient } from '../supabase/client';
|
||||||
|
|
||||||
|
export interface PinnedMessage {
|
||||||
|
conversationId: string;
|
||||||
|
messageId: string;
|
||||||
|
pinnedBy: string;
|
||||||
|
pinnedAt: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Row {
|
||||||
|
conversation_id: string;
|
||||||
|
message_id: string;
|
||||||
|
pinned_by: string;
|
||||||
|
pinned_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapRow(r: Row): PinnedMessage {
|
||||||
|
return {
|
||||||
|
conversationId: r.conversation_id,
|
||||||
|
messageId: r.message_id,
|
||||||
|
pinnedBy: r.pinned_by,
|
||||||
|
pinnedAt: r.pinned_at,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function listPinnedMessages(
|
||||||
|
client: AppSupabaseClient,
|
||||||
|
conversationId: string,
|
||||||
|
): Promise<PinnedMessage[]> {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const { data, error } = await (client as any)
|
||||||
|
.from('pinned_messages')
|
||||||
|
.select('conversation_id, message_id, pinned_by, pinned_at')
|
||||||
|
.eq('conversation_id', conversationId)
|
||||||
|
.order('pinned_at', { ascending: false });
|
||||||
|
if (error) throw error;
|
||||||
|
return (data ?? []).map(mapRow);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function pinMessage(
|
||||||
|
client: AppSupabaseClient,
|
||||||
|
conversationId: string,
|
||||||
|
messageId: string,
|
||||||
|
pinnedBy: string,
|
||||||
|
): Promise<void> {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const { error } = await (client as any)
|
||||||
|
.from('pinned_messages')
|
||||||
|
.insert({ conversation_id: conversationId, message_id: messageId, pinned_by: pinnedBy });
|
||||||
|
if (error) throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function unpinMessage(
|
||||||
|
client: AppSupabaseClient,
|
||||||
|
conversationId: string,
|
||||||
|
messageId: string,
|
||||||
|
): Promise<void> {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const { error } = await (client as any)
|
||||||
|
.from('pinned_messages')
|
||||||
|
.delete()
|
||||||
|
.eq('conversation_id', conversationId)
|
||||||
|
.eq('message_id', messageId);
|
||||||
|
if (error) throw error;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user