feat: implement utility functions to streamline owner selection for backups and domains

This commit is contained in:
Mauricio Siu
2026-08-14 00:40:00 -06:00
parent d583ba8bd3
commit d770e3ce7d
+56 -85
View File
@@ -276,6 +276,18 @@ function ownerFrom(
}; };
} }
// Picks the first non-null relation out of several mutually-exclusive owner candidates.
// Shared by backups, volume backups, and domains — all resolve "which one of these
// nullable relations is actually set" the same way.
function pickFirst<TRow, TType extends string>(
candidates: Array<[row: TRow | null | undefined, id: string | undefined, type: TType]>,
): [TRow, string, TType] | null {
for (const [row, id, type] of candidates) {
if (row && id) return [row, id, type];
}
return null;
}
function pickBackupOwner(backup: { function pickBackupOwner(backup: {
postgres?: (OwnerRow & { postgresId: string }) | null; postgres?: (OwnerRow & { postgresId: string }) | null;
mariadb?: (OwnerRow & { mariadbId: string }) | null; mariadb?: (OwnerRow & { mariadbId: string }) | null;
@@ -284,19 +296,15 @@ function pickBackupOwner(backup: {
libsql?: (OwnerRow & { libsqlId: string }) | null; libsql?: (OwnerRow & { libsqlId: string }) | null;
compose?: (OwnerRow & { composeId: string }) | null; compose?: (OwnerRow & { composeId: string }) | null;
}): Owner | null { }): Owner | null {
if (backup.postgres) const found = pickFirst<OwnerRow, OverviewServiceType>([
return ownerFrom(backup.postgres, backup.postgres.postgresId, "postgres"); [backup.postgres, backup.postgres?.postgresId, "postgres"],
if (backup.mariadb) [backup.mariadb, backup.mariadb?.mariadbId, "mariadb"],
return ownerFrom(backup.mariadb, backup.mariadb.mariadbId, "mariadb"); [backup.mysql, backup.mysql?.mysqlId, "mysql"],
if (backup.mysql) [backup.mongo, backup.mongo?.mongoId, "mongo"],
return ownerFrom(backup.mysql, backup.mysql.mysqlId, "mysql"); [backup.libsql, backup.libsql?.libsqlId, "libsql"],
if (backup.mongo) [backup.compose, backup.compose?.composeId, "compose"],
return ownerFrom(backup.mongo, backup.mongo.mongoId, "mongo"); ]);
if (backup.libsql) return found ? ownerFrom(...found) : null;
return ownerFrom(backup.libsql, backup.libsql.libsqlId, "libsql");
if (backup.compose)
return ownerFrom(backup.compose, backup.compose.composeId, "compose");
return null;
} }
function pickVolumeBackupOwner(volumeBackup: { function pickVolumeBackupOwner(volumeBackup: {
@@ -309,43 +317,17 @@ function pickVolumeBackupOwner(volumeBackup: {
libsql?: (OwnerRow & { libsqlId: string }) | null; libsql?: (OwnerRow & { libsqlId: string }) | null;
compose?: (OwnerRow & { composeId: string }) | null; compose?: (OwnerRow & { composeId: string }) | null;
}): Owner | null { }): Owner | null {
if (volumeBackup.application) const found = pickFirst<OwnerRow, OverviewServiceType>([
return ownerFrom( [volumeBackup.application, volumeBackup.application?.applicationId, "application"],
volumeBackup.application, [volumeBackup.postgres, volumeBackup.postgres?.postgresId, "postgres"],
volumeBackup.application.applicationId, [volumeBackup.mariadb, volumeBackup.mariadb?.mariadbId, "mariadb"],
"application", [volumeBackup.mysql, volumeBackup.mysql?.mysqlId, "mysql"],
); [volumeBackup.mongo, volumeBackup.mongo?.mongoId, "mongo"],
if (volumeBackup.postgres) [volumeBackup.redis, volumeBackup.redis?.redisId, "redis"],
return ownerFrom( [volumeBackup.libsql, volumeBackup.libsql?.libsqlId, "libsql"],
volumeBackup.postgres, [volumeBackup.compose, volumeBackup.compose?.composeId, "compose"],
volumeBackup.postgres.postgresId, ]);
"postgres", return found ? ownerFrom(...found) : null;
);
if (volumeBackup.mariadb)
return ownerFrom(
volumeBackup.mariadb,
volumeBackup.mariadb.mariadbId,
"mariadb",
);
if (volumeBackup.mysql)
return ownerFrom(volumeBackup.mysql, volumeBackup.mysql.mysqlId, "mysql");
if (volumeBackup.mongo)
return ownerFrom(volumeBackup.mongo, volumeBackup.mongo.mongoId, "mongo");
if (volumeBackup.redis)
return ownerFrom(volumeBackup.redis, volumeBackup.redis.redisId, "redis");
if (volumeBackup.libsql)
return ownerFrom(
volumeBackup.libsql,
volumeBackup.libsql.libsqlId,
"libsql",
);
if (volumeBackup.compose)
return ownerFrom(
volumeBackup.compose,
volumeBackup.compose.composeId,
"compose",
);
return null;
} }
// Ownerless backups (e.g. the Dokploy host's own database) are visible to everyone. // Ownerless backups (e.g. the Dokploy host's own database) are visible to everyone.
@@ -645,6 +627,17 @@ function domainOwnerFrom(
}; };
} }
// Same scoping rule as ownerAccessCondition, just without the "ownerless rows are public" OR branch.
function orgScopeCondition(
orgId: string,
idColumn: AnyPgColumn,
accessedServices: string[] | null,
) {
return accessedServices !== null
? and(eq(projects.organizationId, orgId), inArray(idColumn, accessedServices))
: eq(projects.organizationId, orgId);
}
// Preview domains inherit access from their parent application, so they're scoped by the same accessedServices check as applications. // Preview domains inherit access from their parent application, so they're scoped by the same accessedServices check as applications.
async function getDomainScopeIdsInOrg( async function getDomainScopeIdsInOrg(
orgId: string, orgId: string,
@@ -660,12 +653,7 @@ async function getDomainScopeIdsInOrg(
) )
.innerJoin(projects, eq(environments.projectId, projects.projectId)) .innerJoin(projects, eq(environments.projectId, projects.projectId))
.where( .where(
accessedServices !== null orgScopeCondition(orgId, applications.applicationId, accessedServices),
? and(
eq(projects.organizationId, orgId),
inArray(applications.applicationId, accessedServices),
)
: eq(projects.organizationId, orgId),
), ),
db db
.select({ id: compose.composeId }) .select({ id: compose.composeId })
@@ -675,14 +663,7 @@ async function getDomainScopeIdsInOrg(
eq(compose.environmentId, environments.environmentId), eq(compose.environmentId, environments.environmentId),
) )
.innerJoin(projects, eq(environments.projectId, projects.projectId)) .innerJoin(projects, eq(environments.projectId, projects.projectId))
.where( .where(orgScopeCondition(orgId, compose.composeId, accessedServices)),
accessedServices !== null
? and(
eq(projects.organizationId, orgId),
inArray(compose.composeId, accessedServices),
)
: eq(projects.organizationId, orgId),
),
db db
.select({ id: previewDeployments.previewDeploymentId }) .select({ id: previewDeployments.previewDeploymentId })
.from(previewDeployments) .from(previewDeployments)
@@ -696,12 +677,7 @@ async function getDomainScopeIdsInOrg(
) )
.innerJoin(projects, eq(environments.projectId, projects.projectId)) .innerJoin(projects, eq(environments.projectId, projects.projectId))
.where( .where(
accessedServices !== null orgScopeCondition(orgId, applications.applicationId, accessedServices),
? and(
eq(projects.organizationId, orgId),
inArray(applications.applicationId, accessedServices),
)
: eq(projects.organizationId, orgId),
), ),
]); ]);
@@ -765,21 +741,16 @@ export const getAllDomainsForOrganization = async (
const result: OverviewDomain[] = []; const result: OverviewDomain[] = [];
for (const domain of rows) { for (const domain of rows) {
const owner = domain.application const foundOwner = pickFirst<DomainOwnerRow, OverviewDomainOwnerType>([
? domainOwnerFrom( [domain.application, domain.application?.applicationId, "application"],
domain.application, [domain.compose, domain.compose?.composeId, "compose"],
domain.application.applicationId, [
"application", domain.previewDeployment?.application,
) domain.previewDeployment?.application?.applicationId,
: domain.compose "application",
? domainOwnerFrom(domain.compose, domain.compose.composeId, "compose") ],
: domain.previewDeployment?.application ]);
? domainOwnerFrom( const owner = foundOwner ? domainOwnerFrom(...foundOwner) : null;
domain.previewDeployment.application,
domain.previewDeployment.application.applicationId,
"application",
)
: null;
if (!owner || owner.organizationId !== orgId) continue; if (!owner || owner.organizationId !== orgId) continue;
if (accessedServices !== null && !accessedServices.includes(owner.id)) { if (accessedServices !== null && !accessedServices.includes(owner.id)) {