diff --git a/packages/backend/src/graphql/resolvers/tenant.ts b/packages/backend/src/graphql/resolvers/tenant.ts index 19e86bd077..6fdddf16da 100644 --- a/packages/backend/src/graphql/resolvers/tenant.ts +++ b/packages/backend/src/graphql/resolvers/tenant.ts @@ -40,7 +40,7 @@ export const getTenant: QueryResolvers['tenant'] = } const tenantService = await ctx.container.use('tenantService') - const tenant = await tenantService.get(args.id) + const tenant = await tenantService.get(args.id, isOperator) if (!tenant) { throw new GraphQLError('tenant does not exist', { extensions: { diff --git a/packages/backend/src/tenants/service.test.ts b/packages/backend/src/tenants/service.test.ts index 06837c18fe..6bb05fd39e 100644 --- a/packages/backend/src/tenants/service.test.ts +++ b/packages/backend/src/tenants/service.test.ts @@ -75,7 +75,7 @@ describe('Tenant Service', (): void => { expect(tenant).toEqual(createdTenant) }) - test('returns undefined if tenant is deleted', async (): Promise => { + test('returns deletedAt set if tenant is deleted', async (): Promise => { const dbTenant = await Tenant.query(knex).insertAndFetch({ apiSecret: 'test-secret', email: faker.internet.email(), @@ -86,6 +86,10 @@ describe('Tenant Service', (): void => { const tenant = await tenantService.get(dbTenant.id) expect(tenant).toBeUndefined() + + // Ensure Operator is able to access tenant even if deleted: + const tenantDel = await tenantService.get(dbTenant.id, true) + expect(tenantDel?.deletedAt).toBeDefined() }) test('returns undefined if tenant is deleted', async (): Promise => { @@ -99,6 +103,10 @@ describe('Tenant Service', (): void => { const tenant = await tenantService.get(dbTenant.id) expect(tenant).toBeUndefined() + + // Ensure Operator is able to access tenant even if deleted: + const tenantDel = await tenantService.get(dbTenant.id, true) + expect(tenantDel?.deletedAt).toBeDefined() }) }) @@ -414,6 +422,10 @@ describe('Tenant Service', (): void => { // Ensure that cache was set for deletion expect(spyCacheDelete).toHaveBeenCalledTimes(1) expect(spyCacheDelete).toHaveBeenCalledWith(tenant.id) + + // Ensure Operator is able to access tenant even if deleted: + const tenantDel = await tenantService.get(tenant.id, true) + expect(tenantDel?.deletedAt).toBeDefined() } ) ) diff --git a/packages/backend/src/tenants/service.ts b/packages/backend/src/tenants/service.ts index 11eedc73ca..0355187e0c 100644 --- a/packages/backend/src/tenants/service.ts +++ b/packages/backend/src/tenants/service.ts @@ -6,7 +6,7 @@ import { CacheDataStore } from '../middleware/cache/data-stores' import type { AuthServiceClient } from '../auth-service-client/client' export interface TenantService { - get: (id: string) => Promise + get: (id: string, includeDeleted?: boolean) => Promise create: (options: CreateTenantOptions) => Promise update: (options: UpdateTenantOptions) => Promise delete: (id: string) => Promise @@ -28,7 +28,8 @@ export async function createTenantService( } return { - get: (id: string) => getTenant(deps, id), + get: (id: string, includeDeleted?: boolean) => + getTenant(deps, id, includeDeleted), create: (options) => createTenant(deps, options), update: (options) => updateTenant(deps, options), delete: (id) => deleteTenant(deps, id), @@ -39,13 +40,18 @@ export async function createTenantService( async function getTenant( deps: ServiceDependencies, - id: string + id: string, + includeDeleted: boolean = false ): Promise { const inMem = await deps.tenantCache.get(id) - if (inMem) return inMem - const tenant = await Tenant.query(deps.knex) - .findById(id) - .whereNull('deletedAt') + if (inMem) { + if (!includeDeleted && inMem.deletedAt) return undefined + return inMem + } + let query = Tenant.query(deps.knex) + if (!includeDeleted) query = query.whereNull('deletedAt') + + const tenant = await query.findById(id) if (tenant) await deps.tenantCache.set(tenant.id, tenant) return tenant diff --git a/packages/frontend/app/routes/tenants.$tenantId.tsx b/packages/frontend/app/routes/tenants.$tenantId.tsx index 8ba5579c8a..3cd6a6d38f 100644 --- a/packages/frontend/app/routes/tenants.$tenantId.tsx +++ b/packages/frontend/app/routes/tenants.$tenantId.tsx @@ -41,12 +41,13 @@ export async function loader({ request, params }: LoaderFunctionArgs) { if (!tenant) throw json(null, { status: 404, statusText: 'Tenant not found.' }) + const tenantDeleted = tenant.deletedAt ? tenant.deletedAt.length > 0 : false const me = await whoAmI(request) - return json({ tenant, me }) + return json({ tenant, me, tenantDeleted }) } export default function ViewTenantPage() { - const { tenant, me } = useLoaderData() + const { tenant, me, tenantDeleted } = useLoaderData() const response = useActionData() const navigation = useNavigation() const [formData, setFormData] = useState() @@ -78,7 +79,13 @@ export default function ViewTenantPage() {

General Information

- Created at {new Date(tenant.createdAt).toLocaleString()} + {`Created at ${new Date(tenant.createdAt).toLocaleString()}`} + {tenantDeleted && tenant.deletedAt && ( + <> +
+ {`Deleted at ${new Date(tenant.deletedAt).toLocaleString()}`} + + )}

@@ -102,25 +109,29 @@ export default function ViewTenantPage() {
- + {!tenantDeleted && ( + + )}
@@ -147,25 +158,29 @@ export default function ViewTenantPage() {
- + {!tenantDeleted && ( + + )}
@@ -197,7 +212,7 @@ export default function ViewTenantPage() { {/* Sensitive - END */} {/* DELETE TENANT - Danger zone */} - {me.isOperator && me.id !== tenant.id && ( + {!tenantDeleted && me.isOperator && me.id !== tenant.id && (
diff --git a/packages/frontend/app/routes/tenants._index.tsx b/packages/frontend/app/routes/tenants._index.tsx index 3d4f3aac53..ef1fdc848d 100644 --- a/packages/frontend/app/routes/tenants._index.tsx +++ b/packages/frontend/app/routes/tenants._index.tsx @@ -80,12 +80,8 @@ export default function TenantsPage() { tenantEdges.map((tenant) => ( - tenant.node.deletedAt - ? 'return' - : navigate(`/tenants/${tenant.node.id}`) - } + className='cursor-pointer' + onClick={() => navigate(`/tenants/${tenant.node.id}`)} >