From c3e7a552d0446b999ba3d766dabc3ef82f9b8649 Mon Sep 17 00:00:00 2001 From: koekiebox Date: Thu, 13 Feb 2025 13:35:42 +0100 Subject: [PATCH 1/4] feat(3256): able to view deleted tenant --- packages/backend/src/tenants/service.ts | 4 +- .../frontend/app/routes/tenants.$tenantId.tsx | 51 +++++++++++-------- .../frontend/app/routes/tenants._index.tsx | 8 +-- 3 files changed, 34 insertions(+), 29 deletions(-) diff --git a/packages/backend/src/tenants/service.ts b/packages/backend/src/tenants/service.ts index 11eedc73ca..da6dd4d5c5 100644 --- a/packages/backend/src/tenants/service.ts +++ b/packages/backend/src/tenants/service.ts @@ -43,9 +43,7 @@ async function getTenant( ): Promise { const inMem = await deps.tenantCache.get(id) if (inMem) return inMem - const tenant = await Tenant.query(deps.knex) - .findById(id) - .whereNull('deletedAt') + const tenant = await Tenant.query(deps.knex).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..9a251752b0 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,9 @@ export default function ViewTenantPage() {

General Information

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

@@ -102,25 +105,29 @@ export default function ViewTenantPage() {
- + {!tenantDeleted && ( + + )}
@@ -147,25 +154,29 @@ export default function ViewTenantPage() {
- + {!tenantDeleted && ( + + )}
@@ -197,7 +208,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}`)} >
From 84ea65194035dffbf6239000ff1b5566d08417fe Mon Sep 17 00:00:00 2001 From: koekiebox Date: Thu, 13 Feb 2025 14:10:24 +0100 Subject: [PATCH 2/4] feat(3256): fix backend tests. --- packages/backend/src/tenants/service.test.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/backend/src/tenants/service.test.ts b/packages/backend/src/tenants/service.test.ts index 06837c18fe..b0f7c3261b 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(), @@ -85,10 +85,10 @@ describe('Tenant Service', (): void => { }) const tenant = await tenantService.get(dbTenant.id) - expect(tenant).toBeUndefined() + expect(tenant?.deletedAt).toBeDefined() }) - 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(), @@ -98,7 +98,7 @@ describe('Tenant Service', (): void => { }) const tenant = await tenantService.get(dbTenant.id) - expect(tenant).toBeUndefined() + expect(tenant?.deletedAt).toBeDefined() }) }) @@ -409,7 +409,8 @@ describe('Tenant Service', (): void => { .mockImplementation(async () => undefined) await tenantService.delete(tenant.id) - await expect(tenantService.get(tenant.id)).resolves.toBeUndefined() + const tenantDeleted = await tenantService.get(tenant.id) + await expect(tenantDeleted?.deletedAt).toBeDefined() // Ensure that cache was set for deletion expect(spyCacheDelete).toHaveBeenCalledTimes(1) From b10bbc1cfceb9a1bc2be2d3f9ead8d763ce7125e Mon Sep 17 00:00:00 2001 From: koekiebox Date: Fri, 14 Feb 2025 12:15:55 +0100 Subject: [PATCH 3/4] feat(3256): allow for switching between deleted and not. --- .../backend/src/graphql/resolvers/tenant.ts | 2 +- packages/backend/src/tenants/service.test.ts | 21 ++++++++++++++----- packages/backend/src/tenants/service.ts | 18 +++++++++++----- 3 files changed, 30 insertions(+), 11 deletions(-) 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 b0f7c3261b..6bb05fd39e 100644 --- a/packages/backend/src/tenants/service.test.ts +++ b/packages/backend/src/tenants/service.test.ts @@ -85,10 +85,14 @@ describe('Tenant Service', (): void => { }) const tenant = await tenantService.get(dbTenant.id) - expect(tenant?.deletedAt).toBeDefined() + 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 deletedAt set if tenant is deleted', async (): Promise => { + test('returns undefined if tenant is deleted', async (): Promise => { const dbTenant = await Tenant.query(knex).insertAndFetch({ apiSecret: 'test-secret', email: faker.internet.email(), @@ -98,7 +102,11 @@ describe('Tenant Service', (): void => { }) const tenant = await tenantService.get(dbTenant.id) - expect(tenant?.deletedAt).toBeDefined() + 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() }) }) @@ -409,12 +417,15 @@ describe('Tenant Service', (): void => { .mockImplementation(async () => undefined) await tenantService.delete(tenant.id) - const tenantDeleted = await tenantService.get(tenant.id) - await expect(tenantDeleted?.deletedAt).toBeDefined() + await expect(tenantService.get(tenant.id)).resolves.toBeUndefined() // 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 da6dd4d5c5..7fec9d3c48 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 = false) => + getTenant(deps, id, includeDeleted), create: (options) => createTenant(deps, options), update: (options) => updateTenant(deps, options), delete: (id) => deleteTenant(deps, id), @@ -39,11 +40,18 @@ export async function createTenantService( async function getTenant( deps: ServiceDependencies, - id: string + id: string, + includeDeleted: boolean ): Promise { const inMem = await deps.tenantCache.get(id) - if (inMem) return inMem - const tenant = await Tenant.query(deps.knex).findById(id) + 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 From 80d179151b6ee5d36ce5a7209d5f9cf7fcb86a70 Mon Sep 17 00:00:00 2001 From: koekiebox Date: Fri, 14 Feb 2025 14:49:25 +0100 Subject: [PATCH 4/4] feat(3256): review comments. --- packages/backend/src/tenants/service.ts | 4 ++-- packages/frontend/app/routes/tenants.$tenantId.tsx | 10 +++++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/backend/src/tenants/service.ts b/packages/backend/src/tenants/service.ts index 7fec9d3c48..0355187e0c 100644 --- a/packages/backend/src/tenants/service.ts +++ b/packages/backend/src/tenants/service.ts @@ -28,7 +28,7 @@ export async function createTenantService( } return { - get: (id: string, includeDeleted: boolean = false) => + get: (id: string, includeDeleted?: boolean) => getTenant(deps, id, includeDeleted), create: (options) => createTenant(deps, options), update: (options) => updateTenant(deps, options), @@ -41,7 +41,7 @@ export async function createTenantService( async function getTenant( deps: ServiceDependencies, id: string, - includeDeleted: boolean + includeDeleted: boolean = false ): Promise { const inMem = await deps.tenantCache.get(id) if (inMem) { diff --git a/packages/frontend/app/routes/tenants.$tenantId.tsx b/packages/frontend/app/routes/tenants.$tenantId.tsx index 9a251752b0..3cd6a6d38f 100644 --- a/packages/frontend/app/routes/tenants.$tenantId.tsx +++ b/packages/frontend/app/routes/tenants.$tenantId.tsx @@ -79,9 +79,13 @@ export default function ViewTenantPage() {

General Information

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