Skip to content

Commit

Permalink
Merge pull request #901 from bcgov/869-gurj-soft-delete-tenant
Browse files Browse the repository at this point in the history
innkeeper: soft delete tenant
  • Loading branch information
gurjmatharu authored Oct 31, 2023
2 parents e0648f1 + 6fc1ab8 commit 827f812
Show file tree
Hide file tree
Showing 10 changed files with 232 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ class Meta:
fields.Dict(description="Endorser and ledger config", required=False),
example=json.dumps(ENDORSER_LEDGER_CONFIG_EXAMPLE),
required=False,
attribute="connect_to_endorsers"
attribute="connect_to_endorsers",
)

create_public_did = fields.List(
Expand Down Expand Up @@ -278,6 +278,7 @@ def __init__(
created_public_did: List = [],
auto_issuer: bool = False,
enable_ledger_switch=False,
deleted_at: str = None,
**kwargs,
):
"""Construct record."""
Expand All @@ -293,6 +294,7 @@ def __init__(
self.auto_issuer = auto_issuer
self.enable_ledger_switch = enable_ledger_switch
self.curr_ledger_id = curr_ledger_id
self.deleted_at = deleted_at

@property
def tenant_id(self) -> Optional[str]:
Expand All @@ -312,6 +314,7 @@ def record_value(self) -> dict:
"auto_issuer",
"enable_ledger_switch",
"curr_ledger_id",
"deleted_at",
)
}

Expand Down Expand Up @@ -345,6 +348,16 @@ async def query_by_wallet_id(
raise StorageNotFoundError("No TenantRecord found for the given wallet_id")
return result[0]

async def soft_delete(self, session: ProfileSession):
"""
Soft delete the tenant record by setting its state to 'deleted'.
Note: This method should be called on an instance of the TenantRecord.
"""
if self.state != self.STATE_DELETED:
self.state = self.STATE_DELETED
self.deleted_at = datetime_to_str(datetime.utcnow())
await self.save(session, reason="Soft delete")


class TenantRecordSchema(BaseRecordSchema):
"""Innkeeper Tenant Record Schema."""
Expand Down Expand Up @@ -413,6 +426,11 @@ class Meta:
required=False,
description="Current ledger identifier",
)
deleted_at = fields.Str(
required=False,
description="Timestamp of the deletion",
example="2023-10-30T01:01:01Z",
)


class TenantAuthenticationApiRecord(BaseRecord):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@
import uuid

from aiohttp import ClientSession, web
from aiohttp_apispec import docs, match_info_schema, request_schema, response_schema
from aiohttp_apispec import (
docs,
match_info_schema,
request_schema,
response_schema,
use_kwargs,
)
from aries_cloudagent.admin.request_context import AdminRequestContext
from aries_cloudagent.messaging.models.base import BaseModelError
from aries_cloudagent.messaging.models.openapi import OpenAPISchema
Expand All @@ -17,7 +23,7 @@
from aries_cloudagent.storage.error import StorageError, StorageNotFoundError
from aries_cloudagent.wallet.error import WalletSettingsError
from aries_cloudagent.wallet.models.wallet_record import WalletRecord
from marshmallow import fields
from marshmallow import fields, validate

from . import TenantManager
from .config import InnkeeperWalletConfig
Expand Down Expand Up @@ -275,6 +281,19 @@ class TenantListSchema(OpenAPISchema):
)


class TenantListQuerySchema(OpenAPISchema):
"""Query parameters schema for tenants list."""

state = fields.Str(
required=False,
description="The state of the tenants to filter by.",
example=TenantRecord.STATE_ACTIVE,
validate=validate.OneOf(
[TenantRecord.STATE_ACTIVE, TenantRecord.STATE_DELETED, "all"]
),
)


class TenantAuthenticationApiListSchema(OpenAPISchema):
"""Response schema for authentications - users list."""

Expand Down Expand Up @@ -709,6 +728,7 @@ async def innkeeper_reservations_deny(request: web.BaseRequest):
tags=[SWAGGER_CATEGORY],
)
@response_schema(TenantListSchema(), 200, description="")
@use_kwargs(TenantListQuerySchema(), location="query")
@innkeeper_only
@error_handler
async def innkeeper_tenants_list(request: web.BaseRequest):
Expand All @@ -718,7 +738,14 @@ async def innkeeper_tenants_list(request: web.BaseRequest):
mgr = context.inject(TenantManager)
profile = mgr.profile

# Get the state from query parameters, default to 'active'
state = request.query.get("state", TenantRecord.STATE_ACTIVE)
tag_filter = {}

# If the state is not 'all', apply the filter
if state != "all":
tag_filter["state"] = state

post_filter = {}

async with profile.session() as session:
Expand Down Expand Up @@ -757,6 +784,30 @@ async def innkeeper_tenant_get(request: web.BaseRequest):
return web.json_response(rec.serialize())


@docs(
tags=[SWAGGER_CATEGORY],
)
@match_info_schema(TenantIdMatchInfoSchema())
@response_schema(TenantRecordSchema(), 200, description="")
@innkeeper_only
@error_handler
async def innkeeper_tenant_delete(request: web.BaseRequest):
context: AdminRequestContext = request["context"]
tenant_id = request.match_info["tenant_id"]

mgr = context.inject(TenantManager)
profile = mgr.profile

async with profile.session() as session:
rec = await TenantRecord.retrieve_by_id(session, tenant_id)
if rec:
await rec.soft_delete(session)
LOGGER.info("Tenant %s soft deleted.", tenant_id)
return web.json_response({"success": f"Tenant {tenant_id} soft deleted."})
else:
raise web.HTTPNotFound(reason=f"Tenant {tenant_id} not found.")


@docs(tags=[SWAGGER_CATEGORY], summary="Create API Key Record")
@request_schema(TenantAuthenticationsApiRequestSchema())
@response_schema(TenantAuthenticationsApiResponseSchema(), 200, description="")
Expand Down Expand Up @@ -915,6 +966,7 @@ async def register(app: web.Application):
"/innkeeper/tenants/{tenant_id}", innkeeper_tenant_get, allow_head=False
),
web.put("/innkeeper/tenants/{tenant_id}/config", tenant_config_update),
web.delete("/innkeeper/tenants/{tenant_id}", innkeeper_tenant_delete),
web.get(
"/innkeeper/default-config",
tenant_default_config_settings,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@
<Column :expander="true" header-style="width: 3rem" />
<Column :sortable="false" :header="$t('common.actions')">
<template #body="{ data }">
<EditConfig :tenant="data" />
<div class="container">
<EditConfig :tenant="data" />
<DeleteTenant :tenant="data" />
</div>
</template>
</Column>
<Column
Expand Down Expand Up @@ -96,6 +99,7 @@ import { storeToRefs } from 'pinia';
import { TABLE_OPT, API_PATH } from '@/helpers/constants';
import { formatDateLong } from '@/helpers';
import EditConfig from './editConfig/editConfig.vue';
import DeleteTenant from './deleteTenant/DeleteTenant.vue';
import MainCardContent from '@/components/layout/mainCard/MainCardContent.vue';
import RowExpandData from '@/components/common/RowExpandData.vue';
Expand Down Expand Up @@ -148,3 +152,8 @@ const filter = ref({
// necessary for expanding rows, we don't do anything with this
const expandedRows = ref([]);
</script>
<style scoped>
.container {
display: flex;
}
</style>
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<template>
<form @submit.prevent="handleDelete">
<div class="modal-content">
<h4>
{{
$t('tenants.settings.confirmDeletion', {
tenantName: tenant.tenant_name,
})
}}
</h4>
<InputText
v-model.trim="confirmationTenantName"
type="text"
placeholder="Type the tenant name here"
class="w-full mb-4"
/>
<Button
:disabled="!isTenantNameCorrect"
label="Delete"
severity="danger"
class="w-full"
type="submit"
/>
</div>
</form>
</template>

<script setup lang="ts">
import { ref, computed, defineProps, defineEmits } from 'vue';
import InputText from 'primevue/inputtext';
import Button from 'primevue/button';
import { useInnkeeperTenantsStore, useTenantStore } from '@/store';
import { TenantRecord } from '@/types/acapyApi/acapyInterface';
import { useToast } from 'vue-toastification';
const props = defineProps<{
tenant: TenantRecord;
}>();
const emit = defineEmits(['closed', 'success']);
// Using stores
const innkeeperTenantsStore = useInnkeeperTenantsStore();
const tenantStore = useTenantStore();
const confirmationTenantName = ref('');
const isTenantNameCorrect = computed(
() => confirmationTenantName.value === props.tenant.tenant_name
);
const toast = useToast();
async function handleDelete() {
if (!isTenantNameCorrect.value) {
toast.error(
'Incorrect tenant name. Please confirm the correct name before deletion.'
);
return;
}
innkeeperTenantsStore
.deleteTenant(props.tenant.tenant_id)
.catch((err: string) => {
console.log(err);
toast.error('Failure: ${err}');
});
emit('success');
emit('closed');
}
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<template>
<div>
<Button
:title="$t('tenants.settings.deleteTenant')"
icon="pi pi-trash"
class="p-button-rounded p-button-icon-only p-button-text"
:disabled="props.tenant.tenant_name === 'traction_innkeeper'"
@click="openModal"
/>
<Dialog
v-model:visible="displayModal"
:style="{ minWidth: '500px' }"
:header="'Delete Tenant'"
:modal="true"
@update:visible="handleClose"
>
<ConfirmTenantDeletion
:tenant="props.tenant"
@success="$emit('success')"
@closed="handleClose"
/>
</Dialog>
</div>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import Button from 'primevue/button';
import Dialog from 'primevue/dialog';
import ConfirmTenantDeletion from './ConfirmTenantDeletion.vue';
import { TenantRecord } from '@/types/acapyApi/acapyInterface';
defineEmits(['success']);
const props = defineProps<{
tenant: TenantRecord;
}>();
const displayModal = ref(false);
const openModal = async () => {
displayModal.value = true;
};
const handleClose = async () => {
displayModal.value = false;
};
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
:title="$t('tenants.settings.editSettings')"
icon="pi pi-cog"
class="p-button-rounded p-button-icon-only p-button-text"
:disabled="props.tenant.tenant_name === 'traction_innkeeper'"
@click="openModal"
/>
<Dialog
Expand Down
4 changes: 3 additions & 1 deletion services/tenant-ui/frontend/src/plugins/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,9 @@
"enableLedgerSwitch": "Tenant can switch endorser/ledger",
"endorserAlias": "Endorser Alias:",
"ledgerName": "Ledger Name:",
"success": "Tenant Config Updated"
"success": "Tenant Config Updated",
"confirmDeletion": "To confirm, type \"{tenantName}\" in the box below",
"deleteTenant": "Delete Tenant"
},
"tenants": "Tenants"
},
Expand Down
4 changes: 3 additions & 1 deletion services/tenant-ui/frontend/src/plugins/i18n/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -471,7 +471,9 @@
"enableLedgerSwitch": "Tenant can switch endorser/ledger <FR>",
"endorserAlias": "Endorser Alias <FR>",
"ledgerName": "Ledger Name <FR>",
"success": "Tenant Config Updated <FR>"
"success": "Tenant Config Updated <FR>",
"confirmDeletion": "To confirm, type \"{tenantName}\" in the box below <FR>",
"deleteTenant": "Delete Tenant <FR>"
},
"tenants": "Tenants <FR>"
},
Expand Down
4 changes: 3 additions & 1 deletion services/tenant-ui/frontend/src/plugins/i18n/locales/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,9 @@
"enableLedgerSwitch": "Tenant can switch endorser/ledger <JA>",
"endorserAlias": "Endorser Alias <JA>",
"ledgerName": "Ledger Name <JA>",
"success": "Tenant Config Updated <JA>"
"success": "Tenant Config Updated <JA>",
"confirmDeletion": "To confirm, type \"{tenantName}\" in the box below <JA>",
"deleteTenant": "Delete Tenant <JA>"
},
"tenants": "Tenants <JA>"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,22 @@ export const useInnkeeperTenantsStore = defineStore('innkeeperTenants', () => {
}
}

// Delete a Tenant
async function deleteTenant(id: string) {
loading.value = true;
try {
await acapyApi.deleteHttp(API_PATH.INNKEEPER_TENANT(id));
await listTenants();
} catch (err: any) {
error.value = err;
} finally {
loading.value = false;
}
if (error.value != null) {
throw error.value;
}
}

// Create an API key for a tenant
async function createApiKey(payload: TenantAuthenticationsApiRequest) {
error.value = null;
Expand Down Expand Up @@ -376,6 +392,7 @@ export const useInnkeeperTenantsStore = defineStore('innkeeperTenants', () => {
listReservations,
updateTenantConfig,
getDefaultConfigValues,
deleteTenant,
};
});

Expand Down

0 comments on commit 827f812

Please sign in to comment.