feat(api): seed dev admin + worker via admin createUser
This commit is contained in:
@@ -4,10 +4,23 @@ import { db } from './client';
|
|||||||
import { activities, user } from './schema';
|
import { activities, user } from './schema';
|
||||||
import { auth } from '../auth';
|
import { auth } from '../auth';
|
||||||
|
|
||||||
// Dev-only test account so `db:seed` yields ready-made login credentials for local
|
// Dev-only accounts so `db:seed` yields ready-made logins for local testing / phone demos.
|
||||||
// testing / phone demos. Created through better-auth (password is properly hashed).
|
// Created through better-auth's admin createUser (hashes the password, sets the role, and works
|
||||||
// NEVER seeded when NODE_ENV=production — see seedDevUser().
|
// even though public sign-up is disabled). NEVER seeded when NODE_ENV=production.
|
||||||
const DEV_USER = { email: 'worker@solelog.local', password: 'werkplaats123', name: 'Test Werker' };
|
const DEV_ACCOUNTS = [
|
||||||
|
{
|
||||||
|
email: 'worker@solelog.local',
|
||||||
|
password: 'werkplaats123',
|
||||||
|
name: 'Test Werker',
|
||||||
|
role: 'worker' as const,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
email: 'admin@solelog.local',
|
||||||
|
password: 'werkplaats-admin',
|
||||||
|
name: 'Test Beheerder',
|
||||||
|
role: 'admin' as const,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
// Reference activities (realistic Dutch handeling names) — see
|
// Reference activities (realistic Dutch handeling names) — see
|
||||||
// docs/reference/legacy-mobile-app.md §6.2 and the Phase 1 plan.
|
// docs/reference/legacy-mobile-app.md §6.2 and the Phase 1 plan.
|
||||||
@@ -20,24 +33,22 @@ const REFERENCE_ACTIVITIES: { name: string; insoleTypes: string[] }[] = [
|
|||||||
{ name: 'Printen', insoleTypes: ['3D'] },
|
{ name: 'Printen', insoleTypes: ['3D'] },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Idempotent: create the dev account once, and only outside production (it has a
|
async function seedDevUsers(): Promise<void> {
|
||||||
// known password). Uses better-auth's server API so the user + hashed-password
|
|
||||||
// account rows are created exactly as a real sign-up would.
|
|
||||||
async function seedDevUser(): Promise<void> {
|
|
||||||
if (process.env.NODE_ENV === 'production') return;
|
if (process.env.NODE_ENV === 'production') return;
|
||||||
const existing = await db.select().from(user).where(eq(user.email, DEV_USER.email));
|
for (const acc of DEV_ACCOUNTS) {
|
||||||
if (existing.length > 0) return;
|
const existing = await db.select().from(user).where(eq(user.email, acc.email));
|
||||||
await auth.api.signUpEmail({ body: DEV_USER });
|
if (existing.length > 0) continue;
|
||||||
console.log(`Seeded dev account: ${DEV_USER.email} / ${DEV_USER.password}`);
|
await auth.api.createUser({
|
||||||
|
body: { email: acc.email, password: acc.password, name: acc.name, role: acc.role },
|
||||||
|
});
|
||||||
|
console.log(`Seeded dev ${acc.role}: ${acc.email} / ${acc.password}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Idempotent: insert each reference activity only if no activity with that name exists.
|
// Idempotent: insert each reference activity only if no activity with that name exists.
|
||||||
export async function seed(): Promise<void> {
|
export async function seed(): Promise<void> {
|
||||||
for (const activity of REFERENCE_ACTIVITIES) {
|
for (const activity of REFERENCE_ACTIVITIES) {
|
||||||
const existing = await db
|
const existing = await db.select().from(activities).where(eq(activities.name, activity.name));
|
||||||
.select()
|
|
||||||
.from(activities)
|
|
||||||
.where(eq(activities.name, activity.name));
|
|
||||||
if (existing.length === 0) {
|
if (existing.length === 0) {
|
||||||
await db.insert(activities).values({
|
await db.insert(activities).values({
|
||||||
name: activity.name,
|
name: activity.name,
|
||||||
@@ -45,7 +56,7 @@ export async function seed(): Promise<void> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await seedDevUser();
|
await seedDevUsers();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow running directly: `tsx src/db/seed.ts` (cross-platform — pathToFileURL
|
// Allow running directly: `tsx src/db/seed.ts` (cross-platform — pathToFileURL
|
||||||
|
|||||||
@@ -9,36 +9,31 @@ const SEED_NAMES = ['Leerrand', 'Frezen', 'Slijpen', 'Bekleden', 'Afwerken', 'Pr
|
|||||||
describe('seed', () => {
|
describe('seed', () => {
|
||||||
it('seeds the reference activities idempotently', async () => {
|
it('seeds the reference activities idempotently', async () => {
|
||||||
await seed();
|
await seed();
|
||||||
const first = await db
|
const first = await db.select().from(activities).where(inArray(activities.name, SEED_NAMES));
|
||||||
.select()
|
|
||||||
.from(activities)
|
|
||||||
.where(inArray(activities.name, SEED_NAMES));
|
|
||||||
const countFirst = first.length;
|
const countFirst = first.length;
|
||||||
|
|
||||||
await seed();
|
await seed();
|
||||||
const second = await db
|
const second = await db.select().from(activities).where(inArray(activities.name, SEED_NAMES));
|
||||||
.select()
|
|
||||||
.from(activities)
|
|
||||||
.where(inArray(activities.name, SEED_NAMES));
|
|
||||||
|
|
||||||
expect(second.length).toBe(countFirst);
|
expect(second.length).toBe(countFirst);
|
||||||
expect(countFirst).toBe(SEED_NAMES.length);
|
expect(countFirst).toBe(SEED_NAMES.length);
|
||||||
|
|
||||||
const printen = await db
|
const printen = await db.select().from(activities).where(eq(activities.name, 'Printen'));
|
||||||
.select()
|
|
||||||
.from(activities)
|
|
||||||
.where(eq(activities.name, 'Printen'));
|
|
||||||
expect(printen).toHaveLength(1);
|
expect(printen).toHaveLength(1);
|
||||||
expect(printen[0]?.insoleTypes).toEqual(['3D']);
|
expect(printen[0]?.insoleTypes).toEqual(['3D']);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('seeds the dev test account idempotently (and only once)', async () => {
|
it('seeds the dev worker + dev admin idempotently with correct roles', async () => {
|
||||||
await seed();
|
await seed();
|
||||||
const first = await db.select().from(user).where(eq(user.email, 'worker@solelog.local'));
|
const w = await db.select().from(user).where(eq(user.email, 'worker@solelog.local'));
|
||||||
expect(first).toHaveLength(1);
|
const a = await db.select().from(user).where(eq(user.email, 'admin@solelog.local'));
|
||||||
|
expect(w).toHaveLength(1);
|
||||||
|
expect(a).toHaveLength(1);
|
||||||
|
expect((a[0] as { role?: string }).role).toBe('admin');
|
||||||
|
|
||||||
await seed();
|
await seed();
|
||||||
const second = await db.select().from(user).where(eq(user.email, 'worker@solelog.local'));
|
expect(await db.select().from(user).where(eq(user.email, 'admin@solelog.local'))).toHaveLength(
|
||||||
expect(second).toHaveLength(1);
|
1
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user