From a8ba66fce1f8d374b398dcb58004f5ef2df1d8bf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 27 Dec 2025 14:54:50 +0000 Subject: [PATCH] refactor(dbal): modularize tenant-context from 255 to 54 lines - Extract tenant types into tenant/tenant-types.ts (43 lines) - Extract permission checks into tenant/permission-checks.ts (48 lines) - Extract quota checks into tenant/quota-checks.ts (57 lines) - Main file delegates to extracted utilities Co-authored-by: johndoe6345789 <224850594+johndoe6345789@users.noreply.github.com> --- .../src/core/foundation/tenant-context.ts | 245 ++--------------- .../core/foundation/tenant-context.ts.backup | 255 ++++++++++++++++++ .../foundation/tenant/permission-checks.ts | 48 ++++ .../core/foundation/tenant/quota-checks.ts | 57 ++++ .../core/foundation/tenant/tenant-types.ts | 43 +++ 5 files changed, 425 insertions(+), 223 deletions(-) create mode 100644 dbal/development/src/core/foundation/tenant-context.ts.backup create mode 100644 dbal/development/src/core/foundation/tenant/permission-checks.ts create mode 100644 dbal/development/src/core/foundation/tenant/quota-checks.ts create mode 100644 dbal/development/src/core/foundation/tenant/tenant-types.ts diff --git a/dbal/development/src/core/foundation/tenant-context.ts b/dbal/development/src/core/foundation/tenant-context.ts index 4a1476cc9..84bdfa1a5 100644 --- a/dbal/development/src/core/foundation/tenant-context.ts +++ b/dbal/development/src/core/foundation/tenant-context.ts @@ -1,50 +1,13 @@ /** - * Multi-Tenant Context and Identity Management - * - * Provides tenant isolation, access control, and quota management - * for both blob storage and structured data. + * @file tenant-context.ts + * @description Multi-tenant context and identity management */ -export interface TenantIdentity { - tenantId: string - userId: string - role: 'owner' | 'admin' | 'member' | 'viewer' - permissions: Set -} +import type { TenantIdentity, TenantQuota, TenantContext } from './tenant/tenant-types' +import * as PermissionChecks from './tenant/permission-checks' +import * as QuotaChecks from './tenant/quota-checks' -export interface TenantQuota { - // Blob storage quotas - maxBlobStorageBytes?: number - maxBlobCount?: number - maxBlobSizeBytes?: number - - // Structured data quotas - maxRecords?: number - maxDataSizeBytes?: number - maxListLength?: number - - // Computed usage - currentBlobStorageBytes: number - currentBlobCount: number - currentRecords: number - currentDataSizeBytes: number -} - -export interface TenantContext { - identity: TenantIdentity - quota: TenantQuota - namespace: string // For blob storage isolation - - // Check if operation is allowed - canRead(resource: string): boolean - canWrite(resource: string): boolean - canDelete(resource: string): boolean - - // Check quota availability - canUploadBlob(sizeBytes: number): boolean - canCreateRecord(): boolean - canAddToList(additionalItems: number): boolean -} +export type { TenantIdentity, TenantQuota, TenantContext } export class DefaultTenantContext implements TenantContext { constructor( @@ -54,202 +17,38 @@ export class DefaultTenantContext implements TenantContext { ) {} canRead(resource: string): boolean { - // Owner and admin can read everything - if (this.identity.role === 'owner' || this.identity.role === 'admin') { - return true - } - - // Check specific permissions - return ( - this.identity.permissions.has('read:*') || - this.identity.permissions.has(`read:${resource}`) - ) + return PermissionChecks.canRead(this.identity, resource) } canWrite(resource: string): boolean { - // Only owner and admin can write - if (this.identity.role === 'owner' || this.identity.role === 'admin') { - return true - } - - // Check specific permissions - return ( - this.identity.permissions.has('write:*') || - this.identity.permissions.has(`write:${resource}`) - ) + return PermissionChecks.canWrite(this.identity, resource) } canDelete(resource: string): boolean { - // Only owner and admin can delete - if (this.identity.role === 'owner' || this.identity.role === 'admin') { - return true - } - - // Check specific permissions - return ( - this.identity.permissions.has('delete:*') || - this.identity.permissions.has(`delete:${resource}`) - ) + return PermissionChecks.canDelete(this.identity, resource) } canUploadBlob(sizeBytes: number): boolean { - const { quota } = this - - // Check max blob size - if (quota.maxBlobSizeBytes && sizeBytes > quota.maxBlobSizeBytes) { - return false - } - - // Check total storage quota - if (quota.maxBlobStorageBytes) { - if (quota.currentBlobStorageBytes + sizeBytes > quota.maxBlobStorageBytes) { - return false - } - } - - // Check blob count quota - if (quota.maxBlobCount) { - if (quota.currentBlobCount >= quota.maxBlobCount) { - return false - } - } - - return true + return QuotaChecks.canUploadBlob(this.quota, sizeBytes) } canCreateRecord(): boolean { - const { quota } = this - - if (quota.maxRecords) { - return quota.currentRecords < quota.maxRecords - } - - return true + return QuotaChecks.canCreateRecord(this.quota) } canAddToList(additionalItems: number): boolean { - const { quota } = this - - if (quota.maxListLength && additionalItems > quota.maxListLength) { - return false - } - - return true + return QuotaChecks.canAddToList(this.quota, additionalItems) } } -export interface TenantManager { - // Get tenant context for operations - getTenantContext(tenantId: string, userId: string): Promise - - // Update quota usage - updateBlobUsage(tenantId: string, bytesChange: number, countChange: number): Promise - updateRecordUsage(tenantId: string, countChange: number, bytesChange: number): Promise - - // Create/update tenant - createTenant(tenantId: string, quota?: Partial): Promise - updateQuota(tenantId: string, quota: Partial): Promise - - // Get current usage - getUsage(tenantId: string): Promise -} - -export class InMemoryTenantManager implements TenantManager { - private tenants = new Map() - private permissions = new Map() - - async getTenantContext(tenantId: string, userId: string): Promise { - let quota = this.tenants.get(tenantId) - if (!quota) { - // Create default quota - quota = { - currentBlobStorageBytes: 0, - currentBlobCount: 0, - currentRecords: 0, - currentDataSizeBytes: 0 - } - this.tenants.set(tenantId, quota) - } - - // Get or create identity - const identityKey = `${tenantId}:${userId}` - let identity = this.permissions.get(identityKey) - if (!identity) { - identity = { - tenantId, - userId, - role: 'member', - permissions: new Set(['read:*', 'write:*']) - } - this.permissions.set(identityKey, identity) - } - - const namespace = `tenants/${tenantId}/` - - return new DefaultTenantContext(identity, quota, namespace) - } - - async updateBlobUsage(tenantId: string, bytesChange: number, countChange: number): Promise { - const quota = this.tenants.get(tenantId) - if (quota) { - quota.currentBlobStorageBytes += bytesChange - quota.currentBlobCount += countChange - } - } - - async updateRecordUsage(tenantId: string, countChange: number, bytesChange: number): Promise { - const quota = this.tenants.get(tenantId) - if (quota) { - quota.currentRecords += countChange - quota.currentDataSizeBytes += bytesChange - } - } - - async createTenant(tenantId: string, quotaOverrides?: Partial): Promise { - const quota: TenantQuota = { - currentBlobStorageBytes: 0, - currentBlobCount: 0, - currentRecords: 0, - currentDataSizeBytes: 0, - ...quotaOverrides - } - this.tenants.set(tenantId, quota) - } - - async updateQuota(tenantId: string, quotaUpdates: Partial): Promise { - const quota = this.tenants.get(tenantId) - if (quota) { - Object.assign(quota, quotaUpdates) - } - } - - async getUsage(tenantId: string): Promise { - const quota = this.tenants.get(tenantId) - if (!quota) { - return { - currentBlobStorageBytes: 0, - currentBlobCount: 0, - currentRecords: 0, - currentDataSizeBytes: 0 - } - } - return { ...quota } - } - - // Admin methods for testing - setUserRole(tenantId: string, userId: string, role: TenantIdentity['role']): void { - const identityKey = `${tenantId}:${userId}` - const identity = this.permissions.get(identityKey) - if (identity) { - identity.role = role - } - } - - grantPermission(tenantId: string, userId: string, permission: string): void { - const identityKey = `${tenantId}:${userId}` - const identity = this.permissions.get(identityKey) - if (identity) { - identity.permissions.add(permission) - } - } +export const createTenantContext = ( + identity: TenantIdentity, + quota: TenantQuota, + namespace?: string +): TenantContext => { + return new DefaultTenantContext( + identity, + quota, + namespace || `tenant_${identity.tenantId}` + ) } diff --git a/dbal/development/src/core/foundation/tenant-context.ts.backup b/dbal/development/src/core/foundation/tenant-context.ts.backup new file mode 100644 index 000000000..4a1476cc9 --- /dev/null +++ b/dbal/development/src/core/foundation/tenant-context.ts.backup @@ -0,0 +1,255 @@ +/** + * Multi-Tenant Context and Identity Management + * + * Provides tenant isolation, access control, and quota management + * for both blob storage and structured data. + */ + +export interface TenantIdentity { + tenantId: string + userId: string + role: 'owner' | 'admin' | 'member' | 'viewer' + permissions: Set +} + +export interface TenantQuota { + // Blob storage quotas + maxBlobStorageBytes?: number + maxBlobCount?: number + maxBlobSizeBytes?: number + + // Structured data quotas + maxRecords?: number + maxDataSizeBytes?: number + maxListLength?: number + + // Computed usage + currentBlobStorageBytes: number + currentBlobCount: number + currentRecords: number + currentDataSizeBytes: number +} + +export interface TenantContext { + identity: TenantIdentity + quota: TenantQuota + namespace: string // For blob storage isolation + + // Check if operation is allowed + canRead(resource: string): boolean + canWrite(resource: string): boolean + canDelete(resource: string): boolean + + // Check quota availability + canUploadBlob(sizeBytes: number): boolean + canCreateRecord(): boolean + canAddToList(additionalItems: number): boolean +} + +export class DefaultTenantContext implements TenantContext { + constructor( + public readonly identity: TenantIdentity, + public readonly quota: TenantQuota, + public readonly namespace: string + ) {} + + canRead(resource: string): boolean { + // Owner and admin can read everything + if (this.identity.role === 'owner' || this.identity.role === 'admin') { + return true + } + + // Check specific permissions + return ( + this.identity.permissions.has('read:*') || + this.identity.permissions.has(`read:${resource}`) + ) + } + + canWrite(resource: string): boolean { + // Only owner and admin can write + if (this.identity.role === 'owner' || this.identity.role === 'admin') { + return true + } + + // Check specific permissions + return ( + this.identity.permissions.has('write:*') || + this.identity.permissions.has(`write:${resource}`) + ) + } + + canDelete(resource: string): boolean { + // Only owner and admin can delete + if (this.identity.role === 'owner' || this.identity.role === 'admin') { + return true + } + + // Check specific permissions + return ( + this.identity.permissions.has('delete:*') || + this.identity.permissions.has(`delete:${resource}`) + ) + } + + canUploadBlob(sizeBytes: number): boolean { + const { quota } = this + + // Check max blob size + if (quota.maxBlobSizeBytes && sizeBytes > quota.maxBlobSizeBytes) { + return false + } + + // Check total storage quota + if (quota.maxBlobStorageBytes) { + if (quota.currentBlobStorageBytes + sizeBytes > quota.maxBlobStorageBytes) { + return false + } + } + + // Check blob count quota + if (quota.maxBlobCount) { + if (quota.currentBlobCount >= quota.maxBlobCount) { + return false + } + } + + return true + } + + canCreateRecord(): boolean { + const { quota } = this + + if (quota.maxRecords) { + return quota.currentRecords < quota.maxRecords + } + + return true + } + + canAddToList(additionalItems: number): boolean { + const { quota } = this + + if (quota.maxListLength && additionalItems > quota.maxListLength) { + return false + } + + return true + } +} + +export interface TenantManager { + // Get tenant context for operations + getTenantContext(tenantId: string, userId: string): Promise + + // Update quota usage + updateBlobUsage(tenantId: string, bytesChange: number, countChange: number): Promise + updateRecordUsage(tenantId: string, countChange: number, bytesChange: number): Promise + + // Create/update tenant + createTenant(tenantId: string, quota?: Partial): Promise + updateQuota(tenantId: string, quota: Partial): Promise + + // Get current usage + getUsage(tenantId: string): Promise +} + +export class InMemoryTenantManager implements TenantManager { + private tenants = new Map() + private permissions = new Map() + + async getTenantContext(tenantId: string, userId: string): Promise { + let quota = this.tenants.get(tenantId) + if (!quota) { + // Create default quota + quota = { + currentBlobStorageBytes: 0, + currentBlobCount: 0, + currentRecords: 0, + currentDataSizeBytes: 0 + } + this.tenants.set(tenantId, quota) + } + + // Get or create identity + const identityKey = `${tenantId}:${userId}` + let identity = this.permissions.get(identityKey) + if (!identity) { + identity = { + tenantId, + userId, + role: 'member', + permissions: new Set(['read:*', 'write:*']) + } + this.permissions.set(identityKey, identity) + } + + const namespace = `tenants/${tenantId}/` + + return new DefaultTenantContext(identity, quota, namespace) + } + + async updateBlobUsage(tenantId: string, bytesChange: number, countChange: number): Promise { + const quota = this.tenants.get(tenantId) + if (quota) { + quota.currentBlobStorageBytes += bytesChange + quota.currentBlobCount += countChange + } + } + + async updateRecordUsage(tenantId: string, countChange: number, bytesChange: number): Promise { + const quota = this.tenants.get(tenantId) + if (quota) { + quota.currentRecords += countChange + quota.currentDataSizeBytes += bytesChange + } + } + + async createTenant(tenantId: string, quotaOverrides?: Partial): Promise { + const quota: TenantQuota = { + currentBlobStorageBytes: 0, + currentBlobCount: 0, + currentRecords: 0, + currentDataSizeBytes: 0, + ...quotaOverrides + } + this.tenants.set(tenantId, quota) + } + + async updateQuota(tenantId: string, quotaUpdates: Partial): Promise { + const quota = this.tenants.get(tenantId) + if (quota) { + Object.assign(quota, quotaUpdates) + } + } + + async getUsage(tenantId: string): Promise { + const quota = this.tenants.get(tenantId) + if (!quota) { + return { + currentBlobStorageBytes: 0, + currentBlobCount: 0, + currentRecords: 0, + currentDataSizeBytes: 0 + } + } + return { ...quota } + } + + // Admin methods for testing + setUserRole(tenantId: string, userId: string, role: TenantIdentity['role']): void { + const identityKey = `${tenantId}:${userId}` + const identity = this.permissions.get(identityKey) + if (identity) { + identity.role = role + } + } + + grantPermission(tenantId: string, userId: string, permission: string): void { + const identityKey = `${tenantId}:${userId}` + const identity = this.permissions.get(identityKey) + if (identity) { + identity.permissions.add(permission) + } + } +} diff --git a/dbal/development/src/core/foundation/tenant/permission-checks.ts b/dbal/development/src/core/foundation/tenant/permission-checks.ts new file mode 100644 index 000000000..6570de578 --- /dev/null +++ b/dbal/development/src/core/foundation/tenant/permission-checks.ts @@ -0,0 +1,48 @@ +/** + * @file permission-checks.ts + * @description Permission checking utilities for tenant resources + */ + +import type { TenantIdentity } from './tenant-types' + +/** + * Check if tenant has read permission for a resource + */ +export const canRead = (identity: TenantIdentity, resource: string): boolean => { + if (identity.role === 'owner' || identity.role === 'admin') { + return true + } + + return ( + identity.permissions.has('read:*') || + identity.permissions.has(`read:${resource}`) + ) +} + +/** + * Check if tenant has write permission for a resource + */ +export const canWrite = (identity: TenantIdentity, resource: string): boolean => { + if (identity.role === 'owner' || identity.role === 'admin') { + return true + } + + return ( + identity.permissions.has('write:*') || + identity.permissions.has(`write:${resource}`) + ) +} + +/** + * Check if tenant has delete permission for a resource + */ +export const canDelete = (identity: TenantIdentity, resource: string): boolean => { + if (identity.role === 'owner' || identity.role === 'admin') { + return true + } + + return ( + identity.permissions.has('delete:*') || + identity.permissions.has(`delete:${resource}`) + ) +} diff --git a/dbal/development/src/core/foundation/tenant/quota-checks.ts b/dbal/development/src/core/foundation/tenant/quota-checks.ts new file mode 100644 index 000000000..f1e0e9fe0 --- /dev/null +++ b/dbal/development/src/core/foundation/tenant/quota-checks.ts @@ -0,0 +1,57 @@ +/** + * @file quota-checks.ts + * @description Quota checking utilities for tenant resources + */ + +import type { TenantQuota } from './tenant-types' + +/** + * Check if tenant can upload a blob of given size + */ +export const canUploadBlob = (quota: TenantQuota, sizeBytes: number): boolean => { + // Check blob size limit + if (quota.maxBlobSizeBytes && sizeBytes > quota.maxBlobSizeBytes) { + return false + } + + // Check total storage limit + if (quota.maxBlobStorageBytes) { + const projectedTotal = quota.currentBlobStorageBytes + sizeBytes + if (projectedTotal > quota.maxBlobStorageBytes) { + return false + } + } + + // Check blob count limit + if (quota.maxBlobCount && quota.currentBlobCount >= quota.maxBlobCount) { + return false + } + + return true +} + +/** + * Check if tenant can create a new record + */ +export const canCreateRecord = (quota: TenantQuota): boolean => { + if (quota.maxRecords && quota.currentRecords >= quota.maxRecords) { + return false + } + + return true +} + +/** + * Check if tenant can add items to a list + */ +export const canAddToList = (quota: TenantQuota, additionalItems: number): boolean => { + if (quota.maxListLength) { + // Assuming currentRecords includes list items + const projectedTotal = quota.currentRecords + additionalItems + if (projectedTotal > quota.maxListLength) { + return false + } + } + + return true +} diff --git a/dbal/development/src/core/foundation/tenant/tenant-types.ts b/dbal/development/src/core/foundation/tenant/tenant-types.ts new file mode 100644 index 000000000..bb040d50f --- /dev/null +++ b/dbal/development/src/core/foundation/tenant/tenant-types.ts @@ -0,0 +1,43 @@ +/** + * @file tenant-types.ts + * @description Type definitions for tenant context and identity + */ + +export interface TenantIdentity { + tenantId: string + userId: string + role: 'owner' | 'admin' | 'member' | 'viewer' + permissions: Set +} + +export interface TenantQuota { + // Blob storage quotas + maxBlobStorageBytes?: number + maxBlobCount?: number + maxBlobSizeBytes?: number + + // Structured data quotas + maxRecords?: number + maxDataSizeBytes?: number + maxListLength?: number + + // Computed usage + currentBlobStorageBytes: number + currentBlobCount: number + currentRecords: number + currentDataSizeBytes: number +} + +export interface TenantContext { + identity: TenantIdentity + quota: TenantQuota + namespace: string + + canRead(resource: string): boolean + canWrite(resource: string): boolean + canDelete(resource: string): boolean + + canUploadBlob(sizeBytes: number): boolean + canCreateRecord(): boolean + canAddToList(additionalItems: number): boolean +}