4413045f-d05e-4d7a-a70d-81d6be253c85

This commit is contained in:
Z User committed 2026-10-28 16:37:26 +00:00
1 parent 762f3d7373
commit c6adc67479
18 files changed
+5263 -1

No files matched your search

+36 -1
View File
@@ -102,6 +102,40 @@ export async function PATCH(req: Request, { params }: { params: Promise<{ id: st
},
})
// --- Instance lifecycle hook (PRD §5: trial cancel → instance deletion) ---
// If cancelling during the 14-day trial (immediate), schedule the tenant
// instance for deletion. The actual Dokploy + DNS teardown happens via
// the instance DELETE endpoint (idempotent job), not here directly —
// we fire-and-forget so the cancel response is fast.
let instanceDeleted = false
if ((immediate || inTrial) && license.id) {
const inst = await db.instance.findFirst({
where: { licenseId: license.id, status: { notIn: ['deleted', 'deleting'] } },
})
if (inst) {
// Mark instance as grace → will be picked up by the deletion job.
// For immediate trial cancel, set a short grace (1 hour) so the client
// can still access data briefly if they change their mind, then it's gone.
await db.instance.update({
where: { id: inst.id },
data: {
status: 'grace',
graceEndsAt: new Date(Date.now() + 3600000), // 1 hour
},
})
await db.instanceEvent.create({
data: {
instanceId: inst.id,
type: 'license.cancel.trial',
step: 'hook',
result: 'ok',
details: JSON.stringify({ licenseId: license.id, graceEndsIn: '1h' }),
},
})
instanceDeleted = true
}
}
return NextResponse.json({
ok: true,
cancelledAt: now,
@@ -109,8 +143,9 @@ export async function PATCH(req: Request, { params }: { params: Promise<{ id: st
inTrial: !!inTrial,
immediate: immediate || inTrial,
cancelledModules,
instanceScheduled: instanceDeleted,
message: inTrial
? 'Probezeit-Kündigung — sofort wirksam, keine Abbuchung.'
? 'Probezeit-Kündigung — sofort wirksam, keine Abbuchung. Instanz wird gelöscht.'
: `Kündigung eingegangen. Wirksam zum ${effectiveAt.toLocaleDateString('de-DE')} (3-Monate-Frist).`,
})
} catch (err) {