Commit29d00ee
fix(casing): case-insensitive repo/namespace resolution at the central chokepoints
fix(casing): case-insensitive repo/namespace resolution at the central chokepoints Stage 1 of the staged case-insensitive read conversion. namespace.ts (resolveNamespace + loadRepoByPath) and middleware/repo-access.ts back a large share of page routes and already return the canonical row (u.slug / repo.name), so converting their lookups to lower(x)=lower(y) makes most of the site resolve /Ccantynz, /ccantynz, /VAPRON, etc. to the same target without the on-disk casing trap (callers get the canonical name back). Existing repos are left as-is per owner decision — case-insensitive resolution removes the confusion without breaking clone remotes. Remaining per-route eq() resolvers are converted in later verified stages. Typecheck clean; suite 3116 pass / 4 pre-existing fails. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2 files changed+17−929d00ee8dfbcf7555e9a99a4e18aeb39920b863d
2 changed files+17−9
Modifiedsrc/lib/namespace.ts+11−5View fileUnifiedSplit
@@ -10,7 +10,7 @@
1010 * failure so page handlers can fall through to 404 instead of 500.
1111 */
1212
13import { and, eq, isNull } from "drizzle-orm";
13import { and, eq, isNull, sql } from "drizzle-orm";
1414import { db } from "../db";
1515import { users, organizations, repositories } from "../db/schema";
1616
@@ -28,17 +28,20 @@ export async function resolveNamespace(
2828): Promise<Namespace | null> {
2929 if (!slug) return null;
3030 try {
31 // Case-insensitive slug match — /Ccantynz, /ccantynz, /CCANTYNZ all
32 // resolve to the same namespace. The row carries back the CANONICAL slug
33 // (u.slug / o.slug) so downstream disk/URL use stays correct.
3134 const [u] = await db
3235 .select({ id: users.id, slug: users.username })
3336 .from(users)
34 .where(eq(users.username, slug))
37 .where(sql`lower(${users.username}) = lower(${slug})`)
3538 .limit(1);
3639 if (u) return { kind: "user", id: u.id, slug: u.slug };
3740
3841 const [o] = await db
3942 .select({ id: organizations.id, slug: organizations.slug })
4043 .from(organizations)
41 .where(eq(organizations.slug, slug))
44 .where(sql`lower(${organizations.slug}) = lower(${slug})`)
4245 .limit(1);
4346 if (o) return { kind: "org", id: o.id, slug: o.slug };
4447
@@ -67,7 +70,7 @@ export async function loadRepoByPath(
6770 .where(
6871 and(
6972 eq(repositories.ownerId, ns.id),
70 eq(repositories.name, repoName),
73 sql`lower(${repositories.name}) = lower(${repoName})`,
7174 isNull(repositories.orgId)
7275 )
7376 )
@@ -78,7 +81,10 @@ export async function loadRepoByPath(
7881 .select()
7982 .from(repositories)
8083 .where(
81 and(eq(repositories.orgId, ns.id), eq(repositories.name, repoName))
84 and(
85 eq(repositories.orgId, ns.id),
86 sql`lower(${repositories.name}) = lower(${repoName})`
87 )
8288 )
8389 .limit(1);
8490 return r || null;
Modifiedsrc/middleware/repo-access.ts+6−4View fileUnifiedSplit
@@ -27,7 +27,7 @@
2727 * the type; routes can pass this directly to `.use()` / handler chains.
2828 */
2929
30import { and, eq, isNotNull } from "drizzle-orm";
30import { and, eq, isNotNull, sql } from "drizzle-orm";
3131import { db } from "../db";
3232import { repositories, users, repoCollaborators } from "../db/schema";
3333import type { Repository, User } from "../db/schema";
@@ -152,11 +152,13 @@ export function requireRepoAccess(
152152 return c.notFound();
153153 }
154154
155 // Resolve owner -> user row, then repo by (owner, name).
155 // Resolve owner -> user row, then repo by (owner, name). Case-insensitive
156 // on both slugs; the rows carry canonical values (ownerRow.username /
157 // repo.name) for any downstream disk/URL use.
156158 const [ownerRow] = await db
157159 .select()
158160 .from(users)
159 .where(eq(users.username, ownerName))
161 .where(sql`lower(${users.username}) = lower(${ownerName})`)
160162 .limit(1);
161163
162164 if (!ownerRow) {
@@ -169,7 +171,7 @@ export function requireRepoAccess(
169171 .where(
170172 and(
171173 eq(repositories.ownerId, ownerRow.id),
172 eq(repositories.name, repoName)
174 sql`lower(${repositories.name}) = lower(${repoName})`
173175 )
174176 )
175177 .limit(1);
176178