Follow what the live server does with tenants and domains
A run on the production server with throwaway tenants, a role, lists and a domain, all removed, found three things the source reading had not: - Something in a tenant has to be on a domain in that tenant (a list in a tenant on an unassigned domain is invalidForeignKey), while something in no tenant may be on a tenant's domain. The account panel's tenant choice offered every tenant; it offers only the domain's now, and a new account starts in the tenant of the domain it is made on. The domain list reads memberTenantId for it. - A domain created in a tenant puts its DKIM keys there too, and they keep the tenant from being deleted. They are counted with the rest, so Delete is not offered while any remain. - Stalwart lets a domain leave a tenant while the tenant still has accounts on it, stranding them. The panel asks first and refuses while any are there. The refusal to delete a tenant that holds anything was confirmed, as were tenant create, quota pointers, logo and rename. The mock follows the domain rule, filters DKIM keys by tenant, and KNOWN-ISSUES records the run. The non-Enterprise notice is now just "Tenants are a Stalwart Enterprise feature." Two sentences were reworded and one plural added, in all nine catalogues, and the old sentences are gone.
This commit is contained in:
@@ -47,6 +47,8 @@ export const TENANT_MEMBERS = [
|
||||
{ key: "lists", method: "x:MailingList/query", filter: {}, quota: "maxMailingLists" },
|
||||
{ key: "domains", method: "x:Domain/query", filter: {}, quota: "maxDomains" },
|
||||
{ key: "roles", method: "x:Role/query", filter: {}, quota: "maxRoles" },
|
||||
// A domain's keys join the tenant it was created in, and keep it there.
|
||||
{ key: "dkimKeys", method: "x:DkimSignature/query", filter: {}, quota: "maxDkimKeys" },
|
||||
] as const;
|
||||
export type TenantMemberKind = (typeof TENANT_MEMBERS)[number]["key"];
|
||||
|
||||
@@ -118,6 +120,18 @@ export async function tenantDomains(tenantId: string): Promise<{ inTenant: Array
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* How many of a tenant's accounts and groups are on a domain.
|
||||
*
|
||||
* Stalwart lets a domain leave a tenant while the tenant still has accounts on
|
||||
* it (live, 2026-09-15), leaving them in a tenant on a domain outside it --
|
||||
* which it refuses to create. The panel asks this before it offers the move.
|
||||
*/
|
||||
export async function tenantAccountsOnDomain(tenantId: string, domainId: string): Promise<number> {
|
||||
const res = await client.call<{ total?: number }>("x:Account/query", { filter: { domainId, memberTenantId: tenantId }, limit: 0, calculateTotal: true });
|
||||
return res.total ?? 0;
|
||||
}
|
||||
|
||||
/** Put a domain in a tenant, or take it out with null. */
|
||||
export async function setDomainTenant(domainId: string, tenantId: string | null): Promise<void> {
|
||||
const res = await client.call<SetResponse>("x:Domain/set", { update: { [domainId]: { memberTenantId: tenantId } } });
|
||||
|
||||
Reference in New Issue
Block a user