Files
ChatApp/packages/shared/src/chat/mentions.ts
T

61 lines
2.2 KiB
TypeScript

import type { AppSupabaseClient } from '../supabase/client';
// `@anna_b` style — letters, digits, underscore, dot, dash, 2-32 chars.
// Conservative on purpose: false negatives (a real username we don't match)
// are recoverable (no notification fires); false positives (matching a
// non-username) just become an INSERT that the FK check rejects.
const MENTION_RE = /(?:^|[\s,;:!?(])@([a-zA-Z0-9_.-]{2,32})/g;
export function parseMentionUsernames(plaintext: string): string[] {
const out = new Set<string>();
for (const m of plaintext.matchAll(MENTION_RE)) {
if (m[1]) out.add(m[1].toLowerCase());
}
return [...out];
}
export interface MentionResolver {
// Resolves an array of @usernames in this conversation to user-ids.
// Returns only memberships that exist + are accepted.
resolveUsernames(conversationId: string, usernames: string[]): Promise<Map<string, string>>;
}
export function makeMentionResolver(client: AppSupabaseClient): MentionResolver {
return {
async resolveUsernames(conversationId, usernames) {
if (usernames.length === 0) return new Map();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const { data, error } = await (client as any)
.from('conversation_members')
.select('user_id, accepted, profiles!inner(username)')
.eq('conversation_id', conversationId)
.eq('accepted', true)
.in('profiles.username', usernames);
if (error) throw error;
const out = new Map<string, string>();
for (const row of (data ?? []) as Array<{ user_id: string; profiles: { username: string } }>) {
out.set(row.profiles.username.toLowerCase(), row.user_id);
}
return out;
},
};
}
export async function insertMentions(
client: AppSupabaseClient,
messageId: string,
conversationId: string,
mentionedUserIds: string[],
): Promise<void> {
if (mentionedUserIds.length === 0) return;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const { error } = await (client as any).from('message_mentions').insert(
mentionedUserIds.map((uid) => ({
message_id: messageId,
mentioned_user_id: uid,
conversation_id: conversationId,
})),
);
if (error) throw error;
}