61 lines
2.2 KiB
TypeScript
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;
|
|
}
|