mirror of
https://github.com/Afilmory/afilmory
synced 2026-02-01 22:48:17 +00:00
feat: implement storage tenant listing and query enhancements
- Added a new endpoint in SuperAdminTenantController to list tenants with storage plans and their usage statistics. - Updated TenantRepository and TenantService to support filtering tenants by storage plan requirements. - Introduced new API functions and hooks in the dashboard for fetching and managing storage tenant data. - Modified TenantStoragePanel component to utilize the new storage tenant query. Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
@@ -93,6 +93,44 @@ export class SuperAdminTenantController {
|
||||
}
|
||||
}
|
||||
|
||||
@Get('/storage')
|
||||
async listStorageTenants(@Query() query: ListTenantsQueryDto) {
|
||||
const [tenantResult, storagePlanCatalog, managedProviderKey] = await Promise.all([
|
||||
this.tenantService.listTenants({
|
||||
page: query.page,
|
||||
limit: query.limit,
|
||||
search: query.search,
|
||||
status: query.status,
|
||||
sortBy: query.sortBy,
|
||||
sortDir: query.sortDir,
|
||||
requireStoragePlan: true,
|
||||
}),
|
||||
this.systemSettings.getStoragePlanCatalog(),
|
||||
this.systemSettings.getManagedStorageProviderKey(),
|
||||
])
|
||||
|
||||
const { items: tenantAggregates, total } = tenantResult
|
||||
const tenantIds = tenantAggregates.map((aggregate) => aggregate.tenant.id)
|
||||
|
||||
const storageUsageMap =
|
||||
managedProviderKey && tenantIds.length > 0
|
||||
? await this.managedStorageService.getUsageTotalsForTenants(managedProviderKey, tenantIds)
|
||||
: {}
|
||||
|
||||
return {
|
||||
tenants: tenantAggregates.map((aggregate) => ({
|
||||
...aggregate.tenant,
|
||||
storageUsage: storageUsageMap[aggregate.tenant.id] ?? null,
|
||||
})),
|
||||
plans: [],
|
||||
storagePlans: Object.entries(storagePlanCatalog).map(([id, def]) => ({
|
||||
id,
|
||||
...def,
|
||||
})),
|
||||
total,
|
||||
}
|
||||
}
|
||||
|
||||
@Patch('/:tenantId/plan')
|
||||
async updateTenantPlan(@Param() params: TenantIdParamDto, @Body() dto: UpdateTenantPlanDto) {
|
||||
await this.billingPlanService.updateTenantPlan(params.tenantId, dto.planId as BillingPlanId)
|
||||
|
||||
@@ -3,7 +3,7 @@ import { RESERVED_TENANT_SLUGS } from '@afilmory/utils'
|
||||
import { DbAccessor } from 'core/database/database.provider'
|
||||
import { BizException, ErrorCode } from 'core/errors'
|
||||
import type { BillingPlanId } from 'core/modules/platform/billing/billing-plan.types'
|
||||
import { and, asc, count, desc, eq, ilike, notInArray, or } from 'drizzle-orm'
|
||||
import { and, asc, count, desc, eq, ilike, isNotNull, notInArray, or } from 'drizzle-orm'
|
||||
import { injectable } from 'tsyringe'
|
||||
|
||||
import type { TenantAggregate, TenantRecord } from './tenant.types'
|
||||
@@ -88,6 +88,7 @@ export class TenantRepository {
|
||||
status?: TenantRecord['status']
|
||||
sortBy?: 'createdAt' | 'name'
|
||||
sortDir?: 'asc' | 'desc'
|
||||
requireStoragePlan?: boolean
|
||||
}): Promise<{ items: TenantAggregate[]; total: number }> {
|
||||
const db = this.dbAccessor.get()
|
||||
const { page, limit, search, status, sortBy = 'createdAt', sortDir = 'desc' } = options
|
||||
@@ -98,6 +99,10 @@ export class TenantRepository {
|
||||
conditions.push(eq(tenants.status, status))
|
||||
}
|
||||
|
||||
if (options.requireStoragePlan) {
|
||||
conditions.push(isNotNull(tenants.storagePlanId))
|
||||
}
|
||||
|
||||
if (search) {
|
||||
const searchLike = `%${search}%`
|
||||
conditions.push(or(ilike(tenants.name, searchLike), ilike(tenants.slug, searchLike)))
|
||||
|
||||
@@ -122,6 +122,7 @@ export class TenantService {
|
||||
status?: TenantRecord['status']
|
||||
sortBy?: 'createdAt' | 'name'
|
||||
sortDir?: 'asc' | 'desc'
|
||||
requireStoragePlan?: boolean
|
||||
}): Promise<{ items: TenantAggregate[]; total: number }> {
|
||||
return await this.repository.listTenants({
|
||||
page: options?.page ?? 1,
|
||||
@@ -130,6 +131,7 @@ export class TenantService {
|
||||
status: options?.status,
|
||||
sortBy: options?.sortBy,
|
||||
sortDir: options?.sortDir,
|
||||
requireStoragePlan: options?.requireStoragePlan,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import type {
|
||||
|
||||
const SUPER_ADMIN_SETTINGS_ENDPOINT = '/super-admin/settings'
|
||||
const SUPER_ADMIN_TENANTS_ENDPOINT = '/super-admin/tenants'
|
||||
const SUPER_ADMIN_STORAGE_TENANTS_ENDPOINT = '/super-admin/tenants/storage'
|
||||
const STABLE_NEWLINE = /\r?\n/
|
||||
|
||||
type RunBuilderDebugOptions = {
|
||||
@@ -63,6 +64,30 @@ export async function fetchSuperAdminTenants(
|
||||
return camelCaseKeys<SuperAdminTenantListResponse>(response)
|
||||
}
|
||||
|
||||
export async function fetchSuperAdminStorageTenants(
|
||||
params?: SuperAdminTenantListParams,
|
||||
): Promise<SuperAdminTenantListResponse> {
|
||||
const query = new URLSearchParams()
|
||||
if (params) {
|
||||
if (params.page) query.set('page', String(params.page))
|
||||
if (params.limit) query.set('limit', String(params.limit))
|
||||
if (params.search) query.set('search', params.search)
|
||||
if (params.sortBy) query.set('sortBy', params.sortBy)
|
||||
if (params.sortDir) query.set('sortDir', params.sortDir)
|
||||
}
|
||||
|
||||
const queryString = query.toString()
|
||||
const url = queryString
|
||||
? `${SUPER_ADMIN_STORAGE_TENANTS_ENDPOINT}?${queryString}`
|
||||
: SUPER_ADMIN_STORAGE_TENANTS_ENDPOINT
|
||||
|
||||
const response = await coreApi<SuperAdminTenantListResponse>(url, {
|
||||
method: 'GET',
|
||||
})
|
||||
|
||||
return camelCaseKeys<SuperAdminTenantListResponse>(response)
|
||||
}
|
||||
|
||||
export async function updateSuperAdminTenantPlan(payload: UpdateTenantPlanPayload): Promise<void> {
|
||||
await coreApi(`${SUPER_ADMIN_TENANTS_ENDPOINT}/${payload.tenantId}/plan`, {
|
||||
method: 'PATCH',
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useTranslation } from 'react-i18next'
|
||||
import { LinearBorderPanel } from '~/components/common/LinearBorderPanel'
|
||||
import { buildTenantUrl } from '~/modules/auth/utils/domain'
|
||||
|
||||
import { useSuperAdminTenantsQuery } from '../hooks'
|
||||
import { useSuperAdminStorageTenantsQuery } from '../hooks'
|
||||
import type { StoragePlanDefinition } from '../types'
|
||||
import { formatBytes } from './TenantUsageCell'
|
||||
|
||||
@@ -27,7 +27,7 @@ export function TenantStoragePanel() {
|
||||
return () => clearTimeout(timer)
|
||||
}, [search])
|
||||
|
||||
const tenantsQuery = useSuperAdminTenantsQuery({
|
||||
const tenantsQuery = useSuperAdminStorageTenantsQuery({
|
||||
page,
|
||||
limit,
|
||||
search: debouncedSearch,
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import {
|
||||
deleteSuperAdminTenant,
|
||||
fetchSuperAdminSettings,
|
||||
fetchSuperAdminStorageTenants,
|
||||
fetchSuperAdminTenantPhotos,
|
||||
fetchSuperAdminTenants,
|
||||
updateSuperAdminSettings,
|
||||
@@ -23,6 +24,7 @@ import type {
|
||||
|
||||
export const SUPER_ADMIN_SETTINGS_QUERY_KEY = ['super-admin', 'settings'] as const
|
||||
export const SUPER_ADMIN_TENANTS_QUERY_KEY = ['super-admin', 'tenants'] as const
|
||||
export const SUPER_ADMIN_STORAGE_TENANTS_QUERY_KEY = ['super-admin', 'tenants', 'storage'] as const
|
||||
|
||||
export function useSuperAdminSettingsQuery() {
|
||||
return useQuery<SuperAdminSettingsResponse>({
|
||||
@@ -40,6 +42,14 @@ export function useSuperAdminTenantsQuery(params?: SuperAdminTenantListParams) {
|
||||
})
|
||||
}
|
||||
|
||||
export function useSuperAdminStorageTenantsQuery(params?: SuperAdminTenantListParams) {
|
||||
return useQuery<SuperAdminTenantListResponse>({
|
||||
queryKey: [...SUPER_ADMIN_STORAGE_TENANTS_QUERY_KEY, params],
|
||||
queryFn: () => fetchSuperAdminStorageTenants(params),
|
||||
placeholderData: (previousData) => previousData,
|
||||
})
|
||||
}
|
||||
|
||||
type SuperAdminSettingsMutationOptions = {
|
||||
onSuccess?: (data: SuperAdminSettingsResponse) => void
|
||||
}
|
||||
@@ -65,6 +75,7 @@ export function useUpdateTenantPlanMutation() {
|
||||
},
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: SUPER_ADMIN_TENANTS_QUERY_KEY })
|
||||
void queryClient.invalidateQueries({ queryKey: SUPER_ADMIN_STORAGE_TENANTS_QUERY_KEY })
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -78,6 +89,7 @@ export function useUpdateTenantStoragePlanMutation() {
|
||||
},
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: SUPER_ADMIN_TENANTS_QUERY_KEY })
|
||||
void queryClient.invalidateQueries({ queryKey: SUPER_ADMIN_STORAGE_TENANTS_QUERY_KEY })
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -91,6 +103,7 @@ export function useUpdateTenantBanMutation() {
|
||||
},
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: SUPER_ADMIN_TENANTS_QUERY_KEY })
|
||||
void queryClient.invalidateQueries({ queryKey: SUPER_ADMIN_STORAGE_TENANTS_QUERY_KEY })
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -104,6 +117,7 @@ export function useDeleteTenantMutation() {
|
||||
},
|
||||
onSuccess: () => {
|
||||
void queryClient.invalidateQueries({ queryKey: SUPER_ADMIN_TENANTS_QUERY_KEY })
|
||||
void queryClient.invalidateQueries({ queryKey: SUPER_ADMIN_STORAGE_TENANTS_QUERY_KEY })
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user