create-user: look up company by slug and heal corrupted name

If a previous run stored the company name with literal quotes
('B4L'), the current run's name-based select missed the row and
the insert collided on the unique slug. Looking up by slug is the
natural idempotency key here: slug is derived deterministically
from name, so a new run and the corrupted row produce the same
slug and therefore resolve to the same company row. If the stored
name differs from the new one, heal it with an UPDATE and log the
rename.

Also tightens the membership lookup to (user_id, company_id)
instead of first-match on user_id so re-running on a user with
multiple companies does the right thing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-21 16:40:40 +07:00
parent b7807e41e0
commit ad155d6344
+24 -5
View File
@@ -1,11 +1,18 @@
import 'dotenv/config';
import { eq } from 'drizzle-orm';
import { and, eq } from 'drizzle-orm';
import { pool } from '../src/lib/server/db/client';
import { db } from '../src/lib/server/db/client';
import { users, companies, companyUsers } from '../src/lib/server/db/schema/tenancy';
import { hashPassword } from '../src/lib/server/auth/password';
import { normalizeEmail } from '../src/lib/utils/email';
function slugify(s: string): string {
return s
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-|-$/g, '');
}
function stripSurroundingQuotes(s: string | undefined): string | undefined {
if (!s || s.length < 2) return s;
const first = s[0];
@@ -52,27 +59,39 @@ async function main() {
}
if (companyName) {
let [company] = await db.select().from(companies).where(eq(companies.name, companyName)).limit(1);
const slug = slugify(companyName);
// Look up by slug so a previously-corrupted name (e.g. "'B4L'" with literal
// quotes) doesn't cause a duplicate-slug insert on re-run.
let [company] = await db.select().from(companies).where(eq(companies.slug, slug)).limit(1);
if (!company) {
const slug = companyName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
const [created] = await db
.insert(companies)
.values({ name: companyName, slug })
.returning();
company = created;
console.log(`Created company ${companyName} (id ${company.id}).`);
} else if (company.name !== companyName) {
await db.update(companies).set({ name: companyName }).where(eq(companies.id, company.id));
console.log(
`Renamed company ${JSON.stringify(company.name)}${JSON.stringify(companyName)} (id ${company.id}).`
);
company = { ...company, name: companyName };
}
const [link] = await db
.select()
.from(companyUsers)
.where(eq(companyUsers.userId, userId))
.where(and(eq(companyUsers.userId, userId), eq(companyUsers.companyId, company.id)))
.limit(1);
if (!link || link.companyId !== company.id) {
if (!link) {
await db
.insert(companyUsers)
.values({ companyId: company.id, userId, role })
.onConflictDoNothing();
console.log(`Linked user to company ${company.name} as ${role}.`);
} else if (link.role !== role) {
await db.update(companyUsers).set({ role }).where(eq(companyUsers.id, link.id));
console.log(`Updated role in ${company.name} to ${role}.`);
}
}