|
1 | 1 | "use server" |
2 | 2 |
|
3 | 3 | import { db } from '@/lib/db'; |
4 | | -import { users, document_collaborators } from '@/lib/db/schema'; |
| 4 | +import { users, document_collaborators, workspace_members } from '@/lib/db/schema'; |
5 | 5 | import { eq, and } from 'drizzle-orm'; |
6 | 6 | import { revalidatePath } from 'next/cache'; |
7 | 7 | import { broadcastNotesUpdated } from '@/lib/realtime' |
@@ -98,6 +98,65 @@ export async function getUserById(userId: string) { |
98 | 98 | } |
99 | 99 | } |
100 | 100 |
|
| 101 | +// Search users by prefix (case-insensitive) on email or name. Returns up to 10 results. |
| 102 | +export async function searchUsersByPrefix(prefix: string, documentId?: string) { |
| 103 | + if (!prefix || prefix.trim().length === 0) return [] |
| 104 | + const q = prefix.trim().toLowerCase() |
| 105 | + try { |
| 106 | + const { sql } = await import('drizzle-orm') |
| 107 | + const pattern = q + '%' |
| 108 | + |
| 109 | + // If a documentId is provided, try to limit suggestions to users in the same workspace |
| 110 | + let workspaceId: string | null = null |
| 111 | + let docAuthorId: string | null = null |
| 112 | + if (documentId) { |
| 113 | + try { |
| 114 | + const docRows = await db.select({ workspace_id: documents.workspace_id, author_id: documents.author_id }).from(documents).where(eq(documents.id, documentId)).limit(1) |
| 115 | + if (docRows.length > 0) { |
| 116 | + // @ts-ignore |
| 117 | + workspaceId = docRows[0].workspace_id || null |
| 118 | + // @ts-ignore |
| 119 | + docAuthorId = docRows[0].author_id || null |
| 120 | + } |
| 121 | + } catch {} |
| 122 | + } |
| 123 | + |
| 124 | + // Build base where clause for prefix matching |
| 125 | + const whereClause = sql`(LOWER(${users.email}) LIKE ${pattern}) OR (LOWER(${users.name}) LIKE ${pattern})` |
| 126 | + |
| 127 | + if (workspaceId) { |
| 128 | + // Fetch workspace member users that match the prefix |
| 129 | + const rows = await db |
| 130 | + .select({ id: users.id, name: users.name, email: users.email, image: users.image }) |
| 131 | + .from(users) |
| 132 | + .leftJoin(workspace_members, eq(workspace_members.user_id, users.id)) |
| 133 | + .where(sql`(${workspace_members.workspace_id} = ${workspaceId}) AND (${whereClause})`) |
| 134 | + .limit(10) |
| 135 | + |
| 136 | + // Exclude existing collaborators for the document and the document author |
| 137 | + if (documentId) { |
| 138 | + const existing = await db.select({ id: document_collaborators.userId }).from(document_collaborators).where(eq(document_collaborators.documentId, documentId)) |
| 139 | + const excluded = new Set(existing.map((r: any) => r.id)) |
| 140 | + if (docAuthorId) excluded.add(docAuthorId) |
| 141 | + return (rows as any[]).filter(r => !excluded.has(r.id)).slice(0, 10) |
| 142 | + } |
| 143 | + |
| 144 | + return rows |
| 145 | + } |
| 146 | + |
| 147 | + // Fallback: global search across users when no workspace context |
| 148 | + const rows = await db |
| 149 | + .select({ id: users.id, name: users.name, email: users.email, image: users.image }) |
| 150 | + .from(users) |
| 151 | + .where(whereClause) |
| 152 | + .limit(10) |
| 153 | + |
| 154 | + return rows |
| 155 | + } catch { |
| 156 | + return [] |
| 157 | + } |
| 158 | +} |
| 159 | + |
101 | 160 | // Return the current user's role for a given document |
102 | 161 | export async function getUserRoleForDocument(documentId: string) { |
103 | 162 | const session = await getServerSession(topAuthOptions) |
|
0 commit comments