mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-24 22:04:56 +00:00
Merge pull request #221 from johndoe6345789/codex/create-tenant-context-and-audit-hooks
Refactor tenant-aware blob storage context and hooks
This commit is contained in:
@@ -1 +1,5 @@
|
||||
export { TenantAwareBlobStorage } from './tenant-aware-storage/index'
|
||||
export type { TenantAwareDeps } from './tenant-aware-storage/context'
|
||||
export { scopeKey, unscopeKey } from './tenant-aware-storage/context'
|
||||
export { ensurePermission, resolveTenantContext } from './tenant-aware-storage/tenant-context'
|
||||
export { auditCopy, auditDeletion, auditUpload } from './tenant-aware-storage/audit-hooks'
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import type { TenantAwareDeps } from './context'
|
||||
|
||||
const recordUsageChange = async (deps: TenantAwareDeps, bytesChange: number, countChange: number): Promise<void> => {
|
||||
await deps.tenantManager.updateBlobUsage(deps.tenantId, bytesChange, countChange)
|
||||
}
|
||||
|
||||
export const auditUpload = async (deps: TenantAwareDeps, sizeBytes: number): Promise<void> => {
|
||||
await recordUsageChange(deps, sizeBytes, 1)
|
||||
}
|
||||
|
||||
export const auditDeletion = async (deps: TenantAwareDeps, sizeBytes: number): Promise<void> => {
|
||||
await recordUsageChange(deps, -sizeBytes, -1)
|
||||
}
|
||||
|
||||
export const auditCopy = async (deps: TenantAwareDeps, sizeBytes: number): Promise<void> => {
|
||||
await recordUsageChange(deps, sizeBytes, 1)
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import { DBALError } from '../../core/foundation/errors'
|
||||
import type { TenantContext, TenantManager } from '../../core/foundation/tenant-context'
|
||||
import type { TenantManager } from '../../core/foundation/tenant-context'
|
||||
import type { BlobStorage } from '../blob-storage'
|
||||
|
||||
export interface TenantAwareDeps {
|
||||
@@ -9,10 +8,6 @@ export interface TenantAwareDeps {
|
||||
userId: string
|
||||
}
|
||||
|
||||
export const getContext = async ({ tenantManager, tenantId, userId }: TenantAwareDeps): Promise<TenantContext> => {
|
||||
return tenantManager.getTenantContext(tenantId, userId)
|
||||
}
|
||||
|
||||
export const scopeKey = (key: string, namespace: string): string => {
|
||||
const cleanKey = key.startsWith('/') ? key.substring(1) : key
|
||||
return `${namespace}${cleanKey}`
|
||||
@@ -24,17 +19,3 @@ export const unscopeKey = (scopedKey: string, namespace: string): string => {
|
||||
}
|
||||
return scopedKey
|
||||
}
|
||||
|
||||
export const ensurePermission = (context: TenantContext, action: 'read' | 'write' | 'delete'): void => {
|
||||
const accessCheck =
|
||||
action === 'read' ? context.canRead('blob') : action === 'write' ? context.canWrite('blob') : context.canDelete('blob')
|
||||
|
||||
if (!accessCheck) {
|
||||
const verbs: Record<typeof action, string> = {
|
||||
read: 'read',
|
||||
write: 'write',
|
||||
delete: 'delete',
|
||||
}
|
||||
throw DBALError.forbidden(`Permission denied: cannot ${verbs[action]} blobs`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { DBALError } from '../../core/foundation/errors'
|
||||
import type { BlobMetadata } from '../blob-storage'
|
||||
import { ensurePermission, getContext, scopeKey } from './context'
|
||||
import { auditCopy, auditDeletion } from './audit-hooks'
|
||||
import type { TenantAwareDeps } from './context'
|
||||
import { scopeKey } from './context'
|
||||
import { ensurePermission, resolveTenantContext } from './tenant-context'
|
||||
|
||||
export const deleteBlob = async (deps: TenantAwareDeps, key: string): Promise<boolean> => {
|
||||
const context = await getContext(deps)
|
||||
const context = await resolveTenantContext(deps)
|
||||
ensurePermission(context, 'delete')
|
||||
|
||||
const scopedKey = scopeKey(key, context.namespace)
|
||||
@@ -14,7 +16,7 @@ export const deleteBlob = async (deps: TenantAwareDeps, key: string): Promise<bo
|
||||
const deleted = await deps.baseStorage.delete(scopedKey)
|
||||
|
||||
if (deleted) {
|
||||
await deps.tenantManager.updateBlobUsage(deps.tenantId, -metadata.size, -1)
|
||||
await auditDeletion(deps, metadata.size)
|
||||
}
|
||||
|
||||
return deleted
|
||||
@@ -24,7 +26,7 @@ export const deleteBlob = async (deps: TenantAwareDeps, key: string): Promise<bo
|
||||
}
|
||||
|
||||
export const exists = async (deps: TenantAwareDeps, key: string): Promise<boolean> => {
|
||||
const context = await getContext(deps)
|
||||
const context = await resolveTenantContext(deps)
|
||||
ensurePermission(context, 'read')
|
||||
|
||||
const scopedKey = scopeKey(key, context.namespace)
|
||||
@@ -36,7 +38,7 @@ export const copyBlob = async (
|
||||
sourceKey: string,
|
||||
destKey: string,
|
||||
): Promise<BlobMetadata> => {
|
||||
const context = await getContext(deps)
|
||||
const context = await resolveTenantContext(deps)
|
||||
ensurePermission(context, 'read')
|
||||
ensurePermission(context, 'write')
|
||||
|
||||
@@ -50,7 +52,7 @@ export const copyBlob = async (
|
||||
const destScoped = scopeKey(destKey, context.namespace)
|
||||
const metadata = await deps.baseStorage.copy(sourceScoped, destScoped)
|
||||
|
||||
await deps.tenantManager.updateBlobUsage(deps.tenantId, sourceMetadata.size, 1)
|
||||
await auditCopy(deps, sourceMetadata.size)
|
||||
|
||||
return {
|
||||
...metadata,
|
||||
@@ -59,7 +61,7 @@ export const copyBlob = async (
|
||||
}
|
||||
|
||||
export const getStats = async (deps: TenantAwareDeps) => {
|
||||
const context = await getContext(deps)
|
||||
const context = await resolveTenantContext(deps)
|
||||
return {
|
||||
count: context.quota.currentBlobCount,
|
||||
totalSize: context.quota.currentBlobStorageBytes,
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import type { DownloadOptions, BlobMetadata, BlobListOptions, BlobListResult } from '../blob-storage'
|
||||
import { ensurePermission, getContext, scopeKey, unscopeKey } from './context'
|
||||
import type { TenantAwareDeps } from './context'
|
||||
import { scopeKey, unscopeKey } from './context'
|
||||
import { ensurePermission, resolveTenantContext } from './tenant-context'
|
||||
|
||||
export const downloadBuffer = async (deps: TenantAwareDeps, key: string): Promise<Buffer> => {
|
||||
const context = await getContext(deps)
|
||||
const context = await resolveTenantContext(deps)
|
||||
ensurePermission(context, 'read')
|
||||
|
||||
const scopedKey = scopeKey(key, context.namespace)
|
||||
@@ -15,7 +16,7 @@ export const downloadStream = async (
|
||||
key: string,
|
||||
options?: DownloadOptions,
|
||||
): Promise<ReadableStream | NodeJS.ReadableStream> => {
|
||||
const context = await getContext(deps)
|
||||
const context = await resolveTenantContext(deps)
|
||||
ensurePermission(context, 'read')
|
||||
|
||||
const scopedKey = scopeKey(key, context.namespace)
|
||||
@@ -26,7 +27,7 @@ export const listBlobs = async (
|
||||
deps: TenantAwareDeps,
|
||||
options: BlobListOptions = {},
|
||||
): Promise<BlobListResult> => {
|
||||
const context = await getContext(deps)
|
||||
const context = await resolveTenantContext(deps)
|
||||
ensurePermission(context, 'read')
|
||||
|
||||
const scopedOptions: BlobListOptions = {
|
||||
@@ -46,7 +47,7 @@ export const listBlobs = async (
|
||||
}
|
||||
|
||||
export const getMetadata = async (deps: TenantAwareDeps, key: string): Promise<BlobMetadata> => {
|
||||
const context = await getContext(deps)
|
||||
const context = await resolveTenantContext(deps)
|
||||
ensurePermission(context, 'read')
|
||||
|
||||
const scopedKey = scopeKey(key, context.namespace)
|
||||
@@ -63,7 +64,7 @@ export const generatePresignedUrl = async (
|
||||
key: string,
|
||||
expiresIn: number,
|
||||
): Promise<string> => {
|
||||
const context = await getContext(deps)
|
||||
const context = await resolveTenantContext(deps)
|
||||
ensurePermission(context, 'read')
|
||||
|
||||
const scopedKey = scopeKey(key, context.namespace)
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { DBALError } from '../../core/foundation/errors'
|
||||
import type { TenantContext } from '../../core/foundation/tenant-context'
|
||||
import type { TenantAwareDeps } from './context'
|
||||
|
||||
export const resolveTenantContext = async ({ tenantManager, tenantId, userId }: TenantAwareDeps): Promise<TenantContext> => {
|
||||
return tenantManager.getTenantContext(tenantId, userId)
|
||||
}
|
||||
|
||||
export const ensurePermission = (context: TenantContext, action: 'read' | 'write' | 'delete'): void => {
|
||||
const accessCheck =
|
||||
action === 'read' ? context.canRead('blob') : action === 'write' ? context.canWrite('blob') : context.canDelete('blob')
|
||||
|
||||
if (!accessCheck) {
|
||||
const verbs: Record<typeof action, string> = {
|
||||
read: 'read',
|
||||
write: 'write',
|
||||
delete: 'delete',
|
||||
}
|
||||
throw DBALError.forbidden(`Permission denied: cannot ${verbs[action]} blobs`)
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
import { DBALError } from '../../core/foundation/errors'
|
||||
import type { UploadOptions, BlobMetadata } from '../blob-storage'
|
||||
import { auditUpload } from './audit-hooks'
|
||||
import type { TenantAwareDeps } from './context'
|
||||
import { ensurePermission, getContext, scopeKey } from './context'
|
||||
import { scopeKey } from './context'
|
||||
import { ensurePermission, resolveTenantContext } from './tenant-context'
|
||||
import type { UploadOptions, BlobMetadata } from '../blob-storage'
|
||||
|
||||
export const uploadBuffer = async (
|
||||
deps: TenantAwareDeps,
|
||||
@@ -9,7 +11,7 @@ export const uploadBuffer = async (
|
||||
data: Buffer,
|
||||
options?: UploadOptions,
|
||||
): Promise<BlobMetadata> => {
|
||||
const context = await getContext(deps)
|
||||
const context = await resolveTenantContext(deps)
|
||||
ensurePermission(context, 'write')
|
||||
|
||||
if (!context.canUploadBlob(data.length)) {
|
||||
@@ -18,7 +20,7 @@ export const uploadBuffer = async (
|
||||
|
||||
const scopedKey = scopeKey(key, context.namespace)
|
||||
const metadata = await deps.baseStorage.upload(scopedKey, data, options)
|
||||
await deps.tenantManager.updateBlobUsage(deps.tenantId, data.length, 1)
|
||||
await auditUpload(deps, data.length)
|
||||
|
||||
return {
|
||||
...metadata,
|
||||
@@ -33,7 +35,7 @@ export const uploadStream = async (
|
||||
size: number,
|
||||
options?: UploadOptions,
|
||||
): Promise<BlobMetadata> => {
|
||||
const context = await getContext(deps)
|
||||
const context = await resolveTenantContext(deps)
|
||||
ensurePermission(context, 'write')
|
||||
|
||||
if (!context.canUploadBlob(size)) {
|
||||
@@ -42,7 +44,7 @@ export const uploadStream = async (
|
||||
|
||||
const scopedKey = scopeKey(key, context.namespace)
|
||||
const metadata = await deps.baseStorage.uploadStream(scopedKey, stream, size, options)
|
||||
await deps.tenantManager.updateBlobUsage(deps.tenantId, size, 1)
|
||||
await auditUpload(deps, size)
|
||||
|
||||
return {
|
||||
...metadata,
|
||||
|
||||
Reference in New Issue
Block a user