Opening a pond landed on whatever sorted first in the sidebar — stable, but a rule nobody could see, and one whose target moved as soon as someone added a page ahead of it. New ponds landed on the empty-pond hint instead of anything useful. - `startPageId` joins the pond settings. No migration: `Pond.settings` is already jsonb. It stores an id, not a slug, so renaming or moving the page keeps it working. - `PondHomePage` prefers it, but only when the page is in this user's page list. That list already holds just what they may see, so a start page hidden by a page-scoped grant — or trashed — falls back silently instead of landing them on a 404, and it costs no extra request. - Both creation paths give the pond a start page, titled from the creator's stored locale. It happens after the creating transaction commits: the owner's grant is written inside it and permissions cache per pond, so creating the page any earlier would ask about rights the grant has not published yet. A failure is logged, not fatal — a pond without a start page still works. `PagesModule` imported `PondsModule` without using it. Removing that vestigial edge let PondsModule depend on PagesModule in the honest direction instead of tying the two together with forwardRef. Every pond created through the api now owns a page, which broke eight suites whose teardown deleted ponds directly — `Page.pond` deliberately has no cascade, because a real purge removes contents explicitly and audits it. A shared `deletePondsWhere` helper deletes pages first. Two tests that counted pages now account for the start page rather than pretending the pond began empty.
67 lines
2.3 KiB
TypeScript
67 lines
2.3 KiB
TypeScript
import { Prisma, PrismaClient } from '@prisma/client';
|
|
|
|
/** True when database-backed tests can run (see vitest.global-setup.ts). */
|
|
export const hasTestDb = Boolean(process.env.TEST_DATABASE_URL);
|
|
|
|
/** Prisma client bound to the test database. Callers own the lifecycle. */
|
|
export function createTestPrisma(): PrismaClient {
|
|
if (!process.env.TEST_DATABASE_URL) {
|
|
throw new Error('TEST_DATABASE_URL is not set — guard the suite with hasTestDb');
|
|
}
|
|
return new PrismaClient({ datasourceUrl: process.env.TEST_DATABASE_URL });
|
|
}
|
|
|
|
/** Unique suffix so suites never collide on unique columns. */
|
|
export function uniqueSuffix(): string {
|
|
return Math.random().toString(36).slice(2, 10);
|
|
}
|
|
|
|
/**
|
|
* The owner's Pond Admin grant for a pond created directly through Prisma.
|
|
* Production paths create it with the pond (PondsService, issue #52);
|
|
* fixtures that bypass the service need it too, or the owner cannot see
|
|
* their own pond under the grant-based resolution.
|
|
*/
|
|
export async function grantOwnerAdmin(
|
|
prisma: PrismaClient,
|
|
pondId: string,
|
|
ownerId: string,
|
|
): Promise<void> {
|
|
await prisma.roleGrant.create({
|
|
data: {
|
|
pondId,
|
|
subjectType: 'USER',
|
|
subjectId: ownerId,
|
|
role: 'POND_ADMIN',
|
|
scopeType: 'POND',
|
|
scopeId: null,
|
|
effect: 'ALLOW',
|
|
createdBy: ownerId,
|
|
},
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Deletes the ponds matching `where`, their pages first.
|
|
*
|
|
* `Page.pond` deliberately carries no `onDelete: Cascade` — a real purge
|
|
* (TrashService) removes a pond's contents explicitly and audits it, and a
|
|
* silent database cascade would hide that. Since issue #302 every pond
|
|
* created through the api starts with a page, so teardowns that went
|
|
* straight for `pond.deleteMany` now hit the foreign key.
|
|
*
|
|
* Page-owned rows (updates, comments, links, …) do cascade from the page.
|
|
*/
|
|
export async function deletePondsWhere(
|
|
prisma: PrismaClient,
|
|
where: Prisma.PondWhereInput,
|
|
): Promise<void> {
|
|
const pondIds = (await prisma.pond.findMany({ where, select: { id: true } })).map(
|
|
(pond) => pond.id,
|
|
);
|
|
if (pondIds.length === 0) return;
|
|
await prisma.attachment.deleteMany({ where: { pondId: { in: pondIds } } });
|
|
await prisma.page.deleteMany({ where: { pondId: { in: pondIds } } });
|
|
await prisma.pond.deleteMany({ where: { id: { in: pondIds } } });
|
|
}
|