All checks were successful
CD / Build and push images (push) Successful in 1m46s
CI / Lint, typecheck, test (push) Successful in 1m19s
CI / Auth e2e pack (push) Successful in 1m42s
CI / Build container images (push) Has been skipped
CD / Deploy to Test (push) Successful in 8s
CD / Smoke tests against Test (push) Successful in 1m5s
CD / Promote to Int (push) Successful in 10s
- Pond model with pond-level trash columns (ADR 0013) and settings jsonb holding only deviations from the defaults (sidebar sort, font slots per ADR 0016); migration 20260705090100_ponds - shared: pond schemas/views and slugify (German transliteration, URL-safe, length-capped); deterministic -2/-3 suffixes for collisions - InterimAccessService: single place answering pond access questions until the real role model lands in M5 - POST/GET /ponds, GET /ponds/:slug, PATCH/DELETE /ponds/:id, Site-Admin trash + restore; personal pond auto-created on e-mail verification and for active seed fixtures; personal ponds cannot be trashed - e2e pack covering verify-flow pond creation, slug suffixes, rename, foreign-pond 404s, trash/restore; slugify unit tests Closes #21 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UpQz6ypHJsLfMf4S6fyQEB
118 lines
3.7 KiB
TypeScript
118 lines
3.7 KiB
TypeScript
/**
|
|
* Development/Test fixture seeding. Idempotent: running it twice must not
|
|
* duplicate anything. Never run against production data.
|
|
*
|
|
* Fixture matrix (documented in apps/web/e2e/README.md):
|
|
* fixture-admin active, Site Admin
|
|
* fixture-user active, regular account
|
|
* fixture-pending registered but e-mail not verified
|
|
*
|
|
* All fixture accounts share the password below — they exist only on
|
|
* dev machines and disposable CI/Test databases. On shared stages
|
|
* (test/int), set FIXTURE_ADMIN_PASSWORD / FIXTURE_USER_PASSWORD to give
|
|
* those two accounts non-public passwords.
|
|
*/
|
|
import { slugify } from '@dorfteich/shared';
|
|
import { PrismaClient, UserStatus } from '@prisma/client';
|
|
|
|
import { hashPassword } from '../src/users/password';
|
|
|
|
export const FIXTURE_PASSWORD = 'fixture passwort 123';
|
|
|
|
const PASSWORD_OVERRIDES: Record<string, string | undefined> = {
|
|
'fixture-admin': process.env.FIXTURE_ADMIN_PASSWORD,
|
|
'fixture-user': process.env.FIXTURE_USER_PASSWORD,
|
|
};
|
|
|
|
const prisma = new PrismaClient();
|
|
|
|
interface FixtureUser {
|
|
username: string;
|
|
displayName: string;
|
|
status: UserStatus;
|
|
isSiteAdmin: boolean;
|
|
}
|
|
|
|
const FIXTURES: FixtureUser[] = [
|
|
{ username: 'fixture-admin', displayName: 'Fixture Admin', status: 'ACTIVE', isSiteAdmin: true },
|
|
{ username: 'fixture-user', displayName: 'Fixture User', status: 'ACTIVE', isSiteAdmin: false },
|
|
{
|
|
username: 'fixture-pending',
|
|
displayName: 'Fixture Pending',
|
|
status: 'PENDING_VERIFICATION',
|
|
isSiteAdmin: false,
|
|
},
|
|
];
|
|
|
|
async function upsertFixtureUser(fixture: FixtureUser): Promise<void> {
|
|
const email = `${fixture.username}@dorfteich.test`;
|
|
const user = await prisma.user.upsert({
|
|
where: { username: fixture.username },
|
|
create: {
|
|
username: fixture.username,
|
|
email,
|
|
displayName: fixture.displayName,
|
|
locale: 'de',
|
|
status: fixture.status,
|
|
isSiteAdmin: fixture.isSiteAdmin,
|
|
emailVerifiedAt: fixture.status === 'ACTIVE' ? new Date() : null,
|
|
},
|
|
update: {
|
|
status: fixture.status,
|
|
isSiteAdmin: fixture.isSiteAdmin,
|
|
},
|
|
});
|
|
// Re-hash on every run so a changed override takes effect on re-seed.
|
|
const credential = await hashPassword(PASSWORD_OVERRIDES[fixture.username] ?? FIXTURE_PASSWORD);
|
|
await prisma.userIdentity.upsert({
|
|
where: { provider_subject: { provider: 'password', subject: user.id } },
|
|
create: {
|
|
userId: user.id,
|
|
provider: 'password',
|
|
subject: user.id,
|
|
credential,
|
|
},
|
|
update: { credential },
|
|
});
|
|
// Active accounts get their personal pond, mirroring what e-mail
|
|
// verification does for real signups (issue #21).
|
|
if (fixture.status === 'ACTIVE') {
|
|
const existing = await prisma.pond.findFirst({
|
|
where: { ownerId: user.id, type: 'PERSONAL' },
|
|
select: { id: true },
|
|
});
|
|
if (!existing) {
|
|
await prisma.pond.create({
|
|
data: {
|
|
slug: slugify(fixture.displayName) || fixture.username,
|
|
name: fixture.displayName,
|
|
type: 'PERSONAL',
|
|
ownerId: user.id,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
async function main(): Promise<void> {
|
|
// Fresh rate-limit budget for e2e runs — seed targets are always
|
|
// disposable dev/CI databases, never production.
|
|
await prisma.rateLimit.deleteMany({});
|
|
for (const fixture of FIXTURES) {
|
|
await upsertFixtureUser(fixture);
|
|
}
|
|
await prisma.instanceSetting.upsert({
|
|
where: { key: 'seed.marker' },
|
|
create: { key: 'seed.marker', value: { seededAt: new Date().toISOString() } },
|
|
update: { value: { seededAt: new Date().toISOString() } },
|
|
});
|
|
console.log(`seed: done (${FIXTURES.length} fixture users)`);
|
|
}
|
|
|
|
main()
|
|
.catch((error) => {
|
|
console.error(error);
|
|
process.exitCode = 1;
|
|
})
|
|
.finally(() => prisma.$disconnect());
|