( resourceType: string, resourceId: string, ctx: AccessContext = currentAccess(), )
| 135 | * null if they have no access. Loads the resource and relevant share rows. |
| 136 | */ |
| 137 | export async function resolveAccess( |
| 138 | resourceType: string, |
| 139 | resourceId: string, |
| 140 | ctx: AccessContext = currentAccess(), |
| 141 | ): Promise<ResolvedAccess | null> { |
| 142 | const reg = requireShareableResource(resourceType); |
| 143 | const db = reg.getDb() as any; |
| 144 | |
| 145 | const [resource] = await db |
| 146 | .select() |
| 147 | .from(reg.resourceTable) |
| 148 | .where(eq(reg.resourceTable.id, resourceId)); |
| 149 | if (!resource) return null; |
| 150 | |
| 151 | const { userEmail, orgId } = ctx; |
| 152 | |
| 153 | if (userEmail && resource.ownerEmail === userEmail) { |
| 154 | return { role: "owner", resource }; |
| 155 | } |
| 156 | if (resource.visibility === "public") { |
| 157 | // No share row needed; default viewer unless upgraded below. |
| 158 | const role = await highestShareRole(reg, resourceId, ctx); |
| 159 | return { role: role ?? "viewer", resource }; |
| 160 | } |
| 161 | if (resource.visibility === "org" && orgId && resource.orgId === orgId) { |
| 162 | const role = await highestShareRole(reg, resourceId, ctx); |
| 163 | return { role: role ?? "viewer", resource }; |
| 164 | } |
| 165 | const role = await highestShareRole(reg, resourceId, ctx); |
| 166 | if (role) return { role, resource }; |
| 167 | return null; |
| 168 | } |
| 169 | |
| 170 | async function highestShareRole( |
| 171 | reg: ShareableResourceRegistration, |
no test coverage detected